news 2026/9/10 18:58:09

如何用 remix/node-fetch-server 的 createRequestListener 启动 Node HTTP 服务器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
如何用 remix/node-fetch-server 的 createRequestListener 启动 Node HTTP 服务器

如何用 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:httpnode:httpsnode: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:httpnode:httpsnode:fsnode: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,返回ResponsePromise<Response>createRequestListener()会根据 handler 声明的参数个数选择处理方式:接收request参数时按请求处理,声明两个参数时还会收到第二个参数client(客户端地址信息)。
  • http.createServer(createRequestListener(handler))把 listener 交给node:httpserver.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是文档示例值,替换为你自己的域名。注意优先级规则:当hostprotocol显式设置时,它们优先于反向代理头生效。

反向代理头(trustProxy 选项)

应用部署在受信任反向代理后面时,Node 看到的是代理连接而非原始客户端。启用trustProxy后,以下请求头会参与构造request.url和客户端信息:

  • Forwarded: protoX-Forwarded-Proto提供原始协议;
  • Forwarded: hostX-Forwarded-Host提供原始 host;
  • Forwarded: forX-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:httpscreateServerprivate-key.pemcertificate.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.keyserver.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 抛错时的默认错误响应是 500Internal Server Error;自定义onError后由其返回值决定响应。
  • README 附带的基准数据(如 raw throughput 场景下remix/node-fetch-server61,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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 18:57:30

动态功耗管理,航天级SSD在轨节能的关键技术解析

在有限的星载能源供给下&#xff0c;每一瓦功耗都直接影响卫星平台的供电预算、热控设计与任务时长。对于承担数据记录与高速回传任务的固态存储系统而言&#xff0c;读写压力的增加必然带来功耗上升&#xff0c;而真空环境又限制了热量的对流排出——功耗、热量与可靠性之间由…

作者头像 李华
网站建设 2026/9/10 18:56:34

基于UDS协议的BootLoader上位机开发实践

1. 项目概述&#xff1a;UDS BootLoader上位机开发背景在汽车电子和嵌入式系统开发领域&#xff0c;固件升级是不可或缺的关键功能。传统通过JTAG或SWD接口烧录程序的方式&#xff0c;在设备安装到整车或最终产品后变得不可行。基于UDS(Unified Diagnostic Services)协议的Boot…

作者头像 李华
网站建设 2026/9/10 18:56:19

Vivo手机数据备份与恢复全指南

1. Vivo手机备份恢复全攻略作为一名长期从事Android开发的工程师&#xff0c;我经常需要处理各种手机数据备份和恢复的问题。Vivo作为国内主流手机品牌&#xff0c;其备份恢复机制与其他Android设备有些许不同。今天我就来详细解析Vivo手机的备份恢复全流程&#xff0c;包括本地…

作者头像 李华
网站建设 2026/9/10 18:55:21

Spring Boot事务全攻略:回滚机制、传播行为与失效排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华