iOS網(wǎng)絡(luò)監(jiān)控方案

目標(biāo)

  • TCP建立連接時間
  • DNS時間
  • SSL/TLS時間
  • 響應(yīng)總時間
  • 請求頭、請求body、響應(yīng)頭、響應(yīng)body大小
  • 支持統(tǒng)計原生網(wǎng)絡(luò)請求、React Native網(wǎng)絡(luò)請求
  • 代碼無侵害

方案對比

方案一:通過 NSURLProtocol 來實現(xiàn)

通過向 NSURLProtocol 注冊自定義的 NSURLProtocol 子類愈涩,比如是 TESTURLProtocol,然后每個由 NSURLConnectionNSURLSession 發(fā)起請求都會訪問 TESTURLProtocol加矛。

注冊方式

[NSURLProtocol registerClass:[TESTURLProtocol class]]

問題一:如果 NSURLSession 使用下面兩個方法創(chuàng)建的履婉,只注冊是攔截不到的。

+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration 
                                  delegate:(nullable id <NSURLSessionDelegate>)delegate 
                             delegateQueue:(nullable NSOperationQueue *)queue;

需要把TESTURLProtocol 添加到 NSURLSessionConfiguration.protocolClasses 中斟览,可以通過 hook 來實現(xiàn)毁腿。

問題二:POST 請求 body 丟失問題。

TESTURLProtocol 中,使用 HTTPBodyStream 獲取 body已烤,并賦值到 body 中

- (void)startLoading {
    //緩存下來
    TestModel.request = [self.request cyl_getPostRequestIncludeBody];
}
@interface NSURLRequest (CYLNSURLProtocolExtension)

- (NSURLRequest *)cyl_getPostRequestIncludeBody;

@end

@implementation NSURLRequest (CYLNSURLProtocolExtension)

- (NSURLRequest *)cyl_getPostRequestIncludeBody {
    return [[self cyl_getMutablePostRequestIncludeBody] copy];
}

- (NSMutableURLRequest *)cyl_getMutablePostRequestIncludeBody {
    NSMutableURLRequest * req = [self mutableCopy];
    if ([self.HTTPMethod isEqualToString:@"POST"]) {
        if (!self.HTTPBody) {
            NSInteger maxLength = 1024;
            uint8_t d[maxLength];
            NSInputStream *stream = self.HTTPBodyStream;
            NSMutableData *data = [[NSMutableData alloc] init];
            [stream open];
            BOOL endOfStreamReached = NO;
            //不能用 [stream hasBytesAvailable]) 判斷鸠窗,處理圖片文件的時候這里的[stream hasBytesAvailable]會始終返回YES,導(dǎo)致在while里面死循環(huán)胯究。
            while (!endOfStreamReached) {
                NSInteger bytesRead = [stream read:d maxLength:maxLength];
                if (bytesRead == 0) { //文件讀取到最后
                    endOfStreamReached = YES;
                } else if (bytesRead == -1) { //文件讀取錯誤
                    endOfStreamReached = YES;
                } else if (stream.streamError == nil) {
                    [data appendBytes:(void *)d length:bytesRead];
                }
            }
            req.HTTPBody = [data copy];
            [stream close];
        }
        
    }
    return req;
}
@end

參考方案:NSURLProtocol 攔截 NSURLSession 請求時body丟失問題解決方案探討

問題三:NSURLProtocol 可以攔截 UIWebView 的請求稍计,無法攔截 WKWebView

可以通過修改WKWebView的私有方法來實現(xiàn)裕循,注意如果提交 AppStore 需要加密處理

//注冊scheme
Class cls = NSClassFromString(@"WKBrowsingContextController");
SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:");
if ([cls respondsToSelector:sel]) {
    // 通過http和https的請求臣嚣,同理可通過其他的Scheme 但是要滿足ULR Loading System
    [cls performSelector:sel withObject:@"http"];
    [cls performSelector:sel withObject:@"https"];
}

參考方案:NSURLProtocol對WKWebView的處理

NSURLProtocol 實現(xiàn)方案可參考 NetworkEye 可以監(jiān)聽 NSURLConnectionNSURLSession 請求,處理了問題意一和問題二费韭。

小結(jié)

優(yōu)點(diǎn):

  1. 可以比較輕松的獲取請求頭茧球、請求body庭瑰、響應(yīng)頭星持、響應(yīng)body、請求總時長(從開始請求到請求結(jié)束時長)弹灭;
  2. 沒有版本限制督暂。

缺點(diǎn):

  1. 時間方面沒法獲取各個階段的時長,比如穷吮,DNS時長逻翁、TCP時長、SSL時長等捡鱼;
  2. 流量方面需要自己計算大小八回,比如響應(yīng)body很多時候會有壓縮,所以在計算的時候需要模擬壓縮驾诈,得到的結(jié)果還是有誤差的缠诅;
  3. 需要創(chuàng)建新的請求來路由接口,對業(yè)務(wù)有一定的破壞性(最難接受的)乍迄。

方案二:通過監(jiān)聽 URLSession:task:didFinishCollectingMetrics: 中的 NSURLSessionTaskMetrics 來實現(xiàn)

通過 hook NSURLSession 的方法 sessionWithConfiguration:delegate:delegateQueue: <NSURLSessionDelegate>)delegate 使用 NSProxy 來轉(zhuǎn)發(fā)管引,這樣就可以監(jiān)聽 URLSession:task:didFinishCollectingMetrics: 方法。

可參考iOS網(wǎng)絡(luò)性能監(jiān)控

問題一:需要 iOS10 之后才能使用闯两,流量相關(guān)的統(tǒng)計需要 iOS13 之后才能使用

問題二:無法攔截通過 sharedSession 獲取 NSURLSession 的實例

問題三:同樣也有 POST 請求 body 丟失問題褥伴,以及 WKWebView 無法攔截問題, 實現(xiàn)方式跟上面類似

小結(jié)

優(yōu)點(diǎn):通過 NSURLSessionTaskMetrics 可以獲取很方便的獲取各個階段的請求耗時漾狼,以及流量的使用請求重慢。

缺點(diǎn):主要是問題一和問題二帶來的。

最終方案

在實際業(yè)務(wù)開發(fā)中逊躁,網(wǎng)絡(luò)請求的方式主要是 NSURLSession 似踱、NSURLConnectionAFNetworkingAFNetworking 是基于 NSURLSessionNSURLConnection 實現(xiàn)的;業(yè)務(wù)如果是 React Native 來開發(fā)屯援,底層的網(wǎng)絡(luò)請求也是基于 NSURLSession 來實現(xiàn)的猛们。

另外,NSURLConnection iOS9之后就被蘋果棄用了狞洋。

接下來弯淘,我們再看一下官方給的當(dāng)前系統(tǒng)的占有率。

image01.png

數(shù)據(jù)來源

只有 8% 的設(shè)備是低于 iOS13 的吉懊。

攔截目標(biāo):主要考慮 NSURLSession 的攔截庐橙;考慮到實際業(yè)務(wù)用很使用 AFNetworkingNSURLConnection 的請求,也還是要考慮 NSURLConnection 的攔截借嗽。

最終方案:以方案二為主态鳖,覆蓋不到的地方使用方案一來補(bǔ)充。

方案二攔截不到有兩種請求

  • NSURLSession 通過 sharedSession 獲取的對象
  • NSURLConnection 發(fā)起的請求恶导,雖然 iOS9 之后就已經(jīng)拋棄了浆竭,但是 AFNetworking 有部分是基于 NSURLConnection,而我們很多老代碼就是通過 NSURLConnection 發(fā)起的請求

所以針對這個兩種情況通過方案一的 NSURLProtocol 來實現(xiàn)惨寿,內(nèi)部生成 NSURLSession 實例邦泄,路由到方案二。

image02.png
// 方案一裂垦,核心代碼邏輯顺囊,目的是路由接口至方案二上

@interface MTNMURLProtocol ()<NSURLSessionDataDelegate>

@property (nonatomic, strong) NSURLSession *session;

@end

@implementation MTNMURLProtocol

+ (void)install
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [NSURLProtocol registerClass:[MTNMURLProtocol class]];
    });
}

+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
    if ([MTNMDataManager.shareManager isWhitelistURL:request.URL])
    {
        return NO;
    }
    
    if (![request.URL.scheme isEqualToString:@"http"] &&
        ![request.URL.scheme isEqualToString:@"https"])
    {
        return NO;
    }
    if ([NSURLProtocol propertyForKey:@"MTNMURLProtocol" inRequest:request])
    {
        return NO;
    }
    return YES;
}

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
    NSMutableURLRequest *mutableReqeust = [request mutableCopy];
    [NSURLProtocol setProperty:@YES forKey:@"MTNMURLProtocol" inRequest:mutableReqeust];
    return [mutableReqeust copy];
}

- (NSURLSession *)session
{
    if (!_session)
    {
        _session = [NSURLSession sessionWithConfiguration:NSURLSessionConfiguration.defaultSessionConfiguration delegate:self delegateQueue:nil];
    }
    return _session;
}

- (void)startLoading
{
    [[self.session dataTaskWithRequest:self.request] resume];
}

- (void)stopLoading
{
    [self.session getTasksWithCompletionHandler:^(NSArray<NSURLSessionDataTask *> * _Nonnull dataTasks, NSArray<NSURLSessionUploadTask *> * _Nonnull uploadTasks, NSArray<NSURLSessionDownloadTask *> * _Nonnull downloadTasks) {
        for (NSURLSessionDataTask *task in dataTasks)
        {
            [task cancel];
            
        }
    }];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.session finishTasksAndInvalidate];
    });
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
    [self.client URLProtocol:self didLoadData:data];
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
    [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
    if (completionHandler)
    {
        completionHandler(NSURLSessionResponseAllow);
    }
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error
{
    if (error)
    {
        [self.client URLProtocol:self didFailWithError:error];
    }
    else
    {
        [self.client URLProtocolDidFinishLoading:self];
    }
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler
{
    [self.client URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
    if (completionHandler)
    {
        completionHandler(request);
    }
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler
{
    id<NSURLAuthenticationChallengeSender> sender = [MTNMURLAuthenticationChallengeSender senderWithCompletionHandler:completionHandler];
    NSURLAuthenticationChallenge *wrappedChallenge = [[NSURLAuthenticationChallenge alloc] initWithAuthenticationChallenge:challenge sender:sender];
    [self.client URLProtocol:self didReceiveAuthenticationChallenge:wrappedChallenge];
}

- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{
    [self.client URLProtocolDidFinishLoading:self];
}

@end
// 方案二, 核心代碼邏輯

@interface MTNMURLSessionMetricsProxy : NSProxy<NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate>

@property (nonatomic, strong) id target;

- (instancetype)initWithTarget:(id)target;

@end

@implementation MTNMURLSessionMetricsProxy

- (instancetype)initWithTarget:(id)target
{
    self.target = target;
    return self;
}

- (void)dealloc
{
    if (_target)
    {
        _target = nil;
    }
}

- (BOOL)respondsToSelector:(SEL)aSelector
{
    if ([NSStringFromSelector(aSelector) isEqualToString:@"URLSession:task:didFinishCollectingMetrics:"] ||
        [NSStringFromSelector(aSelector) isEqualToString:@"URLSession:dataTask:didReceiveResponse:completionHandler:"] ||
        [NSStringFromSelector(aSelector) isEqualToString:@"URLSession:dataTask:didReceiveData:"] ||
        [NSStringFromSelector(aSelector) isEqualToString:@"URLSession:task:didCompleteWithError:"])
    {
        return YES;
    }
    return [self.target respondsToSelector:aSelector];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
    if (!self.target)
    {
        return [NSMethodSignature signatureWithObjCTypes:"v@"];
    }
    return [self.target methodSignatureForSelector:selector];
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
    if (!self.target)
    {
        return;
    }
    if ([self.target respondsToSelector:invocation.selector])
    {
        [invocation invokeWithTarget:self.target];
    }
}

// dataTaskWithRequest: 和 dataTaskWithURL: 發(fā)起的請求通過 delegate 回調(diào)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
    session.mt_responseBodyMutableData = [NSMutableData data];
    if ([self.target respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)])
    {
        [self.target URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
    }
    else
    {
        if (completionHandler)
        {
            completionHandler(NSURLSessionResponseAllow);
        }
    }
}

// dataTaskWithRequest: 和 dataTaskWithURL: 發(fā)起的請求通過 delegate 回調(diào)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
    if ([self.target respondsToSelector:@selector(URLSession:dataTask:didReceiveData:)])
    {
        [self.target URLSession:session dataTask:dataTask didReceiveData:data];
    }
    if (!session.mt_didAddData)
    {
        if (!session.mt_requestBodyData)
        {
            session.mt_requestBodyData = [self reqeustBobyForRequest:dataTask.originalRequest];
        }
        if (data)
        {
            [session.mt_responseBodyMutableData appendData:data];
        }
    }
}

// dataTaskWithRequest: 和 dataTaskWithURL: 發(fā)起的請求通過 delegate 回調(diào)
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error
{
    if ([self.target respondsToSelector:@selector(URLSession:task:didCompleteWithError:)])
    {
        [self.target URLSession:session task:task didCompleteWithError:error];
    }
    if (!session.mt_didAddData && error)
    {
        session.mt_error = error;
        [session mt_checkAddRecordData];
    }
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
{
    if ([self.target respondsToSelector:@selector(URLSession:task:didFinishCollectingMetrics:)])
    {
        [self.target URLSession:session task:task didFinishCollectingMetrics:metrics];
    }
    if (![MTNMDataManager.shareManager isWhitelistURL:task.originalRequest.URL])
    {
        for (NSURLSessionTaskTransactionMetrics *transMetric in metrics.transactionMetrics) {
            if (transMetric.resourceFetchType == NSURLSessionTaskMetricsResourceFetchTypeNetworkLoad)
            {
                if (!session.mt_didAddData)
                {
                    if (!session.mt_requestBodyData)
                    {
                        session.mt_requestBodyData = [self reqeustBobyForRequest:task.originalRequest];
                    }
                    session.mt_metrics = transMetric;
                    [session mt_checkAddRecordData];
                }
            }
        }
    }
}

- (NSData *)reqeustBobyForRequest:(NSURLRequest *)request
{
    NSMutableData *data;
    if ([request.HTTPMethod isEqualToString:@"POST"] && !request.HTTPBody)
    {
        NSInteger maxLength = 1024;
        uint8_t d[maxLength];
        NSInputStream *stream = request.HTTPBodyStream;
        data = [[NSMutableData alloc] init];
        [stream open];
        BOOL endOfStreamReached = NO;
        //不能用 [stream hasBytesAvailable]) 判斷,處理圖片文件的時候這里的[stream hasBytesAvailable]會始終返回YES蕉拢,導(dǎo)致在while里面死循環(huán)特碳。
        while (!endOfStreamReached)
        {
            NSInteger bytesRead = [stream read:d maxLength:maxLength];
            if (bytesRead == 0)
            { //文件讀取到最后
                endOfStreamReached = YES;
            } else if (bytesRead == -1)
            { //文件讀取錯誤
                endOfStreamReached = YES;
            } else if (stream.streamError == nil)
            {
                [data appendBytes:(void *)d length:bytesRead];
            }
        }
        [stream close];
    }
    return request.HTTPBody ?: data;
}

@end

應(yīng)用時遇到時問題及解決

問題一:在 MTNMURLSessionMetricsProxy 攔截 NSURLSession 請求時,需要獲取相應(yīng)body晕换。

相應(yīng)body的獲取是有必要的午乓,不僅用于 iOS13 以下的接口統(tǒng)計,也可以用于本地網(wǎng)絡(luò)記錄查看届巩,方便排查問題硅瞧。

NSURLSession 的回調(diào)一般有兩種方式, delegateblock 的方式恕汇。

delegate 只需要在 NSProxy 子類中攔截即可

// dataTaskWithRequest: 和 dataTaskWithURL: 發(fā)起的請求通過 delegate 回調(diào)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
    session.mt_responseBodyMutableData = [NSMutableData data];
    if ([self.target respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)])
    {
        [self.target URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
    }
    else
    {
        if (completionHandler)
        {
            completionHandler(NSURLSessionResponseAllow);
        }
    }
}

// dataTaskWithRequest: 和 dataTaskWithURL: 發(fā)起的請求通過 delegate 回調(diào)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
    if ([self.target respondsToSelector:@selector(URLSession:dataTask:didReceiveData:)])
    {
        [self.target URLSession:session dataTask:dataTask didReceiveData:data];
    }
    if (!session.mt_didAddData)
    {
        if (!session.mt_requestBodyData)
        {
            session.mt_requestBodyData = [self reqeustBobyForRequest:dataTask.originalRequest];
        }
        if (data)
        {
            [session.mt_responseBodyMutableData appendData:data];
        }
    }
}

blcok 的方式就復(fù)雜一點(diǎn)腕唧,需要 hookdataTaskWithRequest:completionHandler:dataTaskWithURL:completionHandler: 來攔截獲取。

@implementation NSURLSession (MTNetworkMonitor)

- (NSURLSessionDataTask *)hook_dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler
{
    return [self hook_dataTaskWtihURL:url request:nil fromURL:YES completionHandler:completionHandler];
}

- (NSURLSessionDataTask *)hook_dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler
{
    return [self hook_dataTaskWtihURL:nil request:request fromURL:NO completionHandler:completionHandler];
}

- (NSURLSessionDataTask *)hook_dataTaskWtihURL:(NSURL *)url request:(NSURLRequest *)request fromURL:(BOOL)fromURL completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler
{
    [self addIfNotGuid];
    void(^hookCompletionHandler)(NSData * _Nullable, NSURLResponse * _Nullable, NSError * _Nullable) = ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error){
        if (![MTNMDataManager.shareManager isWhitelistURL:url])
        {
            if (!self.mt_didAddData)
            {
                self.mt_error = error;
                self.mt_responseBodyMutableData = data.mutableCopy;
                [self mt_checkAddRecordData];
            }
        }
        if (completionHandler) {
            completionHandler(data, response, error);
        }
    };
    
    void(^completion)(NSData * _Nullable, NSURLResponse * _Nullable, NSError * _Nullable) = completionHandler ? hookCompletionHandler : completionHandler;
    if (fromURL)
    {
        return [self hook_dataTaskWithURL:url completionHandler:completion];
    }
    else
    {
        return [self hook_dataTaskWithRequest:request completionHandler:completion];
    }
}

@end

問題二:NSURLSessiondelegate 是強(qiáng)引用瘾英,會導(dǎo)致內(nèi)存泄漏枣接。

需要在合適的時機(jī)主動調(diào)用 finishTasksAndInvalidate 來釋放 delegate 對象。

問題三:在iOS14.0和iOS14.0.1系統(tǒng)上閃退缺谴,原因是獲取網(wǎng)絡(luò)類型導(dǎo)致的

網(wǎng)絡(luò)類型是通過 CTTelephonyNetworkInfo 獲取但惶,其中 CTRadioAccessTechnologyNRNSACTRadioAccessTechnologyNR 蘋果文檔是 iOS14.0 就可以用實際上,在 iOS14.0 和 iOS14.0.1 是沒有的。

問題三:在iOS14.0和iOS14.0.1系統(tǒng)上閃退膀曾,原因是獲取網(wǎng)絡(luò)類型導(dǎo)致的

網(wǎng)絡(luò)類型是通過 CTTelephonyNetworkInfo 獲取县爬,其中 CTRadioAccessTechnologyNRNSACTRadioAccessTechnologyNR 蘋果文檔是 iOS14.0 就可以用實際上,在 iOS14.0 和 iOS14.0.1 是沒有的添谊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末财喳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子斩狱,更是在濱河造成了極大的恐慌耳高,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件所踊,死亡現(xiàn)場離奇詭異泌枪,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)秕岛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門碌燕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瓣蛀,你說我怎么就攤上這事陆蟆。” “怎么了惋增?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長改鲫。 經(jīng)常有香客問我诈皿,道長,這世上最難降的妖魔是什么像棘? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任稽亏,我火速辦了婚禮,結(jié)果婚禮上缕题,老公的妹妹穿的比我還像新娘截歉。我一直安慰自己,他們只是感情好烟零,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布瘪松。 她就那樣靜靜地躺著,像睡著了一般锨阿。 火紅的嫁衣襯著肌膚如雪宵睦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天墅诡,我揣著相機(jī)與錄音壳嚎,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛烟馅,可吹牛的內(nèi)容都是我干的说庭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼郑趁,長吁一口氣:“原來是場噩夢啊……” “哼口渔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起穿撮,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤缺脉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后悦穿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體攻礼,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年栗柒,在試婚紗的時候發(fā)現(xiàn)自己被綠了礁扮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡瞬沦,死狀恐怖太伊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情逛钻,我是刑警寧澤僚焦,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站曙痘,受9級特大地震影響芳悲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜边坤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一名扛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧茧痒,春花似錦肮韧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至耸峭,卻和暖如春桩蓉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背劳闹。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工院究, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留洽瞬,地道東北人。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓业汰,卻偏偏與公主長得像伙窃,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子样漆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容