MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Node.js HTTP 模块基础与核心概念

2021-05-214.0k 阅读

Node.js HTTP 模块简介

Node.js 作为一个基于 Chrome V8 引擎的 JavaScript 运行时环境,在服务器端开发领域取得了巨大的成功。其中,HTTP 模块是 Node.js 标准库中非常重要的一部分,它提供了构建 HTTP 服务器和客户端的功能。通过使用 HTTP 模块,开发者可以轻松地创建功能强大的 Web 服务器,处理各种 HTTP 请求,并生成相应的 HTTP 响应。

在传统的服务器端开发中,像 Java、Python(如 Django、Flask)等语言框架,虽然功能强大,但往往需要更多的配置和复杂的代码结构。而 Node.js 的 HTTP 模块以其简洁、高效的特点,让开发者能够以较少的代码量实现基本的 HTTP 服务功能。这使得 Node.js 在快速搭建原型、开发轻量级 Web 应用以及处理高并发场景时具有独特的优势。

HTTP 服务器基础

创建简单的 HTTP 服务器

在 Node.js 中创建一个简单的 HTTP 服务器非常容易。首先,我们需要引入内置的 http 模块:

const http = require('http');

然后,我们可以使用 http.createServer() 方法来创建一个服务器实例。这个方法接受一个回调函数作为参数,该回调函数会在每次有 HTTP 请求到达服务器时被调用。回调函数接受两个参数:requestresponserequest 对象包含了关于请求的所有信息,如请求方法、请求头、请求体等;response 对象则用于向客户端发送响应。

以下是一个简单的示例,创建一个服务器,当客户端请求时,返回 "Hello, World!":

const http = require('http');

const server = http.createServer((request, response) => {
    response.statusCode = 200;
    response.setHeader('Content-Type', 'text/plain');
    response.end('Hello, World!');
});

const port = 3000;
server.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

在上述代码中:

  1. http.createServer((request, response) => {... }) 创建了一个 HTTP 服务器实例,并定义了请求处理逻辑。
  2. response.statusCode = 200; 设置响应状态码为 200,表示请求成功。
  3. response.setHeader('Content-Type', 'text/plain'); 设置响应头,这里指定响应内容类型为纯文本。
  4. response.end('Hello, World!'); 向客户端发送响应体,并结束本次响应。
  5. server.listen(port, () => {... }) 让服务器监听指定的端口(这里是 3000 端口),并在服务器启动成功时打印一条日志。

请求处理

  1. 请求方法 通过 request 对象的 method 属性可以获取客户端的请求方法,常见的请求方法有 GETPOSTPUTDELETE 等。我们可以根据不同的请求方法来执行不同的逻辑。例如:
const http = require('http');

const server = http.createServer((request, response) => {
    if (request.method === 'GET') {
        response.statusCode = 200;
        response.setHeader('Content-Type', 'text/plain');
        response.end('This is a GET request');
    } else if (request.method === 'POST') {
        response.statusCode = 200;
        response.setHeader('Content-Type', 'text/plain');
        response.end('This is a POST request');
    }
});

const port = 3000;
server.listen(port, () => {
    console.log(`Server running on port ${port}`);
});
  1. 请求头 request 对象的 headers 属性是一个包含所有请求头信息的对象。例如,要获取 User - Agent 请求头,可以这样做:
const http = require('http');

const server = http.createServer((request, response) => {
    const userAgent = request.headers['user - agent'];
    response.statusCode = 200;
    response.setHeader('Content-Type', 'text/plain');
    response.end(`Your user - agent is: ${userAgent}`);
});

const port = 3000;
server.listen(port, () => {
    console.log(`Server running on port ${port}`);
});
  1. 请求体 对于 POST 等带有请求体的请求,需要通过 request 对象的 data 事件和 end 事件来处理。data 事件会在有新的请求体数据到达时触发,end 事件则在所有请求体数据接收完毕时触发。例如,处理一个简单的 POST 请求体(假设请求体是 URL 编码格式的数据):
const http = require('http');
const querystring = require('querystring');

const server = http.createServer((request, response) => {
    let body = '';
    request.on('data', (chunk) => {
        body += chunk.toString();
    });
    request.on('end', () => {
        const data = querystring.parse(body);
        response.statusCode = 200;
        response.setHeader('Content-Type', 'text/plain');
        response.end(`Received data: ${JSON.stringify(data)}`);
    });
});

const port = 3000;
server.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

在上述代码中,首先定义了一个变量 body 来存储请求体数据。当 data 事件触发时,将接收到的新数据块追加到 body 中。当 end 事件触发时,使用 querystring.parse 方法将 URL 编码格式的请求体数据解析为对象,并返回给客户端。

HTTP 响应处理

设置响应头

在 Node.js 的 HTTP 模块中,通过 response 对象的 setHeader 方法来设置响应头。响应头包含了关于响应的各种元信息,如内容类型、缓存控制、字符编码等。例如,设置一个自定义的响应头:

const http = require('http');

const server = http.createServer((request, response) => {
    response.setHeader('X - Custom - Header', 'This is a custom header');
    response.statusCode = 200;
    response.setHeader('Content-Type', 'text/plain');
    response.end('Response with custom header');
});

const port = 3000;
server.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

在实际应用中,常见的响应头设置包括:

  1. Content - Type:用于指定响应内容的类型,如 text/html 表示 HTML 文档,application/json 表示 JSON 数据等。例如:
response.setHeader('Content-Type', 'application/json');
response.end(JSON.stringify({ message: 'This is JSON data' }));
  1. Cache - Control:用于控制缓存行为。例如,设置不缓存响应:
response.setHeader('Cache - Control', 'no - cache, no - store, must - revalidate');
  1. Content - Length:用于指定响应体的长度。虽然 Node.js 会在某些情况下自动设置这个头,但在一些场景下手动设置可以提高性能和正确性。例如:
const body = 'This is the response body';
response.setHeader('Content - Length', body.length);
response.end(body);

响应状态码

响应状态码是 HTTP 响应的重要组成部分,它表示了请求处理的结果。在 Node.js 中,通过设置 response.statusCode 来指定响应状态码。常见的状态码有:

  1. 200 OK:表示请求成功,这是最常见的成功状态码。例如:
response.statusCode = 200;
response.end('Request successful');
  1. 404 Not Found:表示请求的资源不存在。例如:
response.statusCode = 404;
response.end('The requested resource was not found');
  1. 500 Internal Server Error:表示服务器内部发生错误。例如:
try {
    // 可能会抛出错误的代码
    throw new Error('Internal error');
} catch (error) {
    response.statusCode = 500;
    response.end('Internal server error occurred');
}

发送响应体

  1. 文本响应体 发送纯文本响应体非常简单,直接使用 response.end 方法并传入文本内容即可,如前面示例中的 response.end('Hello, World!')
  2. JSON 响应体 要发送 JSON 格式的响应体,首先要设置 Content - Typeapplication/json,然后将 JavaScript 对象转换为 JSON 字符串再发送。例如:
const http = require('http');

const server = http.createServer((request, response) => {
    const data = { name: 'John', age: 30 };
    response.statusCode = 200;
    response.setHeader('Content-Type', 'application/json');
    response.end(JSON.stringify(data));
});

const port = 3000;
server.listen(port, () => {
    console.log(`Server running on port ${port}`);
});
  1. HTML 响应体 发送 HTML 响应体时,设置 Content - Typetext/html,然后将 HTML 字符串作为响应体发送。例如:
const http = require('http');

const server = http.createServer((request, response) => {
    const html = `
        <html>
            <head>
                <title>Node.js HTTP Server</title>
            </head>
            <body>
                <h1>Welcome to my Node.js server</h1>
            </body>
        </html>
    `;
    response.statusCode = 200;
    response.setHeader('Content-Type', 'text/html');
    response.end(html);
});

const port = 3000;
server.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

HTTP 客户端基础

使用 http.request 创建 HTTP 客户端请求

在 Node.js 中,http.request 方法用于创建一个 HTTP 客户端请求。它接受一个配置对象作为参数,该对象可以包含请求的 URL、方法、请求头、认证信息等。以下是一个简单的 GET 请求示例:

const http = require('http');

const options = {
    hostname: 'localhost',
    port: 3000,
    path: '/',
    method: 'GET'
};

const req = http.request(options, (res) => {
    let data = '';
    res.on('data', (chunk) => {
        data += chunk.toString();
    });
    res.on('end', () => {
        console.log('Response received:', data);
    });
});

req.end();

在上述代码中:

  1. http.request(options, (res) => {... }) 创建了一个 HTTP 请求,options 对象指定了请求的目标主机(localhost)、端口(3000)、路径(/)和方法(GET)。
  2. 回调函数 (res) => {... } 用于处理服务器的响应。res 对象类似于服务器端的 request 对象,通过 data 事件和 end 事件来获取响应数据。
  3. req.end() 方法表示请求参数和请求体(这里没有请求体)已经发送完毕,通知服务器可以开始处理请求。

发送带有请求体的请求

对于 POST 等需要发送请求体的请求,除了设置正确的请求方法外,还需要将请求体数据写入 req 对象。以下是一个 POST 请求示例,假设服务器端能够处理 URL 编码格式的请求体:

const http = require('http');
const querystring = require('querystring');

const options = {
    hostname: 'localhost',
    port: 3000,
    path: '/',
    method: 'POST',
    headers: {
        'Content - Type': 'application/x - www - form - urlencoded'
    }
};

const data = { username: 'user1', password: 'pass1' };
const body = querystring.stringify(data);

const req = http.request(options, (res) => {
    let responseData = '';
    res.on('data', (chunk) => {
        responseData += chunk.toString();
    });
    res.on('end', () => {
        console.log('Server response:', responseData);
    });
});

req.write(body);
req.end();

在这个示例中:

  1. options 对象中的 method 设置为 POST,并设置了 Content - Typeapplication/x - www - form - urlencoded,表示请求体是 URL 编码格式的数据。
  2. 使用 querystring.stringify 方法将 JavaScript 对象 data 转换为 URL 编码格式的字符串 body
  3. 通过 req.write(body) 方法将请求体数据写入请求,最后使用 req.end() 结束请求。

HTTP 模块高级特性

管道(Piping)

管道是 Node.js 流(Stream)的一个重要特性,在 HTTP 模块中也有广泛应用。通过管道,可以将一个可读流的数据直接传输到一个可写流,而不需要在内存中缓冲大量数据。这在处理大文件上传、下载等场景时非常有用。例如,将一个 HTTP 服务器接收到的请求体数据直接写入到一个文件中:

const http = require('http');
const fs = require('fs');

const server = http.createServer((request, response) => {
    const writeStream = fs.createWriteStream('uploaded_file.txt');
    request.pipe(writeStream);
    writeStream.on('finish', () => {
        response.statusCode = 200;
        response.end('File uploaded successfully');
    });
});

const port = 3000;
server.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

在上述代码中:

  1. fs.createWriteStream('uploaded_file.txt') 创建了一个可写流,用于将数据写入到 uploaded_file.txt 文件中。
  2. request.pipe(writeStream)request(可读流)的数据直接传输到 writeStream(可写流),实现了数据的高效传输。
  3. writeStream 完成写入操作(finish 事件触发)时,向客户端发送成功响应。

代理服务器

Node.js 的 HTTP 模块可以用来构建代理服务器。代理服务器位于客户端和目标服务器之间,它接收客户端的请求,然后将请求转发给目标服务器,并将目标服务器的响应返回给客户端。以下是一个简单的代理服务器示例:

const http = require('http');

const proxyServer = http.createServer((request, response) => {
    const options = {
        hostname: 'example.com',
        port: 80,
        path: request.url,
        method: request.method,
        headers: request.headers
    };

    const proxyReq = http.request(options, (proxyRes) => {
        response.writeHead(proxyRes.statusCode, proxyRes.headers);
        proxyRes.pipe(response);
    });

    request.pipe(proxyReq);
});

const port = 3000;
proxyServer.listen(port, () => {
    console.log(`Proxy server running on port ${port}`);
});

在这个示例中:

  1. 代理服务器创建了一个新的 http.request,将客户端请求的 urlmethodheaders 传递给目标服务器(这里假设为 example.com)。
  2. 当代理服务器接收到目标服务器的响应(proxyRes)时,使用 response.writeHead 方法将目标服务器的状态码和响应头传递给客户端,并通过 proxyRes.pipe(response) 将目标服务器的响应数据直接传输给客户端。
  3. 同时,通过 request.pipe(proxyReq) 将客户端的请求数据直接传输给目标服务器,实现了代理功能。

HTTPS 支持

虽然 Node.js 的 http 模块主要用于 HTTP 协议,但通过 https 模块可以很方便地支持 HTTPS 协议。https 模块与 http 模块的使用方式类似,不过需要提供 SSL/TLS 证书和私钥。以下是一个简单的 HTTPS 服务器示例:

const https = require('https');
const fs = require('fs');

const options = {
    key: fs.readFileSync('privatekey.pem'),
    cert: fs.readFileSync('certificate.pem')
};

const server = https.createServer(options, (request, response) => {
    response.statusCode = 200;
    response.setHeader('Content-Type', 'text/plain');
    response.end('This is an HTTPS server');
});

const port = 443;
server.listen(port, () => {
    console.log(`HTTPS server running on port ${port}`);
});

在上述代码中:

  1. options 对象中指定了私钥文件(privatekey.pem)和证书文件(certificate.pem)的路径。这些文件可以通过 OpenSSL 等工具生成。
  2. https.createServer(options, (request, response) => {... }) 创建了一个 HTTPS 服务器实例,处理请求的逻辑与 HTTP 服务器类似。
  3. 服务器监听在 443 端口(HTTPS 的默认端口)。

性能优化与最佳实践

连接池

在处理大量 HTTP 请求时,频繁地创建和销毁 TCP 连接会消耗大量的系统资源。连接池可以解决这个问题,它预先创建一定数量的连接,并在请求需要时复用这些连接。虽然 Node.js 的 HTTP 模块没有内置的连接池实现,但可以通过一些第三方库(如 http - proxy - agent 等)来实现连接池功能。例如,使用 http - proxy - agent 库来创建一个带有连接池的 HTTP 客户端:

const http = require('http');
const HttpProxyAgent = require('http - proxy - agent');

const agent = new HttpProxyAgent({
    host: 'proxy.example.com',
    port: 8080,
    maxSockets: 10
});

const options = {
    hostname: 'example.com',
    port: 80,
    path: '/',
    method: 'GET',
    agent: agent
};

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

req.end();

在上述代码中:

  1. HttpProxyAgent 创建了一个代理连接池,maxSockets 设置了最大连接数为 10。
  2. http.requestoptions 中指定 agent 为创建的连接池实例,这样请求就会复用连接池中的连接。

缓存策略

合理的缓存策略可以显著提高 Web 应用的性能。在 HTTP 响应头中,Cache - ControlETag 等字段可以用于控制缓存行为。例如,对于不经常变化的静态资源,可以设置较长的缓存时间:

const http = require('http');

const server = http.createServer((request, response) => {
    if (request.url === '/static/file.js') {
        response.statusCode = 200;
        response.setHeader('Content-Type', 'application/javascript');
        response.setHeader('Cache - Control','max - age = 31536000'); // 缓存一年
        response.end('// 静态文件内容');
    } else {
        response.statusCode = 200;
        response.setHeader('Content-Type', 'text/plain');
        response.end('Other content');
    }
});

const port = 3000;
server.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

对于动态资源,可以使用 ETag 来实现条件缓存。ETag 是资源的唯一标识符,服务器可以在响应头中返回 ETag,客户端下次请求时可以在请求头中带上 If - None - Match,如果 If - None - Match 的值与服务器端的 ETag 匹配,服务器可以返回 304 Not Modified 状态码,客户端则可以使用本地缓存的资源。

错误处理

在 HTTP 模块开发中,良好的错误处理非常重要。对于服务器端,可能会遇到各种错误,如监听端口失败、请求处理过程中抛出异常等。对于客户端,可能会遇到连接失败、请求超时等错误。以下是一些常见的错误处理示例:

  1. 服务器端监听错误处理
const http = require('http');

const server = http.createServer((request, response) => {
    // 请求处理逻辑
});

server.on('error', (error) => {
    if (error.code === 'EADDRINUSE') {
        console.log('Port is already in use. Try another port.');
    } else {
        console.error('Server error:', error);
    }
});

const port = 3000;
server.listen(port, () => {
    console.log(`Server running on port ${port}`);
});
  1. 客户端请求错误处理
const http = require('http');

const options = {
    hostname: 'nonexistent.example.com',
    port: 80,
    path: '/',
    method: 'GET'
};

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

req.on('error', (error) => {
    console.error('Request error:', error);
});

req.end();

在上述示例中,服务器端通过监听 error 事件来处理监听端口失败等错误,客户端通过监听 req 对象的 error 事件来处理请求过程中的错误。

与其他框架和库的结合使用

Express 框架

Express 是 Node.js 中最流行的 Web 应用框架之一,它基于 HTTP 模块构建,提供了更简洁、更强大的路由、中间件等功能。以下是一个简单的 Express 应用示例:

const express = require('express');
const app = express();

app.get('/', (req, res) => {
    res.send('Hello from Express');
});

app.listen(3000, () => {
    console.log('Express app running on port 3000');
});

在这个示例中,Express 框架简化了路由定义和响应处理。app.get 用于定义一个 GET 请求的路由,res.send 方法用于发送响应,它会自动设置合适的响应头和状态码。Express 还支持中间件功能,可以在请求处理的不同阶段执行一些通用的逻辑,如日志记录、身份验证等。例如:

const express = require('express');
const app = express();

app.use((req, res, next) => {
    console.log(`Received ${req.method} request to ${req.url}`);
    next();
});

app.get('/', (req, res) => {
    res.send('Hello from Express with middleware');
});

app.listen(3000, () => {
    console.log('Express app running on port 3000');
});

在上述代码中,app.use 定义了一个中间件,它会在每个请求到达时打印请求方法和 URL,然后通过 next() 将控制权传递给下一个中间件或路由处理函数。

Koa 框架

Koa 是另一个流行的 Node.js Web 框架,它同样基于 HTTP 模块,以其轻量级和优雅的异步处理方式而受到青睐。Koa 使用 async/await 语法来处理异步操作,使代码更加简洁易读。以下是一个简单的 Koa 应用示例:

const Koa = require('koa');
const app = new Koa();

app.use(async (ctx) => {
    ctx.body = 'Hello from Koa';
});

app.listen(3000, () => {
    console.log('Koa app running on port 3000');
});

在 Koa 中,ctx 对象包含了 requestresponse 的功能,通过设置 ctx.body 来发送响应。Koa 也支持中间件,并且中间件的执行流程更加直观。例如:

const Koa = require('koa');
const app = new Koa();

app.use(async (ctx, next) => {
    console.log('Before route');
    await next();
    console.log('After route');
});

app.use(async (ctx) => {
    ctx.body = 'Hello from Koa with middleware';
});

app.listen(3000, () => {
    console.log('Koa app running on port 3000');
});

在上述代码中,第一个中间件打印 Before route,然后通过 await next() 调用下一个中间件或路由处理函数,当后续处理完成后,再打印 After route

第三方 HTTP 客户端库

除了使用 Node.js 内置的 HTTP 客户端功能外,还有一些优秀的第三方 HTTP 客户端库可供选择,如 axiossuperagent。这些库提供了更丰富的功能和更简洁的 API。例如,使用 axios 发送一个 GET 请求:

const axios = require('axios');

axios.get('http://localhost:3000')
   .then((response) => {
        console.log('Response data:', response.data);
    })
   .catch((error) => {
        console.error('Request error:', error);
    });

axios 支持 Promise 链式调用,并且在处理响应和错误时非常方便。它还提供了许多配置选项,如设置请求头、超时时间等,使得 HTTP 客户端开发更加灵活。

总结

Node.js 的 HTTP 模块是构建 Web 服务器和客户端的基础,通过深入理解其基础概念和高级特性,开发者可以创建高效、稳定的 Web 应用。从简单的服务器搭建、请求响应处理,到复杂的代理服务器、HTTPS 支持,再到与各种框架和库的结合使用,HTTP 模块为 Node.js 的服务器端开发提供了广阔的空间。同时,合理运用性能优化和最佳实践,能够进一步提升应用的性能和稳定性。无论是开发小型的 Web 服务,还是大型的分布式应用,Node.js 的 HTTP 模块都能发挥重要作用。在实际开发中,开发者可以根据具体需求,灵活选择和运用相关技术,打造出满足业务需求的优秀 Web 应用。