NodeJS 之 HTTP 模块(实现一个基本的 HTTP 服务器)

news2024/11/16 2:36:46

NodeJS 之 HTTP 模块(实现一个基本的 HTTP 服务器)

  • 参考
  • 描述
  • HTTP 模块
  • 搭建 HTTP 服务器
      • http.createServer()
      • 监听
          • 检测服务器端口是否被占用
            • 终端
            • Error Code
      • 超时处理
      • 处理客户端的请求
          • request 事件
            • http.IncomingMessage
            • http.ServerResponse
      • 中文乱码问题
          • 问题
          • 解决
  • 代码总汇
  • 遗留问题

参考

项目描述
Node.js 权威指南陆凌牛
NodeJSAPI 官方文档

描述

项目描述
NodeJS18.13.0
操作系统Windows 10 专业版

HTTP 模块

HTTP 模块是 NodeJS 中核心的内置模块,你可以通过该模块搭建 HTTPHTTPS 服务器,而无需像 PHP 等服务器端语言需要其他软件(Aphche、Nginx、IIS等)提供服务器功能。

你可以向运行在 NodeJS 运行时中的 JavaScript 文件中添加如下代码以导入该模块。

const http = require('http');

搭建 HTTP 服务器

http.createServer()

在 NodeJS 中,你可以通过 http.createServer() 创建一个服务器对象,我们可以通过这个服务器对象来实现许多功能。所以,请记住它:

http.createServer([, requestListener])

注:

  1. 你可以向 http.createServer() 方法传递一个回调函数以指定当服务器端接收到客户端的请求时需要执行的回调函数。该函数可以由两个形参,用以接收 http.IncomingMessage 对象(该对象可用于向客户端发送请求)http.ServerResponse 对象(该对象代表一个服务器端响应对象)
  2. 如果你没有为 http.createServer() 方法传递一个回调函数,那么你可以通过 http.createServer() 返回的服务器对象的 on 方法来监听 request 事件并为该事件指定当服务器端接收到客户端的请求时需要执行的回调函数。就像这样:
const http = require('http');


const server = http.createServer();
server.on('request', (req, res) => {
    // 此处存放指定当服务器端接收到客户端的
    // 请求时需要执行的 JavaScript 代码。
});

监听

你可以通过服务器对象的 listen() 来监听某个端口,计算机可以向这个端口发送请求与服务器进行交互。

server.listen(port, [host], [backlog], [callback]);

其中:

项目描述
port使用该参数为服务器指定需要监听的端口,如果提供的值为 0 或没有为该参数提供一个值,则 NodeJS 将为服务器随机分配一个端口号。
host用于指定服务器需要监听的地址,如果该省略该参数,则服务器将监听来自任何计算机的请求。
backlog用于指定位于等待队列中的客户端请求的最大数量,一旦超过设定的长度,服务器将拒绝新的连接请求。该参数的默认值为 511
callback用于指定服务器开始监听时需要调用的回调函数。

注:

当服务器开始监听后,将触发 listening 事件。如果你没有为 server.listen() 提供一个回调函数来指定服务器开始监听时需要进行的处理。那么,你可以通过 server.on() 方法来监听 listening 事件并为其指定服务器开始监听时需要调用的回调函数。

检测服务器端口是否被占用

在创建服务器时,你需要为服务器指定一个监听端口。如果该端口已经被其他应用所占用,则 NodeJS 将抛出错误。你应该对该错误进行捕获,否则,后续代码将无法正常执行。

你可以提前在终端中使用命令来检测某个端口是否已经被使用;你也可以通过 NodeJS 来对错误进行捕获,并在该错误是由于已经被其他应用所占用使用其他的端口号。

终端

Windows 终端中你可以输入如下命令来检测某个端口是否被占用:

netstat -ano | findstr <端口号> 

举个栗子:

下面我们来尝试检测端口 3360 是否被占用:

检测

注:

若执行命令后,终端中没有输出任何内容,则表明该端口没有被使用;
若执行命令后,终端中输出的内容与图中类似,则表明该端口已经被使用。

Error Code

除了可以使用命令来查看外,我们还可以通过对错误对象(可能是因为端口被占用而抛出的错误对象)的错误代码来判断是否是由于目标端口被占用而导致监听失败并对此进行处理(尝试使用另一个端口)。

const http = require('http');

// 创建服务器对象
const server = http.createServer();
// 默认监听端口 3360
server.listen(3360);

// 对错误进行捕获
server.on('error', (err) => {
    if(err.code == 'EADDRINUSE'){
        // 如果目标端口被占用将使用
        // NodeJS 随机分配的端口号
        server.listen(0);
    }
});

// 在成功监听后,向终端输出被监听的端口号
server.on('listening', () => {
    console.log('【HTTP Server is running at http://127.0.0.1:' + server.address().port + ' 】')
})

打印结果:

【服务器正在监听本机端口 56398】

注:

  1. 由于本机端口 3360 已被使用,所以该程序使用了 NodeJS 为其分配的随机端口 56398
  2. 我们可以通过 server.on() 方法来捕获错误,并在为该方法提供的回调函数中提供一个参数用以接收被捕获错误的错误对象。
  3. 错误对象的 code 属性包含了错误代码,当错误代码为 EADDRINUSE 时,表明该错误是由于被监听端口已经被占用所产生的。
  4. EADDRINUSE 的全称应该为 Error Address In Use ,记住它有助于记住错误代码。
  5. 使用 server.listen()server.listen(0) 在目标端口被占用的情况下,使用由 NodeJS 提供的随机端口。如果随机端口仍被占用,则将继续触发错误代码为 EADDRINUSE 的错误,并再次使用 NodeJS 提供的随机端口,直到成功的监听了一个端口。

超时处理

处理客户端的请求

在服务器对本机端口监听后,客户端便可以通过本机 IP 地址及端口号对本机进行访问。此时我们并没有对来自客户端的请求进行处理,所以此时如果你访问服务器,你可能会看到空白的网页以及一个永不停止的进度条(这是由于我们没有对客户端进行响应而导致的):

加载

我们可以通过监听 request 事件来对客户端的请求进行响应,也可以通过 http.createServer() 创建服务器对象时,为该函数提供一个回调函数来处理客户端的请求。

这里我们将通过监听 request 事件来对客户端的请求进行响应。

request 事件
server.on('request', (req, res) => {});

你可以向 server.on() 方法传递一个回调函数以指定当服务器端接收到客户端的请求时需要执行的回调函数。该函数可以由两个形参,分别用以接收 http.IncomingMessage 对象(该对象可用于向客户端发送请求)http.ServerResponse 对象(该对象代表一个服务器端响应对象)

http.IncomingMessage

该对象的部分属性及方法如下:

项目描述
headers该属性指向一个对象,其中包含了客户端的请求头信息。
httpVersion客户端使用的 HTTP 版本号。
method客户端使用的请求方式(GET、POST 等)。
url客户端请求的 URL 地址的路径及参数信息(不包含主机地址及端口号等其他信息)。
http.ServerResponse

该对象的部分属性及方法如下:

项目描述
setHeader()你可以通过该方法设置服务器端的请求头信息。该方法接收两个参数,第一个参数为响应字段,第二个为响应字段值。你可以传递一个数值来设置多个响应字段值。
writeHead()该方法接收两个参数,第一个参数用于设置响应状态码;你需要为第二个参数提供一个对象,用于设置服务器端的请求信息。该方法与 setHeader() 不同的是,该方法一次可以设置多个请求字段,而 setHeader 仅能一次仅能设置单个请求头字段。
getHeader()你可以通过向该方法传递一个响应字段,用于查看其对应的响应字段值。
removeHeader()你可以通过向该方法传递一个响应字段,NodeJS 将从响应头中删除该字段。
headersSent如果响应信息已被发送,则该属性的值将为 true,否则将为 false
statusCode该属性可用于设置响应状态码。
write()该方法接收三个参数,仅第一个参数为必填项;第一个参数为将发送的响应信息,第二个参数为响应信息的编码方式,第三个参数为发送成功后需要执行的回调函数。
end()此方法与 write() 方法类似,但此方法还能够向服务器发出信号,表明所有响应头和正文都已发送。

注:

  1. 如果你在一个响应中使用了 write()end()end() 方法应该位于所有 write() 之后,否则将有部分 write() (位于 end() 之后的 write())将无法发挥其功能。例如:
const http = require('http');

// 创建服务器对象
const server = http.createServer();
// 默认监听端口 3360
server.listen(3360);

// 对错误进行捕获
server.on('error', (err) => {
    if(err.code == 'EADDRINUSE'){
        // 如果目标端口被占用将使用
        // NodeJS 随机分配的端口号
        server.listen(0);
    }
});

// 在成功监听后,向终端输出被监听的端口号
server.on('listening', () => {
    console.log('【HTTP Server is running at http://127.0.0.1:' + server.address().port + ' 】')
})

// 对客户端的请求进行响应
server.on('request', (req, res) => {
    res.write('<h1>Hello World</h1>', () => {
        console.log('win');
    });
    res.write('<h3>Hello NodeJS</h3>')
})

效果:

效果

而如果我们将该代码中的第一个 write() 使用 end() 进行替换:

// 对客户端的请求进行响应
server.on('request', (req, res) => {
    res.end('<h1>Hello World</h1>', () => {
        console.log('win');
    });
    res.write('<h3>Hello NodeJS</h3>')
})

效果:

效果

  1. 你应该为所有的响应使用 end() 方法,这有利于节约服务器的资源。

中文乱码问题

问题

如果你直接向客户端发送包含中文的响应数据,将会产生中文乱码问题:

const http = require('http');

// 创建服务器对象
const server = http.createServer();
// 默认监听端口 3360
server.listen(3360);

// 对错误进行捕获
server.on('error', (err) => {
    if(err.code == 'EADDRINUSE'){
        // 如果目标端口被占用将使用
        // NodeJS 随机分配的端口号
        server.listen(0);
    }
});

// 在成功监听后,向终端输出被监听的端口号
server.on('listening', () => {
    console.log('【HTTP Server is running at http://127.0.0.1:' + server.address().port + ' 】')
})

// 对客户端的请求进行响应
server.on('request', (req, res) => {
    res.write('<h1>Hello World</h1>', () => {
        console.log('win');
    });
    // 向客户端发送包含中文的响应内容
    res.end('<h3>Hello 中国</h3>')
})

效果:

效果

解决

你可以通过设置合适的响应头信息来使客户端的浏览器正确解析中文字符以解决中文乱码问题。

const http = require('http');

// 创建服务器对象
const server = http.createServer();
// 默认监听端口 3360
server.listen(3360);

// 对错误进行捕获
server.on('error', (err) => {
    if(err.code == 'EADDRINUSE'){
        // 如果目标端口被占用将使用
        // NodeJS 随机分配的端口号
        server.listen(0);
    }
});

// 在成功监听后,向终端输出被监听的端口号
server.on('listening', () => {
    console.log('【HTTP Server is running at http://127.0.0.1:' + server.address().port + ' 】')
})

// 对客户端的请求进行响应
server.on('request', (req, res) => {
    // 通过响应头告知客户端发送内容的文本类型及编码方式
    res.setHeader('content-type', 'text/html; charset=utf-8')
    res.write('<h1>Hello World</h1>', () => {
        console.log('Win');
    });
    // 向客户端发送包含中文的响应内容
    res.end('<h3>Hello 中国</h3>')
})

效果:

效果

代码总汇

const http = require('http');

// 创建服务器对象
const server = http.createServer();
// 默认监听端口 3360
server.listen(3360);

// 对错误进行捕获
server.on('error', (err) => {
    if(err.code == 'EADDRINUSE'){
        // 如果目标端口被占用将使用
        // NodeJS 随机分配的端口号
        server.listen(0);
    }
});

// 在成功监听后,向终端输出被监听的端口号
server.on('listening', () => {
    console.log('【HTTP Server is running at http://127.0.0.1:' + server.address().port + ' 】')
})

// 对客户端的请求进行响应
server.on('request', (req, res) => {
    // 通过响应头告知客户端发送内容的文本类型及编码方式
    res.setHeader('content-type', 'text/html; charset=utf-8')
    res.write('<h1>Hello World</h1>', () => {
        console.log('Win');
    });
    // 向客户端发送包含中文的响应内容
    res.end('<h3>Hello 中国</h3>')
})

遗留问题

在执行最终代码后,你若对服务器进行访问,服务器将打印两次 Win。也就是说 访问一次服务器将触发两次 request 事件,具体原因暂不明确。如有知道的朋友,还望不吝赐教。

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

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

相关文章

Java EE之线程编(进阶版)

这些锁策略能适用于很多中语言&#xff0c;博主是学Java的&#xff0c;所以下面的代码会用Java去写&#xff0c;请大家见谅&#xff0c;但是处理的方法是大差不差的。 一、常见锁和锁策略&#xff1a; (一)、乐观锁和悲观锁 1、何为乐观锁和悲观锁呢&#xff1f; 答&#…

Linux服务器常见运维性能测试(3)CPU测试super_pi、sysbench

Linux服务器常见运维性能测试&#xff08;3&#xff09;CPU测试常见性能测试软件CPU测试&#xff1a;super_pi &#xff08;计算圆周率&#xff09;CPU测试&#xff1a;sysbench&#xff08;CPU功能测试部分&#xff09;下载安装sysbench综合测试功能执行CPU测试最近需要测试一…

Java面试题含答案,最新面试题(1)

Java 中 InvokeDynamic指令是干什么的&#xff1f; JVM字节码指令集一直比较稳定&#xff0c;一直到Java7中才增加了一个InvokeDynamic 指令&#xff0c;这是JAVA为了实现『动态类型语言』支持而做的一种改进&#xff1b;但是在Java7中并没有提供直接生成InvokeDynamic 指令的…

自定义类型:结构体,枚举,联合

目录一、结构体内存对齐二、位段2.1 什么是位段2.2 位段内存分配规则2.3 位段的跨平台问题三、枚举四、联合体4.1 联合类型的定义4.2联合的特点4.3 联合大小的计算4.4 练习一、结构体内存对齐 struct s {char c1;int i;char c2; }; int main() {printf("%d\n", size…

【Hadoop】HDFS体系结构分析

文章目录1. NameNode2. Secondary NameNode3. DataNodeHDFS主要包含NameNode、Secondary NameNode和DataNode三部分&#xff0c;且这三部分在分布式文件系统中分属不同的机器&#xff0c;其中Secondary NameNode不是必须的&#xff0c;在HA架构中Standby NameNode可以替代它。 …

【深度学习】详解 SimCLR

目录 摘要 一、引言 二、方法 2.1 The Contrastive Learning Framework 2.2. Training with Large Batch Size 2.3. Evaluation Protocol 三、用于对比表示学习的数据增广 3.1 Composition of data augmentation operations is crucial for learning good representa…

5-2中央处理器-指令周期的数据流

文章目录一.指令周期二.数据流向1.取指周期2.间址周期3.执行周期4.中断周期三.指令执行方案1.单指令周期2.多指令周期3.流水线方案一.指令周期 指令周期&#xff1a;CPU从主存中每取出并执行一条指令所需的全部时间。 此处&#xff1a;取指周期取指令指令译码 指令周期常用若…

SSM整合(Spring + SpringMVC + MyBatis)

SSM Spring SpringMVC MyBatis 准备数据库 SET FOREIGN_KEY_CHECKS0; DROP TABLE IF EXISTS user; CREATE TABLE user (id int(11) NOT NULL AUTO_INCREMENT,username varchar(20) NOT NULL COMMENT 用户名,password varchar(255) NOT NULL COMMENT 密码,real_name varchar(…

Linux常用命令——startx命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) startx 用来启动X Window 补充说明 startx命令用来启动X Window&#xff0c;实际上启动X Window的程序为xinit。 语法 startx(参数)参数 客户端及选项&#xff1a;X客户端及选项&#xff1b;服务器及选项&a…

[LeetCode周赛复盘] 第 329 场周赛20230122

[LeetCode周赛复盘] 第 329 场周赛20230122 一、本周周赛总结二、 [Easy] 6296. 交替数字和1. 题目描述2. 思路分析3. 代码实现三、[Medium] 6297. 根据第 K 场考试的分数排序1. 题目描述2. 思路分析3. 代码实现四、[Medium] 6298. 执行逐位运算使字符串相等1. 题目描述2. 思路…

深入理解 OpenMP 线程同步机制

深入理解 OpenMP 线程同步机制 前言 在本篇文章当中主要给大家介绍 OpenMP 当中线程的同步和互斥机制&#xff0c;在 OpenMP 当中主要有三种不同的线程之间的互斥方式&#xff1a; 使用 critical 子句&#xff0c;使用这个子句主要是用于创建临界区和 OpenMP 提供的运行时库…

连续系统的数字PID控制仿真-1

被控对象为一电机模型传递函数&#xff1a;式中&#xff0c;J 0.0067;B0.10。采用M函数的形式&#xff0c;利用ODE45的方法求解连续对象方程&#xff0c;输入指令信号为yd(k)0.50sin(2*3.14*t)&#xff0c;采用PID控制方法设计控制器&#xff0c;其中kp20.0 ,kd0.50。PID正弦跟…

12个开源的后台管理系统

1. D2admin 开源地址&#xff1a;https://github.com/d2-projects/d2-admin 文档地址&#xff1a;https://d2.pub/zh/doc/d2-admin/ 效果预览&#xff1a;https://d2.pub/d2-admin/preview/#/index 开源协议&#xff1a;MIT 2. vue-element-admin 开源地址&#xff1a;htt…

Kettle(3):快速入门

1 需求 有一个txt文件&#xff0c;内容如下&#xff1a; id,name,age,gender,province,city,region,phone,birthday,hobby,register_date 392456197008193000,张三,20,0,北京市,昌平区,回龙观,18589407692,1970-8-19,美食;篮球;足球,2018-8-6 9:44 267456198006210000,李四,2…

Vue3 – Composition API

1、认识CompositionAPI 1.1、Options API的弊端 在Vue2中&#xff0c;我们编写组件的方式是Options API&#xff1a; Options API的一大特点就是在对应的属性中编写对应的功能模块&#xff1b;比如data定义数据、methods中定义方法、computed中定义计算属性、watch中监听属性…

【快速简单登录认证】SpringBoot使用Sa-Token-Quick-Login插件快速登录认证

一、解决的问题 Sa-Token-Quick-Login 可以为一个系统快速的、零代码 注入一个登录页面 试想一下&#xff0c;假如我们开发了一个非常简单的小系统&#xff0c;比如说&#xff1a;服务器性能监控页面&#xff0c; 我们将它部署在服务器上&#xff0c;通过访问这个页面&#xf…

学习字符串函数和内存函数必看

字符串函数 1.strlen函数 strlen库函数 #include<stdio.h> #include<string.h> int main() {char arr[] "abc";char arr1[] { a,b,c };int len strlen(arr);int len1 strlen(arr1);//没有\0就无法停止printf("%d\n",len);printf("%…

VUE中的provide和inject用法

一、Vue中 常见的组件通信方式可分为三类 父子通信 父向子传递数据是通过 props&#xff0c;子向父是通过 events&#xff08;$emit&#xff09;&#xff1b; 通过父链 / 子链也可以通信&#xff08;$parent / $children&#xff09;&#xff1b; ref 也可以访问组件实例&…

XLSReadWriteII 写了一个DBGrdiEh创建EXCEL表的函数

XLSReadWriteII 写了一个DBGrdiEh创建EXCEL表的函数 自己通过XLSReadWriteII&#xff0c;写了一个由DBGridEh数据集&#xff0c;通过参数调用&#xff0c;创建EXCEL表格的函数&#xff0c;通过调用的参数设置&#xff0c;可以较为方便地&#xff0c;创建指定数据集的常用EXCEL表…

自动化和Selenium

作者&#xff1a;~小明学编程 文章专栏&#xff1a;测试开发 格言&#xff1a;热爱编程的&#xff0c;终将被编程所厚爱。 目录 什么是自动化&#xff0c;我们为什么需要自动化的测试&#xff1f; 为什么选择selenium来作为我们的web自动化测试的工具&#xff1f; 定位元素…