- 教程
- CLI
【免费下载链接】learnyounode
Learn You The Node.js For Much Win! An intro to Node.js via a set of self-guided workshops.
导读
本指南围绕 learnyounode 第 12 个练习 "HTTP JSON API Server"(对应任务文档 exercises/http_json_api_server/problem.vi.md)展开,目标是用 Node.js 核心模块编写一个返回 JSON 数据的 HTTP 服务器:通过url模块解析路径与查询字符串、用Date解析 ISO 格式时间、用JSON.stringify输出结构化响应。阅读本文后,你将掌握如何基于原生http+url模块完成多端点路由与 JSON 响应,并通过 learnyounode 的验证器(verifier)与官方测试用例检验代码的正确性。
练习目标:一个返回 JSON 的 HTTP 服务器
本练习要求编写一个名为http-json-api-server.js的文件,实现一个 HTTP服务器,在收到对特定路径的 GET 请求时返回 JSON 数据。服务器需要提供两个端点:
/api/parsetime:请求携带查询字符串,其中 key 为iso,value 为 ISO 格式的时间字符串。例如:/api/parsetime?iso=2013-08-10T12:10:15.474Z响应 JSON 只包含三个属性:
hour(时)、minute(分)、second(秒):{ "hour": 14, "minute": 23, "second": 15 }/api/unixtime:接受相同的查询字符串,但返回 UNIX 纪元时间(自 1970-01-01 00:00:00 UTC 起的毫秒数),属性名为unixtime:{ "unixtime": 1376136615474 }
服务器监听端口由程序第一个命令行参数指定。这与 learnyounode 之前的time_server练习一致——端口均由process.argv[2]注入,而非硬编码。
核心技术点拆解
原文档 problem.vi.md 的提示部分给出了四个关键线索,下面逐一结合源码展开。
1. 用request.url做端点路由
HTTP 服务器回调中的request对象带有url属性,它包含请求路径与查询字符串。要实现两个端点,就需要根据该属性"路由"请求。官方解答 exercises/http_json_api_server/solution/solution.js 用正则前缀匹配实现路由:
if (/^\/api\/parsetime/.test(req.url)) { result = parsetime(time) } else if (/^\/api\/unixtime/.test(req.url)) { result = unixtime(time) }注意这里的^锚定只匹配路径开头,因此?iso=...查询串不会影响匹配结果。
2. 用new URL()解析 URL 与查询字符串
Node 核心模块url提供了 WHATWGURL类,new URL(request.url)能解析request.url并返回包含searchParams等有用属性的对象。原文档提示可以在命令行直接观察其结构:
$ node -pe "new URL('/test?q=1', 'http://example.com')"一个重要的细节:new URL()解析相对路径(如/api/parsetime?iso=...)时需要第二个参数作为基础 URL(base),官方解答正是这样使用的:
const parsedUrl = new URL(req.url, 'http://example.com') const time = new Date(parsedUrl.searchParams.get('iso'))URL#searchParams.get('iso')直接提取查询参数值,随后传入Date构造函数完成 ISO 字符串到时间对象的解析。更多细节可参考仓库内置文档 docs-nodejs/url.html。
3. 用Date完成 ISO 解析与时间换算
JavaScript 的Date对象既能以 ISO 格式输出时间(new Date().toISOString()),也能在构造函数中解析 ISO 字符串。官方解答将时间换算拆成两个纯函数:
function parsetime (time) { return { hour: time.getHours(), minute: time.getMinutes(), second: time.getSeconds() } } function unixtime (time) { return { unixtime: time.getTime() } }Date#getHours()/getMinutes()/getSeconds():取出本地时区下的时分秒;Date#getTime():返回自纪元起的毫秒数,直接构成unixtime值。
值得注意:getHours()系列方法返回的是服务器本地时区的时间。由于验证器生成的时间戳与实际比对逻辑(见下文)运行在同一主机上,时区影响会被抵消;但若部署到生产环境并希望统一按 UTC 输出,应改用getUTCHours()等 UTC 变体。
4. 以JSON.stringify输出并正确设置Content-Type
响应体必须是 JSON 字符串,使用JSON.stringify(result)序列化;同时作为"Web 世界的好公民",必须设置正确的Content-Type头:
res.writeHead(200, { 'Content-Type': 'application/json' }) res.end(JSON.stringify(result))官方解答对无法路由的路径返回404:
if (result) { res.writeHead(200, { 'Content-Type': 'application/json' }) res.end(JSON.stringify(result)) } else { res.writeHead(404) res.end() }完整官方解答示例
将上述要点组合起来,官方解答 exercises/http_json_api_server/solution/solution.js 全文如下(可直接作为可运行参考实现):
'use strict' const http = require('http') function parsetime (time) { return { hour: time.getHours(), minute: time.getMinutes(), second: time.getSeconds() } } function unixtime (time) { return { unixtime: time.getTime() } } const server = http.createServer(function (req, res) { const parsedUrl = new URL(req.url, 'http://example.com') const time = new Date(parsedUrl.searchParams.get('iso')) let result if (/^\/api\/parsetime/.test(req.url)) { result = parsetime(time) } else if (/^\/api\/unixtime/.test(req.url)) { result = unixtime(time) } if (result) { res.writeHead(200, { 'Content-Type': 'application/json' }) res.end(JSON.stringify(result)) } else { res.writeHead(404) res.end() } }) server.listen(Number(process.argv[2]))验证器如何校验你的实现(源码级原理)
learnyounode 通过 exercises/http_json_api_server/exercise.js 自动验证提交。理解其机制有助于写出精确满足判定的代码:
- 随机端口:验证器调用 lib/rndport.js 生成端口(范围
1024 + Math.floor(Math.random() * 64511)),提交程序与官方解答分别监听submissionPort与solutionPort,端口作为第一个参数传入子进程。 - 发起请求:
query()函数在服务器启动约 500ms 后,用hyperquest以 GET 方式请求http://localhost:<port>/api/parsetime?iso=<ISO时间>和http://localhost:<port>/api/unixtime?iso=<ISO时间>,并用bl收集完整响应体。 - 归一化比较:
normalizeJSON(data)先把响应JSON.parse再JSON.stringify,消除键顺序与空格差异,然后通过comparestdout与官方解答的输出逐字节比对。这意味着键名顺序、缩进风格都不影响判定,只要语义一致即可通过。 - 测试用例:仓库 test/http_json_api_server/ 中,
valid_01.js是语义正确的实现,而invalid_02.js(unixtime返回getTime() + 1)、invalid_03.js(hour返回getHours() + 1)都会因输出与官方解答不一致而判定失败——这正说明验证器对两个端点返回值的精确性要求非常严格。
运行与验证命令
在学习环境中完成编码后,用验证命令检查正确性(文档 problem.md 中给出):
$ learnyounode verify http-json-api-server.js本地手动联调时,可先启动服务器,再分别访问两个端点观察输出:
# 启动(监听 8000 端口) $ node http-json-api-server.js 8000 # 另开终端发起请求 $ curl "http://localhost:8000/api/parsetime?iso=2013-08-10T12:10:15.474Z" $ curl "http://localhost:8000/api/unixtime?iso=2013-08-10T12:10:15.474Z"常见踩坑点与边界处理
结合源码与测试用例,以下细节最容易导致验证失败:
- 端口必须取
process.argv[2],并转为数字(官方解答用Number(process.argv[2]))。不监听该端口或硬编码端口都会导致连接失败。 - 路由正则要锚定路径开头(
/^\/api\/parsetime/),否则可能误匹配;也不要匹配到query部分。 - 未匹配的路径需返回 404。虽然验证器只请求两个已知端点,但官方解答对
null结果统一返回404,保持行为完整。 Content-Type必须为application/json,与文档要求一致。- 响应体必须是合法 JSON 字符串:
JSON.stringify序列化后res.end输出;验证器的normalizeJSON会先JSON.parse,非法 JSON 会在 try/catch 中静默保留原文从而比对失败。
小结
本练习综合运用了 Node 原生http、url(WHATWGURL)与Date三大核心能力:new URL()负责解析路径与查询参数,Date负责 ISO 时间解析与毫秒换算,JSON.stringify与writeHead负责输出合规的 JSON 响应。通过 learnyounode 的自动验证器(随机端口、双请求、归一化比对)与 官方解答 的对照,你可以确认自己的实现与原文档要求完全一致——这套"路由 + 参数解析 + JSON 响应"的写法,也是日常 Node HTTP 服务开发中最基础、最常用的模式。
- 教程
- CLI
【免费下载链接】learnyounode
Learn You The Node.js For Much Win! An intro to Node.js via a set of self-guided workshops.
相关推荐
NocoBase 工作流 LLM 节点结构化输出(JSON Schema)配置与实现原理指南
NocoBase 工作流 LLM 节点结构化输出(JSON Schema)配置与实现原理指南 导读 本文围绕 NocoBase 工作流中的 LLM 节点「结构化
教程CLIActivepieces 核心基础库解析:@activepieces/core-utils 的设计原则、工具实现与使用实践
Activepieces 核心基础库解析:@activepieces/core utils 的设计原则、工具实现与使用实践 @activepieces/core
教程CLIlearnyounode 实战:用 Node.js 核心模块构建 HTTP JSON API 服务器(http-json-api-server)
learnyounode 实战:用 Node.js 核心模块构建 HTTP JSON API 服务器(http json api server) learnyo
教程CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考