理解HTTP协议

任何一个技术和知识在了解了大概之后还需要在学习和实践中不断总结、思考才能真正掌握,变成自己的东西。
用自己的方式分析技术原理,总结自己的经验,分享他人的见解,转述权威的解读。

1 HTTP 协议简介

HTTP (HyperText Transfer Protocol, 超文本传输协议)是互联网上应用最为广泛的一种网络协议,它是基于 TCP 的应用层协议,简单地说就是客户端和服务器进行通信的一种规则,它的模式非常简单,就是客户端发起请求,服务器响应请求,如下图所示:

http请求-响应模型

HTTP是一个应用层协议,由请求和响应构成,是一个标准的客户端服务器模型。HTTP是一个无状态的协议。
在Internet中所有的传输都是通过TCP/IP进行的。HTTP协议作为TCP/IP模型中应用层的协议也不例外。HTTP协议通常承载于TCP协议之上,有时也承载于TLS或SSL协议层之上,这个时候,就成了我们常说的HTTPS。如下图所示:

111703047392802.png

HTTP默认的端口号为80,HTTPS的端口号为443。
浏览网页是HTTP的主要应用,但是这并不代表HTTP就只能应用于网页的浏览。HTTP是一种协议,只要通信的双方都遵守这个协议,HTTP就能有用武之地。比如咱们常用的QQ,迅雷这些软件,都会使用HTTP协议(还包括其他的协议)。

HTTP 最早于 1991 年发布,是 0.9 版,不过目前该版本已不再用。HTTP 目前在使用的版本主要有:

  • HTTP/1.0,于 1996 年 5 月发布,引入了多种功能,至今仍在使用当中。
  • HTTP/1.1,于 1997 年 1 月发布,持久连接被默认采用,是目前最流行的版本。
  • HTTP/2 ,于 2015 年 5 月发布,引入了服务器推送等多种功能,是目前最新的版本。
  • HTTPS,HTTPS是HTTP协议的安全版本,HTTP协议的数据传输是明文的,是不安全的,HTTPS使用了SSL/TLS协议进行了加密处理。

2 HTTP工作原理

HTTP协议定义Web客户端如何从Web服务器请求Web页面,以及服务器如何把Web页面传送给客户端。HTTP协议采用了请求/响应模型。客户端向服务器发送一个请求报文,请求报文包含请求的方法、URL、协议版本、请求头部和请求数据。服务器以一个状态行作为响应,响应的内容包括协议的版本、成功或者错误代码、服务器信息、响应头部和响应数据。请求的过程:3次握手,请求,响应,断开连接。

在TCP协议中要通过三次握手才能建立可靠连接

三次握手

第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器 进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入 ESTABLISHED状态,完成三次握手。

UDP和TCP的区别
最本质的区别就是TCP是面向连接的,而UDP是非面向连接的

  • 什么叫面向连接呢?事先为所发送的数据开辟出连接好的通道,然后再进行数据发送,像打电话,只能两人打,第三人打就显示占线
  • 非面向连接:是指通信双方不需要事先建立一条通信线路,而是把每个带有目的地址的包(报文分组)送到线路上,由系统自主选定路线进行传输,就像写信,不管对方有多忙,把信放到邮筒,就与自己无关系了。

TCP支持的服务有FTP、SMTP等,而UDP支持的服务有DNS、SNMP、QQ等

以下是 HTTP 请求/响应的步骤:

  1. 客户端连接到Web服务器
    一个HTTP客户端,通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接。例如,https://www.baidu.com

  2. 发送HTTP请求
    通过TCP套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由请求行、请求头部、空行和请求数据4部分组成。

  3. 服务器接受请求并返回HTTP响应
    Web服务器解析请求,定位请求资源。服务器将资源复本写到TCP套接字,由客户端读取。一个响应由状态行、响应头部、空行和响应数据4部分组成。

  4. 释放连接TCP连接
    若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求;

  5. 客户端浏览器解析HTML内容
    客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示。

例如:在浏览器地址栏键入URL,按下回车之后会经历以下流程:

  1. 浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址;
  2. 解析出 IP 地址后,根据该 IP 地址和默认端口 80,和服务器建立TCP连接;
  3. 浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP 请求,该请求报文作为 TCP 三次握手的第三个报文的数据发送给服务器;
  4. 服务器对浏览器请求作出响应,并把对应的 html 文本发送给浏览器;
  5. 释放 TCP连接;
  6. 浏览器将该 html 文本并显示内容;

HTTP协议是基于请求/响应模式的,因此只要服务端给了响应,本次HTTP连接就结束了,或者更准确的说,是本次HTTP请求就结束了,根本没有长连接这一说。那么自然也就没有短连接这一说了。
  之所以网络上说HTTP分为长连接和短连接,其实本质上是说的TCP连接TCP连接是一个双向的通道,它是可以保持一段时间不关闭的,因此TCP连接才有真正的长连接和短连接这一说。
  其实知道了以后,会觉得这很好理解。HTTP协议说到底是应用层的协议,而TCP才是真正的传输层协议,只有负责传输的这一层才需要建立连接。

一个形象的例子就是,拿你在网上购物来说,HTTP协议是指的那个快递单,你寄件的时候填的单子就像是发了一个HTTP请求,等货物运到地方了,快递员会根据你发的请求把货物送给相应的收货人。而TCP协议就是中间运货的那个大货车,也可能是火车或者飞机,但不管是什么,它是负责运输的,因此必须要有路,不管是地上还是天上。那么这个路就是所谓的TCP连接,也就是一个双向的数据通道。

因此,LZ现在甚至觉得,“HTTP连接”这个词就不应该出现,它只是一个应用层的协议,根本就没有所谓的连接这一说,就像FTP也是应用层的协议,但是你有听说过FTP连接吗?(恩,好像是听过,-_-,但你现在知道了,其实所谓的FTP连接,严格来说,依旧是TCP连接)
  实际上,说HTTP请求和HTTP响应会更准确一些,而HTTP请求和HTTP响应,都是通过TCP连接这个通道来回传输的。
  不管怎么说,一定要务必记住,长连接是指的TCP连接,而不是HTTP连接

3 HTTPS传输协议原理

HTTPS(全称:Hypertext Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容请看SSL。

3.1 两种基本的加解密算法类型

  • 对称加密:密钥只有一个,加密解密为同一个密码,且加解密速度快,典型的对称加密算法有DES、AES等。
  • 非对称加密:密钥成对出现(且根据公钥无法推知私钥,根据私钥也无法推知公钥),加密解密使用不同密钥(公钥加密需要私钥解密,私钥加密需要公钥解密),相对对称加密速度较慢,典型的非对称加密算法有RSA、DSA等。

3.2 HTTPS通信过程

HTTPS通信过程

3.3 HTTPS通信的优点

客户端产生的密钥只有客户端和服务器端能得到;
加密的数据只有客户端和服务器端才能得到明文;
客户端到服务端的通信是安全的。

4 HTTP 请求

请求报文

HTTP 请求由三部分组成:

  • 请求行:包含请求方法、请求地址和 HTTP 协议版本
  • 消息报头:包含一系列的键值对
  • 请求正文(可选):注意和消息报头之间有一个空行
    如图所示:
    HTTP请求

下面是一个 HTTP GET 请求的例子:

GET / HTTP/1.1
Host: httpbin.org
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4
Cookie: _ga=GA1.2.475070272.1480418329; _gat=1

上面的第一行就是一个请求行:

GET / HTTP/1.1

其中,GET 是请求方法,表示从服务器获取资源;/ 是一个请求地址;HTTP/1.1 表明 HTTP 的版本是 1.1。

请求行后面的一系列键值对就是消息报头

Host: httpbin.org
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4
Cookie: _ga=GA1.2.475034215.1480418329; _gat=1

其中:

  • Host 是请求报头域,用于指定被请求资源的 Internet 主机和端口号,它通常从 HTTP URL 中提取出来;
  • Connection 表示连接状态,keep-alive 表示该连接是持久连接(persistent connection),即 TCP 连接默认不关闭,可以被多个请求复用,如果客户端和服务器发现对方有一段时间没有活动,就可以主动关闭连接;
  • Cache-Control 用于指定缓存指令,它的值有 no-cache, no-store, max-age 等,max-age=秒表示资源在本地缓存多少秒;
  • User-Agent 用于标识请求者的一些信息,比如浏览器类型和版本,操作系统等;
  • Accept 用于指定客户端希望接受哪些类型的信息,比如 text/html, image/gif 等;
  • Accept-Encoding 用于指定可接受的内容编码;
  • Accept-Language 用于指定可接受的自然语言;
  • Cookie 用于维护状态,可做用户认证,服务器检验等,它是浏览器储存在用户电脑上的文本片段;

5 HTTP 请求方法

HTTP请求方法

HTTP 通过不同的请求方法以多种方式来操作指定的资源,常用的请求方法如下表:

方法 描述
GET 从服务器获取指定(请求地址)的资源的信息,它通常只用于读取数据,就像数据库查询一样,不会对资源进行修改。
POST 向指定资源提交数据(比如提交表单,上传文件),请求服务器进行处理。数据被包含在请求正文中,这个请求可能会创建新的资源或更新现有的资源。
PUT 通过指定资源的唯一标识(在服务器上的具体存放位置),请求服务器创建或更新资源
DELETE 请求服务器删除指定资源。
HEAD 与 GET 方法类似,从服务器获取资源信息,和 GET 方法不同的是,HEAD 不含有呈现数据,仅仅是 HTTP 头信息。HEAD 的好处在于,使用这个方法可以在不必传输全部内容的情况下,就可以获得资源的元信息(或元数据)。
OPTIONS 该方法可使服务器传回资源所支持的所有 HTTP 请求方法。

6 再议 POST 和 PUT

注意到,POST 和 PUT 都可用于创建或更新资源,然而,它们之间还是有比较大的区别:

  • POST 所对应的 URI 并非创建的资源本身,而是资源的接收者,资源本身的存放位置由服务器决定;而 PUT 所对应的 URI 是要创建或更新的资源本身,它指明了具体的存放位置

比如,往某个站点添加一篇文章,如果使用 POST 来创建资源,可类似这样:

POST /articles HTTP/1.1

{
    "author": "ethan",
    "title": "hello world",
    "content": "hello world"
}

在上面,POST 对应的 URI 是 /articles,它是资源的接收者,而非资源的标识,如果资源被成功创建,服务器可以返回 201 Created 状态以及新建资源的位置,比如:

HTTP/1.1 201 Created
Location: /articles/abcdef123

我们如果知道新建资源的标识符,可以使用PUT 来创建资源,比如:

PUT /articles/abcdef234 HTTP/1.1

{
    "author": "peter",
    "title": "hello world",
    "content": "hello world"
}

在上面,PUT 对应的 URI 是/articles/abcdef234,它指明了资源的存放位置,如果资源被成功创建,服务器可以返回 201 Created 状态以及新建资源的位置,比如:

HTTP/1.1 201 Created
Location: /articles/abcdef234
  • 使用 PUT 更新某一资源,需要更新资源的全部属性;而使用 POST,可以更新全部或一部分值

比如使用 PUT 更新地址为 /articles/abcdef234 的文章的标题,我们需要发送所有值:

PUT /articles/abcdef234 HTTP/1.1

{
    "author": "peter",
    "title": "hello python",
    "content": "hello world"
}

而使用 POST,可以更新某个域的值:

POST /articles/abcdef234 HTTP/1.1

{
    "title": "hello python"
}
  • POST 是不幂等的,PUT 是幂等的,这是一个很重要的区别

HTTP 方法的幂等性是指一次和多次请求某一个资源应该具有同样的副作用,注意这里是副作用,而不是返回结果。

GET 方法用于获取资源,不会改变资源的状态,不论调用一次还是多次都没有副作用,因此它是幂等的;DELETE 方法用于删除资源,有副作用,但调用一次或多次都是删除同个资源,产生的副作用是相同的,因此也是幂等的;POST 是不幂等的,因为两次相同的 POST 请求会在服务器创建两份资源,它们具有不同的 URI;PUT 是幂等的,对同一 URI 进行多次 PUT 的副作用和一次 PUT 是相同的。

7 HTTP 响应

响应报文

HTTP 响应与 HTTP 请求相似,由三部分组成:

  • 状态行:包含 HTTP 协议版本、状态码和状态描述,以空格分隔
  • 响应头:即消息报头,包含一系列的键值对
  • 响应正文:返回内容,注意和响应头之间有一个空行
    如图所示:


    HTTP响应

下面是一个 HTTP GET 请求的响应结果:

HTTP/1.1 200 OK
Server: nginx
Date: Tue, 29 Nov 2016 13:08:38 GMT
Content-Type: application/json
Content-Length: 203
Connection: close
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

{
  "args": {}, 
  "headers": {
    "Host": "httpbin.org", 
    "User-Agent": "Paw/2.3.1 (Macintosh; OS X/10.11.3) GCDHTTPRequest"
  }, 
  "origin": "13.75.42.240", 
  "url": "https://httpbin.org/get"
}

上面的第一行就是一个状态行:

HTTP/1.1 200 OK

其中,200 是状态码,表示客户端请求成功,OK 是相应的状态描述。

状态码是一个三位的数字,常见的状态码有以下几类:

HTTP的响应状态码

1XX 消息 -- 请求已被服务接收,继续处理
2XX 成功 -- 请求已成功被服务器接收、理解、并接受
200 OK
201 Created 已创建
202 Accepted 接收
203 Non-Authoritative Information 非认证信息
204 No Content 无内容
3XX 重定向 -- 需要后续操作才能完成这一请求
301 Moved Permanently 请求永久重定向
302 Moved Temporarily 请求临时重定向
304 Not Modified 文件未修改,可以直接使用缓存的文件
305 Use Proxy 使用代理
4XX 请求错误 -- 请求含有词法错误或者无法被执行
400 Bad Request 由于客户端请求有语法错误,不能被服务器所理解
401 Unauthorized 请求未经授权。这个状态代码必须和WWW-Authenticate报头域一起使用
403 Forbidden 服务器收到请求,但是拒绝提供服务。服务器通常会在响应正文中给出不提供服务的原因
404 Not Found 请求的资源不存在,例如,输入了错误的URL
5XX 服务器错误 -- 服务器在处理某个正确请求时发生错误
500 Internal Server Error 服务器发生不可预期的错误,导致无法完成客户端的请求
503 Service Unavailable 服务器当前不能够处理客户端的请求,在一段时间之后,服务器可能会恢复正常
504 Gateway Time-out 网关超时

状态行后面的一系列键值对就是消息报头,即响应头:

Server: nginx
Date: Tue, 29 Nov 2016 13:08:38 GMT
Content-Type: application/json
Content-Length: 203
Connection: close
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

其中:

  • Server 包含了服务器用来处理请求的软件信息,跟请求报头域 User-Agent 相对应;
  • Content-Type 用于指定发送给接收者(比如浏览器)的响应正文的媒体类型,比如 text/html, text/css, image/png, image/jpeg, video/mp4, application/pdf, application/json 等;
  • Content-Length 指明本次回应的数据长度;

8 HTTP 特点

  • 客户端/服务器模式
  • 简单快速:客户端向服务器请求服务时,通过传送请求方法、请求地址和数据体(可选)即可
  • 灵活:允许传输任意类型的数据对象,通过 Content-Type 标识
  • 无状态:对事物处理没记忆能力,HTTP协议是无状态的,就是说每次HTTP请求都是独立的,任何两个请求之间没有什么必然的联系。但是在实际应用当中并不是完全这样的,引入了Cookie和Session机制来关联请求。
  • 多次HTTP请求,在客户端请求网页时多数情况下并不是一次请求就能成功的,服务端首先是响应HTML页面,然后浏览器收到响应之后发现HTML页面还引用了其他的资源,例如,CSS,JS文件,图片等等,还会自动发送HTTP请求这些需要的资源。现在的HTTP版本支持管道机制,可以同时请求和响应多个请求,大大提高了效率。
  • 基于TCP协议,HTTP协议目的是规定客户端和服务端数据传输的格式和数据交互行为,并不负责数据传输的细节。底层是基于TCP实现的。现在使用的版本当中是默认持久连接的,也就是多次HTTP请求使用一个TCP连接。

9 Cookie和Session的区别和联系

Cookie和Session都是为了保存客户端和服务端之间的交互状态,实现机制不同,各有优缺点。首先一个最大的区别就是Cookie是保存在客户端而Session就保存在服务端的。Cookie是客户端请求服务端时服务器会将一些信息以键值对的形式返回给客户端,保存在浏览器中,交互的时候可以加上这些Cookie值。用Cookie就可以方便的做一些缓存。Cookie的缺点是大小和数量都有限制;Cookie是存在客户端的可能被禁用、删除、篡改,是不安全的;Cookie如果很大,每次要请求都要带上,这样就影响了传输效率。Session是基于Cookie来实现的,不同的是Session本身存在于服务端,但是每次传输的时候不会传输数据,只是把代表一个客户端的唯一ID(通常是JSESSIONID)写在客户端的Cookie中,这样每次传输这个ID就可以了。Session的优势就是传输数据量小,比较安全。但是Session也有缺点,就是如果Session不做特殊的处理容易失效、过期、丢失或者Session过多导致服务器内存溢出,并且要实现一个稳定可用安全的分布式Session框架也是有一定复杂度的。在实际使用中就要结合Cookie和Session的优缺点针对不同的问题来设计解决方案。

10 小结

  • HTTP 是在网络上传输 HTML 的协议,用于浏览器和服务器的通信,默认使用 80 端口。
  • URL 地址用于定位资源,HTTP 中的 GET, POST, PUT, DELETE 用于操作资源,比如查询,增加,更新等。
  • GET, PUT, DELETE 是幂等的,POST 是不幂等的
  • POST VS PUT
  1. 使用 PUT 创建资源需要提供资源的唯一标识(具体存放位置),POST 不需要,POST 的数据存放位置由服务器自己决定
  2. 使用 PUT 更新某一资源,需要更新资源的全部属性;而使用 POST,可以更新全部或一部分值
  3. POST 是不幂等的,PUT 是幂等的,这是一个很重要的区别
  • GET 可提交的数据量受到URL 长度的限制,HTTP 协议规范没有对 URL 长度进行限制,这个限制是特定的浏览器及服务器对它的限制。

每种浏览器也会对url的长度有所限制,下面是几种常见浏览器的url长度限制:(单位:字符)

  IE  :  2803ASCII字符
  Firefox  :  65536ASCII字符 
  Chrome  :  8182ASCII字符
  Safari  :  80000ASCII字符
  Opera  :  190000ASCII字符

对于get请求,在url的长度限制范围之内,请求的参数个数没有限制。

  • 理论上讲,POST 是没有大小限制的,HTTP 协议规范也没有进行大小限制,出于安全考虑,服务器软件在实现时会做一定限制。

编程并没有捷径,真正的成长都是来源于实践、思考和总结。

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

推荐阅读更多精彩内容