☰
learnyounode 实战:用 Node http 模块与 fs.createReadStream 构建 HTTP 文件服务器
2026/9/29 8:43:24 网站建设 项目流程
  • 教程
  • CLI

【免费下载链接】learnyounode

Learn You The Node.js For Much Win! An intro to Node.js via a set of self-guided workshops.

项目地址:https://gitcode.com/gh_mirrors/le/learnyounode
点击查看免费下载

本文基于 learnyounode 系列习题中的http_file_server(HTTP 文件服务器)一题展开,讲解如何用 Node.js 核心模块http搭建一个将同一个文本文件以流(Stream)方式返回给每次请求的 HTTP 服务器,并解释request/response为何是流、fs.createReadStream()与src.pipe(dst)如何把文件流无缝接到 HTTP 响应流。读完本文,你将掌握 HTTP 服务器的最小可运行实现、命令行参数的解析方法,以及 learnyounode 自动化校验对createReadStream用法的判定规则,能够独立完成并通过该习题。

一、习题目标与输入约定

本节习题(日文版原文见 exercises/http_file_server/problem.ja.md,英文版见 problem.md)要求编写一个 HTTP文件服务器:对于收到的每一次请求,都返回同一个文本文件的内容。题目对程序输入做了两项硬性约定:

  • 第一个命令行参数:服务器要监听的端口号;
  • 第二个命令行参数:要返回的文本文件的路径。

同时,题目明确给出了一条强制约束——必须使用fs.createReadStream()方法,以流的形式把文件内容送入响应(英文原题措辞为 “Youmustuse thefs.createReadStream()method to stream the file contents to the response”)。

需要说明的是,英文原题还要求先创建一个名为http-file-server.js的文件,然后在项目根目录用{appname} verify http-file-server.js校验,其中{appname}在本仓库对应的 CLI 名称是learnyounode(见 package.json 中的bin字段)。

二、为什么选用http模块而不是net

题目在提示部分特别强调:本题要创建的是 HTTP 服务器,而不是普通的 TCP 服务器,因此应该使用 Node 核心模块http,而不是net。

  • http与net相似,都提供了名为createServer()的函数,但http.createServer()创建的是能够讲 HTTP 协议的服务器:它会解析请求行、请求头,并负责把响应头与响应体序列化后写回客户端。
  • 从仓库的后续习题看,同一阶段还包含基于net的 time_server(TCP 时间服务器)与基于http的 http_client、http_collect 等,正是为了让学习者区分这两类服务器的适用场景。

因此,本题的正确技术选型是:

const http = require('http')

三、http.createServer()与 request/response 两个对象

http.createServer()接收一个回调函数作为参数,该回调会在服务器每次收到连接/请求时被调用一次,其签名固定为:

function listener (request, response) { /* ... */ }

两个参数的含义:

  • request:代表本次 HTTP 请求的对象,用于获取请求相关属性,例如请求头(headers)、查询字符串(query-string)等;
  • response:代表对应的响应对象,用于向客户端回送数据,既包括响应头部(headers),也包括响应体(body)。

题目的提示还给出了一个关键概念:request和response在 Node.js 中本身都是 Stream!这意味着一方面可以直接用流的抽象(如pipe、data事件)来收发数据;另一方面,它让“把文件流接入响应流”这种场景有了天然的对接点——这正是本题的核心考点。

http.createServer()会返回服务器实例,调用server.listen(portNumber)即可在指定端口开始监听。题目给出的一般形态如下:

const http = require('http') const server = http.createServer(function (req, res) { // request handling logic... }) server.listen(8000)

四、用fs.createReadStream()与pipe完成文件流对接

文件系统核心模块fs提供了一批流式(streaming)API。本题要用的是fs.createReadStream():

  • 把命令行第二个参数(文本文件路径)传给fs.createReadStream(),会得到一个表示该文件的流对象(ReadStream);
  • 使用src.pipe(dst)可以把src流的数据"管道"式地导入dst流;
  • 因此,可以一行代码把文件系统流接到HTTP 响应流上:
fs.createReadStream(process.argv[3]).pipe(res)

关于 pipe 的语义(详见仓库内置的 Node 文档 docs-nodejs/stream.html 与 docs-nodejs/fs.html):pipe会监听源流上的data事件并把数据写入目标流,同时处理背压(back-pressure),即当目标流写入速度跟不上时暂停源流读取,从而避免内存被大量未消费的数据撑爆。这正是用流式方案而不是一次性readFile读取大文件的价值所在。

五、一份可直接运行并通过校验的参考实现

仓库为本题提供了官方参考实现 exercises/http_file_server/solution/solution.js,全文如下(含注释解读):

'use strict' const http = require('http') const fs = require('fs') const server = http.createServer(function (req, res) { res.writeHead(200, { 'content-type': 'text/plain' }) // 先写出状态码与响应头 fs.createReadStream(process.argv[3]).pipe(res) // 把文件流 pipe 进响应流 }) server.listen(Number(process.argv[2])) // 监听第一个命令行参数指定的端口

对照题目要求拆解这份实现,有三个容易被忽略的细节值得注意:

  1. 端口要用Number()转换:process.argv[2]拿到的是字符串,而server.listen()在旧版 Node 中对字符串端口与数字端口的处理不同。官方实现(以及测试用例 test/http_file_server/valid_01.js 中的process.argv[2] | 0)都显式把端口转为数字,这是稳妥的写法。
  2. 先用res.writeHead(200, ...)写响应头:HTTP 响应必须先写头部再写主体。这里显式声明了content-type: text/plain,与测试文件一致。
  3. 参数索引:process.argv[2]是端口,process.argv[3]是文件路径。如果搞错索引,就会出现 "文件不存在" 或 "端口无法监听" 之类的运行时错误(参见下文反例)。

六、learnyounode 是如何自动校验这道题的

理解校验器有助于写出能一次通过的代码。本题的自动化校验逻辑在 exercises/http_file_server/exercise.js,整体流程如下:

  1. 准备测试数据:在系统临时目录生成一个随机文本文件_learnyounode_<pid>.txt(内容由boganipsum生成,见 exercise.js),并借助 lib/rndport.js 生成随机端口(1024 + Math.floor(Math.random() * 64511),即 1024~65535 范围内),把端口与文件路径注入到提交程序和参考解法的命令行参数中。
  2. 延迟并发请求:等服务器启动 500ms 后,通过hyperquest向http://localhost:<port>发起 GET 请求(exercise.js),把响应体接到各自的 stdout 流上,最长等待 5 秒。
  3. 对比输出:在verify模式下,把你的程序的响应体与官方解法的响应体经comparestdout做逐字节对比;run模式下则只请求你的程序并把响应体转发到终端,供你肉眼检查。
  4. 强制使用createReadStream:这是本题最特殊的校验点。通过workshopper-wrappedexec包装子进程执行环境,记录程序运行时调用的全部fs.*方法(wrapData.fsCalls),随后在addVerifyProcessor中检查:只要出现过任何一个不是createReadStream的fs调用,就判定失败(exercise.js):
const badCalls = Object.keys(exercise.wrapData.fsCalls).filter(function (m) { exercise.emit('fail', exercise.__('fail.no_createReadStream', { method: 'fs.' + m + '()' })) return !(/createReadStream/).test(m) }) callback(null, badCalls.length === 0)

也就是说,如果你用fs.readFileSync()或fs.readFile()一次性读文件再res.end(),即使响应内容完全一致,也会因为命中 "fail.no_createReadStream" 而无法通过。这一点由测试反例直接印证:

  • 有效用例 test/http_file_server/valid_01.js 使用fs.createReadStream(process.argv[3]).pipe(res);
  • 反例 test/http_file_server/invalid_02.js 使用fs.readFileSync(...)读入后res.end(),属于典型错误写法;
  • 反例 test/http_file_server/invalid_03.js 则同时犯了参数索引错误(用process.argv[2]当作文件路径)与readFileSync两个问题。

另外可以注意到,校验器目前在 exercise.js 的注释中写明 TODO:尚未对content-type与statusCode做自动校验——这属于"从源码结构看"的实现现状,意味着目前核心判据是响应内容一致 + 只使用createReadStream。但官方解法依然主动设置了200与text/plain,作为最佳实践值得保留。

七、运行与验证命令

在仓库中安装好依赖并全局链接learnyounode之后,按以下方式操作:

learnyounode run http-file-server.js learnyounode verify http-file-server.js
  • run:只启动你的程序并用临时文件对它发请求,把响应体打印出来,方便自查;
  • verify:同时运行你的程序与官方解法、随机生成测试文件与端口,做响应对比 +createReadStream用法检查。

对应关系可以在练习页选择http_file_server后按提示执行。若想先跑通整个环境,可参考仓库根目录 README.md 的安装与使用说明。

八、相关文档与延伸学习

  • http模块完整文档已内置在本仓库的 docs-nodejs/http.html,可供离线查阅createServer、listen、请求/响应对象的全部 API;
  • 流的通用机制(pipe、背压、Readable/Writable)见 docs-nodejs/stream.html;
  • 文件流 API(createReadStream等)见 docs-nodejs/fs.html;
  • 同一系列的相关习题可以形成完整学习链路:
    • http_client:作为 HTTP 客户端发起请求,与本题的服务器端视角互补;
    • http_uppercaserer:同样基于http.createServer(),但用through2-map处理请求流,可加深对"request 也是流"的理解;
    • http_json_api_server:在 HTTP 服务器上解析 URL 与查询参数并返回 JSON;
    • juggling_async:异步并发收集多个 HTTP 响应,是对流式读取的补充练习。

小结

http_file_server这一题的核心价值在于:它把"HTTP 服务器"与"流式传输"两个概念第一次组合在一起。掌握http.createServer()的回调签名、server.listen(port)的启动方式、fs.createReadStream(path).pipe(res)的一行式对接,并理解 learnyounode 校验器对fs调用方法的强制检查,即可高质量通过本题,也为后续处理请求流、响应流的习题打下基础。

  • 教程
  • CLI

【免费下载链接】learnyounode

Learn You The Node.js For Much Win! An intro to Node.js via a set of self-guided workshops.

项目地址:https://gitcode.com/gh_mirrors/le/learnyounode
点击查看免费下载
上一篇:小米MiMo-Audio语音大模型开源:引领语音AI进入通用智能时代
下一篇:Blinker-js自定义界面开发:打造专属物联网设备控制面板

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询