文章目录
一、HTTP模块入门:从零搭建第一个服务器
1.1 基础概念解析
HTTP 模块是什么?
Node.js内置的 http 模块提供了创建 HTTP 服务器和客户端的能力。就像快递公司:
- 服务器:像仓库,存储货物(数据)
- 客户端:像快递员,负责收发货物(请求和响应)
核心对象解释:
http.createServer
:创建服务器req
对象:包含客户端请求的信息(地址、请求头等)res
对象:用来给客户端发送响应
1.2 手把手创建服务器
// 导入模块(类似取快递工具)
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 请求参数获取
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 请求数据处理
let body = [];
req.on('data', chunk => {
body.push(chunk); //接收数据块(像接收多个包裹)
}).on('end', () => {
body = Buffer.concat(body).toString(); // 合并所有数据块
console.log('收到POST数据:', body);
})
2.2 实现文件下载功能
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 跨域问题处理
// 在响应头中添加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 防止服务崩溃
// 全局错误捕获
process.on('uncaughtException', (err) => {
console.error('全局异常捕获:', err);
// 可以记录日志或发送警报
});
// 处理Promise拒绝
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的Promise拒绝:', reason);
});
3.3 调试技巧
使用 curl 测试接口:
# 测试GET请求
curl http://localhost:3000/api/data
# 测试POST请求
curl -X POST -d "username=john" http://localhost:3000/login
四、安全最佳实践
4.1 请求头安全设置
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('Content-Security-Policy', "default-src 'self'");
4.2 速率限制(防止DDoS攻击)
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100 // 每个IP最多100次请求
});
// 应用中间件
server.use(limiter);
五、简易版博客系统
5.1 项目结构
/my-blog
├── data/ # 数据存储
│ └── articles.json
├── public/ # 静态资源
│ ├── css/
│ └── js/
│ └── index.html
├── controllers/ # 业务逻辑
│ └── articleController.js
├── routes/ # 路由配置
│ └── articleRoutes.js
├── utils/ # 工具函数
│ └── fileUtils.js
└── server.js # 入口文件
5.2 环境准备
1.初始化项目
mkdir my-blog && cd my-blog
npm init -y
2.安装依赖
npm install uuid # 生成唯一ID
5.3 核心代码实现
1.数据存储(data/articles.json)
{
"articles": [
{
"id": "1",
"title": "Node.js入门指南",
"content": "Node.js是一个基于Chrome V8引擎的JavaScript运行环境...",
"createdAt": "2023-08-20"
}
]
}
2.工具函数(utils/fileUtils.js)
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)
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)
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)
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.页面样式跟逻辑
// 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);
// 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>
// 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;
}