持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情
Node 内置 HTTP 模块
上节我们讲了使用 Node.js 的 net模块
创建 TCP 服务来进行 HTTP 通信。使用 TCP 协议建立连接属于 Socket编程
的内容,在没有 Node.js 时,你无法使用 JavaScript 进行 Socket编程。所以即使说 Node.js 赋予了 JavaScript 新的生命也不为过。
但是利用 Socket 进行 HTTP 请求,并按照 HTTP 协议的规范组织响应内容,这份过程重复且麻烦。Node.js 提供了一个 HTTP 服务的内置模块 http模块
,将此过程进行了封装。
用 http 模块创建 HTTP 服务
💡 如果要使用 HTTP 的 server 以及 client 模块则必须使用
require('http')
加载 http 模块。
为了支持尽可能多的 HTTP 应用,Node.js 的 HTTP API 非常底层。只处理到流(stream)相关的操作以及信息解析。API 将信息解析成为信息头和信息体,但并不解析实际的信息头和信息体的具体内容。
现在,我们将使用 Node 内置的 http 模块创建一个和上一章同样的 HTTP 应用。当我们请求的根路径 /
的时候,返回 200 状态和 <h1>Page Not Found</h1>
,请求其他路由的时候返回 404 状态和<h1>Page Not Found</h1>
。
代码如下:
// http-server.js
const http = require('http');
const url = require('url');
const HOST = "127.0.0.1";
const PORT = 6869;
const server = http.createServer();
server.listen({
port: PORT,
host: HOST
}, () => {
console.log(`server listen on `, server.address());
});
// 监听客户端发起的 request
server.on('request', (req, res) => {
console.log('connect success!\n');
const header = req.headers;
const urlData = url.parse(`http://${header.host}${req.url}`);
if(urlData.pathname.indexOf('404') !== -1) {
res.writeHead(404, {'Content-Type': 'text/html'});
res.end('<h1>Page Not Found!</h1>');
} else {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end('<h1>Hello world!</h1>');
}
})
server.on('clientError', (err, socket) => {
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});
使用浏览器访问服务端,结果如图:
相比于使用 TCP 创建 Socket 来处理 HTTP 请求,使用 http 服务进行请求处理则更为简单。我们无需手动解析 HTTP 请求报文,只需要启动 request事件监听
,针对请求组织响应即可。这样一来,应用层之下的传输层(TCP)对我们而言就是透明的,我们只需要关注给请求对应的响应。
http.createServer
创建一个 HTTP 服务(http.Server
类)
- request 事件,每次接收到一个请求时触发。该监听器执行后会返回一个回调函数,该回调函数拥有两个
http.IncomingMessage
类型的参数。 - close 事件,当服务器关闭时触发
- Server.listen() 开启HTTP服务器监听连接
- Server.close() 停止服务端接收的新连接
http.IncomingMessage
事件
data
事件
当接收到数据时会产生这个事件,事件监听器会接收到数据。注意,如果不对这个事件添加监听器,那么数据会丢失。
incomingMessage.on('data',function(chunk){ ... });
chunk 是个数据片段 Buffer类型
。
end
事件
如果在这个之后不会再产生数据了,那么就会发出这个事件。这个事件只发生一次。
incomingMessage.on('end',function(){ ... })
close
事件
这个事件的产生是当服务器端 response.end() 调用之前客户端关闭造成的。看如下代码:
const http = require("http");
const server = http.createServer();
server.on("request",function(req,res){
req.on("close",function(){
console.log("close")
})
res.write("hello!");
// res.end();
})
server.listen(POST);
这里的 res.end()
不会被调用。当浏览器访问服务器,然后关闭浏览器,这时req就会发出close事件。
http.ServerResponse
事件
这是一个可写流 writable
。可以直接使用 writeHead
来写入 HTTP 响应头, 用res.end
来写入 HTTP 的响应体部分。
response.writeHead(statusCode, [reasonPhrase], [headers])
这个方法的是用来发送一个响应报文头给请求方。第一个参数 statusCode
是由一个3位数字所构成的 HTTP 状态码。最后一个参数 headers
是响应头具体内容。也可以使用一个方便人们直观了解的 reasonPhrase
作为第二个参数:
// 在一次完整信息交互中此方法只能调用一次,并且必须在调用response.end()之前调用。
response.writeHead(200, {
'Content-Length' : body.length,
'Content-Type' : 'text/plain'
});
response.write(chunk, encoding='utf8')
此方法必须在 writeHead
方法调用后才可以被调用,负责发送响应报文中的部分数据。如果要发送一个报文体的多个部分,则可以多次调用此方法。
参数 chunk
可以是一个字符串或者一个buffer。如果 chunk 是一个字符串,则第二个参数指定如何将字符串的编码。缺省情况下,编码为'utf8' 。
response.end([data], [encoding])
这个方法会告诉服务器此响应的所有报文头及报文体已经发出。 服务器在此调用后认为这条信息已经发送完毕!这个方法必须对每个响应调用一次。
如果指定 data 参数,他就相当于调用了 response.write(data, encoding)
然后接着调用了response.end()
。
总结
对比 http 模块与 net 模块,http 模块用起来相对简单,无需关心 HTTP 协议本身的内容——自己解析 HTTP request 或者自己构造 HTTP response,只需要调用 HTTP 相关的API,直接使用相关的req、res对象来处理请求或返回响应即可!
接下来我们将按照 HTTP 报文协议逐步学习 HTTP。