最小 HTTP 服务器

import http from 'http';

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

server.listen(8080, () => {
    console.log('Server on http://localhost:8080');
});

req(IncomingMessage)

req.method              // 'GET' / 'POST' / ...
req.url                 // '/api/users?id=1'
req.headers             // 所有 header 对象
req.headers['user-agent']
req.httpVersion          // '1.1'
req.socket.remoteAddress // 客户端 IP

res(ServerResponse)

res.writeHead(200, { 'Content-Type': 'application/json' });
res.write('part 1');
res.write('part 2');
res.end('done');             // 必须 end()

// 简写
res.setHeader('X-Custom', 'value');
res.statusCode = 404;
res.end('Not Found');

// 重定向
res.writeHead(302, { Location: '/new-path' });
res.end();

解析 URL + 查询参数

const url = new URL(req.url, `http://${req.headers.host}`);
url.pathname            // '/api/users'
url.searchParams.get('id')   // '1'
url.searchParams.getAll('tag')   // ['a', 'b'](多个)

解析请求体

// 接收 JSON
server.on('request', async (req, res) => {
    if (req.method === 'POST') {
        let body = '';
        for await (const chunk of req) {
            body += chunk;
        }
        const data = JSON.parse(body);
        // ...
    }
});

或用现代 req.json()(在某些框架里)——原生 http 模块没这个。

路由(手动)

const server = http.createServer((req, res) => {
    if (req.url === '/' && req.method === 'GET') {
        return res.end('Home');
    }
    if (req.url === '/users' && req.method === 'GET') {
        res.setHeader('Content-Type', 'application/json');
        return res.end(JSON.stringify(users));
    }
    res.statusCode = 404;
    res.end('Not Found');
});

生产应用用 Express / Fastify——见 27-28。

静态文件

import { createReadStream, statSync } from 'fs';
import { extname, join } from 'path';

const types = {
    '.html': 'text/html',
    '.css': 'text/css',
    '.js': 'application/javascript',
    '.json': 'application/json',
    '.png': 'image/png',
};

http.createServer((req, res) => {
    const file = join('./public', req.url === '/' ? '/index.html' : req.url);
    try {
        const ext = extname(file);
        res.setHeader('Content-Type', types[ext] || 'application/octet-stream');
        createReadStream(file).pipe(res);
    } catch {
        res.statusCode = 404;
        res.end();
    }
}).listen(8080);

HTTPS

import https from 'https';
import { readFileSync } from 'fs';

https.createServer({
    key: readFileSync('key.pem'),
    cert: readFileSync('cert.pem'),
}, (req, res) => {
    res.end('Secure');
}).listen(443);

实战 90% 用 Nginx / Caddy 在前面终止 HTTPS,Node 后端只跑 HTTP。

优雅关闭

const server = http.createServer(...).listen(8080);

process.on('SIGTERM', () => {
    console.log('SIGTERM received, closing...');
    server.close(() => {
        console.log('Server closed');
        process.exit(0);
    });
    setTimeout(() => process.exit(1), 10000);    // 强制 10 秒后退
});

错误处理

server.on('error', err => {
    if (err.code === 'EADDRINUSE') {
        console.error('Port already in use');
    } else {
        console.error(err);
    }
});

server.on('clientError', (err, socket) => {
    socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});

实战:常用 status code

200  OK
201  Created
204  No Content
301  Moved Permanently(永久重定向)
302  Found(临时重定向)
304  Not Modified(用了缓存)
400  Bad Request
401  Unauthorized(没登录)
403  Forbidden(已登录但无权限)
404  Not Found
429  Too Many Requests(限流)
500  Internal Server Error
502  Bad Gateway(上游挂)
503  Service Unavailable
504  Gateway Timeout

  • 必须 res.end()——不然请求挂起到超时
  • header 必须在 write/end 之前——writeHead 之后改 header 没用
  • 大响应res.end(bigString)——内存爆;用流 pipe
  • 同步阻塞 = 全站慢——别在请求处理里跑 CPU 密集任务

下一篇:HTTP 客户端。