OkHttp 知识梳理(1) - OkHttp 源码解析之入门

OkHttp 知识梳理系列文章

OkHttp 知识梳理(1) - OkHttp 源码解析之入门
OkHttp 知识梳理(2) - OkHttp 源码解析之异步请求 & 线程调度
OkHttp 知识梳理(3) - OkHttp 之缓存基础


一、简介

OkHttp无疑是目前使用的最多的网络框架,现在最火的Retrofit也就是基于OkHttp来实现的,和以前一样,我们将从最简单的例子开始,一步步剖析OkHttp的使用及内部实现原理。

OkHttp项目相关文档

二、OKHttp 的使用

2.1 应用背景

我们首先用一个简单的例子来演示OkHttp的简单使用 - 通过中国天气网提供的官方API获取城市的天气,完整的代码可以查看我的Github仓库 RepoOkHttp 中第一章的例子。

2.2 引入依赖

目前官网的最新版本为3.9.1,因此我们需要在build.gradle文件中进行声明:

compile 'com.squareup.okhttp3:okhttp:3.9.1'

不要忘了在AndroidManifest.xml中声明网络权限:

<uses-permission android:name="android.permission.INTERNET" />

最后,我们用一个简单的例子演示OkHttp的同步请求,首先,通过HandlerThread创建一个异步线程,通过发送消息的形式,通知它发起网络请求,请求完毕之后,将结果中的body部分发送回主线程用TextView进行展示:

public class SimpleActivity extends AppCompatActivity {

    private static final String URL = "http://www.weather.com.cn/adat/sk/101010100.html";
    private static final int MSG_REQUEST = 0;
    private static final int MSG_UPDATE_UI = 0;

    private Button mBtRequest;
    private Button mBtRequestAsync;
    private TextView mTvResult;
    private BackgroundHandler mBackgroundHandler;
    private MainHandler mMainHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_simple);
        mBtRequest = (Button) findViewById(R.id.bt_request_sync);
        mBtRequestAsync = (Button) findViewById(R.id.bt_request_async);
        mTvResult = (TextView) findViewById(R.id.tv_result);
        mBtRequest.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                startSyncRequest();
            }

        });
        mBtRequestAsync.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                startAsyncRequest();
            }
        });
        HandlerThread backgroundThread = new HandlerThread("backgroundThread");
        backgroundThread.start();
        mBackgroundHandler = new BackgroundHandler(backgroundThread.getLooper());
        mMainHandler = new MainHandler();
    }

    /**
     * 同步发起请求的例子。
     */
    private void startSyncRequest() {
        //发送消息到异步线程,发起请求。
        mBackgroundHandler.sendEmptyMessage(MSG_REQUEST);
    }

    /**
     * 异步发起请求的例子。
     */
    private void startAsyncRequest() {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url(URL).build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {

            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String result = response.body().string();
                //返回结果给主线程。
                Message message = mMainHandler.obtainMessage(MSG_UPDATE_UI, result);
                mMainHandler.sendMessage(message);
            }
        });
    }

    private class BackgroundHandler extends Handler {

        BackgroundHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //在异步线程发起请求。
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder().url(URL).build();
            Call call = client.newCall(request);
            try {
                Response response = call.execute();
                String result = response.body().string();
                //返回结果给主线程。
                Message message = mMainHandler.obtainMessage(MSG_UPDATE_UI, result);
                mMainHandler.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private class MainHandler extends Handler {

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //主线程获取到结果之后进行更新。
            String result = (String) msg.obj;
            mTvResult.setText(result);
        }
    }

}

运行结果为:


运行结果

三、简要流程分析

是不是很简单,仅仅几句话就完成了网络请求,核心的四个步骤为:

  • 构建OkHttpClient对象
  • 构建Request对象
  • 由前两步创建的OkHttpClientRequest对象创建Call对象
  • 通过Call对象发起请求,并得到一个Response,它就是最终返回的结果。
//1.构建 OkHttpClient 对象
OkHttpClient client = new OkHttpClient();
//2.构建 Request 对象。
Request request = new Request.Builder().url(URL).build();
//3.由 OkHttpClient 通过 Request 创建 Call 对象
Call call = client.newCall(request);
try {
    //4.通过 Call 对象发起请求
    Response response = call.execute();
} catch (IOException e) {
    e.printStackTrace();
}

3.1 构建 OkHttpClient 对象

OkHttpClient提供了许多配置项供使用者进行设置,例如缓存、Cookie、超时时间等等,对于其中参数的初始化可以采用建造者模式,其源码地址为 OkHttpClient.java,例如:

OkHttpClient.Builder builder = new OkHttpClient.Builder()
                    .connectTimeout(3000, TimeUnit.MILLISECONDS)
                    .readTimeout(3000, TimeUnit.MILLISECONDS);
OkHttpClient client = builder.build();

如果我们什么也不做,像最开始的那样直接new OkHttpClient(),那么将会采用默认的配置,部分配置如下,关于各个参数的含义可以参考:OkHttpClient.Builder

//(1) Sets the dispatcher used to set policy and execute asynchronous requests.
this.dispatcher = new Dispatcher();

//(2) Configure the protocols used by this client to communicate with remote servers
this.protocols = OkHttpClient.DEFAULT_PROTOCOLS;

//(3) Unknow 
this.connectionSpecs = OkHttpClient.DEFAULT_CONNECTION_SPECS;

//(4) Configure a factory to provide per-call scoped listeners that will receive analytic events for this client
this.eventListenerFactory = EventListener.factory(EventListener.NONE);

//(5) Sets the proxy selection policy to be used if no is specified explicitly
this.proxySelector = ProxySelector.getDefault();

//(6) Sets the handler that can accept cookies from incoming HTTP responses and provides cookies to outgoing HTTP requests
this.cookieJar = CookieJar.NO_COOKIES;

//(7) Sets the socket factory used to create connections
this.socketFactory = SocketFactory.getDefault();

//(8) Sets the verifier used to confirm that response certificates apply to requested hostnames for HTTPS connections
this.hostnameVerifier = OkHostnameVerifier.INSTANCE;

//(9) Sets the certificate pinner that constrains which certificates are trusted
this.certificatePinner = CertificatePinner.DEFAULT;

//(10) Sets the authenticator used to respond to challenges from proxy servers
this.proxyAuthenticator = Authenticator.NONE;

//(11) Sets the authenticator used to respond to challenges from origin servers
this.authenticator = Authenticator.NONE;

//(12) Sets the connection pool used to recycle HTTP and HTTPS connections
this.connectionPool = new ConnectionPool();

//(13) ets the DNS service used to lookup IP addresses for hostnames
this.dns = Dns.SYSTEM;

//(14) Configure this client to follow redirects from HTTPS to HTTP and from HTTP to HTTPS
this.followSslRedirects = true;

//(15) Configure this client to follow redirects
this.followRedirects = true;

//(16) Configure this client to retry or not when a connectivity problem is encountered
this.retryOnConnectionFailure = true;

//(17) TimeOut
this.connectTimeout = 10000;
this.readTimeout = 10000;
this.writeTimeout = 10000;

//(18) Sets the interval between web socket pings initiated by this client
this.pingInterval = 0;

上面的参数很多,后面用到的时候再去分析,这里我们主要关注两个重要的成员变量,它们是Interceptor类型元素的列表,在后面我们将会花很大的篇幅来介绍它:

final List<Interceptor> interceptors = new ArrayList();
final List<Interceptor> networkInterceptors = new ArrayList();

3.2 构建 Request

OkHttpClient用于全局的参数配置,一般来说,一个进程中拥有一个OkHttpClient对象即可。而Request则对应于一个请求的具体信息,每发起一次请求,就需要创建一个新的Request对象,其配置信息包括请求的urlmethodheadersbody等等,这些都是HTTP的基础知识,推荐大家看一下这篇文章 一篇文章带你详解 HTTP 协议(网络协议篇一),总结得很全面。

Request中的参数也可以通过建造者模式来进行配置,其源码地址为 Request.java

3.3 构建 Call

经过前面两步我们得到了OkHttpClientRequest这两个实例,接下来就需要创建请求的具体执行者:

Call call = client.newCall(request);

newCall的代码很简单,其实就是通过RealCall的静态方法返回了一个RealCall对象,并持有OkHttpClientRequest的引用,同时它实现了Call接口:

public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false);
}

Call接口的定义如下,它定义了请求的接口:

public interface Call extends Cloneable {

    //返回原始的请求对象。
    Request request();
    //同步发起请求。
    Response execute() throws IOException;
    //异步发起请求。
    void enqueue(Callback var1);
    //取消请求。
    void cancel();
    //请求是否已经被执行。
    boolean isExecuted();
    //请求是否取消。
    boolean isCanceled();
    //clone 对象。
    Call clone();
    //工厂类。
    public interface Factory {
        Call newCall(Request var1);
    }

}

当使用RealCall发起请求时,有同步和异步两种方式,分别对应于.execute.enqueue两个方法,这里我们先将 同步的方法,因为 异步 也是建立在它的基础之上的。

3.4 同步发起请求 - execute()

    //[同步请求的函数]
    public Response execute() throws IOException {
        synchronized(this) {
            //如果已经发起过请求,那么直接跑出异常。
            if(this.executed) {
                throw new IllegalStateException("Already Executed");
            }
            //标记为已经发起过请求。
            this.executed = true;
        }
        //捕获这个请求的 StackTrace。
        this.captureCallStackTrace();
        //通知监听者已经开始请求。
        this.eventListener.callStart(this);

        Response var2;
        try {
            //通过 OkHttpClient 的调度器执行请求。
            this.client.dispatcher().executed(this);
            //getResponseWithInterceptorChain() 责任链模式。
            Response result = this.getResponseWithInterceptorChain();
            if(result == null) {
                throw new IOException("Canceled");
            }
            
            var2 = result;
        } catch (IOException var7) {
            //通知监听者发生了异常。
            this.eventListener.callFailed(this, var7);
            throw var7;
        } finally {
            //通过调度器结束该任务。
            this.client.dispatcher().finished(this);
        }
        //返回结果。
        return var2;
    }

    Response getResponseWithInterceptorChain() throws IOException {
        List<Interceptor> interceptors = new ArrayList();
        interceptors.addAll(this.client.interceptors());
        interceptors.add(this.retryAndFollowUpInterceptor);
        interceptors.add(new BridgeInterceptor(this.client.cookieJar()));
        interceptors.add(new CacheInterceptor(this.client.internalCache()));
        interceptors.add(new ConnectInterceptor(this.client));
        if(!this.forWebSocket) {
            interceptors.addAll(this.client.networkInterceptors());
        }

        interceptors.add(new CallServerInterceptor(this.forWebSocket));
        Chain chain = new RealInterceptorChain(interceptors, (StreamAllocation)null, (HttpCodec)null, (RealConnection)null, 0, this.originalRequest, this, this.eventListener, this.client.connectTimeoutMillis(), this.client.readTimeoutMillis(), this.client.writeTimeoutMillis());
        return chain.proceed(this.originalRequest);
    }

整个通过请求分为以下几步:

3.4.1 将 Call 任务加入到同步队列当中

this.client.dispatcher().executed(this);

这里的执行并不是真正的执行,默认情况下调度器的实现为Dispatcher,它的executed方法实现为:

    //同步队列。
    private final Deque<RealCall> runningSyncCalls = new ArrayDeque();

    //Dispatcher.executed 仅仅是将 Call 加入到队列当中,而并没有真正执行。
    synchronized void executed(RealCall call) {
        this.runningSyncCalls.add(call);
    }

3.4.2 执行请求

Response result = this.getResponseWithInterceptorChain()

真正地触发了请求的执行是上面这句,我们来简单看一下getResponseWithInterceptorChain是怎么触发请求的。

    Response getResponseWithInterceptorChain() throws IOException {
        List<Interceptor> interceptors = new ArrayList();
        //先加入使用者自定义的拦截器。
        interceptors.addAll(this.client.interceptors());
        //加入标准的拦截器。
        interceptors.add(this.retryAndFollowUpInterceptor);
        interceptors.add(new BridgeInterceptor(this.client.cookieJar()));
        interceptors.add(new CacheInterceptor(this.client.internalCache()));
        interceptors.add(new ConnectInterceptor(this.client));
        if(!this.forWebSocket) {
            interceptors.addAll(this.client.networkInterceptors());
        }
        //访问服务器的拦截器。
        interceptors.add(new CallServerInterceptor(this.forWebSocket));
        //创建调用链,注意第五个参数目前的值为0。
        Chain chain = new RealInterceptorChain(interceptors, (StreamAllocation)null, (HttpCodec)null, (RealConnection)null, 0, this.originalRequest, this, this.eventListener, this.client.connectTimeoutMillis(), this.client.readTimeoutMillis(), this.client.writeTimeoutMillis());
        //执行调用链的 proceed 方法。
        return chain.proceed(this.originalRequest);
    }

这里我们将一系列的Interceptor加入到了List当中,构建完之后,List中的内容如下所示,对于使用者自定义的interceptors将会加在列表的头部,而自定义的networkInterceptors则会加在CallServerInterceptor之前:

List<Interceptor>

接着创建了一个RealInterceptorChain对象,它的构造函数的第1参数就是上面List<Interceptor>列表,除此之外还需要注意第5个参数为0,这个对于下面的分析很重要,最后就是调用了RealInterceptorChainproceed方法,其实参就是前面创建的Request对象,为了便于理解,我们再看一下RealInterceptorChain

RealInterceptorChain

接下来,看一下 最关键的 RealInterceptorChainproceed中的逻辑:

    //RealInterceptorChain 的 proceed 方法。
    public Response proceed(Request request) throws IOException {
        return this.proceed(request, this.streamAllocation, this.httpCodec, this.connection);
    }


    public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException {
        if(this.index >= this.interceptors.size()) {
            throw new AssertionError();
        } else {
            ++this.calls;
            if(this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
                throw new IllegalStateException("network interceptor " + this.interceptors.get(this.index - 1) + " must retain the same host and port");
            } else if(this.httpCodec != null && this.calls > 1) {
                throw new IllegalStateException("network interceptor " + this.interceptors.get(this.index - 1) + " must call proceed() exactly once");
            } else {
                //关键部分的代码是这几句。
                //(1) 创建一个新的 RealInterceptorChain 对象,这里注意前面说的第5个参数变成了 index+1。
                RealInterceptorChain next = new RealInterceptorChain(this.interceptors, streamAllocation, httpCodec, connection, this.index + 1, request, this.call, this.eventListener, this.connectTimeout, this.readTimeout, this.writeTimeout);
                //(2) 取列表中位于 index 位置的拦截器。
                Interceptor interceptor = (Interceptor)this.interceptors.get(this.index);
                //(3) 调用它的 intercept 方法,并传入新创建的 RealInterceptorChain。
                Response response = interceptor.intercept(next);
                if(httpCodec != null && this.index + 1 < this.interceptors.size() && next.calls != 1) {
                    throw new IllegalStateException("network interceptor " + interceptor + " must call proceed() exactly once");
                } else if(response == null) {
                    throw new NullPointerException("interceptor " + interceptor + " returned null");
                } else if(response.body() == null) {
                    throw new IllegalStateException("interceptor " + interceptor + " returned a response with no body");
                } else {
                    return response;
                }
            }
        }
    }

忽略不重要的代码,RealInterceptorChain关键的代码有三个步骤:

  • 创建一个新的RealInterceptorChain对象,这里注意前面说的第5个参数变成了 index+1
  • 取列表中位于index位置的拦截器。
  • 调用它的intercept方法,并传入新创建的RealInterceptorChain

而每个Interceptor在执行完它的操作之后,就会调用RealInterceptorChainproceed方法,使得下一个Interceptorintercept方法可以被执行,以第一个拦截器RetryAndFollowUpInterceptor为例:

    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        RealInterceptorChain realChain = (RealInterceptorChain) chain;
        Call call = realChain.call();
        EventListener eventListener = realChain.eventListener();
        this.streamAllocation = new StreamAllocation(this.client.connectionPool(), this.createAddress(request.url()), call, eventListener, this.callStackTrace);
        int followUpCount = 0;
        Response priorResponse = null;
        //这是一个 While 循环,知道没有达到终止的条件就一直重试。
        while(!this.canceled) {
            boolean releaseConnection = true;

            Response response;
            try {
                //调用下一个拦截器。
                response = realChain.proceed(request, this.streamAllocation, (HttpCodec)null, (RealConnection)null);
                releaseConnection = false;
            } catch (RouteException var16) {
                if(!this.recover(var16.getLastConnectException(), false, request)) {
                    throw var16.getLastConnectException();
                }

                releaseConnection = false;
                continue;
            } catch (IOException var17) {
                boolean requestSendStarted = !(var17 instanceof ConnectionShutdownException);
                if(!this.recover(var17, requestSendStarted, request)) {
                    throw var17;
                }

                releaseConnection = false;
                continue;
            } finally {
                if(releaseConnection) {
                    this.streamAllocation.streamFailed((IOException)null);
                    this.streamAllocation.release();
                }

            }

            if(priorResponse != null) {
                response = response.newBuilder().priorResponse(priorResponse.newBuilder().body((ResponseBody)null).build()).build();
            }

            Request followUp = this.followUpRequest(response);
            if(followUp == null) {
                if(!this.forWebSocket) {
                    this.streamAllocation.release();
                }
                //返回了 response,那么整个调用就结束了。
                return response;
            }
            //....
        }

        this.streamAllocation.release();
        throw new IOException("Canceled");
    }

整个递归调用的过程为:

递归调用结果

在整个递归调用过程中,如果有任意一个Interceptorintercept方法返回了而没有调用proceed方法,那么整个调用将会结束,排在它之后的Interceptor将不会被执行。

CallServerInterceptor

CallServerInterceptor是最后一个Interceptor,与之前的拦截器不同,在它的intercept方法中 不会创建一个新的 RealInterceptorChain ,而是直接返回了Response,使得整个递归调用一步步向上返回。

    public Response intercept(Chain chain) throws IOException {
        //发起请求..
        Response response = responseBuilder.request(request).handshake(streamAllocation.connection().handshake()).sentRequestAtMillis(sentRequestMillis).receivedResponseAtMillis(System.currentTimeMillis()).build();
        realChain.eventListener().responseHeadersEnd(realChain.call(), response);
        int code = response.code();
        if(this.forWebSocket && code == 101) {
            response = response.newBuilder().body(Util.EMPTY_RESPONSE).build();
        } else {
            response = response.newBuilder().body(httpCodec.openResponseBody(response)).build();
        }
        //只有两种选择,抛出异常或者返回结果,不会进行下一步的调用。
        if((code == 204 || code == 205) && response.body().contentLength() > 0L) {
            throw new ProtocolException("HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
        } else {
            return response;
        }
    }

3.4.3 结束任务

回到最开始的代码,当getResponseWithInterceptorChain返回之后,最后通过dispatcher.finish(RealCall call)方法结束任务:

    void finished(RealCall call) {
        this.finished(this.runningSyncCalls, call, false);
    }

    private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
        int runningCallsCount;
        Runnable idleCallback;
        synchronized(this) {
            //从 runningSyncCalls 移除它。
            if (!calls.remove(call)) {
                throw new AssertionError("Call wasn't in-flight!");
            }
            //false 不执行。
            if (promoteCalls) {
                this.promoteCalls();
            }

            runningCallsCount = this.runningCallsCount();
            idleCallback = this.idleCallback;
        }
        //如果当前已经没有可以执行的任务,那么调用 idleCallback.run() 方法。
        if (runningCallsCount == 0 && idleCallback != null) {
            idleCallback.run();
        }

    }

    public synchronized int runningCallsCount() {
        return this.runningAsyncCalls.size() + this.runningSyncCalls.size();
    }

四、小结

可以看到,对于 同步请求,这个函数的调用都是在.execute()调用的线程执行的,其实 异步请求 的核心逻辑和同步请求是相同的,只不过加入了线程的管理。不知不觉说得又有点长了,还是把 异步请求 的分析放在下一篇文章里面讲吧,顺便结合OkHttp中对于线程的管理,这一章只是一个入门,关键是让大家对整个OkHttp请求的流程有个大概的印象,特别是调用链的模式。


更多文章,欢迎访问我的 Android 知识梳理系列:

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

推荐阅读更多精彩内容