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

30 秒速记

  • Node 发 HTTP 请求的底层是 http.request(),返回 ClientRequest,响应是另一个对象 IncomingMessage
  • 请求是可写流,写完必须 end();响应是可读流,必须读完或 destroy(),连接才能回到连接池
  • 超时要自己管:req.setTimeout() 只发事件不中断,Node 15+ 可以传 signal 用 AbortController 取消
  • 连接复用靠 Agent,Node 19 起全局 Agent 默认开 keepAlive
  • 重试只对幂等请求做(GET / PUT / DELETE),带退避和总次数上限;Node 18+ 日常请求直接用内置 fetch 更省事

用 Node 写 HTTP 客户端,把请求发出去只是开始,超时、连接复用、响应读没读完、失败要不要重试,这几件事才是线上出问题的地方。 http.request() 返回的请求对象是个可写流,忘了 end() 请求就根本没发完;响应是可读流,不读完连接就回不到连接池,时间长了请求开始排队。超时也不是自动的,setTimeout 只会触发一个 timeout 事件,要自己调 destroy()。现在 Node 18 以上我一般直接用内置的 fetch,底层 http 模块留给要精细控制连接的场景。

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

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

用一段代码把「发请求」的完整闭环串起来:成功、失败、超时、响应体消费都处理到。

const http = require('http')

function get(url, timeoutMs = 3000) {
  return new Promise((resolve, reject) => {
    const req = http.get(url, (res) => {
      if (res.statusCode !== 200) {
        res.resume() // 不关心内容也要把数据放掉,连接才能复用
        return reject(new Error('HTTP ' + res.statusCode))
      }
      let body = ''
      res.setEncoding('utf8')
      res.on('data', (chunk) => (body += chunk))
      res.on('end', () => resolve(body))
    })

    // setTimeout 只触发事件,必须自己 destroy
    req.setTimeout(timeoutMs, () => req.destroy(new Error('timeout')))
    // DNS 失败、连接被拒、超时 destroy 都会走到这里
    req.on('error', reject)
  })
}

get('http://example.com/').then(
  (html) => console.log('ok', html.length),
  (err) => console.log('fail', err.message), // 比如 fail getaddrinfo ENOTFOUND / fail timeout
)
@前端进阶之旅: 代码已经复制到剪贴板

几个容易漏的点:http.get() 会自动调 end(),http.request() 要自己调;error 不监听的话,一次 DNS 失败就能让进程因为未处理的 error 事件崩掉;非 200 的响应也要消费掉。Node 18 以上同样的事用 fetch 写更短:await fetch(url, { signal: AbortSignal.timeout(3000) }),但它默认不会因为 500 抛错,要自己判断 res.ok。

面试官追问

追问 1请求代码跑完打印了「请求已发送」,能说明请求成功了吗?
参考回答

不能。这只说明 write / end 调用返回了,数据可能还在缓冲区里,连接可能根本没建上。成功要看 response 事件拿到状态码,失败要看 error 事件,两个都得监听,不然出错时什么都看不到。

追问 2req.setTimeout(3000) 设了,为什么请求还是挂了一分钟?
参考回答

setTimeout 只负责到点触发 timeout 事件,不会帮你中断请求。要这样写才会真断:req.setTimeout(3000, () => req.destroy(new Error('timeout'))),destroy 之后 error 事件里会收到这个错误。

追问 3服务端返回 500 时我没读 body 直接 return 了,会有什么问题?
参考回答

响应流没消费完,底层连接一直被占着,回不到 Agent 的连接池。错误多了连接池被占满,后面正常请求都开始排队。不关心内容也要 res.resume() 把数据放掉。

追问 4网络抖一下就失败,能不能统一加个三次重试?
参考回答

只对幂等请求加,比如 GET,而且要用指数退避加随机抖动,比如 200ms、400ms、800ms。下单这种 POST 不能盲重试,服务端可能已经处理了,只是响应没回来,重试一次就重复下单。

追问 5现在还有必要用 http.request 吗,fetch 不香吗?
参考回答

Node 18 起内置 fetch,日常调接口用它就行,AbortSignal.timeout(3000) 一行就能加超时。http.request 适合要控制 Agent、拿底层 socket、处理 upgrade 这类事件的场景,或者还要兼容老版本 Node。

# ClientRequest概览

30 秒速记

  • http.request(options, cb) 返回 ClientRequest:代表「正在发出去的请求」,是个可写流
  • 状态码、响应头、正文都在 cb 拿到的 res(IncomingMessage)上,不在返回值上
  • 创建了不等于发出去了:带正文先 write() 再 end(),不带正文也要 end()
  • http.get() = 方法固定 GET + 自动 end() 的简写
  • 请求侧失败(DNS、连接被拒、超时 destroy)都走 req.on('error'),不监听进程会崩

http.request() 返回的是请求对象 ClientRequest,不是响应;响应要在回调里拿。 可以把它想成一封正在写的信:你可以往里加请求头、写正文,最后 end() 才算寄出去。服务端回信之后,回调收到 res,状态码、响应头、正文都从 res 上读。这个模块和服务端那套是对称的:服务端是收 IncomingMessage、写 ServerResponse,客户端是写 ClientRequest、收 IncomingMessage。

当你调用 http.request(options) 时,会返回 ClientRequest实例,主要用来创建HTTP客户端请求。

在前面的章节里,已经对http模块的的其他方面进行了不少介绍,如http.Server、http.ServerResponse、http.IncomingMessage。

有了前面的基础,详细本文不难理解,本文更多的以例子为主。

面试官追问

追问 1同事写了 const res = http.request(opts); console.log(res.statusCode),打出来 undefined,为什么?
参考回答

返回值是 ClientRequest,它上面没有 statusCode。响应是异步来的,要写在回调或者 req.on('response', res => ...) 里读 res.statusCode。

追问 2批处理脚本创建了请求、注册了回调,可一直没有任何输出,先查什么?
参考回答

先看有没有调 req.end()。不调的话 Node 认为你还要继续写正文,请求一直不算发完,服务端也在等,回调永远不触发。

追问 3同一个封装要支持 GET 和带正文的 POST,用 http.get 还是 http.request?
参考回答

用 http.request。它能指定方法、加请求头、write() 写正文。http.get 写死了 GET 还自动 end(),只适合最简单的拉取。

追问 4接口偶发没返回,怎么分清是请求没发完还是服务端没响应?
参考回答

在几个节点打时间戳:socket 事件(拿到连接)、end() 调用、response 事件(收到响应头)、res 的 end(正文读完)。卡在哪两个点之间,问题就在哪一段。

# 简单的GET请求

30 秒速记

  • http.request({ method: 'GET', ... }) 加 req.end(),或者直接 http.get(url, cb)(自动 end)
  • 回调里的 res 是可读流,正文分多次 data 到达,等 end 才算完整
  • 文本用 res.setEncoding('utf8') 防止中文被切坏;图片、压缩包这类二进制别设编码,收 Buffer
  • 大响应别 data += chunk 全读进内存,直接
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
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
      • ClientRequest概览
      • 简单的GET请求
      • 简单的post请求
      • 各种事件
        • response事件
        • socket事件
        • abort/aborted 事件
        • continue事件
        • upgrade事件
      • 其他
      • 参考链接
    • 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 client|Nodejs专题系列

# 网络服务 http client

30 秒速记

  • Node 发 HTTP 请求的底层是 http.request(),返回 ClientRequest,响应是另一个对象 IncomingMessage
  • 请求是可写流,写完必须 end();响应是可读流,必须读完或 destroy(),连接才能回到连接池
  • 超时要自己管:req.setTimeout() 只发事件不中断,Node 15+ 可以传 signal 用 AbortController 取消
  • 连接复用靠 Agent,Node 19 起全局 Agent 默认开 keepAlive
  • 重试只对幂等请求做(GET / PUT / DELETE),带退避和总次数上限;Node 18+ 日常请求直接用内置 fetch 更省事

用 Node 写 HTTP 客户端,把请求发出去只是开始,超时、连接复用、响应读没读完、失败要不要重试,这几件事才是线上出问题的地方。 http.request() 返回的请求对象是个可写流,忘了 end() 请求就根本没发完;响应是可读流,不读完连接就回不到连接池,时间长了请求开始排队。超时也不是自动的,setTimeout 只会触发一个 timeout 事件,要自己调 destroy()。现在 Node 18 以上我一般直接用内置的 fetch,底层 http 模块留给要精细控制连接的场景。

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

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

用一段代码把「发请求」的完整闭环串起来:成功、失败、超时、响应体消费都处理到。

const http = require('http')

function get(url, timeoutMs = 3000) {
  return new Promise((resolve, reject) => {
    const req = http.get(url, (res) => {
      if (res.statusCode !== 200) {
        res.resume() // 不关心内容也要把数据放掉,连接才能复用
        return reject(new Error('HTTP ' + res.statusCode))
      }
      let body = ''
      res.setEncoding('utf8')
      res.on('data', (chunk) => (body += chunk))
      res.on('end', () => resolve(body))
    })

    // setTimeout 只触发事件,必须自己 destroy
    req.setTimeout(timeoutMs, () => req.destroy(new Error('timeout')))
    // DNS 失败、连接被拒、超时 destroy 都会走到这里
    req.on('error', reject)
  })
}

get('http://example.com/').then(
  (html) => console.log('ok', html.length),
  (err) => console.log('fail', err.message), // 比如 fail getaddrinfo ENOTFOUND / fail timeout
)
@前端进阶之旅: 代码已经复制到剪贴板

几个容易漏的点:http.get() 会自动调 end(),http.request() 要自己调;error 不监听的话,一次 DNS 失败就能让进程因为未处理的 error 事件崩掉;非 200 的响应也要消费掉。Node 18 以上同样的事用 fetch 写更短:await fetch(url, { signal: AbortSignal.timeout(3000) }),但它默认不会因为 500 抛错,要自己判断 res.ok。

面试官追问

追问 1请求代码跑完打印了「请求已发送」,能说明请求成功了吗?
参考回答

不能。这只说明 write / end 调用返回了,数据可能还在缓冲区里,连接可能根本没建上。成功要看 response 事件拿到状态码,失败要看 error 事件,两个都得监听,不然出错时什么都看不到。

追问 2req.setTimeout(3000) 设了,为什么请求还是挂了一分钟?
参考回答

setTimeout 只负责到点触发 timeout 事件,不会帮你中断请求。要这样写才会真断:req.setTimeout(3000, () => req.destroy(new Error('timeout'))),destroy 之后 error 事件里会收到这个错误。

追问 3服务端返回 500 时我没读 body 直接 return 了,会有什么问题?
参考回答

响应流没消费完,底层连接一直被占着,回不到 Agent 的连接池。错误多了连接池被占满,后面正常请求都开始排队。不关心内容也要 res.resume() 把数据放掉。

追问 4网络抖一下就失败,能不能统一加个三次重试?
参考回答

只对幂等请求加,比如 GET,而且要用指数退避加随机抖动,比如 200ms、400ms、800ms。下单这种 POST 不能盲重试,服务端可能已经处理了,只是响应没回来,重试一次就重复下单。

追问 5现在还有必要用 http.request 吗,fetch 不香吗?
参考回答

Node 18 起内置 fetch,日常调接口用它就行,AbortSignal.timeout(3000) 一行就能加超时。http.request 适合要控制 Agent、拿底层 socket、处理 upgrade 这类事件的场景,或者还要兼容老版本 Node。

# ClientRequest概览

30 秒速记

  • http.request(options, cb) 返回 ClientRequest:代表「正在发出去的请求」,是个可写流
  • 状态码、响应头、正文都在 cb 拿到的 res(IncomingMessage)上,不在返回值上
  • 创建了不等于发出去了:带正文先 write() 再 end(),不带正文也要 end()
  • http.get() = 方法固定 GET + 自动 end() 的简写
  • 请求侧失败(DNS、连接被拒、超时 destroy)都走 req.on('error'),不监听进程会崩

http.request() 返回的是请求对象 ClientRequest,不是响应;响应要在回调里拿。 可以把它想成一封正在写的信:你可以往里加请求头、写正文,最后 end() 才算寄出去。服务端回信之后,回调收到 res,状态码、响应头、正文都从 res 上读。这个模块和服务端那套是对称的:服务端是收 IncomingMessage、写 ServerResponse,客户端是写 ClientRequest、收 IncomingMessage。

当你调用 http.request(options) 时,会返回 ClientRequest实例,主要用来创建HTTP客户端请求。

在前面的章节里,已经对http模块的的其他方面进行了不少介绍,如http.Server、http.ServerResponse、http.IncomingMessage。

有了前面的基础,详细本文不难理解,本文更多的以例子为主。

面试官追问

追问 1同事写了 const res = http.request(opts); console.log(res.statusCode),打出来 undefined,为什么?
参考回答

返回值是 ClientRequest,它上面没有 statusCode。响应是异步来的,要写在回调或者 req.on('response', res => ...) 里读 res.statusCode。

追问 2批处理脚本创建了请求、注册了回调,可一直没有任何输出,先查什么?
参考回答

先看有没有调 req.end()。不调的话 Node 认为你还要继续写正文,请求一直不算发完,服务端也在等,回调永远不触发。

追问 3同一个封装要支持 GET 和带正文的 POST,用 http.get 还是 http.request?
参考回答

用 http.request。它能指定方法、加请求头、write() 写正文。http.get 写死了 GET 还自动 end(),只适合最简单的拉取。

追问 4接口偶发没返回,怎么分清是请求没发完还是服务端没响应?
参考回答

在几个节点打时间戳:socket 事件(拿到连接)、end() 调用、response 事件(收到响应头)、res 的 end(正文读完)。卡在哪两个点之间,问题就在哪一段。

# 简单的GET请求

30 秒速记

  • http.request({ method: 'GET', ... }) 加 req.end(),或者直接 http.get(url, cb)(自动 end)
  • 回调里的 res 是可读流,正文分多次 data 到达,等 end 才算完整
  • 文本用 res.setEncoding('utf8') 防止中文被切坏;图片、压缩包这类二进制别设编码,收 Buffer
  • 大响应别 data += chunk 全读进内存,直接 res.pipe(fs.createWriteStream(...))
  • response 触发不等于成功,先看 res.statusCode

简单的 GET 用 http.get() 最省事,它就是 http.request() 把方法定成 GET、再顺手帮你调了 end()。 拿到的 res 是一个流,网页内容会被拆成很多块陆续到达,所以要在 data 里拼、在 end 里用。setEncoding('utf8') 的作用是让 Node 帮你处理跨块的多字节字符,不然一个中文字被切在两块中间就会乱码。不过下载图片就不能设编码了,转成字符串会把二进制弄坏。

下面构造了个GET请求,访问 http://id.qq.com/ ,并将返回的网页内容打印在控制台下。

var http = require('http');
var options = {
    protocol: 'http:',
    hostname: 'id.qq.com',
    port: '80',
    path: '/',
    method: 'GET'
};

var client = http.request(options, function(res){
    var data = '';
    res.setEncoding('utf8');
    res.on('data', function(chunk){
        data += chunk;
    });
    res.on('end', function(){
        console.log(data);
    });
});

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

当然,也可以用便捷方法 http.get(options) 进行重写

var http = require('http');

http.get('http://id.qq.com/', function(res){
    var data = '';
    res.setEncoding('utf8');
    res.on('data', function(chunk){
        data += chunk;
    });
    res.on('end', function(){
        console.log(data);
    });
});
@前端进阶之旅: 代码已经复制到剪贴板

← 4.4 网络服务 http server4.6 网络服务 https →

fe