网络编程:构建 HTTP服务

1. http 模块

Node的 http 模块包含对HTTP处理的封装。在Node中,HTTP服务继承自TCP服务器( net 模块),它能够与多个客户端保持连接,由于其采用事件驱动的形式,并不为每一个连接创建额外的线程或进程,保持很低的内存占用,所以能实现高并发。HTTP服务与TCP服务模型有区别的地方在于,在开启 keepalive 后,一个TCP会话可以用于多次请求和响应。TCP服务以 connection为单位进行服务,HTTP服务以 request 为单位进行服务。 http 模块即是将 connection 到 request 的过程进行了封装,示意图如下图:

                      

1.1. http报文

我们用一个小例子来展示http的一个完整报文:

编写最简单的http服务程序并启动:http_server.js

var http = require('http');
http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World\n');
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');

然后使用curl工具进行请求:

xiao@uXiao:~/nodejs/深入浅出nodejs/chapter7$ curl -v http://127.0.0.1:1337/
*   Trying 127.0.0.1:1337...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 1337 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1:1337
> User-Agent: curl/7.65.3
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Date: Thu, 02 Jan 2020 09:35:38 GMT
< Connection: keep-alive
< Transfer-Encoding: chunked
< 
Hello World
* Connection #0 to host 127.0.0.1 left intact

从上述信息中我们可以看到这次网络通信的报文信息分为四个部分,

第一部分内容为经典的TCP的3次握手过程,如下所示:

* About to connect() to 127.0.0.1 port 1337 (#0)
* Trying 127.0.0.1...
* connected
* Connected to 127.0.0.1 (127.0.0.1) port 1337 (#0)

第二部分是在完成握手之后,客户端向服务器端发送请求报文,如下所示:

GET / HTTP/1.1
User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8r zlib/1.2.5
Host: 127.0.0.1:1337
Accept: */*

第三部分是服务器端完成处理后,向客户端发送响应内容,包括响应头和响应体,如下所示:

< HTTP/1.1 200 OK
< Content-Type: text/plain
< Date: Sat, 06 Apr 2013 08:01:44 GMT
< Connection: keep-alive
< Transfer-Encoding: chunked
<
Hello World

最后部分是结束会话的信息,如下所示:

* Connection #0 to host 127.0.0.1 left intact
* Closing connection #0

从上述的报文信息中可以看出HTTP的特点,它是基于请求响应式的,以一问一答的方式实现服务,虽然基于TCP会话,但是本身却并无会话的特点。

2. 构建示例

下面模拟实现一个简单的文件服务器:http_file_server.js

//实现一个静态文件服务器
var fs = require('fs');
var http = require('http');
var url = require('url');
//创建一个服务器
http.createServer( function(req,res) {
    if (req.url != "/favicon.ico"){
        var urlObj = url.parse(req.url,true,false);
        console.log(urlObj.pathname);
        fs.readFile('.' + urlObj.pathname,  function(err,data) {
                if (err){
                        res.writeHead(404, {'Content-Type': 'text/plain'});
                        res.end(JSON.stringify(err));
                        return;
                }
                console.log(data.toString());
                //将文件的内容写入res响应对象
                res.writeHead(200, {'Content-Type': 'text/plain'});
                res.end(data);
        });
    }
}).listen(8080);

在http_file_server.js同一目录下准备好一个测试文件test_json.json,然后编写客户端程序test_http_client.js :

var http = require('http');
var options = {
        hostname: '127.0.0.1',
        port: 8080,
        path: '/test_json.json',
        method: 'GET'
};
var req = http.request(options, function(res) {
        console.log('STATUS: ' + res.statusCode);
        console.log('HEADERS: ' + JSON.stringify(res.headers));
        res.setEncoding('utf8');
        res.on('data', function (chunk) {
                console.log(chunk);
        });
});

req.end();

启动服务端程序,然后运行客户端,输出为:

xiao@uXiao:~/nodejs/深入浅出nodejs/chapter7$ node test_http_client.js 
STATUS: 200
HEADERS: {"content-type":"text/plain","date":"Thu, 02 Jan 2020 10:28:03 GMT","connection":"close","transfer-encoding":"chunked"}
{
        "rule":{
          "namespace":"strategy",
          "name":"test_exp_1496234234223400",
          "version":0,
          "last_modify_time":1434234236819000,
          "log_rate":1023300,
          "schema_version":"hello_world!"
        }
 }

2.1 服务端分析

报文头:

HEADERS: {"content-type":"text/plain","date":"Thu, 02 Jan 2020 10:28:03 GMT","connection":"close","transfer-encoding":"chunked"}

影 响 响 应 报 文 头 部 信 息 的 API 为 res.setHeader() 和 res.writeHead() 。在上述示例中:res.writeHead(404, {'Content-Type': 'text/plain'});其分为 setHeader() 和 writeHead() 两个步骤。它在 http 模块的封装下,实际生成如下报文:

< HTTP/1.1 200 OK
< Content-Type: text/plain

我们可以调用 setHeader 进行多次设置,但只有调用 writeHead 后,报头才会写入到连接中。除此之外, http 模块会自动帮你设置一些头信息。
报文体部分:

报文体部分则是调用 res.write() 和 res.end() 方法实现,后者与前者的差别在于 res.end() 会先调用 write() 发送数据,然后发送信号告知服务器这次响应结束,响应结果如下所示:

响应结束后,HTTP服务器可能会将当前的连接用于下一个请求,或者关闭连接。值得注意
的是,报头是在报文体发送前发送的,一旦开始了数据的发送, writeHead() 和 setHeader() 将不
再生效。这由协议的特性决定。
另外,无论服务器端在处理业务逻辑时是否发生异常,务必在结束时调用 res.end() 结束请
求,否则客户端将一直处于等待的状态。当然,也可

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值