Skip to content

Node.js HTTP模块:轻松搭建你的第一个Web服务器

引言

你是否曾经想过,当你在浏览器中输入一个网址后,究竟发生了什么神奇的事情?或者,那些提供API服务的后端系统是如何工作的?在Web开发的世界里,HTTP协议是连接客户端和服务器的桥梁,而Node.js通过其内置的HTTP模块,让我们能够轻松地搭建自己的Web服务器或API服务。不需要Apache或Nginx这样的重量级软件,仅仅几行JavaScript代码,你就能创建一个功能完整的HTTP服务!今天,让我们一起探索这个令人兴奋的模块,踏上服务器开发的奇妙旅程。

HTTP基础:Web通信的核心

什么是HTTP协议?

HTTP(超文本传输协议)是互联网上应用最广泛的通信协议,它定义了客户端(如浏览器)和服务器之间交换数据的规则。就像两个人交谈需要共同的语言一样,浏览器和服务器需要HTTP这种"语言"来相互理解。

HTTP通信的基本模式非常简单:

  1. 客户端发送请求(Request)
  2. 服务器返回响应(Response)

每个HTTP请求包含以下主要部分:

  • 请求方法(GET、POST等)
  • 请求路径(如 /users/login)
  • HTTP版本
  • 请求头部(Headers)
  • 请求体(Body,可选)

而HTTP响应则包含:

  • 状态码(如200表示成功,404表示未找到)
  • 状态消息
  • 响应头部
  • 响应体

Node.js中的HTTP模块

Node.js提供了内置的http模块,让我们能够创建HTTP服务器或客户端。首先,我们需要引入它:

javascript
// 引入http模块
const http = require('http');

创建你的第一个Web服务器

让我们直接开始实践,创建一个最简单的HTTP服务器:

javascript
// 引入http模块
const http = require('http');

// 创建HTTP服务器
const server = http.createServer((req, res) => {
  // 设置响应头,指定内容类型为纯文本
  res.setHeader('Content-Type', 'text/plain; charset=utf-8');
  
  // 向客户端发送响应
  res.end('你好,这是我的第一个Node.js服务器!');
});

// 监听3000端口
server.listen(3000, () => {
  console.log('服务器已启动,访问 http://localhost:3000 查看效果');
});

将上面的代码保存为server.js,然后在终端中运行:

bash
node server.js

现在,打开浏览器访问http://localhost:3000,你会看到"你好,这是我的第一个Node.js服务器!"的消息。恭喜你,你刚刚创建了自己的第一个Web服务器!

理解上面的代码

让我们逐行解释一下这段代码:

  1. 首先,我们引入了Node.js内置的http模块。
  2. 然后,使用http.createServer()方法创建了一个HTTP服务器。这个方法接收一个回调函数,每当有HTTP请求到达服务器时,这个回调函数就会被调用。
  3. 回调函数有两个参数:req(请求对象)和res(响应对象)。
    • req对象包含了客户端请求的所有信息。
    • res对象用于构建和发送响应给客户端。
  4. 我们设置了响应头,指定内容类型为纯文本,并支持UTF-8编码(这样可以正确显示中文)。
  5. 使用res.end()方法发送响应内容并结束响应过程。
  6. 最后,服务器开始在3000端口监听请求,并在控制台输出启动成功的消息。

处理不同的请求路径

实际应用中,我们的服务器需要处理不同的URL路径。下面是一个简单的路由示例:

javascript
const http = require('http');

const server = http.createServer((req, res) => {
  // 设置响应头,支持中文
  res.setHeader('Content-Type', 'text/html; charset=utf-8');
  
  // 获取请求的URL路径
  const url = req.url;
  
  // 根据不同的路径返回不同的内容
  if (url === '/' || url === '/home') {
    res.end('<h1>首页</h1><p>欢迎访问我的网站!</p>');
  } else if (url === '/about') {
    res.end('<h1>关于我们</h1><p>这是一个学习Node.js的示例项目。</p>');
  } else if (url === '/contact') {
    res.end('<h1>联系我们</h1><p>邮箱:[email protected]</p>');
  } else {
    // 设置404状态码
    res.statusCode = 404;
    res.end('<h1>404 - 页面不存在</h1><p>抱歉,您请求的页面不存在。</p>');
  }
});

server.listen(3000, () => {
  console.log('服务器已启动,访问 http://localhost:3000 查看效果');
});

现在,你可以尝试访问不同的路径:

HTTP请求方法与RESTful API

HTTP协议定义了多种请求方法,最常用的有:

  • GET:获取资源
  • POST:创建资源
  • PUT:更新资源
  • DELETE:删除资源

这些方法是RESTful API设计的基础。下面是一个简单的示例,展示如何处理不同的HTTP方法:

javascript
const http = require('http');

// 模拟的用户数据
let users = [
  { id: 1, name: '张三', age: 25 },
  { id: 2, name: '李四', age: 30 }
];

const server = http.createServer((req, res) => {
  // 设置响应头,允许返回JSON
  res.setHeader('Content-Type', 'application/json; charset=utf-8');
  
  const url = req.url;
  const method = req.method;
  
  // 用户API路径
  if (url.startsWith('/api/users')) {
    // 获取所有用户 - GET /api/users
    if (method === 'GET' && url === '/api/users') {
      res.end(JSON.stringify(users));
    }
    // 获取特定ID的用户 - GET /api/users/1
    else if (method === 'GET' && url.match(/^\/api\/users\/\d+$/)) {
      const id = parseInt(url.split('/').pop());
      const user = users.find(u => u.id === id);
      
      if (user) {
        res.end(JSON.stringify(user));
      } else {
        res.statusCode = 404;
        res.end(JSON.stringify({ error: '用户不存在' }));
      }
    }
    // 处理其他情况
    else {
      res.statusCode = 404;
      res.end(JSON.stringify({ error: '请求的资源不存在' }));
    }
  } else {
    res.statusCode = 404;
    res.end(JSON.stringify({ error: '请求的API不存在' }));
  }
});

server.listen(3000, () => {
  console.log('API服务器已启动,访问 http://localhost:3000 查看效果');
});

处理POST请求和请求体

在上面的例子中,我们只处理了GET请求。现在,让我们看看如何处理POST请求和请求体:

javascript
const http = require('http');

let users = [
  { id: 1, name: '张三', age: 25 },
  { id: 2, name: '李四', age: 30 }
];
let nextId = 3;

const server = http.createServer((req, res) => {
  res.setHeader('Content-Type', 'application/json; charset=utf-8');
  
  // 处理POST请求 - 创建新用户
  if (req.method === 'POST' && req.url === '/api/users') {
    let body = '';
    
    // 监听数据事件,收集请求体数据
    req.on('data', (chunk) => {
      body += chunk.toString();
    });
    
    // 监听结束事件,处理完整的请求体
    req.on('end', () => {
      try {
        // 解析JSON数据
        const userData = JSON.parse(body);
        
        // 创建新用户
        const newUser = {
          id: nextId++,
          name: userData.name,
          age: userData.age
        };
        
        // 添加到用户数组
        users.push(newUser);
        
        // 设置201状态码(已创建)
        res.statusCode = 201;
        res.end(JSON.stringify(newUser));
      } catch (e) {
        // 处理JSON解析错误
        res.statusCode = 400;
        res.end(JSON.stringify({ error: '无效的JSON数据' }));
      }
    });
  } else if (req.method === 'GET' && req.url === '/api/users') {
    // 获取所有用户
    res.end(JSON.stringify(users));
  } else {
    res.statusCode = 404;
    res.end(JSON.stringify({ error: '请求的资源不存在' }));
  }
});

server.listen(3000, () => {
  console.log('API服务器已启动,访问 http://localhost:3000 查看效果');
});

如果你想测试这个API,可以使用Postman或curl等工具发送POST请求。例如,使用curl:

bash
curl -X POST -H "Content-Type: application/json" -d '{"name":"王五","age":28}' http://localhost:3000/api/users

或者你也可以编写一个简单的HTML表单:

html
<!DOCTYPE html>
<html>
<head>
  <title>添加用户</title>
  <meta charset="utf-8">
</head>
<body>
  <h1>添加新用户</h1>
  <form id="userForm">
    <div>
      <label for="name">姓名:</label>
      <input type="text" id="name" name="name" required>
    </div>
    <div>
      <label for="age">年龄:</label>
      <input type="number" id="age" name="age" required>
    </div>
    <button type="submit">提交</button>
  </form>
  
  <script>
    document.getElementById('userForm').addEventListener('submit', function(e) {
      e.preventDefault();
      
      const name = document.getElementById('name').value;
      const age = parseInt(document.getElementById('age').value);
      
      fetch('http://localhost:3000/api/users', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ name, age })
      })
      .then(response => response.json())
      .then(data => {
        alert('用户创建成功!ID: ' + data.id);
        document.getElementById('userForm').reset();
      })
      .catch(error => {
        alert('错误:' + error.message);
      });
    });
  </script>
</body>
</html>

注意:如果你直接在浏览器中打开上面的HTML文件,可能会遇到跨域问题。要解决这个问题,你需要在服务器端添加CORS(跨域资源共享)头部。

添加CORS支持

要允许跨域请求,你需要在服务器端添加适当的CORS头部:

javascript
const http = require('http');

const server = http.createServer((req, res) => {
  // 添加CORS头部
  res.setHeader('Access-Control-Allow-Origin', '*');  // 允许任何来源
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
  
  // 处理预检请求
  if (req.method === 'OPTIONS') {
    res.statusCode = 204; // No Content
    res.end();
    return;
  }
  
  // 其他请求处理逻辑...
  res.setHeader('Content-Type', 'application/json; charset=utf-8');
  res.end(JSON.stringify({ message: '服务器已启用CORS' }));
});

server.listen(3000, () => {
  console.log('支持CORS的服务器已启动,访问 http://localhost:3000 查看效果');
});

处理静态文件

虽然Node.js的HTTP模块非常强大,但处理静态文件(如HTML、CSS、JavaScript、图片等)需要编写不少代码。下面是一个简单的静态文件服务器示例:

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

// 静态文件目录
const STATIC_DIR = path.join(__dirname, 'public');

// MIME类型映射
const MIME_TYPES = {
  '.html': 'text/html',
  '.css': 'text/css',
  '.js': 'text/javascript',
  '.json': 'application/json',
  '.png': 'image/png',
  '.jpg': 'image/jpeg',
  '.gif': 'image/gif',
  '.svg': 'image/svg+xml',
  '.ico': 'image/x-icon'
};

const server = http.createServer((req, res) => {
  // 只处理GET请求
  if (req.method !== 'GET') {
    res.statusCode = 405; // Method Not Allowed
    res.end('Method Not Allowed');
    return;
  }
  
  // 获取请求的文件路径
  let filePath = path.join(STATIC_DIR, req.url === '/' ? 'index.html' : req.url);
  
  // 获取文件扩展名
  const ext = path.extname(filePath);
  
  // 读取文件
  fs.readFile(filePath, (err, data) => {
    if (err) {
      // 文件不存在
      if (err.code === 'ENOENT') {
        res.statusCode = 404;
        res.end('404 Not Found');
      } else {
        // 服务器错误
        res.statusCode = 500;
        res.end('Internal Server Error');
      }
      return;
    }
    
    // 设置内容类型
    const contentType = MIME_TYPES[ext] || 'application/octet-stream';
    res.setHeader('Content-Type', contentType);
    
    // 发送文件内容
    res.end(data);
  });
});

server.listen(3000, () => {
  console.log('静态文件服务器已启动,访问 http://localhost:3000 查看效果');
});

要使用这个静态文件服务器,你需要创建一个public目录,并在里面放置你的静态文件,如index.html、CSS和JavaScript文件等。

使用HTTPS

在生产环境中,通常需要使用HTTPS来保护数据传输。Node.js内置了https模块,让我们能够轻松地创建HTTPS服务器:

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

// 读取SSL证书文件
const options = {
  key: fs.readFileSync('private-key.pem'),
  cert: fs.readFileSync('certificate.pem')
};

// 创建HTTPS服务器
const server = https.createServer(options, (req, res) => {
  res.setHeader('Content-Type', 'text/plain; charset=utf-8');
  res.end('这是一个安全的HTTPS服务器!');
});

server.listen(443, () => {
  console.log('HTTPS服务器已启动,访问 https://localhost 查看效果');
});

注意:上面的代码需要有SSL证书文件。在开发环境中,你可以使用自签名证书;而在生产环境中,应该使用由受信任的证书颁发机构(如Let's Encrypt)签发的证书。

常见错误和解决方案

在使用Node.js HTTP模块时,初学者可能会遇到一些常见问题:

  1. 端口已被占用
Error: listen EADDRINUSE: address already in use :::3000

解决方案:选择另一个未被占用的端口,或先停止占用该端口的程序。

  1. 请求体解析错误
SyntaxError: Unexpected token ... in JSON at position ...

解决方案:确保发送的JSON数据格式正确,或添加错误处理逻辑。

  1. 跨域请求被阻止
Access to fetch at 'http://localhost:3000/api/users' from origin 'null' has been blocked by CORS policy

解决方案:添加适当的CORS头部,如前面示例所示。

  1. 路径解析错误
Error: ENOENT: no such file or directory

解决方案:确保文件路径正确,使用path.join()处理路径,而不是手动拼接字符串。

最佳实践与性能优化

  1. 使用流处理大文件

当需要发送大文件时,应使用流而不是fs.readFile

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

const server = http.createServer((req, res) => {
  const videoPath = path.join(__dirname, 'videos', 'big-video.mp4');
  const stat = fs.statSync(videoPath);
  
  res.setHeader('Content-Type', 'video/mp4');
  res.setHeader('Content-Length', stat.size);
  
  // 创建文件读取流并直接管道到响应中
  const fileStream = fs.createReadStream(videoPath);
  fileStream.pipe(res);
  
  // 处理流错误
  fileStream.on('error', (err) => {
    console.error('流错误:', err);
    if (!res.headersSent) {
      res.statusCode = 500;
      res.end('Server Error');
    } else {
      res.end();
    }
  });
});

server.listen(3000);
  1. 处理服务器错误

始终监听服务器的错误事件,防止程序崩溃:

javascript
const server = http.createServer((req, res) => {
  // 处理请求...
});

// 监听服务器错误
server.on('error', (err) => {
  console.error('服务器错误:', err);
  if (err.code === 'EADDRINUSE') {
    console.log('端口已被占用,尝试使用另一个端口...');
    // 尝试另一个端口
    server.listen(3001);
  }
});

server.listen(3000);
  1. 合理设置超时时间

对于长时间运行的请求,设置适当的超时时间:

javascript
const server = http.createServer((req, res) => {
  // 设置请求超时时间为60秒
  req.setTimeout(60000);
  // 处理请求...
});

// 设置服务器的请求超时默认值
server.timeout = 120000; // 2分钟

server.listen(3000);

HTTP客户端:发起请求

除了创建HTTP服务器,Node.js的HTTP模块还可以用作客户端,发起HTTP请求:

javascript
const http = require('http');

// 请求选项
const options = {
  hostname: 'api.example.com',
  port: 80,
  path: '/data',
  method: 'GET',
  headers: {
    'Accept': 'application/json'
  }
};

// 发起请求
const req = http.request(options, (res) => {
  let data = '';
  
  // 接收数据
  res.on('data', (chunk) => {
    data += chunk;
  });
  
  // 处理完整响应
  res.on('end', () => {
    console.log('状态码:', res.statusCode);
    console.log('响应头:', res.headers);
    
    try {
      const parsedData = JSON.parse(data);
      console.log('数据:', parsedData);
    } catch (e) {
      console.error('解析JSON失败:', e);
    }
  });
});

// 处理请求错误
req.on('error', (e) => {
  console.error('请求错误:', e);
});

// 发送请求
req.end();

对于简单的GET请求,可以使用http.get()方法:

javascript
const http = require('http');

http.get('http://api.example.com/data', (res) => {
  let data = '';
  
  res.on('data', (chunk) => {
    data += chunk;
  });
  
  res.on('end', () => {
    console.log(JSON.parse(data));
  });
}).on('error', (e) => {
  console.error(`请求出错: ${e.message}`);
});

总结与拓展

Node.js的HTTP模块为我们提供了构建Web服务器和客户端的基础工具。在本文中,我们学习了:

  • HTTP协议的基础知识
  • 创建简单的Web服务器
  • 处理不同的HTTP请求方法
  • 解析请求体和URL参数
  • 发送各种类型的响应
  • 处理静态文件服务
  • 设置CORS头部解决跨域问题
  • 创建HTTPS安全服务器
  • 使用HTTP客户端发起请求

虽然原生的HTTP模块功能强大,但在实际开发中,我们通常会使用基于它构建的更高级框架,如Express、Koa或Fastify,它们提供了更丰富、更便捷的API和中间件系统。

随着你的深入学习,可以进一步探索:

  • Express框架的路由和中间件系统
  • RESTful API的最佳实践
  • WebSocket实时通信
  • GraphQL API设计
  • API认证与授权
  • 微服务架构
  • 服务端渲染(SSR)

希望本文能为你打开Node.js服务器开发的大门,激发你进一步学习和探索的兴趣。记住,学习编程最好的方式是实践,尝试创建自己的项目,解决实际问题!

注:本文档会持续更新,欢迎关注!

用❤️分享前端技术 fedev.wiki