【计算机网络】使用Node.js开发HTTP服务

52 阅读4分钟

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

使用浏览器访问服务端,结果如图:

image.png

image.png

相比于使用 TCP 创建 Socket 来处理 HTTP 请求,使用 http 服务进行请求处理则更为简单。我们无需手动解析 HTTP 请求报文,只需要启动 request事件监听 ,针对请求组织响应即可。这样一来,应用层之下的传输层(TCP)对我们而言就是透明的,我们只需要关注给请求对应的响应。

http.createServer创建一个 HTTP 服务(http.Server类)

  1. request 事件,每次接收到一个请求时触发。该监听器执行后会返回一个回调函数,该回调函数拥有两个 http.IncomingMessage 类型的参数。
  2. close 事件,当服务器关闭时触发
  3. Server.listen() 开启HTTP服务器监听连接
  4. 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。