深入掌握Node.js HTTP模块:从开始到放弃

文章目录

一、HTTP模块入门:从零搭建第一个服务器

1.1 基础概念解析

HTTP 模块是什么?

Node.js内置的 http 模块提供了创建 HTTP 服务器和客户端的能力。就像快递公司:

  • 服务器:像仓库,存储货物(数据)
  • 客户端:像快递员,负责收发货物(请求和响应)

核心对象解释:

  • http.createServer:创建服务器
  • req对象:包含客户端请求的信息(地址、请求头等)
  • res对象:用来给客户端发送响应

1.2 手把手创建服务器

bash 复制代码
// 导入模块(类似取快递工具)
const http = require('http');

// 创建服务器(建立仓库)
const server = http.createServer((req, res) => {
	// 设置响应头(告诉快递员包裹类型)
	res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); // 明确指定编码,不然运行之后是乱码

	// 发送响应内容(实际运送的货物)
	res.end('<h1>欢迎来到我的网站!</h1>');
})

// 启动监听(设置仓库门牌号)
server.listen(3000, () => {
	console.log('服务器已在 http://localhost:3000 启动')
})

二、核心功能深入解析

2.1 处理不同请求类型

GET 请求参数获取

bash 复制代码
const { URL } = require('url');

server.on('request', (req, res) => {
	// 解析URL(类似拆包裹看地址标签)
	const urlObj = new URL(req.url, `http://${req.headers.host}`);

	// 获取查询参数(比如?name=John)
	console.log(urlObj.searchParams.get('name')); // 输出 John
})

POST 请求数据处理

bash 复制代码
let body = [];
req.on('data', chunk => {
	body.push(chunk); //接收数据块(像接收多个包裹)
}).on('end', () => {
	body = Buffer.concat(body).toString(); // 合并所有数据块
	console.log('收到POST数据:', body);
})

2.2 实现文件下载功能

bash 复制代码
const fs = require('fs');

function downloadFile(res, filePath) {
	// 设置响应头(高速浏览器这是要下载的文件)
	res.writeHead(200, {
		'Content-Type': 'application/octet-stream',
		'Content-Disposition': `attachment; filename=${path.basename(filePath)}`
	});
	
	// 创建文件流(像打开水龙头放水)
	const fileStream = fs.createReadStream(filePath);
	fileStream.pipe(res); // 将文件流导向响应
}

三、常见问题解决方案

3.1 跨域问题处理

bash 复制代码
// 在响应头中添加CORS支持
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');

3.2 防止服务崩溃

bash 复制代码
// 全局错误捕获
process.on('uncaughtException', (err) => {
  console.error('全局异常捕获:', err);
  // 可以记录日志或发送警报
});

// 处理Promise拒绝
process.on('unhandledRejection', (reason, promise) => {
  console.error('未处理的Promise拒绝:', reason);
});

3.3 调试技巧

使用 curl 测试接口:

bash 复制代码
# 测试GET请求
curl http://localhost:3000/api/data

# 测试POST请求
curl -X POST -d "username=john" http://localhost:3000/login

四、安全最佳实践

4.1 请求头安全设置

bash 复制代码
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('Content-Security-Policy', "default-src 'self'");

4.2 速率限制(防止DDoS攻击)

bash 复制代码
const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 100 // 每个IP最多100次请求
});

// 应用中间件
server.use(limiter);

五、简易版博客系统

5.1 项目结构

bash 复制代码
/my-blog
├── data/            # 数据存储
│   └── articles.json
├── public/          # 静态资源
│   ├── css/
│   └── js/
│   └── index.html
├── controllers/     # 业务逻辑
│   └── articleController.js
├── routes/          # 路由配置
│   └── articleRoutes.js
├── utils/           # 工具函数
│   └── fileUtils.js
└── server.js        # 入口文件

5.2 环境准备

1.初始化项目

bash 复制代码
mkdir my-blog && cd my-blog
npm init -y

2.安装依赖

bash 复制代码
npm install uuid      # 生成唯一ID

5.3 核心代码实现

1.数据存储(data/articles.json)

bash 复制代码
{
  "articles": [
    {
      "id": "1",
      "title": "Node.js入门指南",
      "content": "Node.js是一个基于Chrome V8引擎的JavaScript运行环境...",
      "createdAt": "2023-08-20"
    }
  ]
}

2.工具函数(utils/fileUtils.js)

bash 复制代码
const fs = require('fs').promises;
const path = require('path');

const dataPath = path.join(__dirname, '../data/articles.json');

async function readData() {
  try {
    const data = await fs.readFile(dataPath, 'utf8');
    return JSON.parse(data);
  } catch (err) {
    return { articles: [] };
  }
}

async function writeData(data) {
  await fs.writeFile(dataPath, JSON.stringify(data, null, 2));
}

module.exports = { readData, writeData };

3. 控制器(controllers/articleController.js)

bash 复制代码
const { v4: uuidv4 } = require('uuid');
const { readData, writeData } = require('../utils/fileUtils');

// 获取所有文章
async function getAllArticles() {
  const data = await readData();
  return data.articles;
}

// 创建新文章
async function createArticle(title, content) {
  const data = await readData();
  const newArticle = {
    id: uuidv4(),
    title,
    content,
    createdAt: new Date().toISOString().split('T')[0]
  };
  data.articles.push(newArticle);
  await writeData(data);
  return newArticle;
}

// 删除文章
async function deleteArticle(id) {
  const data = await readData();
  data.articles = data.articles.filter(article => article.id !== id);
  await writeData(data);
}

module.exports = { getAllArticles, createArticle, deleteArticle };

4. 路由配置(routes/articleRoutes.js)

bash 复制代码
const http = require('http');
const fs = require('fs');
const path = require('path');

// 假设这是文章数据存储
let articles = [];
let nextId = 1;

function handleRoutes(req, res) {
    const urlParts = req.url.split('/');
    if (req.url === '/api/articles' && req.method === 'GET') {
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify(articles));
    } else if (req.url === '/api/articles' && req.method === 'POST') {
        let body = '';
        req.on('data', chunk => {
            body += chunk.toString();
        });
        req.on('end', () => {
            const newArticle = JSON.parse(body);
            newArticle.id = nextId++;
            articles.push(newArticle);
            res.writeHead(201, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify(newArticle));
        });
    } else if (urlParts[1] === 'api' && urlParts[2] === 'articles' && req.method === 'DELETE') {
        if (urlParts.length === 3) {
            // 批量删除
            articles = [];
            res.writeHead(200, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ message: '所有文章删除成功' }));
        } else {
            // 单篇删除
            const id = parseInt(urlParts[3]);
            const index = articles.findIndex(article => article.id === id);
            if (index !== -1) {
                articles.splice(index, 1);
                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ message: '文章删除成功' }));
            } else {
                res.writeHead(404, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ message: '文章未找到' }));
            }
        }
    } else {
        res.writeHead(404, { 'Content-Type': 'text/plain' });
        res.end('Not Found');
    }
}

module.exports = handleRoutes;

5. 主服务器(server.js)

bash 复制代码
const http = require('http');
const fs = require('fs');
const path = require('path');
const handleRoutes = require('./routes/articleRoutes');

// 创建HTTP服务器
const server = http.createServer(async (req, res) => {
  // 静态文件服务
  if (req.url.startsWith('/public/')) {
    const filePath = path.join(__dirname, req.url);
    fs.readFile(filePath, (err, data) => {
      if (err) {
        res.writeHead(404);
        return res.end('File not found');
      }
      res.writeHead(200);
      res.end(data);
    });
    return;
  }

  // API路由处理
  handleRoutes(req, res);
});

// 启动服务器
const PORT = 3000;
server.listen(PORT, () => {
  console.log(`Server running at http://localhost:${PORT}`);
});

6.页面样式跟逻辑

bash 复制代码
// public/js/app.js
// 加载文章列表的函数
async function loadArticles() {
    try {
        const response = await fetch('/api/articles');
        if (!response.ok) {
            throw new Error('网络响应失败');
        }
        const articles = await response.json();
        const articlesDiv = document.getElementById('articles');
        articlesDiv.innerHTML = '';

        articles.forEach(article => {
            const articleElement = document.createElement('div');
            articleElement.innerHTML = `
                <h2>${article.title}</h2>
                <p>${article.content}</p>
                <button onclick="deleteArticle('${article.id}')">删除文章</button>
            `;
            articlesDiv.appendChild(articleElement);
        });
    } catch (error) {
        console.error('加载文章列表时出错:', error);
    }
}

// 删除文章的函数
async function deleteArticle(id) {
    try {
        const response = await fetch(`/api/articles/${id}`, {
            method: 'DELETE'
        });

        if (!response.ok) {
            throw new Error('删除文章失败');
        }

        // 重新加载文章列表
        loadArticles();
    } catch (error) {
        console.error('删除文章时出错:', error);
    }
}

// 添加文章的函数
async function addArticle(event) {
    event.preventDefault();
    const title = document.getElementById('title').value;
    const content = document.getElementById('content').value;

    try {
        const response = await fetch('/api/articles', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ title, content })
        });

        if (!response.ok) {
            throw new Error('添加文章失败');
        }

        // 清空表单
        document.getElementById('articleForm').reset();
        // 重新加载文章列表
        loadArticles();
    } catch (error) {
        console.error('添加文章时出错:', error);
    }
}

// 页面加载完成后自动加载文章列表
window.addEventListener('DOMContentLoaded', loadArticles);
bash 复制代码
// public/index.html
<!DOCTYPE html>
<html>
<head>
  <!-- 添加字符编码声明 -->
  <meta charset="UTF-8">
  <title>我的博客</title>
  <link rel="stylesheet" href="/public/css/style.css">
</head>
<body>
  <div id="app">
    <h1>文章列表</h1>
    <!-- 修改文章表单,移除文件输入框 -->
    <form id="articleForm" onsubmit="addArticle(event)">
      <input type="text" id="title" placeholder="文章标题" required>
      <textarea id="content" placeholder="文章内容" required></textarea>
      <button type="submit">添加文章</button>
    </form>
    <div id="articles"></div>
    <button onclick="loadArticles()">刷新列表</button>
  </div>
  <script src="/public/js/app.js"></script>
</body>
</html>
bash 复制代码
// public/css/style.css
body {
    font-family: Arial, sans-serif;
    line-height: 1.6;
    margin: 0;
    padding: 0;
    background-color: #f4f4f4;
}

#app {
    width: 80%;
    margin: auto;
    overflow: hidden;
    padding: 20px;
}

h1 {
    color: #333;
    text-align: center;
}

#articleForm {
    background-color: #fff;
    padding: 20px;
    margin-bottom: 20px;
    border-radius: 5px;
    box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
}

#articleForm input[type="text"],
#articleForm textarea {
    width: 100%;
    padding: 10px;
    margin-bottom: 10px;
    border: 1px solid #ddd;
    border-radius: 3px;
}

#articleForm button {
    background-color: #333;
    color: #fff;
    padding: 10px 20px;
    border: none;
    border-radius: 3px;
    cursor: pointer;
}

#articleForm button:hover {
    background-color: #555;
}

#articles div {
    background-color: #fff;
    padding: 20px;
    margin-bottom: 10px;
    border-radius: 5px;
    box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
}

#articles h2 {
    margin-top: 0;
}
相关推荐
byxdaz4 分钟前
C++ HTTP框架推荐
http
心扬6 分钟前
python容器
开发语言·python
程序员葵安40 分钟前
【Java Web】1.Maven
前端
我漫长的孤独流浪1 小时前
STL中的Vector(顺序表)
开发语言·c++·算法
i1yo_kiki1 小时前
Ajax快速入门教程
前端·javascript·ajax
一刀到底2111 小时前
java 在用redis 的时候,如何合理的处理分页问题? redis应当如何存储性能最佳
java·开发语言·redis
百锦再1 小时前
微信小程序学习基础:从入门到精通
前端·vue.js·python·学习·微信小程序·小程序·pdf
软考真题app1 小时前
软件设计师考试三大核心算法考点深度解析(红黑树 / 拓扑排序 / KMP 算法)真题考点分析——求三连
java·开发语言·算法·软考·软件设计师
gadiaola2 小时前
【苍穹外卖】Day01—Mac前端环境搭建
前端·nginx·macos·homebrew
乌鸦9442 小时前
《STL--string的使用及其底层实现》
开发语言·c++·stl·string使用及其模拟实现