Skip to content

http 服务(Day9 1/2) ​

本页对应 Day09_Node,共 5 段视频,约 2 小时 5 分。

学习目标 ​

  • 能说明 npm 安装依赖与开发依赖的区别,并解释 package.json 中入口文件、scripts、dependencies、devDependencies 的作用。
  • 能复述 HTTP 请求报文与响应报文的组成,列举请求行三要素、常见请求头及 200/304/403/404/500/503 等状态码含义。
  • 能使用 Node.js 内置 http 模块创建并启动 HTTP 服务,监听指定端口,并通过浏览器访问验证响应结果。
  • 能使用 req.method、req.url、req.httpVersion 获取请求行信息,使用 req.headers 获取请求头,使用 req.socket.remoteAddress 获取客户端 IP。
  • 能使用 url.parse 或 new URL 解析请求 URL 中的路径与查询字符串,并正确读取指定查询参数。
  • 能通过地址栏或表单携带查询字符串发起请求,并在服务端读取参数后返回包含参数内容的响应以完成验证。

知识点 ​

一、先回顾 npm 与 HTTP 协议:知道自己在操作什么 ​

为什么需要:写 Node 服务端程序前,先要分清“包管理”和“网络通信”两件事。npm 解决依赖安装与版本记录,HTTP 协议解决浏览器和服务器之间“怎么发请求、怎么回响应”。

怎么想:npm install 会根据 package.json 安装依赖;安装某个包时默认写入 dependencies,如果只是开发阶段使用,就加 -D 写入 devDependencies。package.json 里重点关注入口文件、scripts、产品依赖和开发依赖。HTTP 侧要记住 IP、域名、端口、URL;请求报文由请求行、请求头、空行、请求体组成,响应报文由状态行、响应头、空行、响应体组成。

怎么做:请求行包含请求方法、URL、HTTP 版本;请求头是键值结构,例如 user-agent;请求体是客户端发给服务端的数据,调试时在浏览器“网络”里常看到“载荷”。响应状态码记住:200 成功,304 多用缓存,404 资源不存在,403 无权限,500 服务器错误,503 服务不可用。

易错点:req.url 不是完整 URL;请求体不是随便就能直接读;状态码和状态描述要对应,但调试时重点看状态码。

二、创建 HTTP 服务:createServer 与 listen ​

为什么需要:只有创建并启动 HTTP 服务,自己的电脑才能临时充当服务器,让浏览器向它发请求。

怎么想:Node 内置 http 模块,不需要额外下载。http.createServer 接收一个回调函数,这个回调不是立刻执行,而是在“接收到请求时”执行;执行时可拿到请求对象 req 和响应对象 res。createServer 返回服务对象,再调用 listen 监听端口,服务才算启动。

怎么做:

js
const http = require('http');

const server = http.createServer((req, res) => {
  res.setHeader('Content-Type', 'text/html; charset=utf-8');
  res.end('<h1>Welcome to my website</h1>');
});

server.listen(8080, () => {
  console.log('HTTP server is running on 8080');
});

课堂现场先运行文件,发现没有输出 ok,因为回调要等请求到来才执行;启动成功后程序不会自动结束,会一直监听。改代码后要先 Ctrl + C 结束,再重新运行;开发时可用 nodemon 自动重启。

易错点:端口被占用会报 EADDRINUSE,可换端口,或用 netstat -ano | findstr 8080 找到进程号后结束进程;127.0.0.1 和 localhost 都表示本机;浏览器刷新时可能额外请求 favicon.ico,所以服务端回调可能执行两次。

三、获取请求行、请求头和客户端 IP ​

为什么需要:服务端要知道“谁用什么方式请求了什么地址”,才能做路由和日志。

怎么想:请求行信息从 req 取:req.method、req.url、req.httpVersion;请求头从 req.headers 取;客户端 IP 不在请求报文里,而在底层连接信息中,用 req.socket.remoteAddress。

怎么做:

js
const http = require('http');

http.createServer((req, res) => {
  console.log('请求方式', req.method);
  console.log('URL', req.url);
  console.log('协议版本', req.httpVersion);
  console.log('请求头', req.headers);
  console.log('客户端IP', req.socket.remoteAddress);
  res.end('ok');
}).listen(8080, () => console.log('running'));

易错点:req.headers['user-agent'] 这种带短横线的属性不能用点语法;req.url 只包含路径名和查询字符串,不含协议、主机名、端口号和锚点;想过滤浏览器自动请求的图标,可判断 req.url === '/favicon.ico' 后直接 res.end()。

四、解析查询字符串:url.parse 与 new URL ​

为什么需要:搜索、分页、表单提交常把信息拼在 URL 查询字符串里,例如 ?wd=暴富&page=1,服务端必须把它拆成可用的数据。

怎么想:req.url 里包含路径名和查询字符串。老办法用 url.parse(req.url, true),第二个参数传 true 会把 query 解析成对象;新办法用 new URL(req.url, 'http://127.0.0.1:8080'),但必须补一个完整的基础 URL,否则会报“无效 URL”。取参数时用 searchParams.get('wd')。

怎么做:

js
const http = require('http');
const url = require('url');

http.createServer((req, res) => {
  const info = url.parse(req.url, true);
  console.log(info.query.wd);

  const info2 = new URL(req.url, 'http://127.0.0.1:8080');
  console.log(info2.searchParams.get('wd'));

  res.end('ok');
}).listen(8080);

表单用 GET 提交时,浏览器会把输入框、下拉框的 name 和 value 自动拼成查询字符串;锚点只对浏览器有意义,不会发给后端。

易错点:url.parse 虽被标记弃用但课堂仍在用;searchParams 不是普通对象,不能直接点属性,要用 get;查询字符串不适合携带大量信息;响应中文要设置 charset=utf-8,否则可能乱码。

本节回顾两块最常用的基础:npm 包管理与 HTTP 协议。下面按“为什么需要→怎么想→怎么做→易错点”把笔记串成教程。

一、npm 包管理工具:会装、会删、会看版本 ​

为什么需要:项目要用第三方包,也要和同事同步依赖,不能靠手工拷贝。怎么想:npm 围绕 package.json 工作,安装、删除、更新、查 outdated、清缓存都是围绕依赖版本展开。怎么做:先 npm init 生成项目描述文件;安装全部依赖用 npm install,简写 npm i;装指定包用 npm install 包名 或 npm i 包名;开发依赖加 -D;全局安装加 -g;删除用 npm remove 包名;更新用 npm update 包名;查看哪些包可更新用 npm outdated 包名;更新前可清缓存 npm cache clean --force。课堂演示中,运行 npm install 后依赖会自动写入 package.json;用 npm outdated 能看到“当前版本”和“最新版”。笔记代码保留如下(把同音错字 inp i、isntall 纠正为 npm i、install):

bash
npm init

npm install
npm i

npm install 包名
npm i 包名
npm install 包名 -D
npm i 包名 -D
npm install 包名 -g
npm i 包名 -g

npm remove 包名

npm update 包名
npm outdated 包名

npm cache clean --force

易错点:默认 npm install 包名 加到产品依赖,只有 -D 才加到开发依赖;版本号前常见 ^,它锁定大版本,所以 npm update 通常只更新次版本和小版本,不跨大版本;更新前清缓存,避免服务器已更新而本地仍用旧缓存。

二、package.json:入口、脚本与两类依赖 ​

为什么需要:它是项目说明书,别人拿到代码后靠它恢复依赖、执行脚本。怎么想:重点看入口文件 main、命令别名 scripts、产品依赖 dependencies、开发依赖 devDependencies。怎么做:scripts 里配命令别名,用 npm run 执行;产品依赖是项目代码运行时要用的,开发依赖只在开发阶段用。易错点:笔记里写的 page.json 实际是 package.json;不要把所有包都塞进产品依赖,-D 安装的包应出现在开发依赖里。

三、cnpm、yarn、cyarn、npx:替代与补充命令 ​

为什么需要:不同团队可能用不同包管理工具,命令要能看懂。怎么想:它们解决的是安装来源、速度或执行方式问题,不是新协议。怎么做:知道 cnpm、yarn、cyarn 可作 npm 的替代命令,npx 可直接执行包命令。易错点:不要混用后以为依赖一定写进同一份锁文件;团队统一工具最重要。

四、网络协议基础:IP、域名、端口号、URL ​

为什么需要:浏览器要找到服务器上的某个程序,再定位具体资源。怎么想:IP 定位电脑,域名是 IP 的别名;端口号定位电脑上的程序;URL 定位资源。怎么做:URL 可拆成协议、主机名、路径、文件名、查询字符串、锚点。易错点:只有 IP 只能找到电脑,找不到具体服务,所以 HTTP 服务要监听端口;端口号是程序级概念,不是电脑级概念。

五、HTTP 请求报文:客户端发给服务器 ​

为什么需要:浏览器要把“我要什么”告诉服务器。怎么想:请求报文四部分——请求行、请求头、空行、请求体。怎么做:请求行含请求方式、URL、协议版本;请求头是键值对;空行分隔;请求体是客户端发给服务端的数据,浏览器调试面板里常叫“载荷”。课堂调试时可在 Network 面板直接看请求报文。易错点:请求行不是请求头;请求头里常见 User-Agent 等标准头;请求体不一定每次都有。笔记结构保留:

text
请求行	请求方式、URL、协议版本
请求头  键值对
空行
请求体  载荷

六、HTTP 响应报文:服务器回给客户端 ​

为什么需要:服务器要告诉浏览器结果和内容。怎么想:响应报文也是四部分——响应行、响应头、空行、响应体。怎么做:响应行含响应状态码、状态描述、协议版本;响应头是键值对;空行分隔;响应体是给客户端的内容。转写稿举例:200 表示成功,304 常表示用缓存,404 表示资源不存在,403 表示无权限,5xx 表示服务器错误。易错点:状态描述通常与状态码对应;响应体可能是 HTML/CSS 等具体内容;调试时不要只看页面,要回到 Network 看响应报文。笔记结构保留:

text
响应行	响应状态码 响应状态描述 协议版本
响应头  键值对
空行
响应体  给客户端的内容

把这两块连起来看:npm 管的是项目依赖,HTTP 管的是客户端与服务器之间“请求—响应”的通信格式。命令用熟、报文结构看清,后面写 Node 服务时就不容易乱。

从“写脚本”到“写服务”:HTTP 服务是什么 ​

为什么需要:前面用 Node 处理文件、目录,程序执行完就结束;后端服务不同,它要长期运行,等客户端发请求,再返回响应。
怎么想:服务器首先是一台电脑,电脑上运行“HTTP 服务程序”;客户端电脑上的浏览器发请求,服务程序接收请求并作出响应。服务器只是角色,不等于高配机器;开发时同一台电脑既当服务器又当客户端,用 127.0.0.1 或 localhost 自己访问自己。
怎么做:用 Node 内置 http 模块创建服务并监听端口。端口用来区分同一台电脑上的不同程序。
易错点:服务启动后不会像普通脚本那样立刻结束;修改代码后要先 Ctrl+C 结束旧进程,再重新启动。

创建服务与启动服务 ​

为什么需要:只有创建服务对象还不够,必须监听端口,浏览器才知道把请求发给哪个程序。
怎么想:http.createServer 接收一个回调函数,这个回调不是创建时执行,而是“接收到请求时”执行;回调能拿到请求对象 req 和响应对象 res。
怎么做:

js
const http = require('http');

const server = http.createServer((req, res) => {
  console.log('接收到一个请求');
  res.setHeader('Content-Type', 'text/html; charset=utf-8');
  res.end('<h1>Welcome to my website</h1>');
});

server.listen(8080, () => {
  console.log('HTTP server is running on 8080');
});

易错点:不调用 res.end() 响应不会结束;不调用 listen 服务不会真正启动;回调里的 req/res 是形参名,可以换名,但语义分别是请求和响应。

请求报文、响应报文与调试 ​

为什么需要:理解报文结构,才能知道 req.method、req.url、req.headers 从哪里来。
怎么想:请求报文由客户端发给服务器,包含请求行、请求头、空行、请求体;响应报文由服务器发给客户端,包含响应行、响应头、空行、响应体。请求行有请求方式、URL、HTTP 版本;响应行有状态码、状态描述、协议版本。
怎么做:常见状态码要记住:200 成功,304 缓存,404 资源不存在,403 无权限,500 服务器错误,503 服务不可用。调试时打开浏览器“网络”面板看请求和响应。
易错点:浏览器地址栏回车发起的是 GET 请求;刷新时可能看到两次请求,其中一次是浏览器自动请求 /favicon.ico,不是你的代码重复执行。可这样规避:

js
if (req.url === '/favicon.ico') {
  res.end();
  return;
}

获取请求行、请求头与客户端 IP ​

为什么需要:后端要根据请求方式、路径、请求头判断客户端要什么。
怎么想:请求行信息从 req 上取;请求头是对象;客户端 IP 不在请求报文里,而在底层 socket 连接信息中。
怎么做:

js
const http = require('http');

const server = http.createServer((req, res) => {
  console.log('请求方式', req.method);
  console.log('URL', req.url);
  console.log('协议版本', req.httpVersion);
  console.log('请求头', req.headers);
  console.log('浏览器信息', req.headers['user-agent']);
  console.log('客户端IP', req.socket.remoteAddress);
  res.end('ok');
});

server.listen(8080, () => {
  console.log('running on 8080');
});

易错点:user-agent 带短横线,不能写 req.headers.user-agent,要用中括号:req.headers['user-agent']。

URL 与查询字符串 ​

为什么需要:搜索、分页等常把信息放在查询字符串里,例如 ?wd=hello&page=1。
怎么想:req.url 只包含路径名和查询字符串,不包含协议、主机名、端口号,也不包含锚点。解析查询字符串有两种常见方式。
怎么做:

js
const url = require('url');

const info = url.parse(req.url, true);
console.log(info.query); // { wd: 'hello', page: '1' }

也可用新 API:

js
const { searchParams } = new URL('http://127.0.0.1' + req.url);
console.log(searchParams.get('wd'));
console.log(searchParams.get('page'));

易错点:new URL(req.url) 会报“URL 无效”,因为 req.url 不完整,需要临时拼上协议和主机名;URLSearchParams 取参数要用 get(),不能直接点属性。url.parse 虽已弃用,但老项目仍常见。

常见错误与开发提效 ​

为什么需要:HTTP 服务开发中会反复改代码,也会遇到端口被占用。
怎么想:端口冲突常见报错是 EADDRINUSE,表示地址已被使用;开发时可用 nodemon 自动重启。
怎么做:先 Ctrl+C 结束旧服务,再运行;或用 nodemon 01-http.js。端口被占用时可换端口,或找到占用进程结束它。监听 80 时浏览器地址可省略端口;监听 8080 时地址后必须写 :8080。listen 第二参数可限制 IP,一般开发不必设置。
易错点:不要随意杀进程,可能影响系统;localhost 与 127.0.0.1 都表示本机;服务已启动时再启动一次同一端口,会直接报端口占用。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

代码示例 ​

示例 1:创建 HTTP 服务(01-创建HTTP服务.js) ​

js
// 01-创建HTTP服务.js
// 1. 导入内置模块 http —— 内置模块不需要 npm 安装
const http = require('http');

// 2. 创建服务对象:参数是回调函数,收到请求时自动执行
const server = http.createServer(function (req, res) {
  // req:请求对象,能拿到客户端发来的请求报文信息
  // res:响应对象,负责给客户端回响应
  console.log('接收到一个请求');

  // 客户端 IP 不在请求报文里,来自底层 TCP 连接
  console.log('客户端 IP:', req.socket.remoteAddress);

  // 设置响应头,解决中文乱码
  res.setHeader('content-type', 'text/html; charset=utf-8');

  // end() 结束响应并返回内容,浏览器会解析其中的 HTML
  res.end('<h1>welcome to my website</h1>');
});

// 3. 启动服务,监听端口
server.listen(8080, function () {
  console.log('HTTP server is running on 8080');
});

关键行 / 易错点

  • http.createServer(回调) 的回调函数不是立即执行,而是在接收到请求时自动执行;listen 的回调才是服务启动成功时立即执行。
  • 回调的两个形参名可以随意取,习惯叫 req(request,请求对象)和 res(response,响应对象);请求报文相关的一切都从 req 取,回响应都走 res。
  • res.end() 既结束响应又能带内容,写完就不要再往下写;响应 HTML 时浏览器会正常解析,<h1> 会又黑又粗又大。
  • 不设置 content-type: text/html; charset=utf-8 时,中文一定乱码。
  • 这个程序不会自动结束(一直监听端口),改代码后必须先 Ctrl + C 结束再重新 node 启动,开发时可用 nodemon 01-创建HTTP服务.js 自动重启。
  • 端口被别人(常常是自己上一次没关掉的进程)占用会报 EADDRINUSE,换端口或杀掉占用端口的进程即可。
  • 监听 80 端口时浏览器地址栏可省略端口号;127.0.0.1 和 localhost 是一个意思,都表示本机。
  • 浏览器会“背着我们”多发一个 /favicon.ico 请求,所以刷新一次控制台常输出两次。

示例 2:获取请求行和请求头(02-获取请求行和请求头.js) ​

js
// 02-获取请求行和请求头.js
const http = require('http');
const { table } = require('table'); // 先 npm i table

const server = http.createServer(function (req, res) {
  res.setHeader('content-type', 'text/html; charset=utf-8');

  // 浏览器自动请求的网站标题栏图标,直接结束掉,避免干扰
  if (req.url === '/favicon.ico') {
    res.end();
    return; // 后面的代码不再执行
  }

  // 请求行三部分
  console.log('请求方式:', req.method);
  console.log('URL:', req.url);
  console.log('协议版本:', req.httpVersion);

  // 请求头是一个对象
  console.log(req.headers);
  // 带短横线的属性名不能点,要用中括号
  console.log('客户端浏览器信息:', req.headers['user-agent']);

  // 把对象转成二维数组,再用 table 打印成表格
  console.log(table(Object.entries(req.headers)));

  res.end('<h1>高小乐的交友网站</h1>');
});

server.listen(8080, function () {
  console.log('HTTP server is running on 8080');
});

关键行 / 易错点

  • 请求行三件套:req.method(请求方式)、req.url(URL)、req.httpVersion(协议版本);从地址栏敲回车发出的请求一定是 GET。
  • req.url 只包含 pathname + 查询字符串,不含协议、主机名、端口号,锚点(# 后面)也不会发给服务器,所以默认访问就是 /。
  • req.headers 是对象,user-agent 这类带短横线的属性名不能用点,必须 req.headers['user-agent'],否则语法就不对。
  • table 包需要 npm i table;它导出的对象里才有 table 方法,所以用解构 const { table } = require('table')(不解构就得写 table.table(...))。
  • table() 要的是二维数组,req.headers 是对象,必须先 Object.entries() 转成 [[键, 值], ...] 才能打印成表格。
  • 过滤 favicon.ico 时 res.end() 后一定要 return,否则后面还会继续执行一次响应,报错 “write after end”。
  • 客户端 IP 用 req.socket.remoteAddress 获取,它不在请求报文里,是从底层 TCP 连接拿到的。

示例 3:获取 URL 中的查询字符串(03-获取查询字符串.js + search.html) ​

js
// 03-获取查询字符串.js
const http = require('http');
const url = require('url');

const server = http.createServer(function (req, res) {
  res.setHeader('content-type', 'text/html; charset=utf-8');

  // 方式一:url.parse 第二个参数传 true,query 直接就是对象
  const urlInfo = url.parse('http://127.0.0.1' + req.url, true);

  // 方式二:new URL(),必须传完整 URL,否则报 Invalid URL
  // const urlInfo2 = new URL('http://127.0.0.1' + req.url);
  // console.log(urlInfo2.searchParams.get('wd'));

  const content = `<h1>恭喜你提交成功</h1>
    <p>wd: ${urlInfo.query.wd}</p>
    <p>type: ${urlInfo.query.type}</p>
    <p>origin: ${urlInfo.query.origin}</p>`;

  res.end(content);
});

server.listen(8080, function () {
  console.log('HTTP server is running on 8080');
});
html
<!-- search.html -->
<form action="http://127.0.0.1:8080">
  <input type="text" name="wd">
  <select name="type">
    <option value="1">网页</option>
    <option value="2">图片</option>
    <option value="3">资讯</option>
  </select>
  <select name="origin">
    <option value="baidu">百度</option>
    <option value="360">360</option>
    <option value="sogou">搜狗</option>
  </select>
  <button>搜索</button>
</form>

关键行 / 易错点

  • url.parse(req.url, true) 的第二个参数 true 是把查询字符串直接解析成对象的开关,不加它 query 只是一串字符串,还得自己再拆。
  • req.url 不完整(缺协议和主机名),new URL(req.url) 会直接报 Invalid URL;所以要手动拼一个 'http://127.0.0.1' + req.url,拼什么主机名都不影响解析结果。
  • url.parse 虽已被标记弃用,但仍能正常使用且最省事;new URL() 的 searchParams 是类 Map 结构,不能点属性,必须 searchParams.get('wd')。
  • 表单默认就是 GET,提交时浏览器会自动把控件拼成 ?wd=xxx&type=1&origin=baidu;没有 name 的控件不会提交,value 才是拼进查询字符串的值(下拉框选中项看 value,不看显示文字)。
  • 表单 action 的端口必须和服务 listen 的端口一致(写成 8090 而服务在 8080,请求就发不到服务上)。
  • 改完代码记得重启服务(或直接用 nodemon),否则页面表现和代码对不上;查询字符串不适合携带大量数据,锚点也不会出现在 req.url 里。

常见错误 ​

1. 改了代码,刷新浏览器还是旧内容

  • 现象:控制台加了 console.log 或改了响应文字,浏览器刷新后毫无变化,日志也不打印。
  • 原因:HTTP 服务进程一直在运行,加载的是启动那一刻的代码;不 Ctrl+C 结束进程,改动不会生效。
  • 修法:先 Ctrl+C 停掉,再重新 node 文件;开发期直接用 nodemon 文件 启动,保存即自动重启。

2. 启动报 EADDRINUSE: address already in use :::8080

  • 现象:服务起不来,终端抛端口被占用错误。
  • 原因:同一端口上已有一个进程在监听(最常见就是自己在另一个终端里已经跑过一遍)。
  • 修法:关掉多余终端里的进程,或换个端口(如 8081);确实找不到占用者时,netstat -ano | findstr 8080 拿到 PID 后在任务管理器结束,Linux/macOS 用 kill -9 PID。

3. 只运行了文件、没发请求,却奇怪回调里的日志没打印

  • 现象:node 01.js 后终端只出现 "server is running",createServer 回调里的内容一条都没有。
  • 原因:把 createServer 的回调当成了启动时执行,实际它要等收到请求才触发;启动成功的日志来自 listen 的回调,两者不是一回事。
  • 修法:用浏览器访问对应地址制造一次请求再看日志;排查时先确认访问的 IP、端口与服务端 listen 的一致。

4. 响应中文变成乱码

  • 现象:浏览器显示 æ¬¢è¿ 之类,终端里中文正常。
  • 原因:响应头没有声明编码,浏览器按默认编码解析响应体。
  • 修法:res.setHeader('Content-Type', 'text/html; charset=utf-8'),且必须写在 res.end() 之前。

5. 刷新一次页面,服务端日志却打印两遍

  • 现象:明明只按了一次 F5,回调执行了两次。
  • 原因:浏览器自动额外请求了 /favicon.ico,不是自己发的请求。
  • 修法:在回调开头判断 req.url === '/favicon.ico' 时直接 res.end() 并 return,避免后续逻辑重复执行。

6. new URL(req.url) 抛 Invalid URL

  • 现象:用 URL 类解析请求地址时直接报错中断。
  • 原因:req.url 只包含路径名和查询字符串,缺少协议和主机名,不是完整 URL。
  • 修法:拼接后再解析,如 new URL('http://127.0.0.1' + req.url);前缀内容不影响解析结果。

7. 取 user-agent 写成 req.headers.user-agent

  • 现象:取到 undefined,甚至语法报错。
  • 原因:属性名含短横线,不符合标识符规范,不能点取。
  • 修法:req.headers['user-agent']。

8. 让同学访问,对方却打不开

  • 现象:自己 127.0.0.1:8080 正常,别人用同样地址访问失败。
  • 原因:127.0.0.1 / localhost 是回环地址,只指向访问者自己的机器;且 listen 第二参数若限定了 127.0.0.1,其他 IP 也进不来。
  • 修法:让对方用本机的局域网 IP(ipconfig 查看);服务端不要写死 127.0.0.1 作为监听地址。

练习 ​

1. 端口与访问路径排查 启动一个监听 8080 的服务,分别用 http://127.0.0.1:8080、http://localhost:8080、http://127.0.0.1 访问,记录哪些能通、哪些不能,并说明原因。再把端口改成 80 重做一次。

2. 请求信息面板 写一个服务:收到请求后,把请求方式、req.url、HTTP 版本、客户端 IP、user-agent 以表格形式打印在终端(可用 Object.entries + table 包),浏览器只返回一句"已收到"。

3. 简易路由 用 req.url 做分支:/ 返回首页 HTML(含中文,注意编码),/about 返回一段文字,/favicon.ico 返回空并 return,其余路径返回 404 状态码与提示。要求用状态码体现差异,而不是只改文字。

4. 查询参数计算器 用 URL 类解析查询字符串,支持 ?a=10&b=20 时响应 a + b = 30;参数缺失或非数字时响应明确错误提示。再用表单或地址栏各提交一次验证。

5. 组合练习:表单回显 自己写一个含文本框、下拉框的 HTML 表单,提交到你的服务;服务端解析查询字符串后,把每个字段名和值以列表形式回显到页面上。要求下拉框的 name 与 value 都由你设计,并说明它们最终出现在请求的哪个位置。

原视频对照 ​

#视频时长
1总结npm和http协议14:27
2创建http服务52:33
3获取请求行和请求头信息25:15
4获取URL中的查询字符串27:08
5获取URL中的查询字符串0206:00

小结 ​

  1. 创建服务(createServer)和启动服务(listen)是两步,缺一不可;createServer 的回调由请求驱动,listen 的回调由启动成功驱动。
  2. 服务进程是常驻的,代码变更必须重启才生效,nodemon 是开发期的标准配置。
  3. 端口是"程序"的身份标识,IP 只能定位到机器;两者结合才能定位到具体服务,因此地址里少写端口通常意味着访问了别的东西。
  4. 127.0.0.1 与 localhost 等价且仅限本机,跨机器访问必须使用局域网/公网 IP。
  5. 一次页面刷新不等于一次请求,浏览器会自作主张地发额外请求,写服务端逻辑时要按 req.url 做分流并及早 return。
  6. req.url 是不完整 URL,只含路径名和查询字符串;锚点不会到达服务端,查询字符串才是前后端传参的常规通道。
  7. 查询字符串解析有 querystring.parse 与 URL 类两条路,前者能直接出对象但已废弃,后者是官方推荐但要拼完整地址、取值走 searchParams.get()。
  8. 响应头决定浏览器如何解读响应体,编码、内容类型这类问题一律优先怀疑响应头,而不是数据本身。