如何用 remix/node-fetch-server 的 createRequestListener 启动 Node HTTP 服务器
【免费下载链接】remixThe fully-stacked web framework项目地址: https://gitcode.com/GitHub_Trending/re/remix
这篇文章解决一个具体的部署任务:把你的 fetch 风格请求处理函数(接收 web 标准Request、返回Response)挂到一个真正的 Node.js HTTP 服务器上。remix/node-fetch-server提供的createRequestListener()会把 Node 的 HTTP 服务器接口转换成Request/Response流程,可以直接用于node:http、node:https和node:http2。完成本文后,你会得到一个在本地监听 3000 端口、能处理 GET/POST 请求并返回 JSON 的服务器,并了解生产部署时的域名、反向代理和 HTTPS 配置项。
准备条件
安装命令来自 node-fetch-server 的 README:
npm i remix包名是remix,子路径导出为remix/node-fetch-server(monorepo 内部包名为@remix-run/node-fetch-server,发布后通过remix包的子路径引用)。下面的示例代码使用 TypeScript 语法(含request: Request类型标注),你可以用支持直接运行 TypeScript 的 Node 运行时执行该文件,或先转译成 JavaScript 再用 Node 运行。代码本身只依赖 Node 内置的node:http、node:https、node:fs、node:http2模块,没有额外运行时依赖。
最短主路径:用 createRequestListener 启动 HTTP 服务器
完整的 README 示例如下,它是可直接运行的完整服务器文件:
import * as http from 'node:http' import { createRequestListener } from 'remix/node-fetch-server' async function handler(request: Request) { let url = new URL(request.url) if (url.pathname === '/' && request.method === 'GET') { return new Response('Welcome to the User API! Try GET /api/users') } if (url.pathname === '/api/users' && request.method === 'GET') { return Response.json([ { id: '1', name: 'Alice', email: 'alice@example.com' }, { id: '2', name: 'Bob', email: 'bob@example.com' }, ]) } return new Response('Not Found', { status: 404 }) } let server = http.createServer(createRequestListener(handler)) server.listen(3000, () => { console.log('Server running at http://localhost:3000') })各部分的职责:
handler是一个FetchHandler,接收 web 标准Request,返回Response或Promise<Response>。createRequestListener()会根据 handler 声明的参数个数选择处理方式:接收request参数时按请求处理,声明两个参数时还会收到第二个参数client(客户端地址信息)。http.createServer(createRequestListener(handler))把 listener 交给node:http,server.listen(3000, ...)绑定 3000 端口。启动回调里的console.log('Server running at http://localhost:3000')是文档示例代码的启动提示,进程能打印这行说明端口监听成功。- handler 内抛错时,listener 默认会发送 500 响应,响应体为
Internal Server Error(见 请求监听器实现);也可以在选项里传入onError自定义错误响应。
验证服务器
服务器运行后,用 Node 自带的 fetch 发请求核对。以下响应体是 README 示例代码中硬编码的返回内容(文档示例),如果你的 handler 逻辑与示例一致,应得到相同结果:
node -e "fetch('http://localhost:3000/').then(r => r.text()).then(console.log)" # 文档示例输出:Welcome to the User API! Try GET /api/users node -e "fetch('http://localhost:3000/api/users').then(r => r.json()).then(console.log)"访问示例 handler 中未定义的其余路径,按示例逻辑会返回 404 和Not Found。
读取请求体和搜索参数
处理 POST JSON、URL 搜索参数的写法同样来自 README,handler 中使用request.json()与url.searchParams:
async function handler(request: Request) { let url = new URL(request.url) // 处理 JSON 数据 if (request.method === 'POST' && url.pathname === '/api/users') { try { let userData = await request.json() if (!userData.name || !userData.email) { return Response.json({ error: 'Name and email are required' }, { status: 400 }) } let newUser = { id: Date.now().toString(), ...userData } return Response.json(newUser, { status: 201 }) } catch (error) { return Response.json({ error: 'Invalid JSON' }, { status: 400 }) } } // 处理 URL 搜索参数 if (url.pathname === '/api/search') { let query = url.searchParams.get('q') let limit = parseInt(url.searchParams.get('limit') || '10') return Response.json({ query, limit, results: [] }) } return new Response('Not Found', { status: 404 }) }GET 和 HEAD 请求不携带 body 流,其余方法的请求体会以ReadableStream形式挂到request上,request.json()、request.text()等标准 API 均可使用。
部署配置:自定义域名与反向代理
固定域名(host 选项)
部署在自有域名或 VPS 时,README 建议用host选项覆盖request.url的 host 部分(默认从 HTTPHost请求头推导):
import * as http from 'node:http' import { createRequestListener } from 'remix/node-fetch-server' let hostname = process.env.HOST || 'api.example.com' async function handler(request: Request) { console.log(request.url) // 文档示例:http://api.example.com/path return Response.json({ message: 'Hello from custom domain!', url: request.url, }) } let server = http.createServer(createRequestListener(handler, { host: hostname })) server.listen(3000)api.example.com是文档示例值,替换为你自己的域名。注意优先级规则:当host或protocol显式设置时,它们优先于反向代理头生效。
反向代理头(trustProxy 选项)
应用部署在受信任反向代理后面时,Node 看到的是代理连接而非原始客户端。启用trustProxy后,以下请求头会参与构造request.url和客户端信息:
Forwarded: proto与X-Forwarded-Proto提供原始协议;Forwarded: host与X-Forwarded-Host提供原始 host;Forwarded: for与X-Forwarded-For提供原始客户端地址。
let server = http.createServer( createRequestListener(handler, { trustProxy: true, }), ) server.listen(3000)这里有一条明确的安全限制,来自 README:只有当服务器只能通过会覆写这些请求头的受信任代理访问时才应启用该选项,否则客户端可以伪造 host、协议和客户端地址。
获取客户端地址(用于日志与限流)
声明第二个参数client即可获得客户端连接信息(IP 地址、端口;启用trustProxy后取可信代理头中的值):
import { type FetchHandler } from 'remix/node-fetch-server' let handler: FetchHandler = async (request, client) => { console.log(`Request from ${client.address}:${client.port}`) // 可用于限流、地理定位等 if (isRateLimited(client.address)) { return new Response('Too Many Requests', { status: 429 }) } return Response.json({ message: 'Hello!', yourIp: client.address }) }可选分支:HTTPS 与 HTTP/2
HTTPS
README 给出的 HTTPS 写法是把 listener 传给node:https的createServer,private-key.pem、certificate.pem是文档示例中的证书文件名,替换为你自己的私钥与证书文件路径:
import * as https from 'node:https' import * as fs from 'node:fs' import { createRequestListener } from 'remix/node-fetch-server' let options = { key: fs.readFileSync('private-key.pem'), cert: fs.readFileSync('certificate.pem'), } let server = https.createServer(options, createRequestListener(handler)) server.listen(443, () => { console.log('HTTPS Server running on port 443') })走 HTTPS 时,request.url的协议默认由连接协议推导,即https:开头,无需额外配置。
HTTP/2
仓库提供了一个可运行的 HTTP/2 示例:demos/http2,其 server.js 使用http2.createSecureServer()加载同目录下的自签名证书文件(server.key、server.crt),监听 44100 端口,通过request事件挂接 listener:
import * as http2 from 'node:http2' import * as fs from 'node:fs' import * as path from 'node:path' import { fileURLToPath } from 'node:url' import { createRequestListener } from '@remix-run/node-fetch-server' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const PORT = 44100 const options = { key: fs.readFileSync(path.join(__dirname, 'server.key')), cert: fs.readFileSync(path.join(__dirname, 'server.crt')), } const server = http2.createSecureServer(options) server.on( 'request', createRequestListener((request) => { let url = new URL(request.url) if (url.pathname === '/') { return new Response('Hello HTTP/2!', { headers: { 'Content-Type': 'text/plain' }, }) } return new Response('Not Found', { status: 404 }) }), ) server.listen(PORT, () => { console.log(`Server running at https://localhost:${PORT}`) })注意与http的差别:http2的 listener 挂在'request'事件上,而不是直接传给createServer第二个参数。启动成功时控制台打印Server running at https://localhost:44100(文档示例输出),访问/返回Hello HTTP/2!。示例依赖的包名为@remix-run/node-fetch-server,这是 monorepo 内的包名,对外发布后对应remix/node-fetch-server。
替代路径:底层 API 与流式响应
createRequest / sendResponse 手动组合
需要自己控制中间件、加响应耗时头或自定义错误处理时,README 给出的底层 API 是createRequest(req, res, options)(把 Node 请求转成 webRequest)和sendResponse(res, response)(把 webResponse写回 Node 响应对象):
import * as http from 'node:http' import { createRequest, sendResponse } from 'remix/node-fetch-server' let server = http.createServer(async (req, res) => { let request = createRequest(req, res, { host: process.env.HOST }) try { let startTime = Date.now() let response = await handler(request) // 确保 response 可变 response = new Response(response.body, response) let duration = Date.now() - startTime response.headers.set('X-Response-Time', `${duration}ms`) await sendResponse(res, response) } catch (error) { console.error('Server error:', error) res.writeHead(500, { 'Content-Type': 'text/plain' }) res.end('Internal Server Error') } }) server.listen(3000)process.env.HOST由你按部署环境设置。
流式响应
Response支持以ReadableStream作为 body,README 示例每 1 秒推送一个 chunk,共 5 个:
async function handler(request: Request) { if (request.url.endsWith('/stream')) { let stream = new ReadableStream({ async start(controller) { for (let i = 0; i < 5; i++) { controller.enqueue(new TextEncoder().encode(`Chunk ${i}\n`)) await new Promise((resolve) => setTimeout(resolve, 1000)) } controller.close() }, }) return new Response(stream, { headers: { 'Content-Type': 'text/plain' }, }) } return new Response('Not Found', { status: 404 }) }限制与注意事项
trustProxy只在受信任代理会覆写Forwarded/X-Forwarded-*请求头时启用,否则可被客户端伪造;host/protocol显式配置时优先于代理头。- handler 抛错时的默认错误响应是 500
Internal Server Error;自定义onError后由其返回值决定响应。 - README 附带的基准数据(如 raw throughput 场景下
remix/node-fetch-server约61,587req/s,环境为 Apple M5 Pro、Node.js v24.18.0)是作者在特定硬件上测得的文档示例数据,仅可作量级参考,不要当作你环境的预期性能。
参考文档:README、createRequestListener 实现、HTTP/2 示例。
【免费下载链接】remixThe fully-stacked web framework项目地址: https://gitcode.com/GitHub_Trending/re/remix
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考