Android网络编程之-HTTP协议详解

PS:
简书的网址真不是给人看的。。。我单独开了一个网址可以重定向到我的简书主页。
博客地址:flutterall.com

WHY

Http协议作为网络编程的核心是重中之重。网络上关于Http协议的在Android中的应用有很多,但是很多仅仅讲到了一些Http报文。这当然是一个知识点,但是如果没有全局的了解,仅仅熟悉Http报文很大程度上是一知半解,真的是书到用时方恨少。这篇文章呢能,是我对Http知识在Android应用中的必须了解的一些总结,在这里与大家分享一下。

WHAT

看完这篇文章你能了解到什么?

  • URL的构成
  • Http报文
    • Http报文的组成
    • Http报文的元素解释
    • Http请求方法简述
  • 一个Http报文的传输流程
    • 如何建立数据传输通道
    • 发送请求与接收响应的流程
  • 周边知识拓展

本篇文章可用一句话总结:Http数据的发送与接收。分为三大部分讲解。

  • 发送的数据源
  • 如何发送
  • 如何解析收到的数据

HOW

URL

URL

生活中得任何一种东西都有一个它的地址。无论是房屋编号、邮箱编号、手机号码、身份证号码等。根据这个编号我们就能拿到这个东西所表示的东西。比如北大地址是:北京市海淀区颐和园路5号。那么我开车到北京市的海淀区的颐和园路的5号就到了北大。
同样,Web中的资源也有它所属的地址。这就是:Uniform Resoure Locator:统一资源定位器,用来标识某个web资源(可以是音频、视频、文本等)在web服务器上的位置。通过这个URL我们可以知道:1.这个网络资源的具体位置。2.访问这个网络资源的方式。

  • 开车-->访问方式
  • 北大地址-->具体位置

URL构成分析

我们在浏览器程序中输入http://www.baidu.com/img/logo.gif 即可看到百度的Logo。这一个小小的请求即包含了一个完整的HTTP生态链。这里引用外国的一个图文来看看一个URL是如果构成的:

URL构成

下面我们一起逐个攻破:

Protocol

也可以用Scheme来表示其意义。它表示解析URL的程序以什么形式去解析URL。比如:Http(超文本传输协议),需要以http开头,不区分大小写。紧接着分号,然后是URL的其他部分。故HTTP协议总体来看长这个样子http://host/index.html。 常见的协议很多,我这里简单列举几个:

协议名称 简介
HTTP 超文本传输协议,大概格式是这样:http://<host>:<port>/<path>?<query>#<flagment>。
HTTPS 可以认为是HTTP+SSL。用来进行加密网络数据的传输
FTP 文件传输协议。详情在百度上Google一下
SMTP 简单邮件传输协议。详情在百度上Google一下

Domain Name

就是存放web资源的老窝。注意!这里讲的是老窝是家,不是家里的某个具体位置。这个东西用来标志WEB资源所在的服务器的地址。我们要访问的web资源都有属于他的IP地址,通过这个IP地址建立对应的TCP连接进而进行访问资源。但是,IP地址不易记忆。故,为了方便W资源的访问,我们用一个我们可以方便记忆的地址来代表着这个IP地址,在进行Web资源的访问时,将这个用来记忆的地址转换成对应的IP地址后再进行访问对应的WEB资源。例如,百度主页的IP地址是119.75.217.109,进行Http访问时,需要在浏览器中输入:http://119.75.217.109/ 即可访问百度主页。但是像119这样的东东,很难记得。所以我们可以用http://www.baidu.com 来表示百度主页的IP地址。当访问百度主页的时候,通过域名的HTTP请求会通过DNS将www.baidu.com解析成119.75.217.109,最终对该IP地址上资源的操作。

这就是DNS【Domain Name System】的来源,域名与IP地址是N:1的关系。我们可以在Windows中的命令提示符中通过ping www.baidu.com 得到百度的IP地址。所以当DNS挂了,通过http://www.baidu.com就可能访问不了百度主页了,但是通过IP地址任然是可以访问百度主页的。

获取域名的IP地址

Port

对于HTTP请求而言,其默认端口号是80。代表着,Web服务器正在监视着80端口。

Http的底层实现不一定使用TCP协议,也可以使用其他协议。对于Http底层使用TCP协议的服务器而言,默认监视着80端口。在我的上篇博客Android网络编程之--Socket编程中,提到建立C/S的连接需要IP地址+端口号。IP地址就是上面Domain Name提到的IP地址,端口号就是指Socket连接监听的端口号。

Path

上面提过Domain Name是WEB资源的的家,那么Path就可以表示这个WEB资源在WEB服务器中的具体位置。例如:https://www.baidu.com/img/logo.gif 中百度的logo的Path就是/img/logo.gif

Query

在访问网络资源时,有时候我们可能不太确信访问的资源是否存在,我们可以通过查询WEB资源的方式去访问网络资源。例如,我要访问百度贴吧的“Android吧”:http://tieba.baidu.com/f?&kw=android ,“?”及其后面是Http的查询组件。以查询的方式访问相应的WEB资源。

尝试了一下,将上面链接的Android替换成IOS即可访问IOS吧。http://tieba.baidu.com/f?&kw=ios
依次类推,替换成xiaomi即可访问小米吧........

Parameters

在上面的访问Android吧的栗子中,kw=android就是该Http的参数。通过这个参数可以缩小要访问的web资源的范围。

通常的HTTP查询参数的例子是:key=value作为一个参数,使用“&”符号将每一对参数依次分开拼接在一起。例如:
http://tieba.baidu.com/f?kw=android&fr=ala0&tpl=5

Fragment

代表网页中的一个位置。其右面的字符,就是该位置的标识符。比如,http://www.example.com/index.html#print 就代表网页index.html的print位置。浏览器读取这个URL后,会自动将print位置滚动至可视区域。

当发起一个http://www.example.com/index.html#print 请求时,print参数是不会发送给服务器的。浏览器发送的是http://www.example.com/index.html 请求,服务器会对http://www.example.com/index.html 进行相应的处理返回,然后浏览器会从print的地方开始显示HTML。

URL小结

  • 构成
    http://host/path
  • 意义
    • 表明了Web资源在网络中得位置
    • 摆明了访问该WEB资源的方式。
  • DNS
    域名的存在方便人们访问WEB资源,域名通过DNS可以转换成对应的IP地址进而进行国事访问。

报文

在上一节的URL的讲解中我们知道了一个Web资源到底是如何存在于服务器中得。这个URL就是一条高速公路,我们知道从北京到上海的路线,那么具体如何把北京的货物运输到上海呢?这就涉及到HTTP报文。
Http报文类似于一个货车🚚,在这个🚚中存储着我们要发往上海的货物,以及对该货物的具体描述。
本节重点在两个地方:

  • 货物📦-->HTTP报文

    • 报文的构成
    • 常见报文举例
  • 货车🚚-->TCP连接通道建立以及传输数据

    • 从URL到TCP连接建立

报文的组成

Http报文从整体来看分为请求报文相应报文。下图,是我抓取的某个网络请求以图文的形式展示出来。

客户端发出一个请求报文,服务端返回一个响应报文。

无论是请求报文还是响应报文,其轮廓差不多。主要有三部分组成:start Line;Headers;Body.
进一步根据请求与响应进行细分,如下:

HTTP报文细分

由上面的图示,引出Http请求报文和响应报文标准格式是:

  • 请求报文
    <Request Method>空格<Request URL>空格<Http版本号>
    <Key>:<Value>
    <Key>:<Value>
    不可缺少的空行
    <Entity Body>

  • 响应报文
    <Http Version>空格<Status Code>空格<Reason Phrase>
    <Key>:<Value>
    <Key>:<Value>
    不可缺少的空行
    <Entity Body>

在 ** 请求报文和相应报文的格式中,需要注意几点: **

  • 每一行的结束均以CRLF(回车+换行)表示该行数据已经结束。
  • 对于Header(包含请求的请求正文、响应的消息报头)来说,均以键值对的形式表示该行header的意义,以CRLF表示该行Header的结束。
  • 对于某些请求而言可以没有请求数据(请求数据可以称之为请求体又有人称之为请求正文)。
  • 起始行和Header之间没有空行,但是Header与Body之间一定有空行作为区分。

OK,下面开始我们就依照上面的图示,一个一个讲解报文的每一部分。这是一条遥远的天路,不过我们乘坐的是火箭🚀。没什么理解上的难度,多看两遍就理解了。

这里我们先对HTTP报文的每一部分进行一个简要介绍,先理解大致轮廓,后面我们再逐个细看。

概论请求报文:

请求行

<Request Method>空格<Request URL>空格<Http版本号>
比如:POST /errconf HTTP/1.1

  • Request Method(请求方法)
    请求方法代表着发起Http请求的源端希望对服务端进行的相应操作。常用的请求方法有:POST、GET。
    在这里POST代表着客户端发起的是一个POST请求方法,这个POST请求方法代表着客户端要上传数据到服务端。
  • Request URL
    上面一小节中我们讲过URL,是Web资源的地址。
    在这个例子中/errconf 就是我们要请求的Web资源地址,可以不是
  • Http版本号
    报文所使用的Http版本号,格式为HTTP/x.y。“x.y”就跟我们app的5.1版本和5.2版本一个意思。
请求头

请求头由许多的键值对组成,每个键值对以CRLF(空格换行符)进行隔开,每一个键值对长这样:<Key>:<Value>
Key有很多种,有Http目前提供的现成的,例如上面例子中的:

  • Content-Type
  • Content-Length
  • User-Agent
  • Host

请求头的具体的每一个现成的key的意义在后面会讲的。

请求数据

又称之为:请求体、请求正文。对于目前的GET方法没有请求数据、POST请求而言需要请求数据。其他的有没有请求数据,后面讲解。

概论响应报文:

状态行

用来返回服务端对于客户端的请求结果,也就是对于客户端的请求,服务端发生了上面。格式为:
<Http Version>空格<Status Code>空格<Reason Phrase>
如:
HTTP/1.1 200 OK

  • Http Version
    表示服务器使用的Http版本
  • Status Code(状态码)
    用来给机器看各种客户端比较统一的一个数字,不同的数字表示的意义不同。常见的状态码及其大概意思如下:
    • 200:正常
    • 302/307:重定向
    • 304:服务器的资源没有被修改
    • 404:请求的资源不存在
    • 500:服务器报错了

又称之为响应码

  • Reason Phrase(原因短语)
    对状态码的描述
    例如上面👆例子中"OK"就是一个原因短语,这是给人看的具体该状态码表示的意思。
消息报头

格式与意义与请求报文一一致,已经提供的Header有的只能用在响应报文中,有的只能用在请求报文中,有的两者皆可用。

响应正文

又称之为响应体,就是Http请求想要的东西,可以是文本、音频、视频等等。

OK!现在我们对Http的报文有了大致了解,下面开始每一寸方的解释了。

Request Method(请求方法)

Http的请求方法代表了客户端想对服务器进行的操作,比如:POST、GET、HEAD、PUT、DELETE、TRACE、OPTIONS。

常用的不过于CRUD四个。增:PUT;删:DELETE;改: POST;查: GET。

GET

这个方法可谓是在熟悉不过了,用于向服务器请求数据
下面以访问 http://apicloud.mob.com/wx/article/category/query?key=1c66066891045 为例子

GET请求图文

GET请求没有请求体。对于GET请求的请求参数在URL后面加上一个“?”的后面,参数以key=value的形式。参数与参数之间使用“&”进行连接。
由于GET请求是通过URL传输参数的,所以GET请求可以传输的参数是有限的。关于GET请求的详细,请移步Google。

POST

POST请求也是我们开发中最常用到的,用于向表单提交数据使用的。该请求要传送的数据是放在请求体中的。
在上面讲解报文组成那个例子中就是一个POST请求例子,下面再拿某APP上的POST请求的例子:

POST请求图文

在POST请求中,POST请求的请求参数放在请求体中.服务器根据POST请求体中的参数创建一个对应的页面,然后返回给服务器。
上面的POST请求后创建的页面如下,可能需要翻墙查看结果

POST请求作用结果

GET请求和POST请求已经能够进行基本的增删改查了。就像上面的POST请求就完成了一个类似PUT的动作。但是其他的几个请求也要有所了解。

HEAD

HEAD请求跟GET请求类似,同样没有请求体。与GET请求不一致的是服务器返回时只返回响应头,不返回响应体【言外之意是HEAD请求的响应头与GET一致,只是没有响应体而已】。
HEAD请求常用于:

  • 检查请求的URL是否有效(可以通过响应码进行判断)
  • 根据响应头判断资源是否被篡改了

PUT

POST请求用于向服务器发送数据,而PUT请求用于向服务器的资源中存储数据。对于POST请求而言,服务器会使用POST的请求体的数据创建一个由所请求的URL命名的新文件。除非特殊说明,否则POST请求的请求体只用于创建或修改该资源上。如果请求的URL在服务器中不存在,则根据该请求的主体部分创建一个由该请求URL命名的新文档;如果该URL在服务器中已经存在,则用该主体替代他。一个PUT请求示例:

一个PUT请求示例

DELETE

顾名思义,这是用来删除服务上的文件。

删除服务器上的文件

TRACE

trace英文指:追踪、痕迹的意思。在HTTP请求中使用这个方法用来查看一个请求在经过网关、代理等等到达服务器后查看这个请求最终变成了什么样。
对于这种请求而言,客户端发送一个请求后,服务端在收到请求后会返回给客户端一个响应,该响应正文就是服务器收到的来自客户端的请求报文。

TRACE请求

如上图示例:

  • 服务器发送了一个TRACE请求,该请求经过代理服务器后在请求头中新增了一个Via首部。服务器最终收到的报文是添加了一个Via首部的报文。
  • 由于是TARCE请求,服务器的处理方式是将收到的请求报文内容原样放入响应正文中返回给客户端。
  • 服务器的返回报文中经过代理服务器同样新增了一个Via首部。
  • 客户端收到响应后,根据响应正文可以看到起初发送的请求头与服务器最终收到的请求头。

OPTIONS

这个请求就很简单了。当客户端不确定发起HTTP请求的请求方法时可以使用这个方法询问服务器对该资源的支持的请求方法,例如:是使用PUT还是使用POST操作等等。

OPTIONS
小结:

至此,HTTP的请求报文中的重要的请求方法已经全部讲完了,我们经常使用的也就是POS和GET。不过多了解下也没有坏处,毕竟技多不压身!下面开始讲解请求报文的请求头。

Request Header(请求头,也叫请求首部)

先看下一个网页注册:

注册百度账号

进行网页注册的时候,输入一个一个的key-value形式的值,然后点击注册的时候进行HTTP请求。这里我们是进行了Form表单提交动作,把注册的文本内容发给服务器。那么服务器怎么知道我们发送的内容是文本内容呢?这就需要在请求报文中通过一个标记来标记我这个请求报文是文本编码的内容。通过请求头我们服务器可以知道客户端发送的是什么格式的内容,从而进行相应的处理。

一个请求头示例

我们在网页注册用户名的时候,
在概论请求报文的请求头时,我提过一句“请求头的具体的每一个现成的key的意义在后面会讲的”。这句话有三层含义:(O(∩_∩)O哈哈~,开启语文模式)

  • 其一:从“现成”两字可以看出作者言外之意对于请求头有先人定义好的供我们使用的,也可以自定义一些请求头。
  • 其二:“key”字表明请求头是,key-value形式的。
  • 其三:没有其三。

请求头的语法:
请求头中的每一个请求头的key-value均以key开始后面是冒号“:”,然后是该key对应的value值;每一个key-value均以CRLF标志该key-value的结束标语。最终请求头以一个空行标志请求头的结束。

一些常见的请求头

  • Content-Type
    这个玩意对应的值用来告诉服务器请求报文的媒体类型。例如:
    上面的application/x-www-form-urlencoded是一种文本编码格式,如果是GET请求,则将请求的数据编码为name=value&name2=value2的形式拼接在GET请求的请求URL后面传递给服务器;如果是POST请求,则将请求的数据放到body中发送给服务器。(故:GET请求没有请求体,POST有请求体)。
    这个玩意又称之为MIME Type,反正就是用来标记请求报文的类型。更多MIME类型,详见W3SCHOOL
  • Content-Length
    用来告知接收端报文数据的大小。这个玩意不仅能用来请求头中,还可以用在响应头中用来告诉客户端响应正文的大小。比如:我要下载一个文件,客户端与服务端建立连接后,客户端要进行流操作下载文件,这是客户端可能就需要Content-Length的值判断磁盘中是否有足够的空间大小去存储要下载的文件。
  • User-Agent
    这个就用的更多了。用来告知服务端发起请求的客户端的设备信息,例如:是手机访问还是电脑访问等等。根据访问设备的不同返回给客户端不同的网页。例如手机端的QQ浏览器中就有设置User-Agent类型的:
浏览器UA标志
  • Host
    客户端通过 Host 首部为服务器提供客户端想要访问的那台机器的因特网主机名(也就是IP地址)端口号。
    例如,Host:www.baidu.com:80
  • Allow
    还记得上面的OPTIONS请求么,在响应头中就有Allow为DELETE,标志对该请求的资源支持的请求方法。

举一反三,更多请求头请百度上面Google一下。

首部分类
按照首部的使用在请求头或者响应头的地方场景,可以分为:

  • 请求首部
  • 响应首部
  • 通用首部
  • 实体首部
    下面图示一下:具体的一些其他的请求首部等等,可以百度。


    首部分类

状态行(也叫响应行)

看下一个状态行示例:

状态行示例

其对应的标准格式为:
<Http Version>空格<Status Code>空格<Reason Phrase>
Http Version
这个没什么好说的,目前使用最多的莫过于就是Http 1.1版本了。还有其他的Http 1.2 ;Http 2.0。
Statu Code
常见的已定义的状态码,范围从100~599分为五种状态码

  • 100~199 信息性状态码
Code 原因短语 意义
100 Continue 服务器告诉客户端收到了请求,请客户端继续
101 Switching Protocols 服务器告知客户端,其(服务端)正将协议切换为请求头Update指定的协议
  • 200~299 成功状态码
    一般为OK,更多详见下图:
成功状态码
  • 300~399 重定向状态码
    告知客户端其请求的资源文件已经被转移了,这是服务器可以用Location返回给客户端一个地址,让浏览器直接重新连入新的资源链接。

  • 400~499 客户端错误
    最常见的莫过于404 Not Found了。要么是客户端的请求的URL不存在或者请求报文出错。

  • 500~599 服务器错误
    常见的500等服务器内部错误。

服务器错误

我们其实主要了解一个错误的大致原因就可以了,具体原因再进行深入Google。

剩下的响应报文中得消息报头、响应正文没什么好讲的了。至此,Http报文已经全部讲解完了。下面开始了解下报文是如何在客户端以及服务器端进行传输的。
发送传输其实想想也就那么几步,就跟吧大象塞进冰箱里一样。

HTTP报文的传输

先来看个流程:

HTTP报文的传输
  • Step1->解析URL
    URL在前面介绍过,通过URL的使用DNS进行解析得到对应的IP地址,默认URL的port号为80。
  • Step2->建立TCP连接通道
    拿到IP+port后,服务器就与客户端建立了稳定的TCP连接通道。
  • Step3->发送HTTP报文
    发送报文的格式也没什么好说的了,上面讲解报文的时候也是讲解的了。至于HTTP报文是如何通过TCP连接发送的。。。这个又是很大的一篇。简单几句了解下,HTTP报文通过TCP传输是通过流的形式将数据进行传输的,在数据传输的时候,会把这一大段HTTP报文数据分为一个一个小片段,这个小片段叫做IP分组。
    IP分组包括:
    • IP分组首部
      包含着源端和目的端的IP地址一起其他信息。
    • TCP分组首部
      包含TCP端口号等。
    • TCP数据块

端口号和IP地址都知道了,HTTP报文就可在客户端与服务器端进行传输了。

  • Step->4解析HTTP报文
    服务器收到报文后,开始解析报文。
    解析规则:
    • 解析请求方法
      报文起始行一直向后读取数据,知道读取到第一个空格后读取到的请求头的请求方法名称就结束了。
    • 每一小节的数据均以空格结束
      例如上面读取到请求方法后继续向后解析数据,再次遇到空格则URL解析完毕
    • 每一行的结束均以CRLF标记代表结束
      请求行以CRLF结束后,开始解析首部信息
    • 解析首部
      首部信息以冒号(:)前面标记key结束;冒号后面直到CRLF代表该key对应的value结束
    • 请求头的结束
      请求头的结束以一个空行用来分割请求头与请求体。
  • Step->5构建响应数据
    服务器根据客户端的请求构建对应的数据放在响应体中,在使用Content-type标志请求体的MIME,以及其他响应头。一起返回给客户端。
  • Step-6>关闭连接
    数据传输完毕后,关闭TCP连接。

结束语

大雨

先了解了资源在万网中位置URL;然后是讲解了报文的组成:请求行及其构成、请求头及其构成、状态行及其构成、消息报头及其构成还有首部的分类;再然后就是请求方法的分类及其意义;紧接着就是响应报文中的状态码;最后了解了一个HTTP数据传输的流程。

细雨

OK!真不容易,这篇文章断断续续写了好久。主要是最近公司里加班干项目,时间还紧。回到家洗漱完毕时间就差不多了,再看看资料,充电一会就差不多可以睡了。
言而总之,欢迎拍砖!城里贵,回家盖房子没砖啊。。。多读书,多看报,少吃零食,多睡觉。

PS:

快速定位到简书主页。flutterall.com 这年头买不起房还买不起域名么!!!

谢谢

参考不限于以下资料
Http状态码
Http协议原理

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

推荐阅读更多精彩内容