第一章 基礎(chǔ)(1)

1.1 執(zhí)行請(qǐng)求

HttpClient最基本的功能是執(zhí)行HTTP方法。一個(gè)HTTP方法的執(zhí)行涉及到一個(gè)或多個(gè)HTTP請(qǐng)求和響應(yīng)的交換,這通常是在HttpClient內(nèi)部處理的微姊。用戶需要提供一個(gè)請(qǐng)求對(duì)象牛隅,HttpClient負(fù)責(zé)傳輸這個(gè)請(qǐng)求到目標(biāo)服務(wù)器并返回相對(duì)應(yīng)的響應(yīng)的對(duì)象,如果執(zhí)行不成功船逮,則拋出異常皿曲。

HttpClient API的入口就是HttpClient接口唱逢。

以下是最簡(jiǎn)單的形式的執(zhí)行請(qǐng)求的例子:

    public void chapter1_1() throws IOException {

        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet("http://www.baidu.com");
        CloseableHttpResponse response = httpClient.execute(httpGet);
        try {
            // handle response
        } finally {
            response.close();
        }

    }

1.1.1 HTTP請(qǐng)求

所有的HTTP請(qǐng)求都有一個(gè)請(qǐng)求頭,其中包含方法名屋休、請(qǐng)求URIHTTP協(xié)議版本號(hào)坞古。

HttpClient支持所有HTTP/1.1規(guī)格中定義的HTTP方法,包括:GET,HEAD, POST, PUT, DELETE, TRACEOPTIONS劫樟。每種方法都有與之對(duì)應(yīng)的類(lèi):HttpGet, HttpHead, HttpPost, HttpPut, HttpDelete, HttpTraceHttpOptions 痪枫。

請(qǐng)求URI是請(qǐng)求資源的統(tǒng)一資源描述符(Uniform Resource Identifier)织堂。HTTP請(qǐng)求URI包含協(xié)議(protocol schema)、主機(jī)名(host name)奶陈、可選的端口(optional port)易阳、資源路徑(resource path)、可選的查詢(optional query)和可選的分塊(optional fragment)吃粒。

HttpGet httpGet = new HttpGet(
                "http://www.google.com/search?h1=en&q=httpclient&btnG=Google+Search&aq=f&oq=");

HttpClient 提供 URIBuilder工具類(lèi)來(lái)簡(jiǎn)化創(chuàng)建和修改請(qǐng)求URI:

      URI uri = new URIBuilder()
                .setScheme("http")
                .setHost("www.google.com")
                .setPath("/search")
                .setParameter("h1", "en")
                .setParameter("q", "httpclient")
                .setParameter("btnG", "Google+Search")
                .setParameter("aq", "f")
                .setParameter("oq", "")
                .build();
        HttpGet httpGet1 = new HttpGet(uri);
        System.out.println(httpGet1.getURI());

1.1.2 HTTP響應(yīng)

HTTP響應(yīng)是服務(wù)器接收和處理完請(qǐng)求報(bào)文后所返回的報(bào)文潦俺。報(bào)文的第一行由協(xié)議版本、狀態(tài)碼和及其描述組成徐勃。

    public void chapter1_1_2() throws Exception {
        HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");

        System.out.println(response.getProtocolVersion());
        System.out.println(response.getStatusLine().getStatusCode());
        System.out.println(response.getStatusLine().getReasonPhrase());
        System.out.println(response.getStatusLine().toString());
    }

輸出>

HTTP/1.1
200
OK
HTTP/1.1 200 OK

1.1.3 報(bào)文頭部

HTTP報(bào)文包含一些用于描述報(bào)文的頭部信息事示,如內(nèi)容長(zhǎng)度、內(nèi)容類(lèi)型等等 僻肖。HttpClient提供方法去獲取很魂、添加、移除和列舉這些頭部信息檐涝。

    public void chapter1_1_3() {
        HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
        response.addHeader("Set-Cookie", "c1=a; path=/; domain=localhost");
        response.addHeader("Set-Cookie", "c2=b; path=\"/\"; domain=\"localhost\"");

        Header h1 = response.getFirstHeader("Set-Cookie");
        System.out.println(h1);

        Header h2 = response.getLastHeader("Set-Cookie");
        System.out.println(h2);

        Header[] hs = response.getHeaders("Set-Cookie");
        System.out.println(hs.length);
    }

輸出>

Set-Cookie: c1=a; path=/; domain=localhost
Set-Cookie: c2=b; path="/"; domain="localhost"
2

獲取所有給定類(lèi)型頭部信息最有效方式是使用HeaderIterator接口。

    public void chapter1_1_3_2() {
        HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
        response.addHeader("Set-Cookie", "c1=a; path=/; domain=localhost");
        response.addHeader("Set-Cookie", "c2=b; path=\"/\"; domain=\"localhost\"");

        BasicHeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator("Set-Cookie"));
        while (it.hasNext()) {
            HeaderElement elem = it.nextElement();
            System.out.println(elem.getName() + " = " + elem.getValue());
            NameValuePair[] params = elem.getParameters();
            for (NameValuePair param : params) {
                System.out.println(" " + param);
            }
        }
    }

輸出>

c1 = a
 path=/
 domain=localhost
c2 = b
 path=/
 domain=localhost

1.1.4 HTTP實(shí)體

HTTP報(bào)文可以攜帶與請(qǐng)求或響應(yīng)相關(guān)聯(lián)的內(nèi)容實(shí)體法挨。因?yàn)閷?shí)體是可選的谁榜,并不是所有的請(qǐng)求和響應(yīng)中都包含實(shí)體。使用實(shí)體的請(qǐng)求被稱為包含實(shí)體的請(qǐng)求(entity enclosing request)凡纳。HTTP規(guī)格中定義了2種包含實(shí)體的請(qǐng)求的方法:POSTPUT窃植。
響應(yīng)通常會(huì)包含一個(gè)內(nèi)容實(shí)體。但也是例外荐糜,如HEAD方法的響應(yīng)和 204 No Content, 304 Not Modified, 205 Reset Content 響應(yīng)巷怜。

HttpClient區(qū)分三種實(shí)體類(lèi)型,取決于它們內(nèi)容的來(lái)源:

  • streamed: 內(nèi)容是從流(stream)中獲得暴氏,或聯(lián)機(jī)生成的延塑。這里包含從HTTP響應(yīng)中的實(shí)體。流式實(shí)體(streamed entities)通常是不能重復(fù)的答渔。
  • self-contained: 內(nèi)容是在內(nèi)存里关带。自包含實(shí)體(self-contained entities)通常是可重復(fù)的。這種類(lèi)型的實(shí)體最多用于包含實(shí)體的請(qǐng)求(entity enclosing request)沼撕。
  • wrapping: 內(nèi)容從另一實(shí)體獲得宋雏。

當(dāng)從HTTP響應(yīng)中獲取數(shù)據(jù)流時(shí),這些區(qū)分對(duì)于連接管理來(lái)說(shuō)是重要的务豺。對(duì)于應(yīng)用創(chuàng)建的請(qǐng)求實(shí)體磨总,且僅使用HttpClient來(lái)發(fā)送,streamed 還是 self-contained 的區(qū)別就不重要了笼沥。這種情況下蚪燕,建議把不可重復(fù)的實(shí)體歸為streamed類(lèi)型娶牌,可重復(fù)的為self-contained類(lèi)型。

1.1.4.1 可重復(fù)實(shí)體

可重復(fù)實(shí)體是指它的內(nèi)容能夠被重復(fù)讀取邻薯。只有自包含實(shí)體(self-contained entities)才是可重復(fù)的(如ByteArrayEntityStringEntity)裙戏。

1.1.4.2 使用HTTP實(shí)體

因?yàn)閷?shí)體可表示二進(jìn)制和字符內(nèi)容,所以它是支持字符編碼的厕诡。
實(shí)體被創(chuàng)建的時(shí)機(jī)有 a) 執(zhí)行包含內(nèi)容的請(qǐng)求累榜; b) 請(qǐng)求成功后,響應(yīng)體使用實(shí)體將結(jié)果返回灵嫌。

為了從輸入報(bào)文的實(shí)體中讀取內(nèi)容壹罚,我們可以通過(guò)HttpEntity#getContent()方法獲取輸入流java.io.InputStream, 或者我們可以通過(guò)HttpEntity#writeTo(OutpusStream)方法將其寫(xiě)到另一個(gè)給定的輸出流中。

當(dāng)從響應(yīng)報(bào)文中接收到實(shí)體后寿羞,HttpEntity#getContentTypeHttpEntity#getContentLength方法可以用來(lái)讀取通用的元數(shù)據(jù)猖凛,如Content-TypeContent-Length頭部信息(如果有的話)。Content-Type頭部對(duì)于文本類(lèi)型的多媒體類(lèi)型(如text/plaintext/html)來(lái)說(shuō)可能包含字符編碼的信息绪穆,HttpEntity#getContentEncoding()方法可能用來(lái)讀取該信息辨泳。如果頭部不可用的話,HttpEntity#getContentLength返回-1玖院,HttpEntity#getContentType返回NULL菠红。如果Content-Type可用的話,Header對(duì)象將會(huì)被返回难菌。

當(dāng)給輸出報(bào)文創(chuàng)建實(shí)體试溯,元數(shù)據(jù)必須使用實(shí)體的創(chuàng)建者方法來(lái)創(chuàng)建:

    public void chapter1_1_4() throws IOException {
        StringEntity myEntity = new StringEntity("important message",
                ContentType.create("text/plain", "UTF-8"));

        System.out.println(myEntity.getContentType());
        System.out.println(myEntity.getContentLength());
        System.out.println(EntityUtils.toString(myEntity));
        System.out.println(EntityUtils.toByteArray(myEntity).length);
    }

輸出>

Content-Type: text/plain; charset=UTF-8
17
important message
17

1.1.5 確保釋放底層資源

為了確保正確地的釋放系統(tǒng)資源,我們必須關(guān)閉實(shí)體關(guān)聯(lián)的內(nèi)容流(stream)或者響應(yīng)(response)本身郊酒。

    public void chapter1_1_5() throws Exception {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet("http://www.baidu.com");
        CloseableHttpResponse response = httpClient.execute(httpGet);
        try {
            HttpEntity entity = response.getEntity();
            if (Objects.nonNull(entity)) {
                InputStream inputStream = entity.getContent();
                try {
                    // do something with inputStream
                } finally {
                    inputStream.close();
                }
            }
        } finally {
            response.close();
        }
    }

關(guān)閉內(nèi)容流和關(guān)閉響應(yīng)的區(qū)別在于遇绞,前者通過(guò)消費(fèi)實(shí)體內(nèi)容來(lái)試圖保持底層連接,后者會(huì)立即關(guān)閉并且丟棄該連接燎窘。

請(qǐng)注意HttpEntity#writeTo(OUtputStream)方法也需要確保正確釋放系統(tǒng)資源摹闽。如果這個(gè)方法通過(guò)調(diào)用HttpEntity#getContent方法來(lái)獲取的java.io.InputStream實(shí)例,這也需要在一個(gè)finally子句中將其關(guān)閉荠耽。

我們也可以使用EntityUtils#consume(HttpEntity)方法來(lái)確認(rèn)實(shí)體內(nèi)容被完全消費(fèi)并且底層流被關(guān)閉钩骇。

有一種情況,如果僅需要讀取響應(yīng)中的一部分內(nèi)容铝量,并且報(bào)文剩余內(nèi)容的性能代價(jià)和保持連接的代價(jià)太高的話倘屹,我們可以通過(guò)關(guān)閉響應(yīng)來(lái)結(jié)束內(nèi)容流。

    public void chapter1_1_5_1() throws Exception {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet("http://www.baidu.com");
        CloseableHttpResponse response = httpClient.execute(httpGet);
        try {
            HttpEntity entity = response.getEntity();
            if (Objects.nonNull(entity)) {
                InputStream inputStream = entity.getContent();
                int byteOne = inputStream.read();
                int byteTwo = inputStream.read();
                // Do not need the rest
            }
        } finally {
            response.close();
        }
    }

連接將不會(huì)被重用慢叨,而且被該連接持有的所有資源將會(huì)被正確地釋放纽匙。

1.1.6 消費(fèi)實(shí)體內(nèi)容

消費(fèi)一個(gè)實(shí)體的內(nèi)容推薦的方法是使用HttpEntity#getContent()HttpEntity#writeTo(OutpusStream)方法。HttpClient也包含EntityUtils類(lèi)拍谐,其他包含一些靜態(tài)方法可以理容易地讀取實(shí)體內(nèi)容或信息烛缔。這樣我們就可以使用這個(gè)類(lèi)的方法來(lái)讀取整個(gè)字符或字節(jié)數(shù)據(jù)內(nèi)容馏段,而不是直接操作java.io.InputStream。然而践瓷,EntityUtils的使用是非常不推薦的院喜,除非響應(yīng)實(shí)體是來(lái)源于一個(gè)受信的HTTP服務(wù)器并且內(nèi)容的長(zhǎng)度是有限的。

    public void chapter1_1_6() throws Exception {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet("http://www.baidu.com");
        CloseableHttpResponse response = httpClient.execute(httpGet);
        try {
            HttpEntity entity = response.getEntity();
            if (Objects.nonNull(entity)) {
                long len = entity.getContentLength();
                if (len != -1 && len < 2048) {
                    System.out.println(EntityUtils.toString(entity));
                } else {
                    // Stream content out
                    // content length is too large
                }
            }
        } finally {
            response.close();
        }
    }

在某些情況下晕翠,我們需要重復(fù)讀取實(shí)體內(nèi)容喷舀。此時(shí),實(shí)體內(nèi)容必須用某種方式來(lái)緩沖淋肾,或者在內(nèi)容或者在磁盤(pán)中硫麻。完成緩存的最簡(jiǎn)單方式就是把原始的實(shí)體用BufferedHttpEntity類(lèi)來(lái)包裝。這能夠使原來(lái)的實(shí)體被讀進(jìn)內(nèi)存的緩存區(qū)中樊卓。

CloseableHttpResponse response = <...>
HttpEntity entity = response.getEntity();
if (entity != null) {
  entity = new BufferedHttpEntity(entity);
}

1.1.7 生產(chǎn)實(shí)體內(nèi)容

HttpClient提供一些類(lèi)拿愧,這些類(lèi)用來(lái)高效地將實(shí)體內(nèi)容通過(guò)HTTP連接輸出到流。這些類(lèi)的實(shí)例能夠與包含實(shí)體的請(qǐng)求關(guān)聯(lián), 如POSTPUT碌尔。HttpClient提供一些類(lèi)來(lái)作為最常見(jiàn)的數(shù)據(jù)的容器浇辜,如字符串、字節(jié)數(shù)組唾戚、輸入流和文件:StringEntity奢赂、ByteArrayEntityInputStreamEntityFileEntity颈走。

    public void chapter1_1_7() throws Exception {
        File file = new File("somefile.txt");
        FileEntity entity = new FileEntity(file, ContentType.create("text/plain", "UTF-8"));

        HttpPost httpPost = new HttpPost("http://localhost/action.do");
        httpPost.setEntity(entity);
    }

請(qǐng)注意InputStreamEntity不是可重復(fù)的,因?yàn)樗荒軓牡讓訑?shù)據(jù)流中讀取一次咱士。通常推薦去實(shí)現(xiàn)一個(gè)自定義的HttpEntity立由,使其成為self-contained類(lèi)型的,而不是去使用InputStreamEntity序厉。FileEntity就是一個(gè)很好的例子锐膜。

1.1.7.1 HTML表單

很多應(yīng)用需要去模擬提交HTML表單的過(guò)程,例如弛房,為了登陸或提交輸入數(shù)據(jù)道盏。HttpClient提供實(shí)體類(lèi)UrlEncodedFormEntity來(lái)幫助這個(gè)提交過(guò)程。

    public void chapter1_1_7_1() throws Exception {
        List<NameValuePair> formParams = new ArrayList<>();
        formParams.add(new BasicNameValuePair("param1", "value1"));
        formParams.add(new BasicNameValuePair("param2", "value2"));
        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);
        
        HttpPost httpPost = new HttpPost("http://localhost/handle.do");
        httpPost.setEntity(entity);
    }

UrlEncodedFormEntity會(huì)使用URL編碼來(lái)對(duì)參數(shù)進(jìn)行編碼文捶,并且產(chǎn)生如下內(nèi)容:

param1=value1&param2=value2

1.1.7.2 內(nèi)容分塊(Content chunking)

通常推薦讓HttpClient基于傳輸?shù)腍TTP報(bào)文的屬性去選擇使用最合適的傳輸編碼荷逞。然而,通過(guò)設(shè)置HttpEntity#setChunked()true來(lái)通知HttpClient使用chunk編碼是可能的粹排。當(dāng)然种远,這僅僅只是一個(gè)提示而已。如果使用不支持chunk編碼的HTTP協(xié)議顽耳,如HTTP/1.0坠敷,該值將會(huì)被忽略妙同。

1.1.8 響應(yīng)處理器(Response handlers)

最簡(jiǎn)單并且最方便的方式去處理響應(yīng)是使用ResponseHandler接口,該接口包含handleResponse(HttpResponse response)方法膝迎。該方法完全地把用戶從連接管理中解放出來(lái)粥帚。當(dāng)使用ResponseHandler,HttpClient會(huì)自動(dòng)地的確保連接會(huì)被釋放回給連接管理器限次,不管執(zhí)行請(qǐng)求是否成功或異常芒涡。

    public void chapter1_1_8() throws Exception {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet("http://www.baidu.com");

        ResponseHandler<MyJsonObject> rh = response -> {
            StatusLine statusLine = response.getStatusLine();
            HttpEntity entity = response.getEntity();
            if (statusLine.getStatusCode() >= 300) {
                throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
            }
            if (Objects.isNull(entity)) {
                throw new ClientProtocolException("Response contains no content");
            }

            Gson gson = new GsonBuilder().create();
            ContentType contentType = ContentType.getOrDefault(entity);
            Charset charset = contentType.getCharset();
            Reader reader = new InputStreamReader(entity.getContent(), charset);
            return gson.fromJson(reader, MyJsonObject.class);

        };
        
        MyJsonObject myJsonObject = httpClient.execute(httpGet, rh);

    }
    
    static class MyJsonObject {
        String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市掂恕,隨后出現(xiàn)的幾起案子拖陆,更是在濱河造成了極大的恐慌,老刑警劉巖懊亡,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件依啰,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡店枣,警方通過(guò)查閱死者的電腦和手機(jī)速警,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)鸯两,“玉大人闷旧,你說(shuō)我怎么就攤上這事【疲” “怎么了忙灼?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)钝侠。 經(jīng)常有香客問(wèn)我该园,道長(zhǎng),這世上最難降的妖魔是什么帅韧? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任里初,我火速辦了婚禮,結(jié)果婚禮上忽舟,老公的妹妹穿的比我還像新娘双妨。我一直安慰自己,他們只是感情好叮阅,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布刁品。 她就那樣靜靜地躺著,像睡著了一般浩姥。 火紅的嫁衣襯著肌膚如雪哑诊。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,156評(píng)論 1 308
  • 那天及刻,我揣著相機(jī)與錄音镀裤,去河邊找鬼竞阐。 笑死,一個(gè)胖子當(dāng)著我的面吹牛暑劝,可吹牛的內(nèi)容都是我干的骆莹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼担猛,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼幕垦!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起傅联,我...
    開(kāi)封第一講書(shū)人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤先改,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蒸走,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體仇奶,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年比驻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了该溯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡别惦,死狀恐怖狈茉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情掸掸,我是刑警寧澤氯庆,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站扰付,受9級(jí)特大地震影響点晴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜悯周,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望陪竿。 院中可真熱鬧禽翼,春花似錦、人聲如沸族跛。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)礁哄。三九已至长酗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間桐绒,已是汗流浹背夺脾。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工之拨, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人咧叭。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓蚀乔,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親菲茬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吉挣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

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