从零开始学习Node.js的HTTP模块

从零开始学习Node.js的HTTP模块

注意,非常重要

  1. 本教程基于ESM与TypeScript
  2. 本人撰写教程时Node.js版本为v24.3.0,建议你的Node.js版本为v20+,部分特性(如WebSocket)需v22.5+

声明

本文由我个人参考Node.js官方文档撰写,未使用AI生成(除极少数地方外)。

背景

Node.js有很多方便的网络框架,如Koa、Express、Fastify等。但原生模块我之前从来没有了解过。
Node.js的官方文档中,罗列出了所有的类,类中的方法、函数、属性,而且是按字母顺序排序的,对初学者不够友好。
于是我以http.createServer()为切入点,来学习Node.js的HTTP模块。
本文以我学习的顺序来写,介绍了我个人认为Node.js Web服务端开发中比较重要的API。

Hello World

一个简单的HTTP服务器

import http from "node:http";

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

server.listen(3000, () => {
    console.log("server is running at http://localhost:3000");
});

http.createServer

http.createServer()用于创建HTTP服务器。其接收两个可选参数,optionsrequestListener,返回http.Server实例。

optionshttp.ServerOptions类型,用于配置服务器的行为。
最常用的 5 个选项及场景

选项类型默认值使用场景示例
keepAlivebooleanfalse高并发服务优化TCP连接{ keepAlive: true }
keepAliveTimeoutnumber5000防止空闲连接占用资源{ keepAliveTimeout: 10000 }
headersTimeoutnumber60000防御慢速HTTP攻击{ headersTimeout: 30000 }
maxHeaderSizenumber16384处理超大Cookie或头信息{ maxHeaderSize: 32768 }
noDelaybooleantrue实时通信禁用Nagle算法{ noDelay: true }

在代码中我们使用如下方式来指定选项

const options: http.ServerOptions = {
    keepAlive: true,
    keepAliveTimeout: 1000,
    headersTimeout: 30000,
    maxHeaderSize: 32768,
    requestTimeout: 60000,
};

const server = http.createServer(options,(req, res) => {
    res.setHeader("X-Content-Type-Options", "nosniff");
    res.writeHead(200, {
        "Content-Type": "text/plain",
    });
    res.end("hello world");
});

接下来是第二个参数requestListener,它是(req: http.IncomingMessage, res: http.ServerResponse) => void类型,是一个自动添加到 “request” 事件中的函数,用于处理客户端请求。

以下两段代码是等价的

import http from 'node:http';

// Create a local server to receive data from
const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({
    data: 'Hello World!',
  }));
});

server.listen(8000);
import http from 'node:http';

// Create a local server to receive data from
const server = http.createServer();

// Listen to the request event
server.on('request', (request, res) => {
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({
    data: 'Hello World!',
  }));
});

server.listen(8000);

http.Server类

上面已经提到了http.Server实例的创建方式,下面我们对照官方文档来看看http.Server类的事件、函数、属性。
在这里插入图片描述

http.Server类的事件

request事件

首先最常用的就是request事件,用于处理客户端请求,我们在上面已经遇到过了。
每次客户端请求时,request事件就会被触发一次。
request事件的回调函数接收requestresponse两个参数,分别是http.IncomingMessagehttp.ServerResponse类型。

listening事件

listening事件,当服务器在调用server.listen()后被绑定时发出。用于通知服务器已经开始监听指定的端口。
listening事件并不是http.Server类的事件,而是被http.Server类继承的net.Server类中的事件。

import http from "node:http";

const server = http.createServer();

server.on("listening", () => {
    console.log("server is running at http://localhost:3000");
});

server.listen(3000);
close事件

close事件,当服务器调用server.close()方法关闭时发出。用于通知服务器已经关闭。

import http from "node:http";

const server = http.createServer();

server.on("close", () => {
    console.log("server is closed");
});

server.listen(3000);
server.close();

http.Server类的方法

listen()

启动 HTTP 服务器侦听连接,继承自net.Server类的listen方法。

close()

停止服务器接收新连接,并关闭所有连接到此服务器的连接,这些连接既不发送请求也不等待响应

closeAllConnections()

强制关闭所有连接的方式,建议在调用server.close之后调用此方法

setTimeout()

设置服务器的超时时间。传入两个可选参数,第一个参数为超时时间,单位为毫秒。默认值为0。第二个参数为回调函数,当超时时间到达时调用。

// 设置所有连接超时(毫秒)
server.setTimeout(30000); // 30秒

// 带回调的超时处理
server.setTimeout(10000, (socket) => {
  console.warn(`Socket timed out: ${socket.remoteAddress}`);
  socket.end('HTTP/1.1 408 Request Timeout\r\n\r\n');
});

http.Server类的属性

  • server.listening <boolean> 表示服务器是否在侦听连接
  • server.timeout <number> 表示服务器的超时时间,单位为毫秒。默认值为0。

http.ServerResponse类

之前在处理客户端请求时,我们使用了一个参数为req <http.IncomingMessage>res <http.ServerResponse>的回调函数,下面我们来看看http.ServerResponse类的属性和方法,了解服务器如何响应客户端的请求。

end([data[, encoding]][, callback])

response.end()方法向服务器发出信号,表明所有响应报头和正文都已发送。
该方法有三个参数

  • data <string> | <Buffer> | <Uint8Array>
  • encoding <string>
  • callback <Function>
    如果指定了 data,其效果类似于先调用response.write (data,encoding),然后调用response.end (callback)
    如果指定了回调函数,它将在 response 流完成时被调用。

写入响应头的方法 setHeader、setHeaders、writeHead

  • setHeader() 写入一个响应头
  • setHeaders() 写入多个响应头
  • writeHead() 写入响应状态码和响应头

注意:setHeaders继承自http.OutgoingMessage类,添加自v19.6.0, v18.15.0
如果代码报错,请确保你使用的是新版Node.js

import http from "node:http";

const server = http.createServer();

server.on("request", (req, res) => {
    res.setHeader("Content-Type", "application/json; charset=utf-8");
    res.setHeaders(new Map([["foo", "bar"]]));
    res.writeHead(200, {
        name: "cxx",
    });
    res.write(JSON.stringify({name: "cxx"}));
    res.end();
});

server.listen(3000, () => {
    console.log("server is running at http://localhost:3000");
});

http.IncomingMessage类

上述我们已经提到req(客户端请求信息)的类型是http.IncomingMessage类的一个实例,下面我们详细了解下这个类

http.IncomingMessage类的属性

  • req.url <string> 表示请求的URL
  • req.method <string> 表示请求的方法 如'GET'
  • req.headers <Object> 表示请求的头信息 如{'user-agent':'curl/7.22.0'}
  • req.httpVersion <string> 表示HTTP版本 如'1.1'
  • req.statusCode <number> 表示响应状态码 如 404
  • req.statusMessage <string> 表示响应状态消息 如Internal Server Error
  • req.socket <stream.Duplex> 表示底层的TCP socket

http.IncomingMessage类的方法

  • req.setTimeout([msecs][, callback]) 设置请求的超时时间
  • req.on(eventName, listener) 监听请求事件
  • req.pipe(destination[, options]) 管道操作

使用req.method和req.url实现路由

http库中是没有路由的,需要我们手动实现路由功能。
建议简单了解即可,常用的Web框架中都已经实现了路由功能。
以下代码实现了原生模块中的路由逻辑,由DeepSeek生成。

import http from "node:http";

// 路由匹配器
const matchRoute = (pattern: string, path: string) => {
    const patternParts = pattern.split('/');
    const pathParts = path.split('/');
    
    if (patternParts.length !== pathParts.length) return null;
    
    const params: Record<string, string> = {};
    
    for (let i = 0; i < patternParts.length; i++) {
        if (patternParts[i].startsWith(':')) {
            const paramName = patternParts[i].substring(1);
            params[paramName] = pathParts[i];
        } else if (patternParts[i] !== pathParts[i]) {
            return null;
        }
    }
    
    return { params };
};

// 路由配置
const routes = [
    {
        method: 'GET',
        path: '/users/:id',
        handler: (req: any, res: any, params: any) => {
            res.end(JSON.stringify({ userId: params.id }));
        }
    },
    {
        method: 'GET',
        path: '/posts/:category/:postId',
        handler: (req, res, params) => {
            res.end(JSON.stringify(params));
        }
    }
];

const server = http.createServer((req, res) => {
    const method = req.method!;
    const url = req.url!;
    
    res.setHeader("Content-Type", "application/json");
    
    let matched = false;
    
    for (const route of routes) {
        const match = matchRoute(route.path, url);
        if (match && route.method === method) {
            route.handler(req, res, match.params);
            matched = true;
            break;
        }
    }
    
    if (!matched) {
        res.statusCode = 404;
        res.end(JSON.stringify({ error: "未找到路由" }));
    }
});

server.listen(3000);

其它需要了解的内容

http.METHODS

http.METHODS是一个数组,包含了所有的HTTP方法。

console.log(http.METHODS);

输出结果

[
  'ACL',        'BIND',        'CHECKOUT',
  'CONNECT',    'COPY',        'DELETE',
  'GET',        'HEAD',        'LINK',
  'LOCK',       'M-SEARCH',    'MERGE',
  'MKACTIVITY', 'MKCALENDAR',  'MKCOL',
  'MOVE',       'NOTIFY',      'OPTIONS',
  'PATCH',      'POST',        'PROPFIND',
  'PROPPATCH',  'PURGE',       'PUT',
  'QUERY',      'REBIND',      'REPORT',
  'SEARCH',     'SOURCE',      'SUBSCRIBE',
  'TRACE',      'UNBIND',      'UNLINK',
  'UNLOCK',     'UNSUBSCRIBE'
]

http.STATUS_CODES

http.STATUS_CODES是一个对象,包含了HTTP状态码对应的状态消息。

console.log(http.STATUS_CODES[200]); // OK
console.log(http.STATUS_CODES["200"]); // 也可以传入字符串

以下是http.STATUS_CODES的输出结果

{
  '100': 'Continue',
  '101': 'Switching Protocols',
  '102': 'Processing',
  '103': 'Early Hints',
  '200': 'OK',
  '201': 'Created',
  '202': 'Accepted',
  '203': 'Non-Authoritative Information',
  '204': 'No Content',
  '205': 'Reset Content',
  '206': 'Partial Content',
  '207': 'Multi-Status',
  '208': 'Already Reported',
  '226': 'IM Used',
  '300': 'Multiple Choices',
  '301': 'Moved Permanently',
  '302': 'Found',
  '303': 'See Other',
  '304': 'Not Modified',
  '305': 'Use Proxy',
  '307': 'Temporary Redirect',
  '308': 'Permanent Redirect',
  '400': 'Bad Request',
  '401': 'Unauthorized',
  '402': 'Payment Required',
  '403': 'Forbidden',
  '404': 'Not Found',
  '405': 'Method Not Allowed',
  '406': 'Not Acceptable',
  '407': 'Proxy Authentication Required',
  '408': 'Request Timeout',
  '409': 'Conflict',
  '410': 'Gone',
  '411': 'Length Required',
  '412': 'Precondition Failed',
  '413': 'Payload Too Large',
  '414': 'URI Too Long',
  '415': 'Unsupported Media Type',
  '416': 'Range Not Satisfiable',
  '417': 'Expectation Failed',
  '418': "I'm a Teapot",
  '421': 'Misdirected Request',
  '422': 'Unprocessable Entity',
  '423': 'Locked',
  '424': 'Failed Dependency',
  '425': 'Too Early',
  '426': 'Upgrade Required',
  '428': 'Precondition Required',
  '429': 'Too Many Requests',
  '431': 'Request Header Fields Too Large',
  '451': 'Unavailable For Legal Reasons',
  '500': 'Internal Server Error',
  '501': 'Not Implemented',
  '502': 'Bad Gateway',
  '503': 'Service Unavailable',
  '504': 'Gateway Timeout',
  '505': 'HTTP Version Not Supported',
  '506': 'Variant Also Negotiates',
  '507': 'Insufficient Storage',
  '508': 'Loop Detected',
  '509': 'Bandwidth Limit Exceeded',
  '510': 'Not Extended',
  '511': 'Network Authentication Required'
}

http.OutgoingMessage类

http.OutgoingMessage类是ClientRequestServerResponse的基类,定义了一些通用的方法和属性。
实际开发中,更多的是使用它的两个子类。

http模块中发送客户端请求的类与方法

之前我完全没有介绍过如何使用http模块向服务端发送请求。这是因为目前,Node.js v24中的fetch()已经完全实现了和浏览器兼容的API,已完全具备替代http(s).request()的能力,且代码更简洁现代。
为了教程的完整性,这里做简要介绍。

注:fetch()API的介绍不在本教程的范围内。请自行查阅MDN文档

使用http.get()获取网页内容

http.get()方法用于向服务端发送GET请求。

import http from "node:http";

http.get("http://www.baidu.com/", (res: http.IncomingMessage) => {
    console.log(res.statusCode);
    console.log(res.statusMessage);
    // 输出响应体(HTML)
    res.on("data", (chunk) => {
        console.log(chunk.toString());
    });
});

使用http.request()发送其它请求

http.request()方法用于向服务端发送请求,包含GET内的所有方法。
以下内容取自官方文档的示例代码

import http from 'node:http';
import { Buffer } from 'node:buffer';

const postData = JSON.stringify({
  'msg': 'Hello World!',
});

const options = {
  hostname: 'www.google.com',
  port: 80,
  path: '/upload',
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Content-Length': Buffer.byteLength(postData),
  },
};

const req = http.request(options, (res) => {
  console.log(`STATUS: ${res.statusCode}`);
  console.log(`HEADERS: ${JSON.stringify(res.headers)}`);
  res.setEncoding('utf8');
  res.on('data', (chunk) => {
    console.log(`BODY: ${chunk}`);
  });
  res.on('end', () => {
    console.log('No more data in response.');
  });
});

req.on('error', (e) => {
  console.error(`problem with request: ${e.message}`);
});

// Write data to request body
req.write(postData);
req.end();

也可以直接指定URL

const options = new URL('http://abc:xyz@example.com');

const req = http.request(options, (res) => {
  // ...
});

http.ClientRequest类

http.ClientRequest类的实例由http.request()方法内部创建。它表示一个正在进行中的请求,其首部已经排队。

http.Agent类

负责管理 HTTP 客户端的连接持久性和重用。它维护一个给定主机和端口的待处理请求队列,为每个请求重用一个套接字连接,直到队列为空。此时,套接字要么被销毁,要么被放入池中,以便再次用于对同一主机和端口的请求。是销毁还是池化取决于 keepAlive 选项。

DLC:http.WebSocket类

http.WebSocket添加于Node.js v22.5,用于WebSocket协议的客户端。
以下示例取自MDN文档

// Create WebSocket connection.
const socket = new WebSocket("ws://localhost:8080");

// Connection opened
socket.addEventListener("open", (event) => {
  socket.send("Hello Server!");
});

// Listen for messages
socket.addEventListener("message", (event) => {
  console.log("Message from server ", event.data);
});

注意:1. 本代码只是客户端,需要事前在本地的8080端口上运行一个WebSocket服务器。
Node.js 并未内置这一功能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值