05

http中请求request和响应response信息说明

Request

http.ServerRequest 是 HTTP请求的信息,是后端开发者最关注的内容。

它一般由http.Server 的 request 事件发送,作为第一个参数传递,

通常简称request 或 req。

HTTP 请求一般可以分为两部分:

请求头(Request Header)

请求体(Requset Body)

以上内容由于长度较短都可以在请求头解析完成后立即读取。

而请求体可能相对较长,需要一定的时间传输,

因此 http.ServerRequest 提供了以下4个事件用于控制请求体传输。


事件 含义

data 当请求体数据到来时,该事件被触发。该事件提供一个参数

chunk 表示接收到的数据。如果该事件没有被监听,那么请求体将会被抛弃。该事件可能会被调用多次。

end 当请求体数据传输完成时,该事件被触发,此后将不会再有数据到来。

close 用户当前请求结束时,该事件被触发。不同于end,如果用户强制终止了传输,也还是调用close

ServerRequest 的属性


事件 含义

complete 客户端请求是否已经发送完成

httpVersion HTTP 协议版本,通常是 1.0 或 1.1

method HTTP 请求方法,如 GET、POST、PUT、DELETE 等

url 原始的请求路径

headers HTTP 请求头

trailers HTTP 请求尾(不常见)

connection 当前 HTTP 连接套接字,为 net.Socket 的实例

socket connection 属性的别名

client client 属性的别名

Response


http.ClientResponse 与 http.ServerRequest 相似,

提供了三个事件data、end和close,

分别在数据到达、传输结束和连接结束时触发,

其中data事件传递一个参数chunk,表示接收到的数据。


ClientResponse 的属性


名称 含义

statusCode HTTP 状态码,如 200、404、500

httpVersion HTTP 协议版本,通常是 1.0 或 1.1

headers HTTP 请求头

trailers HTTP 请求尾(不常见)

Header 头详解


HTTP(HyperTextTransferProtocol)即超文本传输协议,目前网页传输的的通用协议。

HTTP协议采用了请求/响应模型,浏览器或其他客户端发出请求,服务器给与响应。

就整个网络资源传输而言,包括message-header和message-body两部分。

首先传递message- header,即http header消息 。

http header 消息通常被分为4个部分:general  header, request header, response header, entity header。

但是这种分法就理解而言,感觉界限不太明确。

根据维基百科对http header内容的组织形式,大体分为Request和Response两部分。


Requests部分


Header 解释 示例

Accept 指定客户端能够接收的内容类型 Accept: text/plain, text/html

Accept-Charset 浏览器可以接受的字符编码集。 Accept-Charset: iso-8859-5

Accept-Encoding 指定浏览器可以支持的web服务器返回内容压缩编码类型。 Accept-Encoding: compress, gzip

Accept-Language 浏览器可接受的语言 Accept-Language: en,zh

Accept-Ranges 可以请求网页实体的一个或者多个子范围字段 Accept-Ranges: bytes

Authorization HTTP授权的授权证书 Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

Cache-Control 指定请求和响应遵循的缓存机制 Cache-Control: no-cache

Connection 表示是否需要持久连接。(HTTP 1.1默认进行持久连接) Connection: close

Cookie HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器。 Cookie: $Version=1; Skin=new;

Content-Length 请求的内容长度 Content-Length: 348

Content-Type 请求的与实体对应的MIME信息 Content-Type: application/x-www-form-urlencoded

Date 请求发送的日期和时间 Date: Tue, 15 Nov 2010 08:12:31 GMT

Expect 请求的特定的服务器行为 Expect: 100-continue

From 发出请求的用户的Email From: user@email.com

Host 指定请求的服务器的域名和端口号 Host: www.zcmhi.com

If-Match 只有请求内容与实体相匹配才有效 If-Match: “737060cd8c284d8af7ad3082f209582d”

If-Modified-Since 如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码 If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT

If-None-Match 如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变 If-None-Match: “737060cd8c284d8af7ad3082f209582d”

If-Range 如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。 参数也为Etag If-Range: “737060cd8c284d8af7ad3082f209582d”

If-Unmodified-Since 只在实体在指定时间之后未被修改才请求成功 If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT

Max-Forwards 限制信息通过代理和网关传送的时间 Max-Forwards: 10

Pragma 用来包含实现特定的指令 Pragma: no-cache

Proxy-Authorization 连接到代理的授权证书 Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

Range 只请求实体的一部分,指定范围 Range: bytes=500-999

Referer 先前网页的地址,当前请求网页紧随其后,即来路 Referer: http://www.baidu.com

TE 客户端愿意接受的传输编码,并通知服务器接受接受尾加头信息 TE: trailers,deflate;q=0.5

Upgrade 向服务器指定某种传输协议以便服务器进行转换(如果支持) Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11

User-Agent User-Agent的内容包含发出请求的用户信息 User-Agent: Mozilla/5.0 (Linux; X11)

Via 通知中间网关或代理服务器地址,通信协议 Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1)

Warning 关于消息实体的警告信息 Warn: 199 Miscellaneous warning

Responses 部分


Header 解释 示例

Accept-Ranges 表明服务器是否支持指定范围请求及哪种类型的分段请求 Accept-Ranges: bytes

Age 从原始服务器到代理缓存形成的估算时间(以秒计,非负) Age: 12

Allow 对某网络资源的有效的请求行为,不允许则返回405 Allow: GET, HEAD

Cache-Control 告诉所有的缓存机制是否可以缓存及哪种类型 Cache-Control: no-cache

Content-Encoding web服务器支持的返回内容压缩编码类型。 Content-Encoding: gzip

Content-Language 响应体的语言 Content-Language: en,zh

Content-Length 响应体的长度 Content-Length: 348

Content-Location 请求资源可替代的备用的另一地址 Content-Location: /index.htm

Content-MD5 返回资源的MD5校验值 Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ==

Content-Range 在整个返回体中本部分的字节位置 Content-Range: bytes 21010-47021/47022

Content-Type 返回内容的MIME类型 Content-Type: text/html; charset=utf-8

Date 原始服务器消息发出的时间 Date: Tue, 15 Nov 2010 08:12:31 GMT

ETag 请求变量的实体标签的当前值 ETag: “737060cd8c284d8af7ad3082f209582d”

Expires 响应过期的日期和时间 Expires: Thu, 01 Dec 2010 16:00:00 GMT

Last-Modified 请求资源的最后修改时间 Last-Modified: Tue, 15 Nov 2010 12:45:26 GMT

Location 用来重定向接收方到非请求URL的位置来完成请求或标识新的资源 Location: http://www.zcmhi.com/archives/94.html

Pragma 包括实现特定的指令,它可应用到响应链上的任何接收方 Pragma: no-cache

Proxy-Authenticate 它指出认证方案和可应用到代理的该URL上的参数 Proxy-Authenticate: Basic

refresh 应用于重定向或一个新的资源被创造,在5秒之后重定向(由网景提出,被大部分浏览器支持) Refresh: 5; url= http://www.zcmhi.com/archives/94.html

Retry-After 如果实体暂时不可取,通知客户端在指定时间之后再次尝试 Retry-After: 120

Server web服务器软件名称 Server: Apache/1.3.27 (Unix) (Red-Hat/Linux)

Set-Cookie 设置Http Cookie Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1

Trailer 指出头域在分块传输编码的尾部存在 Trailer: Max-Forwards

Transfer-Encoding 文件传输编码 Transfer-Encoding:chunked

Vary 告诉下游代理是使用缓存响应还是从原始服务器请求 Vary: *

Via 告知代理客户端响应是通过哪里发送的 Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1)


关于HTTP部分大致分为如下的重要点:

直接通过http对象使用的有:

http.STATUS_CODES

http.createServer

http.request(http.ClientRequest)

http.get

http.globalAgent

http.IcomingMessage

作为回调参数使用的对象有:

http.serverRequest

http.serverResponse

http.Agent

一、http.STATUS_CODES

   

众所周知,http服务器就是一个状态服务器,可以根据状态码来确定服务器是处于请求的什么状态。

如下列出Node.js status code的全部状态对于的解释。

http: {

   STATUS_CODES: {

       '100': 'Continue',

       '101': 'Switching Protocols',

       '102': 'Processing',

       '200': 'OK',

       '201': 'Created',

       '202': 'Accepted',

       '203': 'Non-Authoritative Information',

       '204': 'No Content',

       '205': 'Reset Content',

       '206': 'Partial Content',

       '207': 'Multi-Status',

       '300': 'Multiple Choices',

       '301': 'Moved Permanently',

       '302': 'Moved Temporarily',

       '303': 'See Other',

       '304': 'Not Modified',

       '305': 'Use Proxy',

       '307': 'Temporary Redirect',

       '400': 'Bad Request',

       '401': 'Unauthorized',

       '402': 'Payment Required',

       '403': 'Forbidden',

       '404': 'Not Found',

       '405': 'Method Not Allowed',

       '406': 'Not Acceptable',

       '407': 'Proxy Authentication Required',

       '408': 'Request Time-out',

       '409': 'Conflict',

       '410': 'Gone',

       '411': 'Length Required',

       '412': 'Precondition Failed',

       '413': 'Request Entity Too Large',

       '414': 'Request-URI Too Large',

       '415': 'Unsupported Media Type',

       '416': 'Requested Range Not Satisfiable',

       '417': 'Expectation Failed',

       '418': 'I\'m a teapot',

       '422': 'Unprocessable Entity',

       '423': 'Locked',

       '424': 'Failed Dependency',

       '425': 'Unordered Collection',

       '426': 'Upgrade Required',

       '428': 'Precondition Required',

       '429': 'Too Many Requests',

       '431': 'Request Header Fields Too Large',

       '500': 'Internal Server Error',

       '501': 'Not Implemented',

       '502': 'Bad Gateway',

       '503': 'Service Unavailable',

       '504': 'Gateway Time-out',

       '505': 'HTTP Version Not Supported',

       '506': 'Variant Also Negotiates',

       '507': 'Insufficient Storage',

       '509': 'Bandwidth Limit Exceeded',

       '510': 'Not Extended',

       '511': 'Network Authentication Required'

   }

}

测试用例:

var http = require('http');

http.createServer(function(req, res) {

   var status = req.url.substr(1);

   if (!http.STATUS_CODES[status]) {

       status = '404';

   }

   res.writeHeader(status, {

       'Content-Type': 'text/plain'

   });

   res.end(http.STATUS_CODES[status]);

}).listen(3000);

测试连接:http://localhost:3000/500  结果输出 Internal Server Error

二、http.createServer

  

http.createServer是创建一台web服务器的关键所在,是处理请求和回应的主函数出口和出口。

我们把http.createServer创建的服务对象定义为server.代码如下。

  

// 引入 http 模块

var http = require('http');

/**

* 创建服务器的两种写法,第一种写法如下

* 由于server已经继承了EventEmitter的事件功能,所以可以使用高级函数编写方式监控事件

* @param {Function} request event

*/

var server = http.createServer(function(req, res) {

   //这里的req为http.serverRequest

   res.writeHeader(200, {

       'Content-Type': 'text/plain'

   });

   res.end('hello world');

});

/**

* 说明:创建服务器的第二种写法

* 有关server对象的事件监听

* @param {Object} req 是http.IncomingMessag的一个实例,在keep-alive连接中支持多个请求

* @param {Object} res 是http.ServerResponse的一个实例

*/

var server = new http.Server();

server.on('request',

function(req, res) {

   res.writeHeader(200, {

       'Content-Type': 'text/plain'

   });

   res.end('hello world');

});

/**

* 说明:新的TCP流建立时出发。 socket是一个net.Socket对象。 通常用户无需处理该事件。

* 特别注意,协议解析器绑定套接字时采用的方式使套接字不会出发readable事件。 还可以通过request.connection访问socket。

* @param {Object} socket

*/

server.on('connection',function(socket) {});

/**

* 源API: Event: 'close'

* 说明:关闭服务器时触发

*/

server.on('close',function() {});

/**

* 说明:每当收到Expect: 100-continue的http请求时触发。 如果未监听该事件,服务器会酌情自动发送100 Continue响应。

* 处理该事件时,如果客户端可以继续发送请求主体则调用response.writeContinue, 如果不能则生成合适的HTTP响应(例如,400 请求无效)

* 需要注意到, 当这个事件触发并且被处理后, request 事件将不再会触发.

* @param {Object} req

* @param {Object} req

*/

server.on('checkContinue',

function(req, res) {});

/**

* 说明:如果客户端发起connect请求,如果服务器端没有监听,那么于客户端请求的该连接将会被关闭

* @param {Object} req 是该HTTP请求的参数,与request事件中的相同。

* @param {Object} socket 是服务端与客户端之间的网络套接字。需要自己写一个data事件监听数据流

* @param {Object} head 是一个Buffer实例,隧道流的第一个包,该参数可能为空。

*/

server.on('connect',

function(req, socket, head) {});

/**

* 说明:这个事件主要是对HTTP协议升级为其他协议后的事件监听,如果服务器端没有监听,那么于客户端请求的该连接将会被关闭

* @param {Object} req 是该HTTP请求的参数,与request事件中的相同。

* @param {Object} socket 是服务端与客户端之间的网络套接字。需要自己写一个data事件监听数据流

* @param {Object} head 是一个Buffer实例,升级后流的第一个包,该参数可能为空。

*/

server.on('upgrade',

function(req, socket, head) {});

/**

* 说明:如果一个客户端连接触发了一个 'error' 事件, 它就会转发到这里

* @param {Object} exception

* @param {Object} socket

*/

server.on('clientError',function(exception, socket) {});

/**

* 源API:server.listen(port, [hostname], [backlog], [callback])

* 说明:监听一个 unix socket, 需要提供一个文件名而不是端口号和主机名。

* @param {Number} port 端口

* @param {String} host 主机

* @param {Number} backlog 等待队列的最大长度,决定于操作系统平台,默认是511

* @param {Function} callback 异步回调函数

*/

server.listen(3000,'localhost',100,function(){});

/**

* 源API:server.listen(path, [callback])

* 说明:启动一个 UNIX 套接字服务器在所给路径 path 上监听连接。

* 可能用处:多路径或渠道数据来源监听分隔

* @param {String} path

* @param {Function} callback

*/

server.listen('path',function(){})

/**

* 源API:server.listen(handle, [callback])

* 说明:Windows 不支持监听一个文件描述符。

* @param {Object} handle 变量可以被设置为server 或者 socket

* @param {Function} callback

*/

server.listen({},function(){});

/**

* 说明:最大请求头数目限制, 默认 1000 个. 如果设置为0, 则代表不做任何限制.

* @type {number}

*/

server.maxHeadersCount = 1000;

/**

* 源API:server.setTimeout(msecs, callback)

* 说明:为套接字设定超时值。如果一个超时发生,那么Server对象上会分发一个'timeout'事件,同时将套接字作为参数传递。

* 设置为0将阻止之后建立的连接的一切自动超时行为

* @param {Number} msecs

* @param

*/

server.setTimeout(1000,function() {});

/**

* 说明:一个套接字被判断为超时之前的闲置毫秒数。 默认 120000 (2 分钟)

* @type {number}

*/

server.timeout = 120000;

/**

* 说明:这里的主机将是本地

* @param {Number} port 端口

* @param {Function} callback 异步回调函数

*/

server.listen(3000,function() {

   console.log('Listen port 3000');

});

三 、http.request

http 模块提供了两个函数 http.request 和 http.get,功能是作为客户端向 HTTP服务器发起请求。

http.request(options, callback) 发起 HTTP 请求。

接受两个参数,

option 是一个类似关联数组的对象,表示请求的参数,-option常用的参数如下所示。

callback 是请求的回调函数。

http.request 返回一个 http.ClientRequest 的实例。

var http = require('http');

var server = http.createServer(function(req, res) {

}).listen(3000);

/**

* 参数配置

* @type {{hostname: string,  port: number,  method: string,  path: string, handers: {} }}

* host:请求的服务器域名或者IP地址

* port:端口

* method:请求方式有POST,GET,INPUT,DELETE,CONNECT,默认为GET

* path:请求地址,可包含查询字符串以及可能存在的锚点。例如'/index.html?page=12'

* handers: 一个包含请求头的对象。

*/

var options = {

   hostname: 'www.google.com',

   port: 80,

   method: 'POST',

   path: '/upload',

   handers: {}

};

/**

* 如下特别的消息头应当注意:

* 发送'Connection: keep-alive'头部将通知Node此连接将保持到下一次请求。

* 发送'Content-length'头将使默认的分块编码无效。

* 发送'Expect'头部将引起请求头部立即被发送。

* 通常情况,当发送'Expect: 100-continue'时,你需要监听continue事件的同时设置超时。参见RFC2616 8.2.3章节以获得更多的信息。

*/

/**

* 说明:官方给出的例子

* 应用场景:模拟客服端请求服务器,是一个HTTP 客户端工具,用于向 HTTP 服务器发起请求。

* @param {Object} options

* @param {Function} callback

*/

var req = http.request(options,function(res) {

   console.log(res);

   console.log('STATUS:' + res.statusCode);

   console.log('HEADERS:' + JSON.stringify(res.headers));

   res.setEncoding('utf8');

   res.on('data', function(chunk) {

       console.log('BODY' + chunk);

   });

});

req.on('response',function() {

});

req.on('connect',function() {

});

req.on('socket',function() {

});

req.on('upgrade',function() {

});

req.on('continue',function() {

})

//如果在请求过程中出现了错误(可能是DNS解析、TCP的错误、或者HTTP解析错误),返回的请求对象上的'error'的事件将被触发。

req.on('error', function(e) {

   console.log(e.message);

});

/**

* 源API:request.write(chunk, [encoding])

* 说明:发送正文中的一块。用户可以通过多次调用这个方法将请求正文以流的方式发送到服务器。此种情况建议在建立请求时使用['Transfer-Encoding', 'chunked']请求头。

* @param {Object or String} chunk 参数chunk应当是一个整数数组或字符串。

* @param {String} encoding 参数encoding是可选的,仅在chunk为字符串时可用。

*/

req.write('data\n');

/**

* 源API:request.end(chunk, [encoding])

* 说明:完成本次请求的发送。如果正文中的任何一个部分没有来得及发送,将把他们全部刷新到流中。如果本次请求是分块的,这个函数将发出结束字符'0\r\n\r\n'。如果使用参数data,就等于在调用request.write(data, encoding)之后紧接着调用request.end()。

* @param {Object or String} chunk 参数chunk应当是一个整数数组或字符串。

* @param {String} encoding 参数encoding是可选的,仅在chunk为字符串时可用。

* example: req.end(),req.end('data\n'),req.end('data','utf8'),req.end(chunk)

*/

req.end();

/**

* 阻止一个请求。(v0.3.8中新增的方法。)

*/

req.abort();

/**

* 源API:request.setTimeout(timeout, [callback])

* 说明:一旦给这个请求分配的是一个socket时此函数会被调用

* @param {Number} timeout 毫秒

* @param {Function} callback 回到函数

*/

req.setTimeout(1000,function() {});

/**

* 源API :request.setNoDelay([noDelay])

* 说明:默认有一定的延迟,设置为0表示无延迟

* @param {Number} noDelay

*/

req.setNoDelay(0)

/**

* 源API:request.setSocketKeepAlive([enable], [initialDelay])

*     类似同上

*/

四、http.get

http.get(options, callback) http 模块还提供了一个更加简便的方法用于处理GET请求:http.get。

它是 http.request 的简化版,唯一的区别在于http.get自动将请求方法设为了 GET 请求,同时不需要手动调用 req.end()。

var http = require('http');

http.createServer(function(req, res) {

}).listen(3000);

/**

* 说明:由于大部分请求是不包含正文的GET请求,Node提供了这个方便的方法。与http.request()唯一的区别是此方法将请求方式设置为GET,并且自动调用req.end()。

* 应用:服务器端测试客服端请求调试等

* @param {String} url 有效地址

* @param {Function} callback

*/

http.get('http://www.baidu.com/index.html',

function(res) {

   console.log('get response Code :' + res.statusCode);

}).on('error', function(e) {

   console.log("Got error: " + e.message);

})


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

推荐阅读更多精彩内容