Menu

Node.js HTTP 服务器:http.createServer 示例

http.createServer() 接收一个函数,每个请求到来时都会带着 req 和 res 运行它。你从 req 中读取请求方法和 URL,然后在 res 上设置状态码、响应头和响应体,再调用 res.end()。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

http.createServer() 用一个函数(请求处理函数)创建 Web 服务器。每个请求到来时,Node 都会用两个对象调用它:req(客户端发来的请求方法、URL 和请求头)和 res(你要发回的内容)。在 res 上设置状态码和响应头,然后带着响应体调用 res.end()。server.listen(3000) 开始在 3000 端口上接受连接。

第一个请求的响应是 You asked for GET /hello?name=Ada,控制台也会打印同样的请求方法和 URL。从请求栏发送 DELETE /anything:同一个处理函数会响应它,因为普通的 http 服务器没有路由。所有请求都经过这一个函数。

发送 JSON 并设置状态码

res.writeHead(status, headers) 一次调用就能设置状态码和响应头。发送 JSON 时,设置 Content-Type: application/json,然后发送 JSON.stringify() 的结果;res.end() 只接受字符串或 Buffer。

GET /users?limit=1 返回 [{"id":1,"name":"Ada"}]。req.url 是路径加查询字符串,所以用 new URL(req.url, 'http://localhost') 把它拆分成 pathname 和 searchParams(加上基础地址只是因为 URL 需要一个绝对地址)。发送 GET /nothing,状态码就会变成 404。

用 switch 做路由

一个小型 API 只需要对请求方法和路径做一次 switch。拼出 'GET /users' 这样的键,再给每个路由一个自己的 case:

发送 POST /users,请求体为 {"name": "Grace"}:响应是 201 和 {"id":2,"name":"Grace"},现在 GET /users 会列出两个用户。用请求体 hello 发送同样的 POST,你会得到 400 和 {"error":"Body must be JSON"}。

读取 POST 请求体

Node 不会替你解析请求体。req 是一个可读流,请求体会通过 data 事件分块到达,全部到齐后触发 end。上面的 readBody() 把这些块收集成一个字符串,并返回一个 Promise,这样处理函数就可以 await 它。由于 req 是异步可迭代对象,下面这个循环也能完成同样的工作:

let data = '';
for await (const chunk of req) data += chunk;
const body = JSON.parse(data);

真实的服务器还会限制大小(一旦 data.length 超过上限,就停止读取并返回 413),并在调用 JSON.parse 之前检查 req.headers['content-type']。

常见错误

忘记调用 res.end()。 某个分支设置了状态码却没有结束响应,客户端就会一直等到超时。

在响应体之后设置响应头。 一旦 res.end() 或第一次 res.write() 运行过,响应头就已经发出,res.setHeader() 会抛出 ERR_HTTP_HEADERS_SENT。像示例那样,提前发送响应时使用 return。

拿 req.url 和路径比较。 req.url 包含查询字符串,所以对于 /users?limit=1,req.url === '/users' 为 false。应该比较 pathname。

什么时候改用 Express

做健康检查、接收 webhook 或学习 HTTP 的工作原理,http 模块就够了。一旦你需要 /users/:id 这样的路由参数、JSON 请求体解析、中间件和错误处理,你最后会自己写出一个小框架。Express 就是这样一个框架,它基于同样的 req 和 res 对象构建;Express 路由 用 app.get() 和 app.post() 展示了同样的 API。

常见问题

res.write 和 res.end 有什么区别?

res.write() 发送响应体的一块内容,并保持响应打开,所以可以多次调用。res.end() 发送可选的最后一块内容,并结束响应。每个响应都需要恰好一次 res.end()。

如何停止 Node HTTP 服务器?

调用 server.close()。它会立即停止接受新连接,并在已打开的连接都结束后运行回调,这正是收到 SIGTERM 时优雅关闭所需要的。

如何在 http 模块中使用 HTTPS?

改用 https 模块:https.createServer({ key, cert }, handler) 接收同样的处理函数,外加一个私钥和一个证书。在生产环境中,通常由 Nginx 这样的代理或托管平台终止 TLS,你的 Node 服务器继续监听普通 HTTP。

Node 服务器应该监听哪个端口?

任何 1024 以上的空闲端口都不需要管理员权限;开发时常用 3000 和 8080。托管平台通常会通过环境变量告诉你端口,所以写成 server.listen(process.env.PORT || 3000)。

Coddy 编程语言插图

和 Coddy 一起学编程

开始