前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket

# 网络服务 http

30 秒速记

  • http 是流式模型:req 是可读流、res 是可写流,数据边到边处理
  • 每个请求都要有结局:请求体要读完或丢掉,响应必须 res.end(),否则连接一直挂着
  • 给慢请求设上限:server.headersTimeout、server.requestTimeout(Node 18 起默认 300s)、请求体大小自己限
  • keep-alive 复用连接,server.keepAliveTimeout(默认 5s)要比前面负载均衡的空闲超时长,否则偶发 502
  • 客户端中途断开要监听 close,把下游请求、数据库查询一起取消

Node 的 http 模块是流式的:请求进来是一个可读流,响应出去是一个可写流,每个请求都必须读完、写完、结束。 这一点决定了很多坑的形态:忘了 res.end(),客户端就一直转圈;不限制请求体大小就往内存里拼,一个大包能把进程撑爆;keep-alive 的超时和前面 Nginx、负载均衡对不上,就会出现偶发 502。所以写原生 http 服务,我关心的是每条分支最后有没有收尾,以及超时和大小上限有没有设。

理解「网络服务 http」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 network 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

面试官追问

追问 1接口偶发一直转圈,进程和端口都正常,第一反应查什么?
参考回答

查有没有分支漏了 res.end(),尤其是 catch 里和提前 return 的地方。Node 不会替你结束响应,漏一个分支,那个请求就挂到超时为止。

追问 2Node 服务前面挂了负载均衡,偶尔报 502,后端日志里根本没这个请求,可能是什么?
参考回答

大概率是 keep-alive 超时没对齐。Node 默认 keepAliveTimeout 是 5s,负载均衡空闲超时一般是 60s,Node 先关了连接,负载均衡还往这条旧连接上发请求就会 502。把 server.keepAliveTimeout 调到比负载均衡长,比如 65000,headersTimeout 再大一点。

追问 3用户上传到一半关掉页面,服务端要做什么?
参考回答

监听 req.on('close') 或 res.on('close'),判断 res.writableFinished 为 false 就说明是中途断开,这时把还在跑的下游请求用 AbortController 取消掉,临时文件删掉。

追问 4为什么说不读请求体会出问题?
参考回答

客户端那边:http.get 拿到响应却不消费,socket 一直被占着回不到连接池,并发一高就卡死,不需要就调 res.resume() 丢掉。服务端没读完请求体就响应,连接可能没法复用。

追问 5线上排查原生 http 问题时,为什么建议先缩成一个最小服务复现?
参考回答

框架的中间件、默认超时会叠在一起干扰判断。用十几行原生 http.createServer 复现,如果问题还在,就是协议或网络层的事;不在,再一层层把中间件加回去。

# http模块概览

30 秒速记

  • http 是 Node 写 Web 服务最核心的模块,几行就能起一个服务器
  • 四个核心实例:http.Server、http.ClientRequest、http.ServerResponse、http.IncomingMessage
  • 理顺角色:服务端「收请求 IncomingMessage + 回响应 ServerResponse」,客户端「发请求 ClientRequest + 收响应 IncomingMessage」
  • 底下全是流:IncomingMessage 可读、ServerResponse / ClientRequest 可写
  • Express、Koa 都是在这套对象外面包了一层,排障时还得回到这四个对象

http 模块上手简单、深入头疼,原因是它同时管服务端和客户端,四个核心对象来回出现。 把它们按「谁发、谁收」排好就清楚了:服务端是 http.Server,每来一个请求给你一对 req(IncomingMessage)和 res(ServerResponse);客户端发请求用 ClientRequest,收到的响应同样是一个 IncomingMessage。Express、Koa 的 req、res 也是这两个对象扩展出来的,所以学透这四个,看框架源码也不吃力。

大多数nodejs开发者都是冲着开发web server的目的选择了nodejs。正如官网所展示的,借助http模块,可以几行代码就搞定一个超迷你的web server。

在nodejs中,http可以说是最核心的模块,同时也是比较复杂的一个模块。上手很简单,但一旦深入学习,不少初学者就会觉得头疼,不知从何入手。

本文先从一个简单的例子出发,引出http模块最核心的四个实例。看完本文,应该就能够对http模块有个整体的认识。

面试官追问

追问 1会写 http.createServer 算不算掌握了 http 模块?
参考回答

只会了一半。Node 里调别的服务也用 http.request,那边的 ClientRequest 和收到的 IncomingMessage 才是做网关、做代理时天天打交道的东西。

追问 2Express 的 req 和原生 req 是什么关系?
参考回答

Express 的 req 就是原生 IncomingMessage,只是把原型链接到了自己的扩展对象上,加了 req.query、req.params 这些。所以原生的 req.on('data')、req.headers 在 Express 里照样能用。

追问 3健康检查接口要不要引入框架?
参考回答

一般不用。if (req.url === '/health') return res.end('ok') 一行就搞定,依赖越少越不容易挂。路由和中间件多起来了再上框架。

追问 4为什么说 http 的对象都是流?
参考回答

因为 HTTP 报文可能很大或者持续很久,Node 不会等全部收齐再给你。req 可以 pipe 到文件,文件也可以 pipe 到 res,内存占用跟文件大小无关。

# 一个简单的例子

30 秒速记

  • 一个脚本里同时起服务端和客户端:服务端回显访问路径,客户端把响应打印出来
  • 服务端:http.createServer((req, res) => res.end(...)) + server.listen(3000)
  • 客户端:http.get(url, cb),回调里的 clientRes 是可读流,pipe(process.stdout) 直接输出
  • 没写路径时 req.url 是 '/',不是空字符串
  • http.get 会自动调用 req.end();http.request 要自己调

这个例子用一个脚本把一次完整的 HTTP 往返跑了一遍:服务端收到请求、回写访问路径,客户端发请求、把响应打到控制台。 运行后控制台会打印 您访问的地址是:/,因为 http.get('http://127.0.0.1:3000') 没写路径时,请求行里的路径就是 /。客户端回调拿到的 clientRes 是可读流,所以能直接 pipe 到 process.stdout。真要写进项目,还得补错误处理,示例里一个 error 监听都没有,端口占用会直接让进程崩掉。

在下面的例子中,我们创建了1个web服务器、1个http客户端

  • 服务器server:接收来自客户端的请求,并将客户端请求的地址返回给客户端。
  • 客户端client:向服务器发起请求,并将服务器返回的内容打印到控制台。

代码如下所示,只有几行,但包含了不少信息量。下一小节会进行简单介绍。

var http = require('http');

// http server 例子
var server = http.createServer(function(serverReq, serverRes){
    var url = serverReq.url;
    serverRes.end( '您访问的地址是:' + url );
});

server.listen(3000);

// http client 例子
var client = http.get('http://127.0.0.1:3000', function(clientRes){
    clientRes.pipe(process.stdout);
});

@前端进阶之旅: 代码已经复制到剪贴板

面试官追问

追问 1http.get('http://127.0.0.1:3000') 没写路径,服务端 req.url 拿到什么?
参考回答

拿到 '/'。HTTP 请求行里路径不能为空,Node 会补上根路径。带查询串的话 req.url 是 '/a?x=1',路径和参数要自己用 new URL(req.url, 'http://x') 拆。

追问 2客户端报 ECONNREFUSED,可服务端明明写了 listen(3000),可能是什么?
参考回答

listen 是异步的,而且失败不会抛到同步代码里。给 server.on('error') 打个日志,多半是端口被占了;要严格保证顺序,就把 http.get 放进 server.listen(3000, () => {...}) 的回调里。

基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
      • http模块概览
      • 一个简单的例子
      • 例子解释
      • 关于http.IncomingMessage、http.ServerResponse
      • 关于继承与扩展
        • http.Server
        • http.ClientRequest
        • http.ServerResponse
        • http.IncomingMessage
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket
完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

网络服务 http|Nodejs专题系列

# 网络服务 http

30 秒速记

  • http 是流式模型:req 是可读流、res 是可写流,数据边到边处理
  • 每个请求都要有结局:请求体要读完或丢掉,响应必须 res.end(),否则连接一直挂着
  • 给慢请求设上限:server.headersTimeout、server.requestTimeout(Node 18 起默认 300s)、请求体大小自己限
  • keep-alive 复用连接,server.keepAliveTimeout(默认 5s)要比前面负载均衡的空闲超时长,否则偶发 502
  • 客户端中途断开要监听 close,把下游请求、数据库查询一起取消

Node 的 http 模块是流式的:请求进来是一个可读流,响应出去是一个可写流,每个请求都必须读完、写完、结束。 这一点决定了很多坑的形态:忘了 res.end(),客户端就一直转圈;不限制请求体大小就往内存里拼,一个大包能把进程撑爆;keep-alive 的超时和前面 Nginx、负载均衡对不上,就会出现偶发 502。所以写原生 http 服务,我关心的是每条分支最后有没有收尾,以及超时和大小上限有没有设。

理解「网络服务 http」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 network 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

面试官追问

追问 1接口偶发一直转圈,进程和端口都正常,第一反应查什么?
参考回答

查有没有分支漏了 res.end(),尤其是 catch 里和提前 return 的地方。Node 不会替你结束响应,漏一个分支,那个请求就挂到超时为止。

追问 2Node 服务前面挂了负载均衡,偶尔报 502,后端日志里根本没这个请求,可能是什么?
参考回答

大概率是 keep-alive 超时没对齐。Node 默认 keepAliveTimeout 是 5s,负载均衡空闲超时一般是 60s,Node 先关了连接,负载均衡还往这条旧连接上发请求就会 502。把 server.keepAliveTimeout 调到比负载均衡长,比如 65000,headersTimeout 再大一点。

追问 3用户上传到一半关掉页面,服务端要做什么?
参考回答

监听 req.on('close') 或 res.on('close'),判断 res.writableFinished 为 false 就说明是中途断开,这时把还在跑的下游请求用 AbortController 取消掉,临时文件删掉。

追问 4为什么说不读请求体会出问题?
参考回答

客户端那边:http.get 拿到响应却不消费,socket 一直被占着回不到连接池,并发一高就卡死,不需要就调 res.resume() 丢掉。服务端没读完请求体就响应,连接可能没法复用。

追问 5线上排查原生 http 问题时,为什么建议先缩成一个最小服务复现?
参考回答

框架的中间件、默认超时会叠在一起干扰判断。用十几行原生 http.createServer 复现,如果问题还在,就是协议或网络层的事;不在,再一层层把中间件加回去。

# http模块概览

30 秒速记

  • http 是 Node 写 Web 服务最核心的模块,几行就能起一个服务器
  • 四个核心实例:http.Server、http.ClientRequest、http.ServerResponse、http.IncomingMessage
  • 理顺角色:服务端「收请求 IncomingMessage + 回响应 ServerResponse」,客户端「发请求 ClientRequest + 收响应 IncomingMessage」
  • 底下全是流:IncomingMessage 可读、ServerResponse / ClientRequest 可写
  • Express、Koa 都是在这套对象外面包了一层,排障时还得回到这四个对象

http 模块上手简单、深入头疼,原因是它同时管服务端和客户端,四个核心对象来回出现。 把它们按「谁发、谁收」排好就清楚了:服务端是 http.Server,每来一个请求给你一对 req(IncomingMessage)和 res(ServerResponse);客户端发请求用 ClientRequest,收到的响应同样是一个 IncomingMessage。Express、Koa 的 req、res 也是这两个对象扩展出来的,所以学透这四个,看框架源码也不吃力。

大多数nodejs开发者都是冲着开发web server的目的选择了nodejs。正如官网所展示的,借助http模块,可以几行代码就搞定一个超迷你的web server。

在nodejs中,http可以说是最核心的模块,同时也是比较复杂的一个模块。上手很简单,但一旦深入学习,不少初学者就会觉得头疼,不知从何入手。

本文先从一个简单的例子出发,引出http模块最核心的四个实例。看完本文,应该就能够对http模块有个整体的认识。

面试官追问

追问 1会写 http.createServer 算不算掌握了 http 模块?
参考回答

只会了一半。Node 里调别的服务也用 http.request,那边的 ClientRequest 和收到的 IncomingMessage 才是做网关、做代理时天天打交道的东西。

追问 2Express 的 req 和原生 req 是什么关系?
参考回答

Express 的 req 就是原生 IncomingMessage,只是把原型链接到了自己的扩展对象上,加了 req.query、req.params 这些。所以原生的 req.on('data')、req.headers 在 Express 里照样能用。

追问 3健康检查接口要不要引入框架?
参考回答

一般不用。if (req.url === '/health') return res.end('ok') 一行就搞定,依赖越少越不容易挂。路由和中间件多起来了再上框架。

追问 4为什么说 http 的对象都是流?
参考回答

因为 HTTP 报文可能很大或者持续很久,Node 不会等全部收齐再给你。req 可以 pipe 到文件,文件也可以 pipe 到 res,内存占用跟文件大小无关。

# 一个简单的例子

30 秒速记

  • 一个脚本里同时起服务端和客户端:服务端回显访问路径,客户端把响应打印出来
  • 服务端:http.createServer((req, res) => res.end(...)) + server.listen(3000)
  • 客户端:http.get(url, cb),回调里的 clientRes 是可读流,pipe(process.stdout) 直接输出
  • 没写路径时 req.url 是 '/',不是空字符串
  • http.get 会自动调用 req.end();http.request 要自己调

这个例子用一个脚本把一次完整的 HTTP 往返跑了一遍:服务端收到请求、回写访问路径,客户端发请求、把响应打到控制台。 运行后控制台会打印 您访问的地址是:/,因为 http.get('http://127.0.0.1:3000') 没写路径时,请求行里的路径就是 /。客户端回调拿到的 clientRes 是可读流,所以能直接 pipe 到 process.stdout。真要写进项目,还得补错误处理,示例里一个 error 监听都没有,端口占用会直接让进程崩掉。

在下面的例子中,我们创建了1个web服务器、1个http客户端

  • 服务器server:接收来自客户端的请求,并将客户端请求的地址返回给客户端。
  • 客户端client:向服务器发起请求,并将服务器返回的内容打印到控制台。

代码如下所示,只有几行,但包含了不少信息量。下一小节会进行简单介绍。

var http = require('http');

// http server 例子
var server = http.createServer(function(serverReq, serverRes){
    var url = serverReq.url;
    serverRes.end( '您访问的地址是:' + url );
});

server.listen(3000);

// http client 例子
var client = http.get('http://127.0.0.1:3000', function(clientRes){
    clientRes.pipe(process.stdout);
});

@前端进阶之旅: 代码已经复制到剪贴板

面试官追问

追问 1http.get('http://127.0.0.1:3000') 没写路径,服务端 req.url 拿到什么?
参考回答

拿到 '/'。HTTP 请求行里路径不能为空,Node 会补上根路径。带查询串的话 req.url 是 '/a?x=1',路径和参数要自己用 new URL(req.url, 'http://x') 拆。

追问 2客户端报 ECONNREFUSED,可服务端明明写了 listen(3000),可能是什么?
参考回答

listen 是异步的,而且失败不会抛到同步代码里。给 server.on('error') 打个日志,多半是端口被占了;要严格保证顺序,就把 http.get 放进 server.listen(3000, () => {...}) 的回调里。

追问 3能把 http.get 换成 http.request 吗?
参考回答

能,但要多做一步:http.request 返回的请求对象必须手动 req.end() 才会真正发出去,http.get 帮你调过了。只发简单 GET 就用 http.get,要带请求体或自定义方法再用 http.request。

追问 4同时发 100 个请求都 pipe 到 process.stdout,输出会怎样?
参考回答

会交错在一起,分不清哪段是哪个请求的。每个响应先收齐再带上路径一起打印,或者用 fetch(Node 18 起内置)加 await res.text() 更直观。

# 例子解释

30 秒速记

  • server = http.Server,负责监听端口、接收连接
  • client = http.ClientRequest,负责向服务端发请求
  • serverReq 和 clientRes 是同一个类 http.IncomingMessage:都是「我收到的那条消息」,只是一个在服务端、一个在客户端
  • serverRes = http.ServerResponse,服务端往回写响应的出口
  • 记法:谁「收」谁就是 IncomingMessage,服务端「发」是 ServerResponse,客户端「发」是 ClientRequest

例子里出现四个实例,记住一条就不会乱:收到的消息都是 IncomingMessage,发出去的才分服务端、客户端两种。 服务端收到的请求 serverReq 和客户端收到的响应 clientRes,名字一个叫请求一个叫响应,其实是同一个类,因为对各自那一侧来说都是「收进来的消息」。发出去的那一方,服务端用 ServerResponse,客户端用 ClientRequest。日常写业务,打交道最多的就是服务端那一对 req 和 res。

← 3.2 本地调试远程服务器上的Node代码4.2 网络服务 http res →

fe