Android網(wǎng)絡(luò)之HttpUrlConnection和Socket關(guān)系解析

個人博客地址 http://dandanlove.com/

多年以前Android的網(wǎng)絡(luò)請求只有Apache開源的HttpClient和JDK的HttpUrlConnection,近幾年隨著OkHttp的流行Android在高版本的SDK中加入了OkHttp停做。但在Android官方文檔中推薦使用HttpUrlConnection并且其會一直被維護(hù)晤愧,所以在學(xué)習(xí)Android網(wǎng)絡(luò)相關(guān)的知識時我們隊HttpUrlConnection要有足夠的了解。蛉腌。官份。只厘。

前幾天因為時間的關(guān)系只畫了圖 HttpUrlConnection和Socket的關(guān)系圖 ,本來說好的第二天續(xù)寫舅巷,結(jié)果一直拖到了周末晚上羔味。幸好時間還來的及,趁這短時間影響深刻钠右,將自己解析代碼過程記錄下來赋元。(PS:解析的過程有什么地方不明白的可以看看 HttpUrlConnection和Socket的關(guān)系圖 圖中講出的過程和這次代碼分析的過程是一樣的,只不過代碼講述更加詳細(xì)飒房。所有源碼都是來自Android4.0.4搁凸。有代碼就有真相:萏骸)

類結(jié)構(gòu)圖

先給大家展示一張相關(guān)類的結(jié)構(gòu)圖:


HttpUrlConnection和Socket關(guān)系類圖

HttpUrlConnection 使用

在分析代碼的時候我希望首相腦海中要有一個URL的請求過程护糖。
這是我在網(wǎng)上摘的一個HttpUrlConnection請求小Demo:

public class EsmTest {
    /**
     * 通過HttpURLConnection模擬post表單提交
     * @throws Exception
     */
    @Test
    public void sendEms() throws Exception {
        String wen = "MS2201828";
        String btnSearch = "EMS快遞查詢";
        URL url = new URL("http://www.kd185.com/ems.php");
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("POST");// 提交模式
        // conn.setConnectTimeout(10000);//連接超時 單位毫秒
        // conn.setReadTimeout(2000);//讀取超時 單位毫秒
        conn.setDoOutput(true);// 是否輸入?yún)?shù)
        StringBuffer params = new StringBuffer();
        // 表單參數(shù)與get形式一樣
        params.append("wen").append("=").append(wen).append("&")
              .append("btnSearch").append("=").append(btnSearch);
        byte[] bypes = params.toString().getBytes();
        conn.getOutputStream().write(bypes);// 輸入?yún)?shù)
        InputStream inStream=conn.getInputStream();
        System.out.println(new String(StreamTool.readInputStream(inStream), "gbk"));
 
    }

    public void sendSms() throws Exception{
        String message="貨已發(fā)到";
        message=URLEncoder.encode(message, "UTF-8");
        System.out.println(message);
        String path ="http://localhost:8083/DS_Trade/mobile/sim!add.do?message="+message;
        URL url =new URL(path);
        HttpURLConnection conn = (HttpURLConnection)url.openConnection();
        conn.setConnectTimeout(5*1000);
        conn.setRequestMethod("GET");
        InputStream inStream = conn.getInputStream();    
        byte[] data = StreamTool.readInputStream(inStream);
        String result=new String(data, "UTF-8");
        System.out.println(result);
    }
}

URL產(chǎn)生請求

/*****************URL.java************************/
/**
 * 創(chuàng)建一個新的URL實例
 */
public URL(String spec) throws MalformedURLException {
    this((URL) null, spec, null);
}
public URL(URL context, String spec, URLStreamHandler handler) throws MalformedURLException {
    if (spec == null) {
        throw new MalformedURLException();
    }
    if (handler != null) {
        streamHandler = handler;
    }
    spec = spec.trim();
    //獲取url的協(xié)議類型,http,https
    protocol = UrlUtils.getSchemePrefix(spec);
    //請求開始部分的位置
    int schemeSpecificPartStart = protocol != null ? (protocol.length() + 1) : 0;
    if (protocol != null && context != null && !protocol.equals(context.protocol)) {
        context = null;
    }
    if (context != null) {
        set(context.protocol, context.getHost(), context.getPort(), context.getAuthority(),
                context.getUserInfo(), context.getPath(), context.getQuery(),
                context.getRef());
        if (streamHandler == null) {
            streamHandler = context.streamHandler;
        }
    } else if (protocol == null) {
        throw new MalformedURLException("Protocol not found: " + spec);
    }
    //這里為重點嚼松,獲取StreamHandler
    if (streamHandler == null) {
        setupStreamHandler();
        if (streamHandler == null) {
            throw new MalformedURLException("Unknown protocol: " + protocol);
        }
    }
    try {
        //對url的處理
        streamHandler.parseURL(this, spec, schemeSpecificPartStart, spec.length());
    } catch (Exception e) {
        throw new MalformedURLException(e.toString());
    }
}

void setupStreamHandler() {
    //從緩存中獲取
    streamHandler = streamHandlers.get(protocol);
    if (streamHandler != null) {
        return;
    }
    //通過工廠方法創(chuàng)建
    if (streamHandlerFactory != null) {
        streamHandler = streamHandlerFactory.createURLStreamHandler(protocol);
        if (streamHandler != null) {
            streamHandlers.put(protocol, streamHandler);
            return;
        }
    }
    //在同名包下檢測一個可用的hadnler
    String packageList = System.getProperty("java.protocol.handler.pkgs");
    ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
    if (packageList != null && contextClassLoader != null) {
        for (String packageName : packageList.split("\\|")) {
            String className = packageName + "." + protocol + ".Handler";
            try {
                Class<?> c = contextClassLoader.loadClass(className);
                streamHandler = (URLStreamHandler) c.newInstance();
                if (streamHandler != null) {
                    streamHandlers.put(protocol, streamHandler);
                }
                return;
            } catch (IllegalAccessException ignored) {
            } catch (InstantiationException ignored) {
            } catch (ClassNotFoundException ignored) {
            }
        }
    }
    //如果還是沒有創(chuàng)建成功那么new一個handler
    if (protocol.equals("file")) {
        streamHandler = new FileHandler();
    } else if (protocol.equals("ftp")) {
        streamHandler = new FtpHandler();
    } else if (protocol.equals("http")) {
        streamHandler = new HttpHandler();
    } else if (protocol.equals("https")) {
        streamHandler = new HttpsHandler();
    } else if (protocol.equals("jar")) {
        streamHandler = new JarHandler();
    }
    if (streamHandler != null) {
        streamHandlers.put(protocol, streamHandler);
    }
}

/**
 * streamHandler實現(xiàn)類為HttpURLConnectionImpl
 */
public final class HttpHandler extends URLStreamHandler {

    @Override protected URLConnection openConnection(URL u) throws IOException {
        return new HttpURLConnectionImpl(u, getDefaultPort());
    }

    @Override protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
        if (url == null || proxy == null) {
            throw new IllegalArgumentException("url == null || proxy == null");
        }
        return new HttpURLConnectionImpl(url, getDefaultPort(), proxy);
    }

    @Override protected int getDefaultPort() {
        return 80;
    }
}

創(chuàng)建連接請求準(zhǔn)備

/*****************HttpURLConnectionImpl.java start************************/
/**
 * 無論是get還是post都需要建立連接
 * post
 */
@Override 
public final OutputStream getOutputStream() throws IOException {
    connect();
    OutputStream result = httpEngine.getRequestBody();
    if (result == null) {
        throw new ProtocolException("method does not support a request body: " + method);
    } else if (httpEngine.hasResponse()) {
        throw new ProtocolException("cannot write request body after response has been read");
    }
    return result;
}
/**
 * 無論是get還是post都需要建立連接
 * get
 */
@Override 
public final InputStream getInputStream() throws IOException {
    if (!doInput) {
        throw new ProtocolException("This protocol does not support input");
    }
    //獲取http響應(yīng)
    HttpEngine response = getResponse();
    //返回400拋異常
    if (getResponseCode() >= HTTP_BAD_REQUEST) {
        throw new FileNotFoundException(url.toString());
    }
    InputStream result = response.getResponseBody();
    if (result == null) {
        throw new IOException("No response body exists; responseCode=" + getResponseCode());
    }
    return result;
}
private HttpEngine getResponse() throws IOException {
    //初始化http引擎
    initHttpEngine();
    //是否有響應(yīng)頭信息
    if (httpEngine.hasResponse()) {
        return httpEngine;
    }
    try {
        while (true) {
            //發(fā)送請求
            httpEngine.sendRequest();
            httpEngine.readResponse();
            //為下次請求做準(zhǔn)備
            Retry retry = processResponseHeaders();
            if (retry == Retry.NONE) {
                httpEngine.automaticallyReleaseConnectionToPool();
                break;
            }
            //如果一個請求不能完成那么接下來為下次請求做準(zhǔn)備
            String retryMethod = method;
            OutputStream requestBody = httpEngine.getRequestBody();

            /*
             * Although RFC 2616 10.3.2 specifies that a HTTP_MOVED_PERM
             * redirect should keep the same method, Chrome, Firefox and the
             * RI all issue GETs when following any redirect.
             */
            int responseCode = getResponseCode();
            if (responseCode == HTTP_MULT_CHOICE || responseCode == HTTP_MOVED_PERM
                    || responseCode == HTTP_MOVED_TEMP || responseCode == HTTP_SEE_OTHER) {
                retryMethod = HttpEngine.GET;
                requestBody = null;
            }

            if (requestBody != null && !(requestBody instanceof RetryableOutputStream)) {
                throw new HttpRetryException("Cannot retry streamed HTTP body",
                        httpEngine.getResponseCode());
            }

            if (retry == Retry.DIFFERENT_CONNECTION) {
                httpEngine.automaticallyReleaseConnectionToPool();
            }

            httpEngine.release(true);

            httpEngine = newHttpEngine(retryMethod, rawRequestHeaders,
                    httpEngine.getConnection(), (RetryableOutputStream) requestBody);
        }
        return httpEngine;
    } catch (IOException e) {
        httpEngineFailure = e;
        throw e;
    }
}

@Override 
public final void connect() throws IOException {
    initHttpEngine();
    try {
        httpEngine.sendRequest();
    } catch (IOException e) {
        httpEngineFailure = e;
        throw e;
    }
}
/**
 * 無論是get還是post都需要初始化Http引擎
 */
private void initHttpEngine() throws IOException {
    if (httpEngineFailure != null) {
        throw httpEngineFailure;
    } else if (httpEngine != null) {
        return;
    }
    connected = true;
    try {
        if (doOutput) {
            if (method == HttpEngine.GET) {
                //如果要寫入那么這就是一個post請求
                method = HttpEngine.POST;
            } else if (method != HttpEngine.POST && method != HttpEngine.PUT) {
                //如果你要寫入嫡良,那么不是post請求也不是put請求那就拋異常吧。
                throw new ProtocolException(method + " does not support writing");
            }
        }
        httpEngine = newHttpEngine(method, rawRequestHeaders, null, null);
    } catch (IOException e) {
        httpEngineFailure = e;
        throw e;
    }
}

創(chuàng)建Socket連接

/********************HttpEngine.java**************/
/**
 * Figures out what the response source will be, and opens a socket to that
 * source if necessary. Prepares the request headers and gets ready to start
 * writing the request body if it exists.
 */
public final void sendRequest() throws IOException {
    if (responseSource != null) {
        return;
    }
    //填充請求頭和cookies
    prepareRawRequestHeaders();
    //初始化響應(yīng)資源献酗,計算緩存過期時間寝受,判斷是否讀取緩沖中數(shù)據(jù),或者進(jìn)行網(wǎng)絡(luò)請求
    //responseSource = ?
    //CACHE:返回緩存信息
    //CONDITIONAL_CACHE:進(jìn)行網(wǎng)絡(luò)請求如果網(wǎng)絡(luò)請求結(jié)果無效則使用緩存
    //NETWORK:返回網(wǎng)絡(luò)請求
    initResponseSource();
    //請求行為記錄
    if (responseCache instanceof HttpResponseCache) {
        ((HttpResponseCache) responseCache).trackResponse(responseSource);
    }
    //請求資源需要訪問網(wǎng)絡(luò)罕偎,但請求頭部禁止請求很澄。在這種情況下使用BAD_GATEWAY_RESPONSE替代
    if (requestHeaders.isOnlyIfCached() && responseSource.requiresConnection()) {
        if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
            IoUtils.closeQuietly(cachedResponseBody);
        }
        this.responseSource = ResponseSource.CACHE;
        this.cacheResponse = BAD_GATEWAY_RESPONSE;
        RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(cacheResponse.getHeaders());
        setResponse(new ResponseHeaders(uri, rawResponseHeaders), cacheResponse.getBody());
    }

    if (responseSource.requiresConnection()) {
        //socket網(wǎng)絡(luò)連接
        sendSocketRequest();
    } else if (connection != null) {
        HttpConnectionPool.INSTANCE.recycle(connection);
        connection = null;
    }
}
private void sendSocketRequest() throws IOException {
    if (connection == null) {
        connect();
    }
    if (socketOut != null || requestOut != null || socketIn != null) {
        throw new IllegalStateException();
    }
    socketOut = connection.getOutputStream();
    requestOut = socketOut;
    socketIn = connection.getInputStream();
    if (hasRequestBody()) {
        initRequestBodyOut();
    }
}
//打開Socket連接
protected void connect() throws IOException {
    if (connection == null) {
        connection = openSocketConnection();
    }
}
protected final HttpConnection openSocketConnection() throws IOException {
    HttpConnection result = HttpConnection.connect(
            uri, policy.getProxy(), requiresTunnel(), policy.getConnectTimeout());
    Proxy proxy = result.getAddress().getProxy();
    if (proxy != null) {
        policy.setProxy(proxy);
    }
    result.setSoTimeout(policy.getReadTimeout());
    return result;
}

/********************HttpConnection.java**************/
public static HttpConnection connect(URI uri, Proxy proxy, boolean requiresTunnel,
        int connectTimeout) throws IOException {
    //代理直連
    if (proxy != null) {
        Address address = (proxy.type() == Proxy.Type.DIRECT)
                ? new Address(uri)
                : new Address(uri, proxy, requiresTunnel);
        return HttpConnectionPool.INSTANCE.get(address, connectTimeout);
    }
    //尋找代理直連
    ProxySelector selector = ProxySelector.getDefault();
    List<Proxy> proxyList = selector.select(uri);
    if (proxyList != null) {
        for (Proxy selectedProxy : proxyList) {
            if (selectedProxy.type() == Proxy.Type.DIRECT) {
                // the same as NO_PROXY
                // TODO: if the selector recommends a direct connection, attempt that?
                continue;
            }
            try {
                Address address = new Address(uri, selectedProxy, requiresTunnel);
                return HttpConnectionPool.INSTANCE.get(address, connectTimeout);
            } catch (IOException e) {
                // failed to connect, tell it to the selector
                selector.connectFailed(uri, selectedProxy.address(), e);
            }
        }
    }
    //創(chuàng)建一個直連接
    return HttpConnectionPool.INSTANCE.get(new Address(uri), connectTimeout);
}
private HttpConnection(Address config, int connectTimeout) throws IOException {
    this.address = config;
    Socket socketCandidate = null;
    InetAddress[] addresses = InetAddress.getAllByName(config.socketHost);
    for (int i = 0; i < addresses.length; i++) {
        socketCandidate = (config.proxy != null && config.proxy.type() != Proxy.Type.HTTP)
                ? new Socket(config.proxy)
                : new Socket();
        try {
            //DNS解析,socket連接(這塊不做詳細(xì)分析)
            socketCandidate.connect(
                    new InetSocketAddress(addresses[i], config.socketPort), connectTimeout);
            break;
        } catch (IOException e) {
            if (i == addresses.length - 1) {
                throw e;
            }
        }
    }
    this.socket = socketCandidate;
}
/********************HttpConnectionPool.java**************/
public HttpConnection get(HttpConnection.Address address, int connectTimeout)
        throws IOException {
    //首先嘗試重用現(xiàn)有的HTTP連接锨亏。
    synchronized (connectionPool) {
        List<HttpConnection> connections = connectionPool.get(address);
        if (connections != null) {
            while (!connections.isEmpty()) {
                HttpConnection connection = connections.remove(connections.size() - 1);
                if (!connection.isStale()) { // TODO: this op does I/O!
                    // Since Socket is recycled, re-tag before using
                    final Socket socket = connection.getSocket();
                    SocketTagger.get().tag(socket);
                    return connection;
                }
            }
            connectionPool.remove(address);
        }
    }
    //無法找到可以復(fù)用的鏈接是痴怨,創(chuàng)建一個新的鏈接
    return address.connect(connectTimeout);
}

/********************HttpConnection.Address.java**************/
public HttpConnection connect(int connectTimeout) throws IOException {
    return new HttpConnection(this, connectTimeout);
}

輸出內(nèi)容獲取

/********************HttpEngine.java**************/
public final void readResponse() throws IOException {
    //如果有響應(yīng)頭
    if (hasResponse()) {
        return;
    }
    //readResponse之前是否sendRequest
    if (responseSource == null) {
        throw new IllegalStateException("readResponse() without sendRequest()");
    }
    //如果不進(jìn)行網(wǎng)絡(luò)請求直接返回
    if (!responseSource.requiresConnection()) {
        return;
    }
    //刷新請求頭
    if (sentRequestMillis == -1) {
        int contentLength = requestBodyOut instanceof RetryableOutputStream
                ? ((RetryableOutputStream) requestBodyOut).contentLength()
                : -1;
        writeRequestHeaders(contentLength);
    }
    //刷新請求體
    if (requestBodyOut != null) {
        requestBodyOut.close();
        if (requestBodyOut instanceof RetryableOutputStream) {
            ((RetryableOutputStream) requestBodyOut).writeToSocket(requestOut);
        }
    }

    requestOut.flush();
    requestOut = socketOut;
    //解析響應(yīng)頭
    readResponseHeaders();
    responseHeaders.setLocalTimestamps(sentRequestMillis, System.currentTimeMillis());
    //判斷響應(yīng)體類型
    if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
        if (cachedResponseHeaders.validate(responseHeaders)) {
            if (responseCache instanceof HttpResponseCache) {
                ((HttpResponseCache) responseCache).trackConditionalCacheHit();
            }
            //釋放資源
            release(true);
            //返回緩存信息
            setResponse(cachedResponseHeaders.combine(responseHeaders), cachedResponseBody);
            return;
        } else {
            IoUtils.closeQuietly(cachedResponseBody);
        }
    }

    if (hasResponseBody()) {
        maybeCache(); // reentrant. this calls into user code which may call back into this!
    }
    
    initContentStream(getTransferStream());
}
private InputStream getTransferStream() throws IOException {
    if (!hasResponseBody()) {
        return new FixedLengthInputStream(socketIn, cacheRequest, this, 0);
    }

    if (responseHeaders.isChunked()) {
        return new ChunkedInputStream(socketIn, cacheRequest, this);
    }

    if (responseHeaders.getContentLength() != -1) {
        return new FixedLengthInputStream(socketIn, cacheRequest, this,
                responseHeaders.getContentLength());
    }
    return new UnknownLengthHttpInputStream(socketIn, cacheRequest, this);
}
private void initContentStream(InputStream transferStream) throws IOException {
    //是否gzip壓縮
    if (transparentGzip && responseHeaders.isContentEncodingGzip()) {
        responseHeaders.stripContentEncoding();
        responseBodyIn = new GZIPInputStream(transferStream);
    } else {
        responseBodyIn = transferStream;
    }
}

整個請求的響應(yīng)流程大概就是這樣子的忙干,其中的涉及的路由信息獲取器予,DNS解析與緩存,請求的緩存過期等都還沒有仔細(xì)研讀捐迫。不過這些也夠自己消化一段時間了_乾翔,相信自己現(xiàn)在回過頭來看OkHttp的實現(xiàn)應(yīng)該不是那么困難了。

默默肅立的路燈施戴,像等待檢閱的哨兵悼凑,站姿筆挺毯焕,瞪著炯炯有神的眼睛,時刻守護(hù)著這城市的安寧。一排排呈昔、一行行路燈不斷向遠(yuǎn)方延伸,匯聚成了一支支流光溢彩的河流牡辽,偶爾有汽車疾馳而去呜袁,也是一尾尾魚兒在河里游動度迂。夜已深猜揪!~惭墓!

想閱讀作者的更多文章,可以查看我 個人博客 和公共號:

振興書城

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末而姐,一起剝皮案震驚了整個濱河市腊凶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拴念,老刑警劉巖钧萍,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異政鼠,居然都是意外死亡划煮,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門缔俄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弛秋,“玉大人,你說我怎么就攤上這事俐载⌒仿裕” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵遏佣,是天一觀的道長挖炬。 經(jīng)常有香客問我,道長状婶,這世上最難降的妖魔是什么意敛? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮膛虫,結(jié)果婚禮上草姻,老公的妹妹穿的比我還像新娘。我一直安慰自己稍刀,他們只是感情好撩独,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著账月,像睡著了一般综膀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上局齿,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天剧劝,我揣著相機(jī)與錄音,去河邊找鬼抓歼。 笑死讥此,一個胖子當(dāng)著我的面吹牛示绊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播暂论,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼面褐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了取胎?” 一聲冷哼從身側(cè)響起展哭,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎闻蛀,沒想到半個月后匪傍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡觉痛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年役衡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片薪棒。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡手蝎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出俐芯,到底是詐尸還是另有隱情棵介,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布吧史,位于F島的核電站邮辽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏贸营。R本人自食惡果不足惜吨述,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望钞脂。 院中可真熱鬧揣云,春花似錦、人聲如沸芳肌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽亿笤。三九已至,卻和暖如春栋猖,著一層夾襖步出監(jiān)牢的瞬間净薛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工蒲拉, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留肃拜,地道東北人痴腌。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像燃领,于是被迫代替她去往敵國和親士聪。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

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