Node.js 核心模块:HTTP 创建服务器

http 模块是 Node.js 的核心模块之一,它提供了创建 HTTP 服务器和客户端的功能。通过 http 模块,你可以轻松构建 Web 应用、REST API 或静态文件服务器。本章将详细介绍如何使用 http 模块创建服务器、处理请求与响应、实现路由以及构建一个简单的 REST API。

1. 引入 http 模块

http 是内置模块,无需安装,直接引入即可:

const http = require('http');

2. 创建第一个 HTTP 服务器

使用 http.createServer() 方法创建一个服务器实例,它接受一个回调函数,每当有请求到达时就会调用该函数。回调函数接收两个参数:req(请求对象)和 res(响应对象)。

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello World\n');
});

server.listen(3000, () => {
  console.log('服务器运行在 http://localhost:3000/');
});

运行该脚本,访问 http://localhost:3000,你将看到 "Hello World"。

提示: res.end() 必须被调用,否则客户端会一直等待响应。可以先用 res.write() 写入响应体,最后用 res.end() 结束响应。

3. 请求对象 (req) 详解

reqhttp.IncomingMessage 的实例,包含请求的所有信息:

  • req.method - 请求方法(GET、POST、PUT 等)
  • req.url - 请求的 URL(包含路径和查询字符串)
  • req.headers - 请求头对象
  • req.httpVersion - HTTP 版本
const server = http.createServer((req, res) => {
  console.log('请求方法:', req.method);
  console.log('请求URL:', req.url);
  console.log('请求头:', req.headers);
  res.end('Check your console');
});

3.1 获取请求体

对于 POST、PUT 等包含请求体的请求,需要通过监听 dataend 事件来获取数据:

const server = http.createServer((req, res) => {
  if (req.method === 'POST') {
    let body = '';
    req.on('data', chunk => {
      body += chunk.toString(); // 默认是 Buffer,转成字符串
    });
    req.on('end', () => {
      console.log('请求体:', body);
      res.end('Data received');
    });
  } else {
    res.end('Send a POST request');
  }
});

4. 响应对象 (res) 详解

reshttp.ServerResponse 的实例,用于构建响应:

  • res.writeHead(statusCode, headers) - 设置状态码和响应头(可多次调用,但只有第一次有效)
  • res.setHeader(name, value) - 单独设置响应头
  • res.write(chunk) - 写入响应体(可以多次调用)
  • res.end([data]) - 结束响应,可选地发送最后的数据

示例:返回 JSON 数据

const server = http.createServer((req, res) => {
  const data = { name: 'Node.js', type: 'runtime' };
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify(data));
});

5. 路由基础

根据请求方法和 URL 进行分发,实现简单的路由:

const server = http.createServer((req, res) => {
  const { method, url } = req;

  if (method === 'GET' && url === '/') {
    res.writeHead(200, { 'Content-Type': 'text/html' });
    res.end('<h1>Home Page</h1>');
  } else if (method === 'GET' && url === '/about') {
    res.writeHead(200, { 'Content-Type': 'text/html' });
    res.end('<h1>About Page</h1>');
  } else if (method === 'POST' && url === '/api/users') {
    res.writeHead(201, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ message: 'User created' }));
  } else {
    res.writeHead(404, { 'Content-Type': 'text/plain' });
    res.end('404 Not Found');
  }
});

6. 获取 URL 参数和查询字符串

使用 Node.js 内置的 url 模块或 URL 类来解析 URL:

const url = require('url');

const server = http.createServer((req, res) => {
  // 使用 url.parse (旧版)
  const parsedUrl = url.parse(req.url, true); // true 表示将查询字符串解析为对象
  console.log('路径名:', parsedUrl.pathname);
  console.log('查询参数:', parsedUrl.query); // 例如 { id: '123', name: 'node' }

  // 使用 URL 类 (现代方式)
  const myURL = new URL(req.url, `http://${req.headers.host}`);
  console.log('路径名:', myURL.pathname);
  console.log('查询参数:', Object.fromEntries(myURL.searchParams));

  res.end('Check console');
});

7. 提供静态文件服务

结合 fs 模块读取文件并返回:

const fs = require('fs');
const path = require('path');

const server = http.createServer((req, res) => {
  if (req.method === 'GET' && req.url === '/') {
    const filePath = path.join(__dirname, 'public', 'index.html');
    fs.readFile(filePath, (err, content) => {
      if (err) {
        res.writeHead(500);
        res.end('Server Error');
      } else {
        res.writeHead(200, { 'Content-Type': 'text/html' });
        res.end(content);
      }
    });
  } else {
    // 处理其他请求...
  }
});

更好的做法是使用流式传输以提高性能:

const stream = fs.createReadStream(filePath);
res.writeHead(200, { 'Content-Type': 'text/html' });
stream.pipe(res); // 自动处理结束和错误

8. 错误处理

在服务器中,应始终处理可能发生的错误:

const server = http.createServer((req, res) => {
  // ... 业务逻辑
});

server.on('error', (err) => {
  console.error('服务器错误:', err);
});

9. 创建 HTTPS 服务器

https 模块与 http 类似,但需要提供 SSL 证书:

const https = require('https');
const fs = require('fs');

const options = {
  key: fs.readFileSync('private-key.pem'),
  cert: fs.readFileSync('certificate.pem')
};

https.createServer(options, (req, res) => {
  res.writeHead(200);
  res.end('Hello HTTPS\n');
}).listen(443);

10. 完整示例:简单的 REST API 服务器

以下是一个模拟用户资源的 REST API:

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

let users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' }
];

const server = http.createServer((req, res) => {
  const parsedUrl = url.parse(req.url, true);
  const method = req.method;
  const pathname = parsedUrl.pathname;

  // 设置 CORS 头(可选)
  res.setHeader('Access-Control-Allow-Origin', '*');

  // 路由:GET /users
  if (method === 'GET' && pathname === '/users') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify(users));
  }
  // 路由:GET /users/:id
  else if (method === 'GET' && pathname.startsWith('/users/')) {
    const id = parseInt(pathname.split('/')[2]);
    const user = users.find(u => u.id === id);
    if (user) {
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify(user));
    } else {
      res.writeHead(404);
      res.end(JSON.stringify({ error: 'User not found' }));
    }
  }
  // 路由:POST /users
  else if (method === 'POST' && pathname === '/users') {
    let body = '';
    req.on('data', chunk => body += chunk);
    req.on('end', () => {
      try {
        const newUser = JSON.parse(body);
        newUser.id = users.length + 1;
        users.push(newUser);
        res.writeHead(201, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify(newUser));
      } catch (err) {
        res.writeHead(400);
        res.end(JSON.stringify({ error: 'Invalid JSON' }));
      }
    });
  }
  // 404
  else {
    res.writeHead(404);
    res.end(JSON.stringify({ error: 'Route not found' }));
  }
});

server.listen(3000, () => {
  console.log('REST API 服务器运行在 http://localhost:3000');
});

11. 注意事项

  • 必须调用 res.end() 结束响应,否则请求会挂起。
  • 响应头应在写入响应体之前设置。
  • 对于 JSON 响应,确保设置正确的 Content-Type 并使用 JSON.stringify()
  • 处理 POST/PUT 请求体时,注意数据可能分块到达,要监听 dataend 事件。
  • 在生产环境中,通常使用框架(如 Express)来简化开发,但理解底层 http 模块仍然很重要。
性能提示: 不要直接在请求回调中执行阻塞操作(如同步文件读取),否则会阻塞事件循环,降低并发能力。应使用异步 API 或将耗时任务交给 Worker 线程。

小结

本章介绍了如何使用 Node.js 的 http 模块创建 HTTP 服务器,包括处理请求、响应、路由、获取参数和构建简单的 REST API。虽然实际开发中可能会使用 Express 等框架,但深入理解底层实现有助于更好地调试和优化应用。下一章我们将介绍 Node.js 的另一个核心模块 url,用于更精细的 URL 处理。