Node.js: HTTP 模块与 Web 服务

最后更新:2026-08-26

Bob 需要快速验证一个 API 想法,不想搭建整个 Express 项目,用原生 HTTP 模块 20 行代码就跑起了一个 API 服务器。从处理请求方法、解析 URL 路径,到返回 JSON 数据和设置状态码,Bob 发现理解了底层原理后,用框架反而更得心应手。

1. 你将学到



2. 创建第一个 HTTP 服务器

http.createServer 接受一个回调函数,每次有请求进来都会触发。回调接收两个参数:request(请求对象)和 response(响应对象)。server.listen 指定监听端口。

▶ 示例:最小化 HTTP 服务器

JAVASCRIPT
const http = require('http');

const server = http.createServer((req, res) => {
  res.end('Hello, World!');
});

server.listen(3000, () => {
  console.log('Server running at http://localhost:3000/');
});
▶ 试一试
BASH
node server.js
TEXT 📖 仅展示
Server running at http://localhost:3000/

浏览器访问 http://localhost:3000/ 即可看到 Hello, World!



3. HTTP 请求-响应生命周期

每次 HTTP 交互都遵循 请求→路由→处理→响应 的流程,理解这个生命周期是构建 Web 服务的基础。

100%
flowchart LR
    A["客户端"] -->|"发送请求"| B["request 对象<br/>method / url / headers"]
    B --> C["路由解析<br/>pathname + searchParams"]
    C --> D{"请求方法?"}
    D -->|GET| E["读取查询参数"]
    D -->|POST / PUT| F["收集请求体"]
    E --> G["业务处理"]
    F --> G
    G --> H["response 对象<br/>statusCode / headers / body"]
    H -->|"返回响应"| A


4. request 对象核心属性

request 对象承载了客户端发来的全部请求信息,最常用的三个属性是 methodurlheaders

属性 / 方法 类型 说明 示例值
req.method string 请求方法 'GET''POST'
req.url string 请求路径(含查询字符串) '/api/users?id=1'
req.headers object 请求头对象 { 'content-type': 'application/json' }
req.httpVersion string HTTP 协议版本 '1.1'
req.socket object 底层 socket 对象

▶ 示例:打印请求信息

JAVASCRIPT
const http = require('http');

const server = http.createServer((req, res) => {
  console.log(`Method: ${req.method}`);
  console.log(`URL: ${req.url}`);
  console.log(`Content-Type: ${req.headers['content-type'] || 'N/A'}`);
  res.end('Check your terminal for request info.');
});

server.listen(3000);
▶ 试一试

用 curl 发送测试请求:

BASH
curl -X POST http://localhost:3000/api/data -H "Content-Type: application/json"
TEXT 📖 仅展示
Method: POST
URL: /api/data
Content-Type: application/json


5. response 对象核心方法

response 对象用于向客户端发送响应数据,包括状态码、响应头和响应体。

方法 / 属性 说明 示例
res.writeHead(statusCode, headers) 一次性写入状态码和多个响应头 res.writeHead(200, { 'Content-Type': 'text/plain' })
res.statusCode = n 单独设置状态码 res.statusCode = 404
res.setHeader(name, value) 单独设置一个响应头 res.setHeader('Content-Type', 'application/json')
res.write(data) 写入响应体数据(可多次调用) res.write('partial')
res.end(data) 发送响应体并结束响应 res.end('done')
res.writeHead 已调用后再 res.end() 发送已缓冲的数据

▶ 示例:返回 JSON 响应

JAVASCRIPT
const http = require('http');

const server = http.createServer((req, res) => {
  const data = { message: 'Success', timestamp: Date.now() };

  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify(data));
});

server.listen(3000);
▶ 试一试
BASH
curl http://localhost:3000/
TEXT 📖 仅展示
{"message":"Success","timestamp":1719792000000}


6. URL 路由解析

req.url 包含完整的请求路径和查询字符串。使用 new URL() 可以方便地拆分 pathname 和 searchParams,实现基于路径的路由分发。

▶ 示例:基于路径的路由

JAVASCRIPT
const http = require('http');

const server = http.createServer((req, res) => {
  const url = new URL(req.url, `http://${req.headers.host}`);
  const pathname = url.pathname;

  res.writeHead(200, { 'Content-Type': 'text/plain' });

  if (pathname === '/') {
    res.end('Home Page');
  } else if (pathname === '/about') {
    res.end('About Page');
  } else if (pathname === '/api/status') {
    res.end('OK');
  } else {
    res.writeHead(404, { 'Content-Type': 'text/plain' });
    res.end('Not Found');
  }
});

server.listen(3000);
▶ 试一试

7. GET 请求与查询参数

GET 请求的参数附在 URL 的查询字符串中,通过 url.searchParams 可以直接获取键值对。

▶ 示例:解析查询参数

JAVASCRIPT
const http = require('http');

const server = http.createServer((req, res) => {
  if (req.method !== 'GET') {
    res.writeHead(405, { 'Content-Type': 'text/plain' });
    res.end('Method Not Allowed');
    return;
  }

  const url = new URL(req.url, `http://${req.headers.host}`);
  const name = url.searchParams.get('name') || 'Guest';
  const page = url.searchParams.get('page') || '1';

  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ name, page }));
});

server.listen(3000);
▶ 试一试
BASH
curl "http://localhost:3000/?name=Bob&page=3"
TEXT 📖 仅展示
{"name":"Bob","page":"3"}

注意:searchParams.get() 返回的值始终是字符串,需要手动转换为数字等类型。



8. POST 请求与请求体收集

POST 请求的数据通过请求体(body)传输。request 对象是一个可读流,需要监听 data 事件收集数据块,监听 end 事件处理完整数据。

▶ 示例:收集 POST 请求体

JAVASCRIPT
const http = require('http');

const server = http.createServer((req, res) => {
  if (req.method === 'POST' && req.url === '/api/users') {
    let body = '';

    req.on('data', (chunk) => {
      body += chunk.toString();
    });

    req.on('end', () => {
      try {
        const data = JSON.parse(body);
        res.writeHead(201, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ id: 1, ...data }));
      } catch (e) {
        res.writeHead(400, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ error: 'Invalid JSON' }));
      }
    });
  } else {
    res.writeHead(404, { 'Content-Type': 'text/plain' });
    res.end('Not Found');
  }
});

server.listen(3000);
▶ 试一试
BASH
curl -X POST http://localhost:3000/api/users -H "Content-Type: application/json" -d "{\"name\":\"Bob\",\"age\":30}"
TEXT 📖 仅展示
{"id":1,"name":"Bob","age":30}


9. Content-Type 与响应头

Content-Type 告诉客户端响应体的数据格式,是 HTTP 通信中最关键的响应头之一。设置错误会导致客户端无法正确解析数据。

Content-Type 用途 说明
text/plain 纯文本 最基本的文本类型,无格式
text/html HTML 页面 浏览器会渲染为网页
application/json JSON 数据 API 最常用的响应格式
application/x-www-form-urlencoded 表单数据 默认表单提交格式
multipart/form-data 文件上传 含文件的表单提交
application/xml XML 数据 SOAP API 或传统接口
text/css CSS 样式表 样式文件
application/octet-stream 二进制流 文件下载场景

▶ 示例:同一数据不同 Content-Type 的效果

JAVASCRIPT
const http = require('http');

const server = http.createServer((req, res) => {
  const url = new URL(req.url, `http://${req.headers.host}`);

  if (url.pathname === '/plain') {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('<h1>This is plain text</h1>');
  } else if (url.pathname === '/html') {
    res.writeHead(200, { 'Content-Type': 'text/html' });
    res.end('<h1>This is HTML</h1>');
  } else if (url.pathname === '/json') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ message: 'This is JSON' }));
  }
});

server.listen(3000);
▶ 试一试

访问 /plain 浏览器显示源码标签,访问 /html 浏览器渲染大标题,访问 /json 浏览器显示 JSON 数据。



10. HTTP 状态码速查

状态码是服务器对请求处理结果的标准化表达,客户端根据状态码决定后续行为。

状态码 类别 含义 常用场景
200 2xx 成功 OK GET 请求成功返回数据
201 2xx 成功 Created POST 创建资源成功
204 2xx 成功 No Content 删除成功,无返回内容
301 3xx 重定向 Moved Permanently 永久重定向到新 URL
302 3xx 重定向 Found 临时重定向
304 3xx 重定向 Not Modified 缓存命中,无需重新传输
400 4xx 客户端错误 Bad Request 请求参数格式错误
401 4xx 客户端错误 Unauthorized 未认证,需要登录
403 4xx 客户端错误 Forbidden 已认证但无权限
404 4xx 客户端错误 Not Found 路由或资源不存在
405 4xx 客户端错误 Method Not Allowed 请求方法不被允许
500 5xx 服务器错误 Internal Server Error 服务器内部异常
502 5xx 服务器错误 Bad Gateway 网关/代理收到无效响应
503 5xx 服务器错误 Service Unavailable 服务暂时不可用

▶ 示例:根据条件返回不同状态码

JAVASCRIPT
const http = require('http');

const server = http.createServer((req, res) => {
  const url = new URL(req.url, `http://${req.headers.host}`);
  const id = url.searchParams.get('id');

  if (!id) {
    res.writeHead(400, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ error: 'Missing id parameter' }));
  } else if (id === '0') {
    res.writeHead(404, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ error: 'User not found' }));
  } else {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ id, name: 'Bob' }));
  }
});

server.listen(3000);
▶ 试一试

11. 综合示例:简单 REST API 服务器

将前面所有知识点整合,构建一个支持 GET/POST 路由、JSON 响应、查询参数解析的 REST API 服务器。内存中维护一个用户列表,支持查询全部用户、查询单个用户、创建用户三种操作。

JAVASCRIPT
const http = require('http');

const users = [
  { id: 1, name: 'Bob', email: 'bob@example.com' },
  { id: 2, name: 'Alice', email: 'alice@example.com' },
];
let nextId = 3;

function parseBody(req) {
  return new Promise((resolve, reject) => {
    let body = '';
    req.on('data', (chunk) => { body += chunk.toString(); });
    req.on('end', () => {
      try { resolve(JSON.parse(body)); }
      catch (e) { reject(e); }
    });
    req.on('error', reject);
  });
}

function sendJSON(res, statusCode, data) {
  res.writeHead(statusCode, {
    'Content-Type': 'application/json',
    'X-Powered-By': 'Node.js',
  });
  res.end(JSON.stringify(data));
}

const server = http.createServer(async (req, res) => {
  const url = new URL(req.url, `http://${req.headers.host}`);
  const pathname = url.pathname;

  // GET /api/users
  if (req.method === 'GET' && pathname === '/api/users') {
    const limit = parseInt(url.searchParams.get('limit')) || 10;
    sendJSON(res, 200, users.slice(0, limit));
    return;
  }

  // GET /api/users/:id
  if (req.method === 'GET' && pathname.startsWith('/api/users/')) {
    const id = parseInt(pathname.split('/').pop());
    const user = users.find((u) => u.id === id);
    if (!user) {
      sendJSON(res, 404, { error: 'User not found' });
    } else {
      sendJSON(res, 200, user);
    }
    return;
  }

  // POST /api/users
  if (req.method === 'POST' && pathname === '/api/users') {
    try {
      const data = await parseBody(req);
      if (!data.name || !data.email) {
        sendJSON(res, 400, { error: 'name and email are required' });
        return;
      }
      const newUser = { id: nextId++, name: data.name, email: data.email };
      users.push(newUser);
      sendJSON(res, 201, newUser);
    } catch (e) {
      sendJSON(res, 400, { error: 'Invalid JSON body' });
    }
    return;
  }

  // 404 fallback
  sendJSON(res, 404, { error: 'Route not found' });
});

server.listen(3000, () => {
  console.log('REST API server running at http://localhost:3000/');
});

测试所有接口:

BASH
# 查询全部用户
curl http://localhost:3000/api/users

# 查询单个用户
curl http://localhost:3000/api/users/1

# 创建新用户
curl -X POST http://localhost:3000/api/users -H "Content-Type: application/json" -d "{\"name\":\"Charlie\",\"email\":\"charlie@example.com\"}"

# 访问不存在的路由
curl http://localhost:3000/unknown
TEXT 📖 仅展示
[{"id":1,"name":"Bob","email":"bob@example.com"},{"id":2,"name":"Alice","email":"alice@example.com"}]

{"id":1,"name":"Bob","email":"bob@example.com"}

{"id":3,"name":"Charlie","email":"charlie@example.com"}

{"error":"Route not found"}

❓ 常见问题

Q http 模块和 Express 有什么区别?
A http 是 Node.js 内置的底层模块,只提供最基础的请求/响应处理;Express 是基于 http 模块封装的框架,内置路由、中间件、模板引擎等高级功能,开发效率更高但引入了额外依赖。
Q 如何获取 POST 请求的请求体?
A req 对象是可读流,需要监听 data 事件收集 Buffer 数据块并拼接,再监听 end 事件表示数据接收完毕,最后用 JSON.parse()Buffer.concat() 处理完整数据。
Q res.end() 和 res.write() 有什么区别?
A res.end() 发送数据并关闭响应,每个请求必须调用一次且只能一次;res.write() 只写入数据但不关闭响应,可以多次调用,用于流式传输或分块发送,最后仍需调用 res.end() 结束响应。
Q 为什么返回 JSON 要设置 Content-Type?
A 不设置 Content-Type 时默认为 text/plain,客户端(浏览器、fetch、curl)不会自动按 JSON 解析,可能导致 response.json() 报错或数据无法正确展示。设置为 application/json 后客户端才知道如何解析响应体。
Q 如何处理 URL 中的查询参数?
A 使用 new URL(req.url, 'http://localhost') 创建 URL 对象,然后通过 url.searchParams.get('key') 获取参数值,或用 url.searchParams.entries() 遍历所有参数。
Q server.listen 的第二个回调有什么用?
A 它是服务器启动成功后的回调,常用于打印启动日志。如果不传回调,也可以监听 server.on('listening', callback) 事件,效果相同。
Q 为什么 new URL 的第二个参数要传 base?
A req.url 只包含路径部分(如 /api?id=1),不是完整 URL,new URL() 需要一个 base 参数来补全协议和主机名,否则会抛出 TypeError。base 的值不影响 pathname 和 searchParams 的解析结果。

📖 小节


📝 作业

  1. 完成本课所有代码示例,确保每个示例都能正确运行
  2. 修改综合示例,添加自己的扩展功能
  3. 查阅官方文档,找出本课未涉及的1-2个API并编写测试代码
  4. 思考:在实际项目中,你会如何应用本课学到的知识?
  5. 尝试将本课知识与前面课程的内容结合,构建一个小项目
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏