持续创作,加速成长!这是我参与「掘金日新计划 · 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。