前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
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 res

30 秒速记

  • 响应分两段:先头(状态码 + 响应头),后体;第一次 write / end 时头就发出去了
  • 头发出后再 setHeader / writeHead 会抛 ERR_HTTP_HEADERS_SENT,用 res.headersSent 判断
  • write() 返回 false = 缓冲满,等 drain 再写
  • 状态码、Content-Type、缓存头、安全头要设对;错误堆栈别直接回给客户端
  • 流式响应中途出错,头已经是 200 改不了,只能 res.destroy() 中断并记日志

res 是服务端往回写响应的对象,最关键的规则是头先于体:第一次写响应体的瞬间,状态码和响应头就已经发出去了。 之后再想改状态码或加头,Node 会直接抛 ERR_HTTP_HEADERS_SENT。这也是流式响应的一个现实问题:已经吐了一半数据才出错,没法再改成 500 加一段 JSON 错误,只能把连接断掉让客户端感知失败,同时记好日志。另外错误处理别把 err.stack 原样回给客户端,内部路径和依赖版本全暴露了。

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

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

res 的生命周期可以用一个例子看清楚:

const http = require('http')

http.createServer((req, res) => {
  res.statusCode = 200
  res.setHeader('Content-Type', 'text/plain; charset=utf-8')
  console.log(res.headersSent) // false,头还在内存里,可以随便改

  res.write('第一段\n')
  console.log(res.headersSent) // true,第一次 write 时头已经发出

  try {
    res.setHeader('X-Late', '1') // 头发出后再改
  } catch (err) {
    console.log(err.code) // ERR_HTTP_HEADERS_SENT
  }
  res.end('结束\n')
}).listen(3000)
@前端进阶之旅: 代码已经复制到剪贴板

几个规则:

  • res.statusCode 和 res.setHeader 只是改内存里的值,真正发送发生在第一次 write、end 或者调用 writeHead 的时候。
  • writeHead 会立即把头写出,它传入的头会和之前 setHeader 设的合并,同名时以 writeHead 为准。
  • 只调 res.end(data) 且没写过别的内容时,Node 会自动算 Content-Length;多次 write 又没设长度,就自动用 Transfer-Encoding: chunked。
  • 写入有背压:res.write() 返回 false 时等 drain 事件再继续,大文件直接 pipeline 交给流处理。

面试官追问

追问 1报错 Cannot set headers after they are sent to the client,一般是什么原因?
参考回答

同一个请求被响应了两次,常见的是异步回调里 res.end() 后没 return,下面又走到另一个 res.end() 或 setHeader。在错误处理里先判断 if (res.headersSent) return。

追问 2流式导出写到一半数据库报错了,怎么让前端知道失败?
参考回答

头已经是 200 发出去了改不了,只能 res.destroy(err) 掐断连接,前端会收到一个不完整的响应或网络错误。要求更严的话,在内容最后加一个结束标记,前端没读到标记就当失败。

追问 3错误处理里把 err.stack 返回给前端方便调试,可以吗?
参考回答

生产环境不行,堆栈里有文件路径、依赖名和版本,等于给攻击者递地图。返回一个错误码和 requestId,详细堆栈只进日志,排查时按 requestId 去查。

追问 4不设 Content-Type 直接 res.end(JSON.stringify(data)) 有什么问题?
参考回答

Node 不会帮你补 Content-Type,有的客户端会当纯文本处理,axios 也可能不自动解析。另外缺 X-Content-Type-Options: nosniff 时浏览器可能自己猜类型,有安全隐患。

# 概览

30 秒速记

  • res 的职责:把状态码 / 状态描述、响应头、响应体返回给客户端
  • 最小写法 res.end('ok'):状态码默认 200、描述默认 OK
  • Node 会自动补 Date、Connection、Content-Length 这类头,但不会补 Content-Type
  • 状态码要反映真实结果:失败分支还回 200 ok,监控和调用方都会被骗
  • 想看真实响应用 curl -i,别只看浏览器页面上的文字

res 负责把一个完整的 HTTP 响应发回去,包括状态码、响应头和响应体三部分。 最简单的 res.end('ok') 也是一个完整响应:状态码默认 200,Node 自动带上 Date、Content-Length 和 Connection 头。但它不会替你判断内容类型,Content-Type 要自己设;状态码也只会是默认值,失败分支要自己改。我验证接口时习惯 curl -i localhost:3000,三部分一眼看全。

http模块四剑客之一的res,应该都不陌生了。一个web服务程序,接受到来自客户端的http请求后,向客户端返回正确的响应内容,这就是res的职责。

返回的内容包括:状态代码/状态描述信息、响应头部、响应主体。下文会举几个简单的例子。

var http = require('http');
var server = http.createServer(function(req, res){
    res.end('ok');
});
server.listen(3000);
@前端进阶之旅: 代码已经复制到剪贴板

面试官追问

追问 1res.end('ok') 实际发出去的响应长什么样?
参考回答

curl -i 能看到:HTTP/1.1 200 OK,加上 Date、Connection: keep-alive、Keep-Alive: timeout=5、Content-Length: 2 几个头,然后是 ok。没有 Content-Type。

追问 2健康检查接口所有分支都 res.end('ok'),有什么问题?
参考回答

数据库挂了它也返回 200,负载均衡和监控只看状态码,会继续把流量往这个坏实例上打。依赖不可用时要回 503,让探测真正能摘掉实例。

追问 3监控报接口异常,但浏览器打开显示正常,先核对什么?
参考回答

先对齐是不是同一个请求:监控可能带了不同的头、走了不同的入口或者有缓存。用 curl -i 模拟监控的请求,看状态码和响应头,而不是比页面上的文字。

追问 4简单返回一段文本,有必要引入 Express 吗?
参考回答

没必要,http.createServer 加 res.end 就够了,少一个依赖少一个风险。路由、中间件、参数校验多起来了再换框架。

# 例子

30 秒速记

  • res.writeHead(200, 'ok', { 'Content-Type': 'text/plain' }) 一次写完状态码、描述和头
  • 再 res.end('hello') 写响应体并结束
  • 另一种写法:res.statusCode = 200 + res.setHeader(...),到第一次写体时才发
  • writeHead 会立即发头,调用后不能再改;之前 setHeader 的头会被合并进来
  • 状态描述(reason phrase)在 HTTP/2 里已经没有了,客户端别依赖它

这个例子用 res.writeHead 一次把状态码、状态描述和响应头发出去,再用 res.end 发响应体并结束。 要分清两种写法:writeHead 是马上把头写出去,之后改不了;statusCode 加 setHeader 是先存在内存里,等第一次写体时才发,中间还能改。我写中间件时倾向后者,因为后面的环节可能还要补头;最终处理函数里头已经确定了,用 writeHead 一行写完更紧凑。

在下面的例子中,我们同时设置了 状态代码/状态描述信息、响应头部、响应主体,就是这么简单。

var http = require('http');

// 设置状态码、状态描述信息、响应主体
var server = http.createServer(function(req, res){
    res.writeHead(200, 'ok', {
        'Content-Type': 'text/plain'
    });
    res.end('hello');
});

server.listen(3000);
@前端进阶之旅: 代码已经复制到剪贴板

面试官追问

追问 1writeHead 之后再调 setHeader 会怎样?
参考回答

抛 ERR_HTTP_HEADERS_SENT,因为 writeHead 已经把头写出去了。反过来先

基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
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
      • 概览
      • 例子
        • 设置状态代码、状态描述信息
        • 设置响应头部
        • 其他响应头部操作
      • 设置响应主体
      • chunk数据
      • 超时处理
      • 事件 close/finish
      • 其他不常用属性/方法
      • 相关链接
    • 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 res|Nodejs专题系列

# 网络服务 http res

30 秒速记

  • 响应分两段:先头(状态码 + 响应头),后体;第一次 write / end 时头就发出去了
  • 头发出后再 setHeader / writeHead 会抛 ERR_HTTP_HEADERS_SENT,用 res.headersSent 判断
  • write() 返回 false = 缓冲满,等 drain 再写
  • 状态码、Content-Type、缓存头、安全头要设对;错误堆栈别直接回给客户端
  • 流式响应中途出错,头已经是 200 改不了,只能 res.destroy() 中断并记日志

res 是服务端往回写响应的对象,最关键的规则是头先于体:第一次写响应体的瞬间,状态码和响应头就已经发出去了。 之后再想改状态码或加头,Node 会直接抛 ERR_HTTP_HEADERS_SENT。这也是流式响应的一个现实问题:已经吐了一半数据才出错,没法再改成 500 加一段 JSON 错误,只能把连接断掉让客户端感知失败,同时记好日志。另外错误处理别把 err.stack 原样回给客户端,内部路径和依赖版本全暴露了。

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

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

res 的生命周期可以用一个例子看清楚:

const http = require('http')

http.createServer((req, res) => {
  res.statusCode = 200
  res.setHeader('Content-Type', 'text/plain; charset=utf-8')
  console.log(res.headersSent) // false,头还在内存里,可以随便改

  res.write('第一段\n')
  console.log(res.headersSent) // true,第一次 write 时头已经发出

  try {
    res.setHeader('X-Late', '1') // 头发出后再改
  } catch (err) {
    console.log(err.code) // ERR_HTTP_HEADERS_SENT
  }
  res.end('结束\n')
}).listen(3000)
@前端进阶之旅: 代码已经复制到剪贴板

几个规则:

  • res.statusCode 和 res.setHeader 只是改内存里的值,真正发送发生在第一次 write、end 或者调用 writeHead 的时候。
  • writeHead 会立即把头写出,它传入的头会和之前 setHeader 设的合并,同名时以 writeHead 为准。
  • 只调 res.end(data) 且没写过别的内容时,Node 会自动算 Content-Length;多次 write 又没设长度,就自动用 Transfer-Encoding: chunked。
  • 写入有背压:res.write() 返回 false 时等 drain 事件再继续,大文件直接 pipeline 交给流处理。

面试官追问

追问 1报错 Cannot set headers after they are sent to the client,一般是什么原因?
参考回答

同一个请求被响应了两次,常见的是异步回调里 res.end() 后没 return,下面又走到另一个 res.end() 或 setHeader。在错误处理里先判断 if (res.headersSent) return。

追问 2流式导出写到一半数据库报错了,怎么让前端知道失败?
参考回答

头已经是 200 发出去了改不了,只能 res.destroy(err) 掐断连接,前端会收到一个不完整的响应或网络错误。要求更严的话,在内容最后加一个结束标记,前端没读到标记就当失败。

追问 3错误处理里把 err.stack 返回给前端方便调试,可以吗?
参考回答

生产环境不行,堆栈里有文件路径、依赖名和版本,等于给攻击者递地图。返回一个错误码和 requestId,详细堆栈只进日志,排查时按 requestId 去查。

追问 4不设 Content-Type 直接 res.end(JSON.stringify(data)) 有什么问题?
参考回答

Node 不会帮你补 Content-Type,有的客户端会当纯文本处理,axios 也可能不自动解析。另外缺 X-Content-Type-Options: nosniff 时浏览器可能自己猜类型,有安全隐患。

# 概览

30 秒速记

  • res 的职责:把状态码 / 状态描述、响应头、响应体返回给客户端
  • 最小写法 res.end('ok'):状态码默认 200、描述默认 OK
  • Node 会自动补 Date、Connection、Content-Length 这类头,但不会补 Content-Type
  • 状态码要反映真实结果:失败分支还回 200 ok,监控和调用方都会被骗
  • 想看真实响应用 curl -i,别只看浏览器页面上的文字

res 负责把一个完整的 HTTP 响应发回去,包括状态码、响应头和响应体三部分。 最简单的 res.end('ok') 也是一个完整响应:状态码默认 200,Node 自动带上 Date、Content-Length 和 Connection 头。但它不会替你判断内容类型,Content-Type 要自己设;状态码也只会是默认值,失败分支要自己改。我验证接口时习惯 curl -i localhost:3000,三部分一眼看全。

http模块四剑客之一的res,应该都不陌生了。一个web服务程序,接受到来自客户端的http请求后,向客户端返回正确的响应内容,这就是res的职责。

返回的内容包括:状态代码/状态描述信息、响应头部、响应主体。下文会举几个简单的例子。

var http = require('http');
var server = http.createServer(function(req, res){
    res.end('ok');
});
server.listen(3000);
@前端进阶之旅: 代码已经复制到剪贴板

面试官追问

追问 1res.end('ok') 实际发出去的响应长什么样?
参考回答

curl -i 能看到:HTTP/1.1 200 OK,加上 Date、Connection: keep-alive、Keep-Alive: timeout=5、Content-Length: 2 几个头,然后是 ok。没有 Content-Type。

追问 2健康检查接口所有分支都 res.end('ok'),有什么问题?
参考回答

数据库挂了它也返回 200,负载均衡和监控只看状态码,会继续把流量往这个坏实例上打。依赖不可用时要回 503,让探测真正能摘掉实例。

追问 3监控报接口异常,但浏览器打开显示正常,先核对什么?
参考回答

先对齐是不是同一个请求:监控可能带了不同的头、走了不同的入口或者有缓存。用 curl -i 模拟监控的请求,看状态码和响应头,而不是比页面上的文字。

追问 4简单返回一段文本,有必要引入 Express 吗?
参考回答

没必要,http.createServer 加 res.end 就够了,少一个依赖少一个风险。路由、中间件、参数校验多起来了再换框架。

# 例子

30 秒速记

  • res.writeHead(200, 'ok', { 'Content-Type': 'text/plain' }) 一次写完状态码、描述和头
  • 再 res.end('hello') 写响应体并结束
  • 另一种写法:res.statusCode = 200 + res.setHeader(...),到第一次写体时才发
  • writeHead 会立即发头,调用后不能再改;之前 setHeader 的头会被合并进来
  • 状态描述(reason phrase)在 HTTP/2 里已经没有了,客户端别依赖它

这个例子用 res.writeHead 一次把状态码、状态描述和响应头发出去,再用 res.end 发响应体并结束。 要分清两种写法:writeHead 是马上把头写出去,之后改不了;statusCode 加 setHeader 是先存在内存里,等第一次写体时才发,中间还能改。我写中间件时倾向后者,因为后面的环节可能还要补头;最终处理函数里头已经确定了,用 writeHead 一行写完更紧凑。

在下面的例子中,我们同时设置了 状态代码/状态描述信息、响应头部、响应主体,就是这么简单。

var http = require('http');

// 设置状态码、状态描述信息、响应主体
var server = http.createServer(function(req, res){
    res.writeHead(200, 'ok', {
        'Content-Type': 'text/plain'
    });
    res.end('hello');
});

server.listen(3000);
@前端进阶之旅: 代码已经复制到剪贴板

面试官追问

追问 1writeHead 之后再调 setHeader 会怎样?
参考回答

抛 ERR_HTTP_HEADERS_SENT,因为 writeHead 已经把头写出去了。反过来先 setHeader 再 writeHead 没问题,两边的头会合并,同名的以 writeHead 为准。

追问 2这个接口的测试只断言了响应体是 hello,还该测什么?
参考回答

状态码 200 和 Content-Type: text/plain。有人把类型改成 text/html 或者错误分支还回 200,只看响应体的测试是发现不了的。

追问 3客户端拿到 hello,却按 JSON 去解析报错了,先查哪?
参考回答

先 curl -i 看实际的 Content-Type,再看中间有没有代理或网关改写了头。服务端日志里看到 res.end('hello') 只能说明代码走到了,不能证明最终到客户端的头没被动过。

追问 4状态描述写成 'ok' 还是 'OK',有区别吗?
参考回答

对浏览器和绝大多数客户端没区别,它们只认数字状态码。HTTP/2 干脆去掉了状态描述,所以业务逻辑不要依赖它,一般直接省略,Node 会按状态码填标准描述。

# 设置状态代码、状态描述信息

30 秒速记

  • 两种写法:res.writeHead(200, 'OK') 一次性提交 / res.statusCode = 200; res.statusMessage = 'OK' 先攒着
  • writeHead 会立刻定死状态行,还能顺带传响应头;属性赋值要等第一次 write / end 才真正发出去
  • 不写 statusMessage 时 Node.js 按 http.STATUS_CODES 自动补,比如 404 → Not Found
  • 头发出去以后再改 res.statusCode 不报错也不生效,最隐蔽的坑

设置状态码有两条路:res.writeHead() 当场提交,res.statusCode / res.statusMessage 先记着、等第一次写主体时才发。 说白了,属性赋值是「草稿」,writeHead 是「盖章」。我一般在状态要等异步结果才能定的接口里用属性赋值,等数据库查完再决定是 200 还是 404;状态一开始就确定、还要一起带响应头的,就用 writeHead 一行写完。要记住的坑是:头一旦发出去,再改 res.statusCode 既不报错也不生效,客户端拿到的还是最早那个值。

← 4.1 网络服务 http4.3 网络服务 http req →

fe