了解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 模块创建一个基础服务器。
js
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 请求体。他的报文通常如下:
bash
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请求解析器:
js
// 解析请求
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服务器的示例
js
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
参考
- 掘金小册: 《从前端到全栈》
- 交互式的教程,设有 8 个任务,每个任务完成一项服务器的功能,在提交代码时网站会验证是否完成,支持大部分语言:Courses | Build your own HTTP server