iOS源码解析—AFNetworking(URLConnection)

概述

AFNetwokring目前是3.x版本,基于NSURLSession的功能进行封装,而2.x版本是基于NSURLConnection。由于NSURLConnection逐渐被NSURLSession所取代,2.x版本逐渐被3.x取代。本篇分析一下2.x版本,因为该版本涉及的一些代码值得学习,例如NSOperation、KVO的使用。

AFHTTPRequestOperationManager

AFHTTPRequestOperationManager是AFN封装的管理HTTP请求的类,首先初始化方法中设置了一些参数值,代码注释如下:

- (instancetype)initWithBaseURL:(NSURL *)url {
    ...
    self.baseURL = url;
    self.requestSerializer = [AFHTTPRequestSerializer serializer]; //序列化
    self.responseSerializer = [AFJSONResponseSerializer serializer];//反序列化
    self.securityPolicy = [AFSecurityPolicy defaultPolicy]; //默认的安全策略
    self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];//监听网络状态
    self.operationQueue = [[NSOperationQueue alloc] init]; //任务队列
    self.shouldUseCredentialStorage = YES;
    return self;
}

初始化方法设置了请求报文序列化/反序列化对象,以及默认的安全策略,网络监听对象,任务队列。

AFHTTPRequestOperationManager提供了一系列HTTP请求相关的方法,例如GET、POST、PATCH等,内部实现相同,只是method参数值不同,以GET请求方法为例,代码注释如下:

- (AFHTTPRequestOperation *)GET:(NSString *)URLString
                     parameters:(id)parameters
                        success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                        failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
    AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"GET" URLString:URLString parameters:parameters success:success failure:failure];
    [self.operationQueue addOperation:operation];
    return operation;
}

首先创建一个AFHTTPRequestOperation类型的NSOperation对象,然后将NSOperation对象加入operationQueue队列中,开始执行operation。在创建AFHTTPRequestOperation对象的方法中,首先通过requestSerializer构建NSURLRequest对象,然后设置相关属性,设置completionBlock,代码注释如下:

- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request
                                                    success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                                                    failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
    AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
    ... //设置相关属性
    [operation setCompletionBlockWithSuccess:success failure:failure]; //设置operation结束时的completionBlock
    operation.completionQueue = self.completionQueue; //执行completionBlock的队列
    operation.completionGroup = self.completionGroup; //执行completionBlock的group
    return operation;
}

AFHTTPRequestOperation继承AFURLConnectionOperation,AFURLConnectionOperation真正负责网络请求的发出以及处理,AFHTTPRequestOperation设置completionBlock的方法如下:

- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                              failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"
    self.completionBlock = ^{
        if (self.completionGroup) {
            dispatch_group_enter(self.completionGroup);
        }
        //在异步队列中执行
        dispatch_async(http_request_operation_processing_queue(), ^{
            if (self.error) {
                if (failure) { //网络请求失败
                    //在completionQueue或者主线程队列中执行失败block
                    dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                        failure(self, self.error);
                    });
                }
            } else { //网络请求成功
                //反序列化报文数据
                id responseObject = self.responseObject;
                if (self.error) {
                    if (failure) {
                        //反序列化报文数据失败,在completionQueue或者主线程队列中执行失败block
                        dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                            failure(self, self.error);
                        });
                    }
                } else {
                    if (success) {
                        //反序列化报文数据成功,在completionQueue或者主线程队列中执行成功block
                        dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                            success(self, responseObject);
                        });
                    }
                }
            }
            if (self.completionGroup) {
                dispatch_group_leave(self.completionGroup);
            }
        });
    };
#pragma clang diagnostic pop
}

该方法定义一个void (^)(void)类型的block,设置给父类的completionBlock属性,当执行completionBlock时,首先会判断网络请求是否错误,如果出错,直接在completionQueue或者主线程中调用failure的block抛给调用层。如果网络请求成功,则调用responseObject方法反序列化响应报文数据responseData,代码注释如下:

- (id)responseObject {
    [self.lock lock];
    if (!_responseObject && [self isFinished] && !self.error) {
        NSError *error = nil;
        //反序列化响应报文数据
        self.responseObject = [self.responseSerializer responseObjectForResponse:self.response data:self.responseData error:&error];
        if (error) {
            self.responseSerializationError = error;
        }
    }
    [self.lock unlock];
    return _responseObject;
}

由于该方法在http_request_operation_processing_queue()中执行,不影响主线程的性能。如果反序列化成功,调用success的blcok将error抛给调用层,如果失败,调用failure的block将反序列化后的对象抛给调用层。

AFURLConnectionOperation

AFURLConnectionOperation负责发送网络请求,处理delegate回调方法。AFURLConnectionOperation继承NSOperation,众所周知,当实现一个自定义的NSOperation时,需要重写NSOperation的相关方法,以确保operation机制的正常运行。

初始化方法

初始化方法设置了相关参数,代码注释如下:

- (instancetype)initWithRequest:(NSURLRequest *)urlRequest {
    NSParameterAssert(urlRequest);
    self = [super init];
    if (!self) {
        return nil;
    }
    _state = AFOperationReadyState; //状态设置为准备执行
    self.lock = [[NSRecursiveLock alloc] init]; //创建递归锁
    self.lock.name = kAFNetworkingLockName;
    self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes];
    self.request = urlRequest; //设置urlRequest
    self.shouldUseCredentialStorage = YES;
    self.securityPolicy = [AFSecurityPolicy defaultPolicy]; //设置securityPolicy
    return self;
}

该方法设置用于网络请求的request,初始化了递归锁,安全策略对象securityPolicy,同时设置了状态为AFOperationReadyState(准备执行)。

状态机制

当operation加入到operationQueue中时,operationQueue会通过KVO的方式监听operation的状态,NSOperation有几种状态,分别对应以下属性:

isReady(是否准备执行)

isExecuting(是否正在执行)

isCancelled(是否取消)

isPaused(是否暂停)

isFinished(是否完成)

上面的属性状态决定operation的生命周期,operationQueue监听operation的状态属性,当operation的isFinished属性为YES时,说明operation生命周期结束,operation会在队列中被释放。AFURLConnectionOperation实现了自定义的operation,重写了以下属性方法:

- (BOOL)isReady {
    return self.state == AFOperationReadyState && [super isReady];
}
- (BOOL)isExecuting {
    return self.state == AFOperationExecutingState;
}
- (BOOL)isFinished {
    return self.state == AFOperationFinishedState;
}

通过getter方法访问operation的属性时,返回的状态值会根据AFNetworking维护的枚举值来确定。下面是枚举类型AFOperationState的代码:

typedef NS_ENUM(NSInteger, AFOperationState) {
    AFOperationPausedState      = -1, //暂停
    AFOperationReadyState       = 1, //准备执行
    AFOperationExecutingState   = 2, //正在执行
    AFOperationFinishedState    = 3, //完成
};

分别对应operation的状态属性,同时实现-setState:方法来更新AFOperationState的枚举值,下面是代码注释:

- (void)setState:(AFOperationState)state {
    if (!AFStateTransitionIsValid(self.state, state, [self isCancelled])) {
        return;
    }
    [self.lock lock];
    NSString *oldStateKey = AFKeyPathFromOperationState(self.state); //原状态
    NSString *newStateKey = AFKeyPathFromOperationState(state); //新状态
    [self willChangeValueForKey:newStateKey]; //手动发送KVO通知
    [self willChangeValueForKey:oldStateKey]; //手动发送KVO通知
    _state = state; //切换状态
    [self didChangeValueForKey:oldStateKey]; //手动发送KVO通知
    [self didChangeValueForKey:newStateKey]; //手动发送KVO通知
    [self.lock unlock];
}

首先通过AFKeyPathFromOperationState方法将新旧AFOperationState枚举值映射成operation的状态属性名,然后更新AFOperationState枚举值,当外界访问operation的状态属性时,状态已经改变。同时手动发送KVO通知,通知operationQueue,operation的状态属性发生了改变。

start方法

如果实现自定义的NSOperation,需要重写start方法,下面是代码注释:

- (void)start {
    [self.lock lock];
    if ([self isCancelled]) { //如果operation之前被取消,调用cancelConnection方法取消connection
        [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    } else if ([self isReady]) { //如果operation准备执行,调用operationDidStart方法开始构建connection,发送网络请求
        self.state = AFOperationExecutingState;
        [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    }
    [self.lock unlock];
}

该方法首先判断operation当前的状态,如果之前已经被取消了,则调用cancelConnection方法进一步处理,该方法放在后文分析。如果新建operation,在初始化方法中,设置初始状态是AFOperationReadyState,即operation的状态isReady=YES,调用operationDidStart方法开始进行网络请求。同时AFNetworking创建了一个常驻线程来执行connection相关的方法。常驻线程由类方法networkRequestThread创建,代码如下:

+ (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; //创建常驻线程
        [_networkRequestThread start];
    });
    return _networkRequestThread;
}
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; //为runloop添加port,使runloop永不退出
        [runLoop run];
    }
}

首先在dispatch_once()中创建一个线程,由于dispatch_once中的block只会执行一次,所以线程只会创建一次,且_networkRequestThread是static类型的,所以会常驻内存不被释放。每次调用networkRequestThread方法都会返回该线程指针。由于是手动创建的子线程,需要手动开启它的runloop,并且在runloop中添加port,使其永不退出。我们将这个常驻线程称为AFN线程。在AFN线程中执行operationDidStart方法,下面是代码注释:

- (void)operationDidStart {
    [self.lock lock];
    if (![self isCancelled]) {
        self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        for (NSString *runLoopMode in self.runLoopModes) {
            [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
            [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];
        }
        [self.outputStream open];
        [self.connection start];
    }
    [self.lock unlock];
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self];
    });
}

首先创建了connection,设置当前operation对象为connection对象的delegate,处理网络请求回调的各个方法。如果startImmediately参数为YES,connection会立刻启动,开始下载数据,且connection在当前线程的runloop中执行,如果为NO,暂不开始请求数据,需要调用start方法手动开始,同时调用scheduleInRunLoop:forMode:方法把NSURLConnection加入到指定线程的run loop中去运行,否则会加入当前线程的runloop中去,使用outputStream来接收网络请求回来的数据。

NSURLConnectionDelegate

在connection网络请求的过程中,将回调方法抛给delegate执行,下面分析一下主要方法:

  1. -(void)connection:willSendRequestForAuthenticationChallenge:方法

    当客户端发送HTTPS请求给服务端时,会进行SSL握手,在握手的过程中,服务端需要客户端进行授权的响应,客户端对服务端发来的信息进行校验,在iOS代码中,抽象为系统抛出delegate方法给上层代码,同时传入一个challenge对象,封装了需要验证的信息,下面是代码部分注释:

    - (void)connection:(NSURLConnection *)connection
    willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
    {
        ...
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) 
        {
         //对serverTrust对象和host进行校验
         if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                 //校验通过,生成一个凭证对象credential
                NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                 //使用credential给系统
                [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
            } else {
                 //验证失败,取消后续SSL连接
                [[challenge sender] cancelAuthenticationChallenge:challenge];
            }
        } else {
            if ([challenge previousFailureCount] == 0) {
                if (self.credential) { //直接用现有的credential给系统
                    [[challenge sender] useCredential:self.credential forAuthenticationChallenge:challenge];
                } else {
                    [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
                }
            } else {
                [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
            }
        }
    }
    

    challenge中的属性protectionSpace是一个NSURLProtectionSpace对象,包含服务器的host、port、isProxy等信息,同时包含authenticationMethod,即校验方式的类型,如果类型是NSURLAuthenticationMethodServerTrust,则验证信任对象serverTrust,如果验证通过,生成一个凭证对象credential返回给服务端,具体的流程可以参考腾讯Bugly的文章。

  2. -(void)connection: didReceiveData:

    当网络连接建立后,服务器开始向客户端传输数据,系统会回调该方法,上层代码负责接收并且拼装response数据。下面是部分代码注释:

    - (void)connection:(NSURLConnection __unused *)connection
        didReceiveData:(NSData *)data
    {
        NSUInteger length = [data length]; //需要读取的字节长度
        while (YES) {
            NSInteger totalNumberOfBytesWritten = 0; //本次一共写入的字节长度
            if ([self.outputStream hasSpaceAvailable]) { //outputStream有空间写入
                const uint8_t *dataBuffer = (uint8_t *)[data bytes];
                NSInteger numberOfBytesWritten = 0;
                while (totalNumberOfBytesWritten < (NSInteger)length) {
                    numberOfBytesWritten = [self.outputStream write:&dataBuffer[(NSUInteger)totalNumberOfBytesWritten] maxLength:(length - (NSUInteger)totalNumberOfBytesWritten)]; //将dataBuffer中的数据写入outputStream中
                    if (numberOfBytesWritten == -1) {
                        break;
                    }
                    totalNumberOfBytesWritten += numberOfBytesWritten; //累加写入的字节长度
                }
                break;
            } else { //outputStream没有空间写入
                [self.connection cancel];
                if (self.outputStream.streamError) {
                    [self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:self.outputStream.streamError];
                }
                return;
            }
        }
        ...
    }
    

    该方法主要将data数据写入outputStream中,对于本次数据data,如果一次性写不全进outputStream,则通过totalNumberOfBytesWritten记录共写入的字节长度,通过while循环控制,直到全部写入。outputStream通过[NSOutputStream outputStreamToMemory]创建,是写入内存的流对象,如果hasSpaceAvailable返回NO,即后续返回的response数据没有空间存放,则直接断开网络请求。

  3. -(void)connectionDidFinishLoading:

    当网络请求结束时,调用该方法,获取最终的response数据,结束本次operation。

    - (void)connectionDidFinishLoading:(NSURLConnection __unused *)connection {
        self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey]; //获取response数据
        [self.outputStream close]; //关闭outputStream
        if (self.responseData) {
           self.outputStream = nil;
        }
        self.connection = nil;
        [self finish]; //结束operation
    }
    
  4. -(NSCachedURLResponse *)connection: willCacheResponse:

    如果服务端需要将response数据缓存到客户端的NSURLCache缓存系统,在缓存到客户端本地之前,会首先调用该方法,可以修改缓存的数据,默认是接口返回的response数据。

    - (NSCachedURLResponse *)connection:(NSURLConnection *)connection
                      willCacheResponse:(NSCachedURLResponse *)cachedResponse
    {
        if (self.cacheResponse) {
            return self.cacheResponse(connection, cachedResponse); //修改服务端返回缓存数据
        } else {
            if ([self isCancelled]) {
                return nil;
            }
            return cachedResponse;
        }
    }
    

    默认网络请求的缓存策略是UseProtocolCachePolicy,根据服务端返回的Cache-Control字段来开启HTTP缓存功能,字段值可能包含 max-age,是公共 public 还是私有 private,或者不缓存no-cache 等信息。关于NSURLCache的相关讲解,可以参考这篇文章

    - (NSCachedURLResponse *)connection:(NSURLConnection *)connection
                      willCacheResponse:(NSCachedURLResponse *)cachedResponse
    {
        if (self.cacheResponse) {
            return self.cacheResponse(connection, cachedResponse); //修改需要缓存的数据
        } else {
            if ([self isCancelled]) {
                return nil;
            }
            return cachedResponse;
        }
    }
    
控制生命周期

上文所述,AFURLConnectionOperation通过setState:方法实现了operation状态的切换,从而控制operation的生命周期。下面分析一下,另外几个方法:

  1. finish方法

    当网络请求结束时,该方法被调用,负责结束operation的生命周期:

    - (void)finish {
        [self.lock lock];
        self.state = AFOperationFinishedState; //设置结束状态,结束operation的生命周期
        [self.lock unlock];
        ...
    }
    

    在setState方法里更改为AFOperationFinishedState状态并且手动触发KVO,通知operationQueue,isFinished属性变化,触发completionBlock,执行block里面的代码。

  2. cancel方法

    该方法取消一个cancel这个operation,同时调用cancelConnection方法取消当前的connection连接。

    - (void)cancel {
        [self.lock lock];
        if (![self isFinished] && ![self isCancelled]) {
            [super cancel]; //调用NSOperation的cancel方法
            if ([self isExecuting]) {
                [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
            }
        }
        [self.lock unlock];
    }
    

    注意cancelConnection方法也会在start方法中调用,新建一个请求时,当发现这个operation之前被取消了,进一步判断connection是否建立,如果connection存在,先取消connection,然后调用finish方法,结束operation的生命周期。

  3. pause方法和resume方法

    AFURLConnectionOperation提供了pause和resume方法,pause方法将状态改为AFOperationPausedState,同时取消当前的connection。resume方法将状态重新改为AFOperationReadyState,同时调用start方法,重新请求connection,将状态改为AFOperationExecutingState。下面是代码注释:

    - (void)pause {
        if ([self isPaused] || [self isFinished] || [self isCancelled]) {
            return;
        }
        [self.lock lock];
        if ([self isExecuting]) { //取消当前conneciton
            [self performSelector:@selector(operationDidPause) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
     ...
        self.state = AFOperationPausedState; //暂停operation
        [self.lock unlock];
    }
      
    - (void)resume {
        if (![self isPaused]) {
            return;
        }
        [self.lock lock];
        self.state = AFOperationReadyState; //operation重置为isReady状态
        [self start]; //新建connection,重新请求数据,状态职位isExecuting
        [self.lock unlock];
    }
    

    pause方法只是取消本次网络请求,不会结束operation的生命周期,当外界调用resume方法时,也只是重新进行网络请求。

小结

虽然NSURLConnection及其基础上封装的AF2.x版本逐渐被废弃,但是作者关于operation的使用,以及如何实现一个网络请求的处理流程,对于初学者来说,具有参考和学习的价值。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容