Nodejs 第二十五章 http
Nodejs 第二十五章 http
在 Node.js 中,http
模块是一个内置的模块,用于构建HTTP服务器和客户端应用程序。它提供了一系列的功能,使得开发者可以轻松地处理HTTP请求和响应,而无需额外的库。
核心功能
- 创建 HTTP 服务器:
http
模块可以快速创建一个 HTTP 服务器,监听来自客户端的 HTTP 请求,并返回响应。
- 发送 HTTP 请求:
- 使用
http
模块,可以编写 Node.js 客户端应用程序,用于向 HTTP 服务器发送请求并接收响应。
- 使用
- 处理请求和响应:
- 服务器可以解析请求的 URL、头部(headers)和正文(body),以及发送包含头部和正文的响应。
- 流和性能:
http
模块支持流,这意味我们可以在数据到达时逐块处理请求和响应的正文,优化内存使用和增强性能。
数据请求
- 我们从前面知道,服务器可以监听来自客户端的HTTP请求。
- 那HTTP请求,在前端中,常用的就ajax、axios、fetch等等这些方式
- 或者通过路由(Router)去处理不同的业务逻辑,在切换页面或者切换组件时返回不同的数据。像图下的话,在不同的路由界面,用到数据的不同,那接口也会不同,比如
/api/xxx
- HTTP在处理GET和POST的传参方式也不同
创建http服务器
createServer()
: 这个方法接受一个请求监听器作为参数。请求监听器是一个函数,它有两个参数request
和response
。request
对象表示客户端的 HTTP 请求,response
对象用于向客户端发送响应.listen(3000)
: 这是服务器对象的一个方法,用于启动服务器并使其监听指定的端口。在这个例子中,服务器将监听 3000 端口。.listen
方法还可以接受第二个参数,一个回调函数,当服务器开始监听时,这个函数会被调用
const http = require("node:http")
http.createServer((req,res) => {
//内容主体
}).listen(3000, () => {
console.log("3000端口已经进入监听");
})
插件辅助
-
REST Client 插件通常用于集成开发环境(IDE)中,它允许开发者在开发过程中方便地发送和测试 RESTful API 请求
- 快速测试API:开发者可以通过插件快速构建和发送HTTP请求,测试后端服务的响应。
- 编写和发送请求:插件提供了一个用户友好的界面,允许用户输入请求的URL、HTTP方法(如GET、POST、PUT、DELETE等)、请求头和请求体。
- 查看响应:插件可以显示服务器的响应状态码、响应头和响应体,帮助开发者理解API的行为。
- 环境变量管理:允许用户定义环境变量,如基础URL、认证令牌等,以便于在不同的环境(如开发、测试、生产)中快速切换。
- 历史记录:插件通常能够保存请求历史,方便开发者回顾之前发送过的请求。
- 脚本和自动化:一些REST Client插件支持编写脚本来自动化测试流程,提高测试效率。
- 数据模拟:在某些情况下,插件还可以用于模拟服务器响应,帮助前端开发者在后端API尚未完成时进行开发。
- 代码生成:一些插件能够根据API的响应自动生成客户端代码,简化客户端开发过程。
-
好处很多,但对我们目前来说,就相当于不需要打开postman或者apifox了,这会让我们需要打开或者下载(如果你没有的话)的软件少一点
如何分清 GET 和 POST
- 在网络请求中,往往有很多种请求的方式,比如说:
- PUT: 用于上传指定资源,如果资源不存在则创建,如果资源已存在则更新。
- DELETE: 用于删除指定的资源。
- HEAD: 类似于
GET
请求,但是不返回响应体,只返回响应头。 - OPTIONS: 请求服务器告知客户端服务器支持哪些HTTP请求方法。
- PATCH: 用于对资源进行部分修改,不同于
PUT
请求,PATCH
请求只修改部分数据而不是整个资源。 - CONNECT: HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。
- TRACE: 请求服务器回送收到的请求信息,主要用于测试或诊断。
- PURGE: 用于请求网络服务器删除指定的缓存内容。
- LINK: 用于建立与资源的关联。
- UNLINK: 用于取消与资源的关联。
- 但最常用的还是
GET
、POST
、PUT
、DELETE
这四个,分别对应了CRUD(创建、读取、更新、删除)操作- 而在这里,我们就先讲前两者
const http = require("node:http")
http.createServer((req,res) => {
//内容主体
if(req.method == 'POST'){
res.end('这是POST请求')
}else if(req.method == 'GET'){
res.end('这是一个GET请求')
}
}).listen(3000, () => {
console.log("3000端口已经进入监听");
})
-
首先我们可以通过req参数,也就是requst请求中的method方法,获取当用户传递过来的请求方式。目前我们只是设置了最基础的POST和GET判断,还有更多的边界情况没有去处理
-
接着就需要用到我们的插件辅助了,模拟用户发送数据请求来测试我们刚写好的http服务器
- 我们需要先创建一个文件,文件名无所谓,但后缀要是
http
,在该文件中,进行发送模拟请求测试 - 结果也是正常返回了
- 我们需要先创建一个文件,文件名无所谓,但后缀要是
//index.http文件
POST http://localhost:3000/post HTTP/1.1
Content-Type: application/json
{
"name":"小余"
}
- 接下来是进行GET请求的发送测试,代码也是类似的
- GET和POST最大的不同就在于他的信息数据是直接拼接在URL后面的,所有人都能直接看到这个信息,所以最好别在这里放敏感数据
GET http://localhost:3000?a=1&b=2 HTTP/1.1
url模块
url
模块提供了用于解析和格式化 URL 的工具。url.parse()
方法是这个模块中非常有用的一个函数,它能够将一个 URL 字符串分解为不同的组件- 这里讲到的内容,我们等下在区分路由的时候就会用到了,所以需要提前了解一下
返回值
url.parse()
方法返回一个对象,其中包含了 URL 的各个组成部分。这个对象的属性如下:
href
: 完整的 URL。protocol
: URL 的协议部分,例如https:
。slashes
: 布尔值,表示在协议和主机名之间是否有斜杠。auth
: URL 的认证信息部分,例如user:pass
。hostname
: URL 的主机名部分,不包括端口号。port
: URL 的端口号部分。host
:hostname
和port
的组合,如果端口号存在的话。pathname
: URL 的路径部分,从/
开始,比如http://localhost:3000/login 就是从**/login**开始。search
: 查询字符串部分,即?
后面的部分。query
: 查询字符串被解析成的对象。如果没有查询字符串,它就是一个空对象,GET请求会用到hash
: URL 的片段标识符部分,即#
后面的部分。
查询字符串解析
url.parse()
方法还可以接受一个可选的 true
参数作为第二个参数,这将使得查询字符串被解析为一个对象,而不是一个简单的字符串。
const parsedUrlWithQuery = url.parse(myUrl, true);
console.log(parsedUrlWithQuery.query); // 输出: { name: 'ferret' }
如果没有提供 true
参数,query
属性将是一个空对象。
格式化 URL
除了 parse
方法,url
模块还提供了 format
方法,它将一个解析后的 URL 对象转换回 URL 字符串。
const formattedUrl = url.format(parsedUrl);
console.log(formattedUrl); // 输出: 'https://user:pass@example.com:8888/path/to/page?name=ferret#hash'
区分路由
POST区分
- 在要是路由不同,我们要怎么样做到在我们需要的路由界面正确的返回数据呢?
- 我们在判断POST和GPT请求的时候,通过用户传递过来的请求,获取其中的method方法进行判断
- 而如果是路由的话,也一定会从用户那里传递过来,我们最重要的就是从用户传递过来的数据中筛选除路由,然后进行判断处理。而这就需要用到我们node的
url内置模块
了 - 通过
url内置模块
,我们把req.url
,也就是用户传递过来的地址传递进去。然后我们从其中解构出pathname变量,这个也就是我们的路由。最后在POST的判断里继续嵌套一层对路由的判断就OK了 - 如果你疑惑为什么是在POST中加,而不是GET中加。那是因为我们测试的是login登录接口,账号密码是隐私的数据,当然不能让所有人都看见了
const http = require("node:http")
const url = require("node:url")
http.createServer((req, res) => {
const { pathname } = url.parse(req.url); // 解析请求的 URL,获取路径
//内容主体
if (req.method == 'POST') {
//请注意看这里,我们这次的区分路由逻辑就在这里
if(pathname == '/login'){
res.end('这是一个login路由,登录成功了')
}else{
//在处理完所有情况后,我们还会进行边界处理,万一出现我们意料之外的结果就返回404网络状态码,告诉用户,这个内容找不到
res.statusCode = 404
res.end('404')
}
} else if (req.method == 'GET') {
res.end('这是一个GET请求')
}
}).listen(3000, () => {
console.log("3000端口已经进入监听");
})
- 做好了基础工作之后,我们其实还能接收一下POST发送过来的参数。比如上面我们在写一个login登录功能,那首先我们肯定要先拿到用户传递过来的数据,才能和我们自己数据库内的数据进行一个判断
- 那怎么拿到数据呢?
- 通过
req.on
进行获取,就跟事件监听有点像,我们监听data参数,然后通过回调函数返回的chunk片段参数进行拼接成最后的完整数据
if (pathname == 'login') {
let data = '';
req.on('data', (chunk) => {
data += chunk
})
}
- 但其实这样还没有结束,还记得我们一开始的时候,也是有通过
res.end("xxx")
把内容进行返回的,比如说登录的话,你至少要返回说是不是登录成功了,不成功的话会是什么问题?成功了也要有所提示- 在这里我们不直接使用res.end方法了,而是通过on进行监听end方法,这样就能处理更多的事情,比如返回的状态码和向应头。
- 由于是测试,所以我们直接返回数据看看,正常情况下返回提示或者错误即可
req.on('end', () => {
//设置响应头
res.setHeader('Content-Type','application/json')
//设置响应状态码,2xx表示请求成功
res.statusCode = 200
//返回给用户的数据
res.end(data)
})
- 写好了判断逻辑,当然要来测试一下啦
- 测试的数据还是使用我们一开始测试POST的数据
- 也是成果将响应状态码和数据返回给了用户
GET区分
- 那在前面,我们已经了解到了GET最大的区别在于他的信息是放在URL里的,所以我们想要拿到信息,就需要拿到URL中
?
后面的内容- 那我们在了解
内置模块url
的时候,也知道了这里面其实是有拿到?
后面内容的方式的,也就是query,但距离想要使用,还需要进一步的处理
- 那我们在了解
//那首先,进行判断路由的方式,我们也是先直接照搬了过来,这是一样的
... else if (req.method == 'GET') {
if(pathname === '/get'){
console.log(query);
}else{
res.statusCode = 404
res.end('404')
}
}
- 接下来我们就先从url模块中取出query打印一下,同时在url的parse方法中继续加上true,这能使query参数进行序列化
const { pathname, query } = url.parse(req.url, true); // 解析请求的 URL,获取路径和查询参数
...else if (req.method == 'GET') {
if (pathname === '/get') {
console.log(query);
} else {
res.statusCode = 404
res.end('404')
}
}
- 而没经过序列号的数据是这样的:a=1&b=2。很显然,还是对象形式的键值对更方便拿取数值。直接
query.a
的去取值
完整代码
- 完整的代码,我们能看到if嵌套的层级是有点多的,阅读起来比较不方便。因为这是原生的写法,在正式的开发中,我们往往会上框架,就会简略很多,阅读观感也会更好
const http = require("node:http")
const url = require("node:url")
http.createServer((req, res) => {
const { pathname, query } = url.parse(req.url); // 解析请求的 URL,获取路径和查询参数
//内容主体
if (req.method === 'POST') {
if (pathname === '/login') {
let data = '';
req.on('data', (chunk) => {
data += chunk
})
req.on('end', () => {
res.setHeader('Content-Type', 'application/json')
res.statusCode = 200
res.end(data)
})
} else {
res.statusCode = 404
res.end('404')
}
} else if (req.method == 'GET') {
if (pathname === '/get') {
console.log(query);
} else {
res.statusCode = 404
res.end('404')
}
}
}).listen(3000, () => {
console.log("3000端口已经进入监听");
})
- 那对原生的代码呢,我们也进行一个映射优化,让其更加精简适合阅读
const http = require("node:http");
const url = require("node:url");
// 创建路由处理映射
const routes = {
'GET': {
'/get': (req, res) => {
const { query } = url.parse(req.url, true); // 解析查询字符串为对象
console.log(query);
res.statusCode = 200;
res.end('Data received');
}
},
'POST': {
'/login': (req, res) => {
let data = '';
req.on('data', (chunk) => {
data += chunk; // 读取请求体数据
});
req.on('end', () => {
res.setHeader('Content-Type', 'application/json');
res.statusCode = 200;
res.end(data); // 返回接收到的数据作为响应体
});
}
}
};
// 通用的404处理
function handleNotFound(req, res) {
res.statusCode = 404;
res.end("404 Not Found");
}
// 创建服务器
http.createServer((req, res) => {
const { pathname } = url.parse(req.url);
const methodRoutes = routes[req.method];
// 检查请求方法和路径是否有对应的处理函数
if (methodRoutes && methodRoutes[pathname]) {
methodRoutes[pathname](req, res);
} else {
handleNotFound(req, res); // 无匹配路由,返回404
}
}).listen(3000, () => {
console.log("Server is running on port 3000");
});
转载自:https://juejin.cn/post/7360928627633094682