【6】深入学习http模块(万字)-Nodejs开发入门

深入学习http模块

  • 前言
  • http
    • 一个Web服务器
      • 项目创建
      • 代码运行
      • 代码解析
    • Server
      • 属性:keepAlive
      • 属性:keepAliveTimeout
      • 属性:maxHeaderSize
      • 属性:requestTimeout
      • 属性:maxRequestsPerSocket
      • 方法:close()
      • 方法:closeAllConnections()
      • 方法:setTimeout()
      • 方法:listen()
      • 事件:connection
      • 事件:dropRequest
      • 事件:request
    • 案例
    • ClientRequest
      • 创建ClientRequest对象
      • 实例方法
      • 事件监听
    • ServerResponse
      • 属性
      • 方法
    • IncomingMessage
    • 其他属性
      • http.METHODS
      • http.STATUS_CODES
  • 其他

前言

本章详细介绍了http模块的各部分概念和用途,包括创建一个简单的 Web 服务器、配置服务器属性、处理客户端请求以及响应客户端。

在学习本章之前,你最好具有一些前置的基础知识,如:计算机网络知识、Ajax或者Fetch请求、跨域相关知识等。

http

一个Web服务器

项目创建

const http = require('node:http');const hostname = '127.0.0.1';
const port = 3000;const server = http.createServer((req, res) => {res.statusCode = 200;res.setHeader('Content-Type', 'text/plain');res.end('Hello, World!\n');
});server.listen(port, hostname, () => {console.log(`Server running at http://${hostname}:${port}/`);
});

这是官方给出的一个案例,我们创建一个目录为:node-server,并创建index.js文件,将上面内容写入。

通过npm init创建一个package.json配置文件,根据提示填入相应的配置信息。

package.json中找到scripts配置,写入:"start": "node ./index.js"

代码运行

我们可以通过node index.js的方式运行上面的文件,但是为了方便操作,我们写入了npm命令脚本,脚本的内容就是node index.js,这样我们只需要运行start命令即可:

npm run start

运行之后,命令行会输出:
在这里插入图片描述
我们的Web服务就启动成功了,在浏览器打开 http://127.0.0.1:3000/,就可以看到Hello, World!

代码解析

我们来简单解析一下上面的代码,如果你有一定的Nodejs基础,这段代码是可以看得懂的:

  1. 导入http模块;
  2. 定义主机名和端口号;
  3. 创建Web服务,并在回调中设置响应状态、响应头和响应体;
  4. 监听设定的端口号,并在服务启动成功之后给出提示信息。

代码的核心在于http.createServerserver.listen它们分别用于创建服务和监听端口号。

我们对Web服务器的要求也很简单,能够接收客户端请求,并做出响应即可,这段代码正好符合我们的要求。

不过这段代码有点问题,比如,

  • 所有发送到localhost:3000的请求,无论路径和参数是什么,它总会返回Hello,World!
  • 如果发生异常,如大并发、网络缓慢、服务端出错等,它无法做出“人性化”反馈。

第一点是我们必须要考虑的问题,我们希望不同的请求路径响应不同的结果;

第二点对于普通项目来说需要考虑的场景并不是很多,但是我们也希望能够做出适当配置,让项目达到一个较好的运行状态。

Server

Server类用于创建一个http实例。

官方给出了http.server相关的方法和属性以及“事件”,我们只需要学会常用的配置即可。

你可以将配置写在一个options对象中,并作为http.createServer的第一个参数传入,也可以将配置作为http实例的属性直接修改。

一般我们用第一种方式为服务设置通用配置信息,而第二种方式用于定制化项目配置,根据自己需要选择。

const server = http.createServer({ ...options }, () => {});

属性:keepAlive

我们知道,http的无状态的,服务器无法知晓客户端状态,客户端每发送一次请求,客户端都会建立三次握手(SYN, SYN-ACK, ACK),这会消耗时间和资源。

假设你的官网加载了很多资源(图片、文本、视频…),服务器的握手会很占用时间,有没有一种可能,我们让服务器在某个设定的时间段内在客户端和服务器之间保持持久连接,从而允许在同一个 TCP 连接上发送多个 HTTP 请求和接收响应,而不是为每个请求都打开和关闭一个新的连接。

这种做法既可以保证连接的安全性,也极大提升了“高并发”下的加载速度。

HTTP/1.1中,keep-alive是默认行为,不需要显式设置,你可以在一些请求中看到相关的信息:
在这里插入图片描述
单个请求的KeepAlive并没有什么意义,我们会在学习完其他属性之后进行测试。

属性:keepAliveTimeout

为了“高并发”下的请求效率,浏览器默认启用了KeepAlive,我们不考虑其他非浏览器情况。

这里又会产生一个问题,开启KeepAlive之后,服务器需要分配一部分内存来管理连接,如果瞬时用户量很大,可能会造成服务器崩溃。

为了解决这个问题,我们约定,给KeepAlive一个时长,在这个时长范围内保持链接,超过时长则断开,这个时长默认为5000ms,当然你也可以通过设置keepAliveTimeout来自定义时长,它的单位为毫秒,当客户端发送请求在这个范围内时,会一直使用同一个连接。

连接会自动延长,当下一次请求触发之后,会自动顺延时长。

属性:maxHeaderSize

请求头的最大长度,没什么特殊的地方,默认长度为16384,也就是16k。

属性:requestTimeout

请求超时时长,默认是300000ms,也就是300秒,可以应付大部分需求,如果你需要上传大问题,可以在指定的请求内修改并覆盖。

属性:maxRequestsPerSocket

每次连接的最大请求数量,默认为0表示不限制,这个属性需要放在server实例上配置:

server.maxRequestsPerSocket = 3;

方法:close()

关闭服务器本身,使其停止监听新的连接请求。

server.close(() => {console.log('server on port 8000 closed successfully');
});

方法:closeAllConnections()

关闭所有连接到此服务器的连接,包括那些正在处理请求或等待响应的连接,以及空闲连接。

我们一般先调用server.close再调用server.closeAllConnections(),这样既能够保证清晰的逻辑顺序也能确保服务被正确关闭。

server.closeAllConnections();

方法:setTimeout()

除了在创建服务时配置的timeout之外,还可以使用server.setTimeout方法进行设置。

// 设置连接超时时间为 5000 毫秒(5 秒)
server.setTimeout(5000, () => {console.log('A connection was closed due to inactivity.');
});

方法:listen()

开始监听特定端口或路径,你可以监听多个端口来实现不同的项目。

server.listen(3344, () => {console.log(`Server running at http://localhost:${3344}/`);
});server.listen(1122, () => {console.log(`Server running at http://localhost:${1122}/`);
});

事件:connection

当有新的 TCP 连接时,‘connection’ 事件被触发,我们可以用connection时间来查看相关信息。

server.on('connection', (socket) => {console.log('Connection connected!', socket.localAddress, socket.localPort, socket.remoteAddress, socket.remotePort);
});

事件:dropRequest

当连接的请求超过maxRequestsPerSocket的阈值时,连接会删除新的请求,并触发dropRequest,不过在浏览器端并不会发生该事件,而是会将请求分成多批次进行响应。

事件:request

每当有一个请求都会触发该事件,一个连接中的多个请求都会触发一次。

server.on('request', (request) => {console.log('Received request', request.url);
});

案例

我们来写一个案例测试上面的配置:

const http = require('node:http');const hostname = '127.0.0.1';
const port = 3000;const server = http.createServer({keepAlive: true,keepAliveTimeout: 1000
},(req, res) => {res.setHeader('Access-Control-Allow-Origin', '*');res.setHeader('Access-Control-Allow-Methods', '*');res.setHeader('access-control-allow-headers', '*');res.statusCode = 200;res.setHeader('Content-Type', 'text/plain');res.end('Hello, World!\n');
});
server.maxRequestsPerSocket = 3;
server.on('connection', (socket) => {console.log('Connection connected!', socket.localAddress, socket.localPort, socket.remoteAddress, socket.remotePort);
});
server.on('request', (request) => {console.log('Received request', request.url);
});
server.listen(port, hostname, () => {console.log(`Server running at http://${hostname}:${port}/`);
});

我们启用了KeepAlive,并设置1000毫秒的持续时长,同时设置maxRequestsPerSocket为3表示一个连接最多传递3个请求。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><button onclick="button()">点击</button><script>function button() {Promise.all([request(),request(),request(),request(),request()])}function request(url = 'http://127.0.0.1:3000'){fetch(url, {method: 'get',keepalive: true,})}</script>
</body>
</html>

我们在html中同时发送五个请求,我们来尝试运行,注意由于index.html并没有通过Nodejs的服务访问,因此存在跨域,我们在代码中做了简单处理。

当我们点击按钮,浏览器会发出五次请求,由于限定了maxRequestsPerSocket的大小,connection事件会被触发两次。
在这里插入图片描述
我们修改maxRequestsPerSocket为0,然后快速点击按钮:

server.maxRequestsPerSocket = 0;

设置为0表示不限制单个连接的请求数量,由于点击速度在1秒内,因此除了第一次,后面每次点击无需再次建立连接:
在这里插入图片描述
在你最后一次点击之后,停留1秒之后再次点击,则会再次触发connection事件,我们设置的超时时间为1秒,超过一秒原来的连接会中断并建立新链接。

ClientRequest

ClientRequest是一个非常重要的类,用于表示一个正在进行中的 HTTP 客户端请求。

通俗一点就是,Nodejs向其它服务器发出请求时所创建的请求对象。

我们学习它是为了更好地理解后面的请求对象。

创建ClientRequest对象

我们通常使用http.request()方法创建ClientRequest对象,下面的是一个案例告诉你如何创建:

const http = require('http');const options = {hostname: 'www.example.com',port: 80,path: '/',method: 'GET'
};const req = http.request(options, (res) => {console.log(`STATUS: ${res.statusCode}`);console.log(`HEADERS: ${JSON.stringify(res.headers)}`);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}`);
});req.end();

我们通过http.requesthttp://www.example.com发出了一个get请求,并监听其回调。

回调函数接受一个参数,这个参数称为响应对象,类型为“IncomingMessage”,在后面也会再次遇到。

实例方法

ClientRequest提供了多种方法来配置和发送请求,我们可以用这些方法来定制化请求。

  • setHeader(name, value):设置请求头的单个字段。
req.setHeader('Content-Type', 'application/json');
  • getHeader(name):获取请求头的单个字段的值。
const contentType = req.getHeader('Content-Type');
  • removeHeader(name):移除请求头的某个字段。
req.removeHeader('Content-Type');
  • write(chunk[, encoding]):将数据写入请求体,get请求无效。
req.write('{"name": "John Doe"}');
  • end([data][, encoding][, callback]):结束请求并发送数据,如果你用过write则无需再添加参数。
req.end('{"email": "john.doe@example.com"}');
  • abort():终止请求。
req.abort(); // 立即终止请求
  • setTimeout(timeout[, callback]):设置请求的超时时间。
req.setTimeout(5000, () => {console.log('Request timed out');req.abort();
});

事件监听

我们可以通过监听事件在不同阶段做出一些操作。

  • ‘response’:当接收到响应头时触发,传递一个 http.IncomingMessage 对象作为参数。
req.on('response', (res) => {console.log(`STATUS: ${res.statusCode}`);
});
  • ‘socket’:当为请求分配了一个 net.Socket 时触发。
req.on('socket', (socket) => {console.log('Socket assigned.');
});
  • ‘error’:当请求过程中发生错误时触发。
req.on('error', (e) => {console.error(`problem with request: ${e.message}`);
});
  • ‘abort’:当请求被终止时触发。
req.on('abort', () => {console.log('Request aborted.');
});
  • ‘timeout’:当请求超时时触发。
req.on('timeout', () => {console.log('Request timed out.');req.abort();
});

在实际开发中,我们一般会用第三方工具辅助开发,但是最基本的原理也要学会,这样才能应付出现的问题。

ServerResponse

ServerResponse用于表示 HTTP 服务器的响应对象。每当 HTTP 服务器接收到请求时,都会创建一个http.ServerResponse对象,因此我们无需手动创建该对象。

你可能会有疑问

在上面的案例中:

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

通过http.request创建请求的第二个参数表示回调,我们上面说了,这个回调函数的参数类型是IncomingMessage,按照我们正常的逻辑来说,它应该是响应对象,那为什么会是IncomingMessage呢?

如果你问出了这样的疑问,说明有思考,我们需要区分一下场景。

上面说到了Server,这是个服务器,服务器接受请求,发送响应;
上面说到了ClientRequest,这是个客户端工具,用于请求服务器;
现在说的ServerResponse,这是个中介,它在服务器叫ServerResponse,当它被发送到客户端之后称为IncomingMessage,因此很容易被迷惑。

如果再网上,在Server的案例中,有这样一段代码:

server.on('request', (request) => {console.log('Received request', request.url);
});

注意,这里的request可不是ClientRequest,它只是被我们碰巧命名为request,为了区分,可以将它改为req

我们思考一下,IncomingMessage叫“即将到来的消息”,对于服务器来说,发送过来的请求是“即将到来的消息”,对于客户端来说发送过来的响应是“即将到来的消息”。我们要理清一下关系:

  1. 客户端通过ClientRequest创建一个请求;
  2. 请求发送到服务器,这个请求对象被包装变成Serverrequest事件的回调函数的参数,称为IncomingMessage,它用于获取请求信息;
  3. Serverrequest事件的回调函数还接受第二个参数,作为响应对象,也就是上面所说的ServerResponse,我们给ServerResponse进行配置之后,返回给客户端;
  4. 客户端通过ClientRequest的回调响应请求,拿到服务端发送回来的ServerResponse,此时它也被称为IncomingMessage

记住这个关系,后面还会遇到。

属性

作为一个响应对象,它会有下列常用属性:

  • statusCode,状态码;
  • statusMessage:状态消息
  • header:响应头(使用方法代替)
  • 响应体
response.statusCode = 200;
response.setHeader('Content-Type', 'text/plain');
response.end('Goodbye!');

方法

  • setHeader(name, value),设置响应头的单个字段。
  • getHeader(name),获取响应头的单个字段的值。
  • removeHeader(name),移除响应头的某个字段。
  • write(chunk[, encoding][, callback]),向响应体写入数据块。
  • writeHead(statusCode[, statusMessage][, headers]),同时设置响应状态码、状态消息和响应头,然后准备发送响应体。
  • end([data][, encoding][, callback]),结束响应过程,并可选地发送数据块。

IncomingMessage

IncomingMessage用于表示 HTTP 请求(在服务器端)或 HTTP 响应(在客户端)。它提供了访问请求/响应头和读取请求/响应体的接口。

  • 在服务器端:
    • http.Server对象在接收到请求时自动创建,并通过回调函数的第一个参数传递给开发者。
  • 在客户端:
    • http.ClientRequest对象在接收到响应时自动创建,并通过response事件传递给开发者。

这个对象的相关属性和方法其实在上面基本上都讲过了,我们需要做的就是理清楚它在客户端与服务器之间具体的用途。

其他属性

http.METHODS

一个字符串数组,列出来所有能被解析的请求方法。

http.STATUS_CODES

包含了所有标准 HTTP 状态码及其对应的描述性文本。

console.log(http.STATUS_CODES[200]); // 输出: 'OK'
console.log(http.STATUS_CODES[404]); // 输出: 'Not Found'
console.log(http.STATUS_CODES[500]); // 输出: 'Internal Server Error'

其他

通过上面的内容学习,我们能够实现一个简单的http服务器,它能够接收请求并做出响应,同时能够根据请求的url和method做出简单的路由,但是我们还有一些部分没有提到,如参数的解析,nodejs本身没有直接从body或者查询字符串提取数据的能力,需要我们自己进行提取,因此我们要在学完BufferStream之后再来解析参数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/901253.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

buuctf sql注入类练习

BUU SQL COURSE 1 1 实例无法访问 / Instance cant be reached at that time | BUUCTF但是这个地方很迷惑就是这个 一个 # 我们不抓包就不知道这个是sql注入类的判断是 get 类型的sql注入直接使用sqlmap我们放入到1.txt中 目的是 优先检测 ?id1>python3 sqlmap.py -r 1.t…

(即插即用模块-特征处理部分) 三十二、(TGRS 2024) MDAF 多尺度双表示对齐过滤器

文章目录 1、Multiscale Dual-Representation Alignment Filter2、代码实现 paper&#xff1a;SFFNet: A Wavelet-Based Spatial and Frequency Domain Fusion Network for Remote Sensing Segmentation Code&#xff1a;https://github.com/yysdck/SFFNet 1、Multiscale Dual-…

Python 中为什么 hash(-1) == hash(-2)?

推荐超级课程: 本地离线DeepSeek AI方案部署实战教程【完全版】Docker快速入门到精通Kubernetes入门到大师通关课AWS云服务快速入门实战目录 让我们从哪里开始?获取源代码!让我们浏览一下这是正确/完整的答案吗?结论前几天在浏览 Reddit 时,我在 r/Python 上看到了这样一个…

基于PySide6与pycatia的CATIA绘图比例智能调节工具开发全解析

引言&#xff1a;工程图纸自动化处理的技术革新 在机械设计领域&#xff0c;CATIA图纸的比例调整是高频且重复性极强的操作。传统手动调整方式效率低下且易出错。本文基于PySide6pycatia技术栈&#xff0c;提出一种支持智能比例匹配、实时视图控制、异常自处理的图纸批处理方案…

macos下 ragflow二次开发环境搭建

参考官网链接 https://ragflow.io/docs/dev/launch_ragflow_from_source虚拟环境 git clone https://github.com/infiniflow/ragflow.git cd ragflow/ # if not pipx, please install it at first pip3 install pipxpipx install uv uv sync --python 3.10 --all-extras 安装 …

libva之ffavdemo分析

ffavdemo 代码库实现了一个基于FFmpeg和VAAPI的硬件加速视频解码与渲染框架&#xff0c;主要用于演示视频解码与渲染的完整硬件加速流程。支持多种渲染后端&#xff08;X11、DRM、EGL&#xff09;&#xff0c;适应不同显示环境。包含视频处理过滤器&#xff0c;可进行格式转换和…

JavaWeb 课堂笔记 —— 09 MySQL 概述 + DDL

本系列为笔者学习JavaWeb的课堂笔记&#xff0c;视频资源为B站黑马程序员出品的《黑马程序员JavaWeb开发教程&#xff0c;实现javaweb企业开发全流程&#xff08;涵盖SpringMyBatisSpringMVCSpringBoot等&#xff09;》&#xff0c;章节分布参考视频教程&#xff0c;为同样学习…

精品推荐 | 湖仓一体电商数据分析平台实践教程合集(视频教程+设计文档+完整项目代码)

精品推荐&#xff0c;湖仓一体电商数据分析平台实践教程合集&#xff0c;包含视频教程、设计文档及完整项目代码等资料&#xff0c;供大家学习。 1、项目背景介绍及项目架构 2、项目使用技术版本及组件搭建 3、项目数据种类与采集 4、实时业务统计指标分析一——ODS分层设计与数…

【人工智能】大语言模型多义词解析技术揭秘——以“项目“歧义消解为例

今天田辛老师和小伙伴探讨了一个有趣的多义词问题&#xff0c; 在人工智能技术日新月异的今天&#xff0c;大语言模型&#xff08;LLM&#xff09;对自然语言的理解能力已经达到令人惊叹的水平。大模型到底是如何去区分多义词的&#xff1f; 比如&#xff1a;当用户提到"…

【maxENT】最大熵模型(Maximum Entropy Model)R语言实现

文章目录 一、相关package介绍1.1 dismo 包1.2 raster包1.3 常见问题与解决 二、代码示例 &#x1f7e2;&#x1f7e0;先看&#xff1a;【maxENT】最大熵模型&#xff08;Maximum Entropy Model&#xff09;介绍与使用&#xff08;maxENT软件&#xff09; ASCII文件太大&#…

哈希表-算法小结

哈希表 map set 数组 在C中&#xff0c;set 和 map 分别提供以下三种数据结构&#xff0c;其底层实现以及优劣如下表所示&#xff1a; 集合底层实现是否有序数值是否可以重复能否更改数值查询效率增删效率std::set红黑树有序否否O(log n)O(log n)std::multiset红黑树有序是否…

OpenCompass模型评估

OpenCompass面向大模型的开源方和使用者&#xff0c; 提供开源、高效、全面的大模型评测开放平台。 一、OpenCompass文档 1.基础安装 使用Conda准备 OpenCompass 运行环境&#xff1a; conda create --name opencompass python3.10 -y conda activate opencompass2. 安装 Op…

博途 TIA Portal之1200做主站与有意思的板子做MODBUS_RTU通讯

做为博途的硬件,1200和1500本体都不具有串口通讯功能,只能使用扩展板或是通讯模块完成。 其中1200使用CB1241或CM1241进行串口通讯,本文将使用CM1241进行演示。 1、硬件介绍 1200的PLC一台,有意思的板子(以下简单4D板)一台。 其中1200带扩展模块CM1241 RS232;4D板使…

【深度学习与实战】3.1 逻辑回归模型

‌1. 定义与核心思想‌ 逻辑回归&#xff08;Logistic Regression&#xff09;是一种用于‌二分类问题‌的统计学习方法&#xff0c;通过‌sigmoid函数‌将线性回归的输出映射到[0,1]区间&#xff0c;表示样本属于某一类别的概率‌。 ‌本质‌&#xff1a;广义线性模型&#x…

AI三万字论文生成效果——随机森林在信用卡欺诈分析

以下内容全文由AI制作&#xff0c;有gemini和gpt模型配合一次性生成&#xff08;即未来我们会发布的功能&#xff09;&#xff0c;一次性生成的三万多字论文效果。 标题&#xff1a;随机森林在信用卡欺诈分析中的应用研究 摘要 信用卡欺诈已成为全球金融领域面临的严峻挑战…

质检LIMS系统在半导体制造行业的应用 半导体质量革命的现状

在半导体这个“工业皇冠上的明珠”领域&#xff0c;纳米级的精度要求与质量管控如同硬币的两面。随着芯片制程向3nm、2nm演进&#xff0c;传统质检模式已难以满足海量数据、复杂工艺的质量追溯需求。质检LIMS实验室系统作为质量管理的中枢神经&#xff0c;正在重构半导体制造的…

idea手动创建resources文件夹

有时maven没有构建成功可能造成&#xff0c;resources文件夹不创建的现象 此时我们可以手动创建 手动创建

利用Ruby的Typhoeus编写爬虫程序

Typhoeus是一个基于libcurl的HTTP客户端&#xff0c;支持并行请求&#xff0c;适合高效爬取数据。用户可能想要一个简单的例子&#xff0c;或者需要处理更复杂的情况&#xff0c;比如分页、并发请求或者数据解析。 首先&#xff0c;我应该检查用户是否已经安装了Typhoeus。通常…

【mllm】——x64模拟htp的后端无法编译debug

mllm, qnn, x64 code:https://github.com/UbiquitousLearning/mllm 1. 问题 通过自定义qualcomm graph使用高通的htp后端进行llm推理&#xff0c;网络暂时只有mllm&#xff0c;和https://github.com/chraac/llama.cpp。qualcomm是支持x64模拟htp推理的&#xff0c;这样比较好d…

JDK(Java Development Kit)从发布至今所有主要版本 的详细差异、新增特性及关键更新的总结,按时间顺序排列

以下是 JDK&#xff08;Java Development Kit&#xff09;从发布至今所有主要版本 的详细差异、新增特性及关键更新的总结&#xff0c;按时间顺序排列&#xff1a; 1. JDK 1.0 (1996) 发布年份&#xff1a;1996年1月23日关键特性&#xff1a; Java首次正式发布。核心语言特性…