likes
comments
collection
share

从 0 构建自己的 HTTP 服务器

作者站长头像
站长
· 阅读数 18

了解HTTP协议与TCP协议,并重 0 开始构建一个服务器。

http 与 net 库的对比

Node.js 提供了两个用于处理 HTTP 请求的库:http 和 net。这两个库都提供了类似的功能,但也有一些关键的区别。

http 库

http 库是 Node.js 内置的 HTTP 库。它提供了一个简单易用的 API,用于创建和处理 HTTP 服务器和客户端。http 库的 API 基于事件驱动,这使得它非常适合处理大量并发请求。

net 库

net 库是 Node.js 提供的另一个 HTTP 库。它提供了一个更底层的 API,用于创建和处理 HTTP 连接。net 库的 API 更灵活,可以用于创建自定义 HTTP 服务器和客户端。

区别

以下是 http 库和 net 库的一些关键区别:

特性http 库net 库
易用性易于使用更复杂
功能提供基本的 HTTP 功能提供更丰富的 HTTP 功能
性能通常比 net 库更快通常比 http 库更慢
适用场景适用于大多数 HTTP 应用程序适用于需要自定义 HTTP 服务器或客户端的应用程序

除了上述区别之外,http 库和 net 库还有一些其他的区别:

  • http 库使用事件驱动,而 net 库使用回调函数。 这意味着 http 库的 API 更简洁,而 net 库的 API 更灵活。
  • http 库提供了一个 Server 类,用于创建 HTTP 服务器。 net 库没有提供专门的 HTTP 服务器类,但可以使用 Server 类来创建 HTTP 服务器。
  • http 库提供了一个 Client 类,用于创建 HTTP 客户端。 net 库没有提供专门的 HTTP 客户端类,但可以使用 Socket 类来创建 HTTP 客户端。

使用 net 实现一个 TCP 服务器

使用 net 模块创建一个基础服务器。

const net = require('net')


// Uncomment this to pass the first stage
const server = net.createServer(socket => {
   socket.on('close', () => {
     socket.end()
     server.close()
   })

  socket.on('data', (request) => {
    const htmlStr = `
      <h1>hello world!</h1>
    `
    socket.write('HTTP/1.1 200 OK \r\n\r\n ' + htmlStr, 'utf8')
    socket.end()
  })
})

server.listen(4221, 'localhost')

它监听4221端口,socket.on(),中回调函数 request 是 http 请求体。他的报文通常如下:

POST / HTTP/1.1
Host: localhost:4221
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.207.132.170 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*\/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
BODY

当时 GET 请求时,POST 替换为 GET,最后一行的 body 去掉。服务器通常要根据请求内容,做出不同的响应。因此需要解析请求体。

下面代码是一个基本的HTTP请求解析器:

// 解析请求
function parseRequest(requestData) {
  // 使用'\r\n'(回车和换行)作为分隔符
  const [request, ...requestHeaders] = requestData.split('\r\n')
  let body = null
  const n = requestHeaders.length

  if (requestHeaders[n - 2] == '') {
    body = requestHeaders.pop()
    requestHeaders.pop()
  }
  const [method, path, version] = request.split(' ')
  const headers = {}
  
  requestHeaders.forEach(header => {
    if (!header) return
    const [key, value] = header.split(': ')
    headers[key] = value
  })
  return { method, path, version, headers, body }
}

// 请求
  socket.on('data', data => {
    const requestData = data.toString()
    const request = parseRequest(requestData)
    console.log('requestData: ', requestData)
    console.log('request: ', request)
  })
  • 检查requestHeaders数组中倒数第二个元素(即requestHeaders[n - 2])是否为空字符串。这是一个检查,用于确定请求是否是带有正文的POST请求。
  • 函数返回一个包含解析后的请求信息的对象,包括method(方法)、path(路径)、version(版本)、headers(标头)和body(如果是POST请求则包括正文)。

我们直接使用 TCP 协议建立连接虽然可以实现 HTTP 协议通讯,但是我们需要自己去解析 HTTP 请求内容,并按照 HTTP 协议的规范组织响应内容。这显然很麻烦。

但是Node.js 提供了更加简单的创建 HTTP 服务的模块 —— http 模块。

http 库实现

使用Node.js创建简单HTTP服务器的示例

const http = require('http')

const server = http.createServer((req, res) => {
  const { pathname } = new URL(`http://${req.headers.host}${req.url}`)
  if (pathname === '/') {
    res.writeHead(200, { 'Content-Type': 'text/html' })
    res.end('<h1>Hello world</h1>')
  } else {
    res.writeHead(404, { 'Content-Type': 'text/html' })
    res.end('<h1>Not Found</h1>')
  }
})

server.on('clientError', (err, socket) => {
  socket.end('HTTP/1.1 400 Bad Request\r\n\r\n')
})

server.listen(8080, () => {
  console.log('opened server on', server.address())
})

使用http.createServer()方法创建了一个HTTP服务器实例,并传入一个回调函数作为参数。这个回调函数会在每次有HTTP请求到达服务器时执行。

总结

http 模块比 net 模块用起来更简单,不需要自己解析 HTTP 请求的内容,或者自己拼接响应的内容,直接使用回调函数中的 req、res 对象来处理请求或响应即可。

以上示例代码都可在此找到: Github

参考