OKHttp源碼解析(一)--初階

這段時間老李的新公司要更換網絡層,知道現在主流網絡層的模式是RxJava+Retrofit+OKHttp,所以老李開始研究這三個項目的源代碼,在更換網絡層后,開始分享這個三個項目源碼的分析。*
本篇文章 主要講解OKHttp源碼解析(3.7.0)
OKHttp官網
github地址
本文大體上分為11個部分

本篇文章的主要內容如下:

    1. OkHttp介紹
    1. OkHttp使用
    1. OkHttp流程源碼跟蹤

一、OKHTTP簡介

  • 1.支持HTTP2/SPDY
  • 2.socket自動選擇最好路線,并支持自動重連
  • 3.擁有自動維護的socket連接池,減少握手次數
  • 4.擁有隊列線程池,輕松寫并發
  • 5.擁有Interceptors輕松處理請求與響應(比如透明GZIP壓縮)基于Headers的緩存策略

二、OKHTTP使用:

1、GET請求

  OkHttpClient client = new OkHttpClient();

  Request request = new Request.Builder()
      .url(url)
      .build();

  Response response = client.newCall(request).execute();
  return response.body().string();
}

2、POST請求

   public static final MediaType JSON
    = MediaType.parse("application/json; charset=utf-8");

  OkHttpClient client = new OkHttpClient();

  RequestBody body = RequestBody.create(JSON, json);

  Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();

  Response response = client.newCall(request).execute();

  return response.body().string();
}

三、OKHTTP源碼流程分析

(一)、OKHTTP 同步請求debug代碼跟蹤:

  OkHttpClient client = new OkHttpClient();
  Request request = new Request.Builder()
      .url(url)
      .build();
  Response response = client.newCall(request).execute();

從上面代碼所示,先是new了一個OKHttpClient對象。

1、OKHttpClient類詳解

OKHttpClient類就比較簡單了:

  • 1、里面包含了很多對象,其實OKhttp的很多功能模塊都包裝進這個類,讓這個類單獨提供對外的API,這種外觀模式的設計十分的優雅。外觀模式
  • 2、而內部模塊比較多,就使用了Builder模式(建造器模式)。Builder模式(建造器模式)
  • 3、它的方法只有一個:newCall.返回一個Call對象(一個準備好了的可以執行和取消的請求)。

而大家仔細讀源碼又會發現構造了OKHttpClient后又new了一個Rquest對象。那么咱們就來看下Request,說道Request又不得不提Response。所以咱們一起講了

2、Request、Response類詳解

Request Response

  • 1、Request、Response分別抽象成請求和相應
  • 2、其中Request包括Headers和RequestBody,而RequestBody是abstract的,他的子類是有FormBody (表單提交的)和 MultipartBody(文件上傳),分別對應了兩種不同的MIME類型
    FormBody :"application/x-www-form-urlencoded"
    MultipartBody:"multipart/"+xxx.
  • 3、其中Response包括Headers和RequestBody,而ResponseBody是abstract的,所以他的子類也是有兩個:RealResponseBody和CacheResponseBody,分別代表真實響應和緩存響應。
  • 4、由于RFC協議規定,所以所有的頭部信息不是隨便寫的,request的header與response的header的標準都不同。具體的見 List of HTTP header fields。OKHttp的封裝類Request和Response為了應用程序編程方便,會把一些常用的Header信息專門提取出來,作為局部變量。比如contentType,contentLength,code,message,cacheControl,tag...它們其實都是以name-value對的形勢,存儲在網絡請求的頭部信息中。

根據從上面的GET請求,顯示用builder構建了Request對象,然后執行了OKHttpClient.java的newCall方法,那么咱們就看看這個newCall里面都做什么操作?

  /**
   * Prepares the {@code request} to be executed at some point in the future.
   */
  @Override public Call newCall(Request request) {
    return new RealCall(this, request, false /* for web socket */);
  }

Call是個什么東西,那咱們看下Call這個類

3、Call類詳解

Call: HTTP請求任務封裝
可以說我們能用到的操縱基本上都定義在這個接口里面了,所以也可以說這個類是OKHttp類的核心類了。我們可以通過Call對象來操作請求了。而Call接口內部提供了Factory工廠方法模式(將對象的創建延遲到工廠類的子類去進行,從而實現動態配置)
Call接口提供了內部接口Factory(用于將對象的創建延遲到該工廠類的子類中進行,從而實現動態的配置).

/**
 * A call is a request that has been prepared for execution. A call can be canceled. As this object
 * represents a single request/response pair (stream), it cannot be executed twice.
 */
public interface Call extends Cloneable {
  /** Returns the original request that initiated this call. */
  Request request();

  /**
   * Invokes the request immediately, and blocks until the response can be processed or is in
   * error.
   *
   * <p>To avoid leaking resources callers should close the {@link Response} which in turn will
   * close the underlying {@link ResponseBody}.
   *
   * <pre>@{code
   *
   *   // ensure the response (and underlying response body) is closed
   *   try (Response response = client.newCall(request).execute()) {
   *     ...
   *   }
   *
   * }</pre>
   *
   * <p>The caller may read the response body with the response's {@link Response#body} method. To
   * avoid leaking resources callers must {@linkplain ResponseBody close the response body} or the
   * Response.
   *
   * <p>Note that transport-layer success (receiving a HTTP response code, headers and body) does
   * not necessarily indicate application-layer success: {@code response} may still indicate an
   * unhappy HTTP response code like 404 or 500.
   *
   * @throws IOException if the request could not be executed due to cancellation, a connectivity
   * problem or timeout. Because networks can fail during an exchange, it is possible that the
   * remote server accepted the request before the failure.
   * @throws IllegalStateException when the call has already been executed.
   */
  Response execute() throws IOException;

  /**
   * Schedules the request to be executed at some point in the future.
   *
   * <p>The {@link OkHttpClient#dispatcher dispatcher} defines when the request will run: usually
   * immediately unless there are several other requests currently being executed.
   *
   * <p>This client will later call back {@code responseCallback} with either an HTTP response or a
   * failure exception.
   *
   * @throws IllegalStateException when the call has already been executed.
   */
  void enqueue(Callback responseCallback);

  /** Cancels the request, if possible. Requests that are already complete cannot be canceled. */
  void cancel();

  /**
   * Returns true if this call has been either {@linkplain #execute() executed} or {@linkplain
   * #enqueue(Callback) enqueued}. It is an error to execute a call more than once.
   */
  boolean isExecuted();

  boolean isCanceled();

  /**
   * Create a new, identical call to this one which can be enqueued or executed even if this call
   * has already been.
   */
  Call clone();

  interface Factory {
    Call newCall(Request request);
  }
}

在源碼中,OKHttpClient實現了Call.Factory接口,返回了一個RealCall對象。那我們就來看下RealCall這個類

4、RealCall類詳解

RealCall

  • 1、OkHttpClient的newCall方法里面new了RealCall的對象,但是RealCall的構造函數需要傳入一個OKHttpClient對象和Request對象(PS:第三個參數false表示不是webSokcet).因此RealCall包裝了Request對象。所以RealCall可以很方便地使用這兩個對象。
  • 2、RealCall里面的兩個關鍵方法是:execute 和 enqueue。分別用于同步和異步得執行網絡請求。
  • 3、RealCall還有一個重要方法是:getResponseWithInterceptorChain,添加攔截器,通過攔截器可以將一個流式工作分解為可配置的分段流程,既增加了靈活性也實現了解耦,關鍵還可以自有配置,非常完美。

所以client.newCall(request).execute();實際上執行的是RealCall的execute方法,現在咱們再回來看下RealCall的execute的具體實現

    @Override
    public Response execute() throws IOException {
        synchronized (this) {
            if (executed) throw new IllegalStateException("Already Executed");
            executed = true;
        }
        captureCallStackTrace();
        try {
            client.dispatcher().executed(this);
            Response result = getResponseWithInterceptorChain();
            if (result == null) throw new IOException("Canceled");
            return result;
        } finally {
            client.dispatcher().finished(this);
        }
    }

首先是

        synchronized (this) {
            if (executed) throw new IllegalStateException("Already Executed");
            executed = true;
        }

判斷call是否執行過,可以看出每個Call對象只能使用一次原則。然后調用了captureCallStackTrace()方法。
RealCall.java

  private void captureCallStackTrace() {
    Object callStackTrace = Platform.get().getStackTraceForCloseable("response.body().close()");
    retryAndFollowUpInterceptor.setCallStackTrace(callStackTrace);
  }

RealCall的captureCallStackTrace() 又調用了Platform.get().getStackTraceForCloseable()

public class Platform {
  public static Platform get() {
    return PLATFORM;
  }
  /**
   * Returns an object that holds a stack trace created at the moment this method is executed. This
   * should be used specifically for {@link java.io.Closeable} objects and in conjunction with
   * {@link #logCloseableLeak(String, Object)}.
   */
  public Object getStackTraceForCloseable(String closer) {
    if (logger.isLoggable(Level.FINE)) {
      return new Throwable(closer); // These are expensive to allocate.
    }
    return null;
  }
}

其實是調用AndroidPlatform. getStackTraceForCloseable(String closer)方法。這里就不詳細說了,后面詳細說。
然后retryAndFollowUpInterceptor.setCallStackTrace(),在這個方法里面什么都沒做就是set一個object進去

public final class RetryAndFollowUpInterceptor implements Interceptor {
  public void setCallStackTrace(Object callStackTrace) {
    this.callStackTrace = callStackTrace;
  }
}

綜上所示captureCallStackTrace()這個方法其實是捕獲了這個請求的StackTrace。
然后進入了第一個核心類---Dispatcher的的execute方法了,由于下面是進入了關鍵部分,所以重點講解下,代碼如何:

    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }

看下OKHttpClient的dispatcher()方法的具體內容如下圖

 //OKHttpClient.java
  public Dispatcher dispatcher() {
    return dispatcher;
  }

大家發現client.dispatcher()返回的是Dispatcher對象,那么這個Dispatcher對象是何時創建的那?在OkHttpClient.java里面Build類里面的構造函數里面,如下圖

//OkHttpClient.java
public static final class Builder {
   //其它代碼先忽略掉
    public Builder() {
      dispatcher = new Dispatcher();
      //其它代碼先忽略掉
    }
}

所以默認執行Builder()放到時候就創建了一個Dispatcher。那么咱們看下dispatcher里面的execute()是如何處理的

  /** Used by {@code Call#execute} to signal it is in-flight. */
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

里面發現是runningSyncCalls執行了add方法莫非runningSyncCalls是個list,咱們查看dispatcher里面怎么定義runningSyncCalls的。

  /** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

原來runningSyncCalls是雙向隊列啊,突然發現Dispatcher里面定義了三個雙向隊列,看下注釋,我們大概能明白readyAsyncCalls 是一個存放了等待執行任務Call的雙向隊列,runningAsyncCalls是一個存放異步請求任務Call的雙向任務隊列,runningSyncCalls是一個存放同步請求的雙向隊列。關于隊列咱們在下篇文章里面詳細介紹。

執行完client.dispatcher().executed(this);要走到getResponseWithInterceptorChain();方法了里面了,看下這個方法是具體做什么的?

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    //添加開發者應用層自定義的Interceptor
    interceptors.addAll(client.interceptors());
    //這個Interceptor是處理請求失敗的重試,重定向    
    interceptors.add(retryAndFollowUpInterceptor);
    //這個Interceptor工作是添加一些請求的頭部或其他信息
    //并對返回的Response做一些友好的處理(有一些信息你可能并不需要)
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //這個Interceptor的職責是判斷緩存是否存在,讀取緩存,更新緩存等等
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //這個Interceptor的職責是建立客戶端和服務器的連接
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      //添加開發者自定義的網絡層攔截器
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));
    //一個包裹這request的chain
    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    //把chain傳遞到第一個Interceptor手中
    return chain.proceed(originalRequest);
  }

發現 new了一個ArrayList,然后就是不斷的add,后面 new了 RealInterceptorChain對象,最后調用了chain.proceed()方法。先看下RealInterceptorChain的構造函數。

 public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
      HttpCodec httpCodec, RealConnection connection, int index, Request request) {
    this.interceptors = interceptors;
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpCodec = httpCodec;
    this.index = index;
    this.request = request;
  }

發現什么都沒做就是做了賦值操作,后面跟蹤下chain.proceed()方法
由于Interceptor是個接口,所以應該是具體實現類RealInterceptorChain的proceed實現

public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    Request request();

    Response proceed(Request request) throws IOException;

    Connection connection();
  }
}
public final class RealInterceptorChain implements Interceptor.Chain{
  Response intercept(Chain chain) throws IOException;

      @Override 
      public Response proceed(Request request) throws IOException {
        return proceed(request, streamAllocation, httpCodec, connection);
      }

    public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
                            RealConnection connection) throws IOException {
        if (index >= interceptors.size()) throw new AssertionError();

        calls++;

        // If we already have a stream, confirm that the incoming request will use it.
        if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
            throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
                    + " must retain the same host and port");
        }

        // If we already have a stream, confirm that this is the only call to chain.proceed().
        if (this.httpCodec != null && calls > 1) {
            throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
                    + " must call proceed() exactly once");
        }

        // Call the next interceptor in the chain.
        RealInterceptorChain next = new RealInterceptorChain(
                interceptors, streamAllocation, httpCodec, connection, index + 1, request);
        Interceptor interceptor = interceptors.get(index);
        Response response = interceptor.intercept(next);

        // Confirm that the next interceptor made its required call to chain.proceed().
        if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
            throw new IllegalStateException("network interceptor " + interceptor
                    + " must call proceed() exactly once");
        }

        // Confirm that the intercepted response isn't null.
        if (response == null) {
            throw new NullPointerException("interceptor " + interceptor + " returned null");
        }

        return response;
    }

}

由于在構造RealInterceptorChain對象時候httpCodec直接賦予了null,所以下面代碼直接略過。

   // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

然后看到在proceed方面里面又new了一個RealInterceptorChain類的next對象,溫馨提示下,里面的streamAllocation, httpCodec, connection都是null,所以這個next對象和chain最大的區別就是index屬性值不同chain是0.而next是1,然后取interceptors下標為1的對象的interceptor。由從上文可知,如果沒有開發者自定義的Interceptor時,首先調用的RetryAndFollowUpInterceptor,如果有開發者自己定義的interceptor則調用開發者interceptor。

這里重點說一下,由于后面的interceptor比較多,且涉及的也是重要的部分,而咱們這里主要是講流程,所以這里就不詳細和大家說了,由后面再詳細講解,后面的流程是在每一個interceptor的intercept方法里面都會調用chain.proceed()從而調用下一個interceptorintercept(next)方法,這樣就可以實現遍歷getResponseWithInterceptorChain里面interceptors的item,實現遍歷循環,縮減后的代碼如下:

  //RetryAndFollowUpInterceptor.java
public Response intercept(Chain chain) throws IOException {
 //忽略部分代碼
 response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
 //忽略部分代碼
}
 
//BridgeInterceptor.java
public Response intercept(Chain chain) throws IOException {
  //忽略部分代碼
  Response networkResponse = chain.proceed(requestBuilder.build());
  //忽略部分代碼
}
//CacheInterceptor.java
public Response intercept(Chain chain) throws IOException {
   //忽略部分代碼
   networkResponse = chain.proceed(networkRequest);
   //忽略部分代碼
}
//ConnectInterceptor.java
public Response intercept(Chain chain) throws IOException {
     //忽略部分代碼
     return realChain.proceed(request, streamAllocation, httpCodec, connection);
}

讀過源碼我們知道getResponseWithInterceptorChain里面interceptors的最后一個item是CallServerInterceptor.java,最后一個Interceptor(即CallServerInterceptor)里面是直接返回了response 而不是進行繼續遞歸,具體里面是通過OKio實現的,具體代碼,等后面再詳細說明,CallServerInterceptor返回response后返回給上一個interceptor,一般是開發者自己定義的networkInterceptor,然后開發者自己的networkInterceptor把他的response返回給前一個interceptor,依次以此類推返回給第一個interceptor,這時候又回到了realCall里面的execute()里面了,代碼如下:

  @Override 
  public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }
最后把response返回給get請求的返回值。至此整體GET請求的大體流程就已經結束了。(PS:最后別忘記走client.dispatcher().finished(this))

大體流程如下圖:

image.png

(二)、OKHTTP 異步請求debug代碼跟蹤:

OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  Response response = client.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
        }
        @Override
        public void onResponse(Call call, Response response) throws IOException {
        }
    });

前面和同步一樣new了一個OKHttp和Request。這塊和同步一樣就不說了,那么說說和同步不一樣的地方,后面異步進入enqueue()方法

   //RealCall.java
  @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

由于executed默認為false,所以先進行判斷是否為true,為true則直接跑異常,沒有則設置為true,可以看出executed這個是一個標志,標志這個請求是否已經正在請求中,合同步一樣先調用了captureCallStackTrace();然后調用 client.dispatcher().enqueue(new AsyncCall(responseCallback));client.dispatcher()返回的是Dispatcher對象所以實際調用的是Dispatcher的enqueue(),那么咱們進入源碼看下

  //Dispatcher.java
  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;

  synchronized void enqueue(AsyncCall call) {
  //如果正在執行的請求小于設定值即64,并且請求同一個主機的request小于設定值即5
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      //添加到執行隊列,開始執行請求
      runningAsyncCalls.add(call);
      //獲得當前線程池,沒有則創建一個
      executorService().execute(call);
    } else {
      //添加到等待隊列中
      readyAsyncCalls.add(call);
    }
  }

根據源碼和注釋大家可以看到如果正在執行的異步請求小于64,并且請求同一個主機小于5的時候就先往正在運行的隊列里面添加這個call,然后用線程池去執行這個call,否則就把他放到等待隊列里面。執行這個call的時候,自然會去走到這個call的run方法,那么咱們看下AsyncCall.java這個類,而AsyncCall.java又繼承自NamedRunnable.java咱們就一起看下他們的源碼

public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }

  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}

final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }

    String host() {
      return originalRequest.url().host();
    }

    Request request() {
      return originalRequest;
    }

    RealCall get() {
      return RealCall.this;
    }

    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }

上面看到NamedRunnable的構造方法設置了name在的run方法里面設定為當前線程的name,而NamedRunnable的run方法里面調用了它自己的抽象方法execute,由此可見NamedRunnable的作用就是設置了線程的name,然后回調子類的execute方法,那么我們來看下AsyncCall的execute方法。貌似好像又回到了之前同步的getResponseWithInterceptorChain()里面,根據返回的response來這只callback回調。所以我們得到了OKHTTP的大體流程,如下圖:

OKHTTP大體流程.png

三、OKHTTP類詳解

大體核心類主要下圖:


核心類.png

最后給大家看一下整體的流程圖

流程.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,125評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,506評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,402評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,934評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,168評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,690評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,596評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,027評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,398評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,743評論 2 370

推薦閱讀更多精彩內容