Skip to content

http server.js

文件信息

  • 📄 原文件:02_http_server.js
  • 🔤 语言:javascript

Node.js HTTP 服务器 本文件介绍 Node.js 中的 HTTP 服务器开发。

完整代码

javascript
/**
 * ============================================================
 *                Node.js HTTP 服务器
 * ============================================================
 * 本文件介绍 Node.js 中的 HTTP 服务器开发。
 * ============================================================
 */

const http = require("http");
const https = require("https");
const url = require("url");
const querystring = require("querystring");
const fs = require("fs");
const path = require("path");

// ============================================================
//                    1. 基础 HTTP 服务器
// ============================================================

console.log("=".repeat(60));
console.log("Node.js HTTP 服务器示例");
console.log("=".repeat(60));

/**
 * 创建基础服务器
 */
function createBasicServer() {
    const server = http.createServer((req, res) => {
        // 设置响应头
        res.setHeader("Content-Type", "text/plain; charset=utf-8");

        // 发送响应
        res.statusCode = 200;
        res.end("Hello, World!\n你好,世界!");
    });

    return server;
}

// ============================================================
//                    2. 路由处理
// ============================================================

/**
 * 简单的路由器
 */
class Router {
    constructor() {
        this.routes = {
            GET: {},
            POST: {},
            PUT: {},
            DELETE: {}
        };
    }

    // 注册路由
    get(path, handler) {
        this.routes.GET[path] = handler;
    }

    post(path, handler) {
        this.routes.POST[path] = handler;
    }

    put(path, handler) {
        this.routes.PUT[path] = handler;
    }

    delete(path, handler) {
        this.routes.DELETE[path] = handler;
    }

    // 处理请求
    async handle(req, res) {
        const parsedUrl = url.parse(req.url, true);
        const pathname = parsedUrl.pathname;
        const method = req.method;

        // 添加解析后的信息到请求对象
        req.query = parsedUrl.query;
        req.pathname = pathname;

        // 查找路由
        const handler = this.routes[method]?.[pathname];

        if (handler) {
            try {
                await handler(req, res);
            } catch (error) {
                console.error("Handler error:", error);
                res.statusCode = 500;
                res.setHeader("Content-Type", "application/json");
                res.end(JSON.stringify({ error: "Internal Server Error" }));
            }
        } else {
            res.statusCode = 404;
            res.setHeader("Content-Type", "application/json");
            res.end(JSON.stringify({ error: "Not Found" }));
        }
    }
}

// ============================================================
//                    3. 请求体解析
// ============================================================

/**
 * 解析 JSON 请求体
 */
function parseJsonBody(req) {
    return new Promise((resolve, reject) => {
        let body = "";

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

            // 防止请求体过大
            if (body.length > 1e6) {
                req.destroy();
                reject(new Error("Request body too large"));
            }
        });

        req.on("end", () => {
            try {
                const data = body ? JSON.parse(body) : {};
                resolve(data);
            } catch (error) {
                reject(new Error("Invalid JSON"));
            }
        });

        req.on("error", reject);
    });
}

/**
 * 解析 URL 编码的请求体
 */
function parseUrlEncodedBody(req) {
    return new Promise((resolve, reject) => {
        let body = "";

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

        req.on("end", () => {
            const data = querystring.parse(body);
            resolve(data);
        });

        req.on("error", reject);
    });
}

// ============================================================
//                    4. 中间件模式
// ============================================================

/**
 * 中间件管理器
 */
class MiddlewareManager {
    constructor() {
        this.middlewares = [];
    }

    use(middleware) {
        this.middlewares.push(middleware);
    }

    async execute(req, res) {
        let index = 0;

        const next = async () => {
            if (index < this.middlewares.length) {
                const middleware = this.middlewares[index++];
                await middleware(req, res, next);
            }
        };

        await next();
    }
}

// --- 常用中间件 ---

/**
 * 日志中间件
 */
function loggerMiddleware(req, res, next) {
    const start = Date.now();
    const { method, url } = req;

    res.on("finish", () => {
        const duration = Date.now() - start;
        console.log(`${method} ${url} ${res.statusCode} - ${duration}ms`);
    });

    next();
}

/**
 * CORS 中间件
 */
function corsMiddleware(req, res, next) {
    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, Authorization");

    if (req.method === "OPTIONS") {
        res.statusCode = 204;
        res.end();
        return;
    }

    next();
}

/**
 * JSON 解析中间件
 */
async function jsonParserMiddleware(req, res, next) {
    if (req.headers["content-type"]?.includes("application/json")) {
        try {
            req.body = await parseJsonBody(req);
        } catch (error) {
            res.statusCode = 400;
            res.setHeader("Content-Type", "application/json");
            res.end(JSON.stringify({ error: error.message }));
            return;
        }
    }
    next();
}

// ============================================================
//                    5. RESTful API 示例
// ============================================================

/**
 * 创建 RESTful API 服务器
 */
function createRestApiServer() {
    // 模拟数据库
    const todos = new Map();
    let nextId = 1;

    const router = new Router();
    const middleware = new MiddlewareManager();

    // 注册中间件
    middleware.use(loggerMiddleware);
    middleware.use(corsMiddleware);
    middleware.use(jsonParserMiddleware);

    // --- API 路由 ---

    // 获取所有 Todo
    router.get("/api/todos", (req, res) => {
        const list = Array.from(todos.values());
        res.setHeader("Content-Type", "application/json");
        res.end(JSON.stringify(list));
    });

    // 获取单个 Todo
    router.get("/api/todo", (req, res) => {
        const id = parseInt(req.query.id);
        const todo = todos.get(id);

        res.setHeader("Content-Type", "application/json");

        if (todo) {
            res.end(JSON.stringify(todo));
        } else {
            res.statusCode = 404;
            res.end(JSON.stringify({ error: "Todo not found" }));
        }
    });

    // 创建 Todo
    router.post("/api/todos", (req, res) => {
        const { title, completed = false } = req.body;

        if (!title) {
            res.statusCode = 400;
            res.setHeader("Content-Type", "application/json");
            res.end(JSON.stringify({ error: "Title is required" }));
            return;
        }

        const todo = {
            id: nextId++,
            title,
            completed,
            createdAt: new Date().toISOString()
        };

        todos.set(todo.id, todo);

        res.statusCode = 201;
        res.setHeader("Content-Type", "application/json");
        res.end(JSON.stringify(todo));
    });

    // 更新 Todo
    router.put("/api/todo", (req, res) => {
        const id = parseInt(req.query.id);
        const todo = todos.get(id);

        res.setHeader("Content-Type", "application/json");

        if (!todo) {
            res.statusCode = 404;
            res.end(JSON.stringify({ error: "Todo not found" }));
            return;
        }

        const { title, completed } = req.body;
        if (title !== undefined) todo.title = title;
        if (completed !== undefined) todo.completed = completed;
        todo.updatedAt = new Date().toISOString();

        res.end(JSON.stringify(todo));
    });

    // 删除 Todo
    router.delete("/api/todo", (req, res) => {
        const id = parseInt(req.query.id);
        const deleted = todos.delete(id);

        res.setHeader("Content-Type", "application/json");

        if (deleted) {
            res.end(JSON.stringify({ success: true }));
        } else {
            res.statusCode = 404;
            res.end(JSON.stringify({ error: "Todo not found" }));
        }
    });

    // 最后注册路由处理
    middleware.use((req, res) => router.handle(req, res));

    // 创建服务器
    const server = http.createServer((req, res) => {
        middleware.execute(req, res);
    });

    return server;
}

// ============================================================
//                    6. 静态文件服务
// ============================================================

/**
 * 静态文件服务器
 */
function createStaticServer(staticDir) {
    // MIME 类型映射
    const mimeTypes = {
        ".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",
        ".txt": "text/plain",
        ".pdf": "application/pdf"
    };

    const server = http.createServer((req, res) => {
        // 解析 URL
        const parsedUrl = url.parse(req.url);
        let pathname = parsedUrl.pathname;

        // 默认页面
        if (pathname === "/") {
            pathname = "/index.html";
        }

        // 防止目录遍历攻击
        const safePath = path.normalize(pathname).replace(/^(\.\.[\/\\])+/, "");
        const filePath = path.join(staticDir, safePath);

        // 检查文件是否在静态目录内
        if (!filePath.startsWith(staticDir)) {
            res.statusCode = 403;
            res.end("Forbidden");
            return;
        }

        // 读取文件
        fs.readFile(filePath, (err, data) => {
            if (err) {
                if (err.code === "ENOENT") {
                    res.statusCode = 404;
                    res.end("Not Found");
                } else {
                    res.statusCode = 500;
                    res.end("Internal Server Error");
                }
                return;
            }

            // 设置 MIME 类型
            const ext = path.extname(filePath).toLowerCase();
            const mimeType = mimeTypes[ext] || "application/octet-stream";

            res.setHeader("Content-Type", mimeType);
            res.setHeader("Content-Length", data.length);
            res.end(data);
        });
    });

    return server;
}

// ============================================================
//                    7. 流式响应
// ============================================================

/**
 * 演示流式响应
 */
function createStreamServer() {
    const server = http.createServer((req, res) => {
        if (req.url === "/stream") {
            // 流式发送数据
            res.setHeader("Content-Type", "text/plain");
            res.setHeader("Transfer-Encoding", "chunked");

            let count = 0;
            const interval = setInterval(() => {
                res.write(`数据块 ${++count}\n`);

                if (count >= 5) {
                    clearInterval(interval);
                    res.end("流结束\n");
                }
            }, 500);

            // 客户端断开连接时清理
            req.on("close", () => {
                clearInterval(interval);
            });
        } else if (req.url === "/file") {
            // 流式发送文件
            const filePath = __filename;
            const stat = fs.statSync(filePath);

            res.setHeader("Content-Type", "text/plain");
            res.setHeader("Content-Length", stat.size);

            const readStream = fs.createReadStream(filePath);
            readStream.pipe(res);
        } else {
            res.setHeader("Content-Type", "text/html; charset=utf-8");
            res.end(`
                <h1>流式响应示例</h1>
                <ul>
                    <li><a href="/stream">分块数据流</a></li>
                    <li><a href="/file">文件流</a></li>
                </ul>
            `);
        }
    });

    return server;
}

// ============================================================
//                    8. HTTP 客户端
// ============================================================

/**
 * 简单的 HTTP GET 请求
 */
function httpGet(urlString) {
    return new Promise((resolve, reject) => {
        const parsedUrl = new URL(urlString);
        const protocol = parsedUrl.protocol === "https:" ? https : http;

        protocol.get(urlString, (res) => {
            let data = "";

            res.on("data", chunk => {
                data += chunk;
            });

            res.on("end", () => {
                resolve({
                    statusCode: res.statusCode,
                    headers: res.headers,
                    body: data
                });
            });
        }).on("error", reject);
    });
}

/**
 * HTTP POST 请求
 */
function httpPost(urlString, body, headers = {}) {
    return new Promise((resolve, reject) => {
        const parsedUrl = new URL(urlString);
        const protocol = parsedUrl.protocol === "https:" ? https : http;

        const options = {
            method: "POST",
            hostname: parsedUrl.hostname,
            port: parsedUrl.port,
            path: parsedUrl.pathname + parsedUrl.search,
            headers: {
                "Content-Type": "application/json",
                "Content-Length": Buffer.byteLength(body),
                ...headers
            }
        };

        const req = protocol.request(options, (res) => {
            let data = "";

            res.on("data", chunk => {
                data += chunk;
            });

            res.on("end", () => {
                resolve({
                    statusCode: res.statusCode,
                    headers: res.headers,
                    body: data
                });
            });
        });

        req.on("error", reject);
        req.write(body);
        req.end();
    });
}

// ============================================================
//                    主程序
// ============================================================

async function main() {
    const PORT = process.env.PORT || 3000;

    // 选择要启动的服务器
    const serverType = process.argv[2] || "rest";

    let server;

    switch (serverType) {
        case "basic":
            server = createBasicServer();
            console.log("启动基础服务器...");
            break;

        case "rest":
            server = createRestApiServer();
            console.log("启动 REST API 服务器...");
            console.log(`
API 端点:
  GET    /api/todos     - 获取所有 Todo
  GET    /api/todo?id=1 - 获取单个 Todo
  POST   /api/todos     - 创建 Todo
  PUT    /api/todo?id=1 - 更新 Todo
  DELETE /api/todo?id=1 - 删除 Todo
`);
            break;

        case "static":
            server = createStaticServer(__dirname);
            console.log("启动静态文件服务器...");
            break;

        case "stream":
            server = createStreamServer();
            console.log("启动流式响应服务器...");
            break;

        default:
            console.log("未知服务器类型。可用: basic, rest, static, stream");
            process.exit(1);
    }

    // 启动服务器
    server.listen(PORT, () => {
        console.log(`服务器运行在 http://localhost:${PORT}`);
        console.log("按 Ctrl+C 停止服务器");
    });

    // 优雅关闭
    process.on("SIGINT", () => {
        console.log("\n正在关闭服务器...");
        server.close(() => {
            console.log("服务器已关闭");
            process.exit(0);
        });
    });
}

// 只在直接运行时启动服务器
if (require.main === module) {
    main();
}

// 导出模块
module.exports = {
    Router,
    MiddlewareManager,
    createBasicServer,
    createRestApiServer,
    createStaticServer,
    createStreamServer,
    httpGet,
    httpPost
};

💬 讨论

使用 GitHub 账号登录后即可参与讨论

基于 MIT 许可发布