# HTTP 协议,请求响应报文、状态码、HTTP 模块实践
TIP
从本节内容开始,系统性学习和介绍 HTTP 协议,网络基础、http 模块 等相关原理、应用实践
- HTTP 协议,请求响应报文、状态码
- HTTP 模块
# 一、HTTP 协议
TIP
深入浅出 HTTP 协议、HTTP 报文、请求报文结构、请求行、请求头、请求体、响应报文结构 与 响应行,响应头 与 响应体 等。
之前我们在 前后端数据交互 与 HTTP 协议 (opens new window) 的章节,有学习过相关知识。
# 1、HTTP 协议是什么
TIP
HTTP 全称:Hyper Text Transfer Protocol 超文本传输协议(HTTP 是这几个英文单词的首字母)
HTTP 协议也是互联网应用最广泛的协议之一,在 Web 中(无论前后端)都是非常重要的,且必须掌握
# 1.1、协议的本质
TIP
- 协议:双方必须共同遵从的一组约定,协议的本质就是 “约定”
- 如:保密协议(对员工和公司的行为做了约束,员工按照企业要求保守商业秘密、公司也要按照协议的要求支付员工一定的酬劳);离婚协议(对夫妻双方的行为的一种约束,父亲按要求支付孩子抚养费、母亲按协议要求对孩子进行抚养,按照协议平分家庭资产)等
# 1.2、HTTP 协议的约束对象
TIP
HTTP 协议是对浏览器 和 服务器之间的通信进行了约束。
- 浏览器 给 服务器发送数据,这个行为称之为 “请求”
- 服务器 给 浏览器返回结果,这个行为称之为 “响应”
- 浏览器 给 服务器发送的数据,对应的内容 称之为 “请求报文”
- 服务器 给 浏览器返回的结果内容,称之为 “响应报文”
注:
日常使用过程中,在浏览器地址栏中输入 https://www.baidu.com
回车,此时
- 给服务器发送了一个 HTTP 请求报文
- 然后,服务器也返回了一个 HTTP 响应
- 准确来说整个过程中不是一次请求,而是多次请求,包括请求报文 和 响应后边详细来解读
# 2、查看 HTTP 报文
TIP
查看 HTTP 报文内容,需要安装一个客户端软件 Fiddler
Fiddler 官方下载地址:https://www.telerik.com/download/fiddler (opens new window)(直接默认下一步安装即可)安装包也可在裙文件中下载
# 2.1、Fiddler 的工作原理
TIP
Fiddler 是位于客户端和服务器端的 HTTP 代理,也是目前最常用的 http 抓包工具之一 。 它能够记录客户端和服务器之间的所有 HTTP 请求,可以针对特定的 HTTP 请求,分析请求数据、设置断点、调试 web 应用、修改请求的数据,甚至可以修改服务器返回的数据。
既然是代理,也就是说:客户端的所有请求都要先经过 Fiddler,然后转发到相应的服务器,反之,服务器端的所有响应,也都会先经过 Fiddler 然后发送到客户端。
所以 web 客户端和服务器的请求如下图
# 2.2、Fiddler 的基础配置
TIP
安装完成后,在 Windows 菜单栏中输入 “Fiddler ” 点击即可打开
①、打开 Fiddler 后,选择 “Cancel”
②、添加 Windows 信任的 CA 列表
点击 “是”
③、关闭 Fiddler 软件,重新打开(重启)
④、查看浏览器的请求
为了只查看浏览器的请求,点击 Fiddler 底部状态栏中的 “All Processes” 即:监听所有的进程 -> 选择 “Web Browsers” 即:只会监听浏览器发送的请求
# 2.3、使用 Fiddler 查看请求报文
①、在浏览器中输入 “www.baidu.com” ,在 Fiddler 可以看到很多 HTTP 请求
其中第一个 HTTP 请求,即:我们输入 “www.baidu.com” 网址时 发送的请求
②、双击 Fiddler 列表中的 “www.baidu.com” 网址,即可查看请求报文 和 响应报文
③、响应返回结果乱码问题,点击 “黄色警告提示” 默认响应体会编码,点击进行解码即可
解码后就正常显示了
# 3、HTTP 请求报文结构
TIP
将 Fiddler 获取到的百度的 HTTP 请求报文信息拿到,如下
GET https://www.baidu.com/ HTTP/1.1
Host: www.baidu.com
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: BD_UPN=12314753; PSTM=1670490582;BIDUPSID=68EEAE691F2B30B2B80666CE4FAA26DF; ...
请求报文结构解读
HTTP 请求报文主要包括三部分
- 第一部分:请求行,是请求报文的第一行内容;
- 第二部分:从报文的第二行开始 -> 到空行之前 为 请求头;
- 第三部分:请求体(以上给百度发送请求的请求体目前是空的,后边演示非空清空下的请求体);
注:请求头 与 请求体 之间是有一个空行的。以上就是 HTTP 请求报文的基本组成部分
# 4、HTTP 请求报文 - 请求行
TIP
请求行相关内容
GET https://www.baidu.com/ HTTP/1.1
请求行结构解读
# 4.1、HTTP 请求方法
TIP
请求行中的第一个部分:HTTP 协议的常见请求方法
请求方法 | 作用 |
---|---|
GET | 主要用于获取数据 |
POST | 主要用于新增数据 |
PUT / PATCH | 主要用于更新数据 |
DELETE | 主要用于删除数据 |
OPTIONS、TRACE、CONNECT、HEAD | 很少用到 |
# 4.2、URL
TIP
- 请求行中的第二个部分:URL(Uniform Resource Locator)统一资源定位符
- URL 它本身也是一个字符串,用来定位服务器中的资源
- 服务器上会存在很多资源,浏览器向服务器发送请求获取其中的某一个资源,此时就需要通过 URL 来定位其中的某一个资源。将请求发送到服务器后,服务器就会通过 URL 去定位,找到后再把结果返回给浏览器
因此,我们将 URL 是用来定位资源的
# 4.3、URL 的组成部分
以当前请求的 URL 为例
https://www.baidu.com/
注:
https
协议名称www.baidu.com
主机名,可以是域名 或 IP 地址。主机名用来定位网络上的计算机/
路径,用来定位服务器中的某一个资源://
协议的固定组成部分,不论什么协议都是必须得有的
# 4.4、复杂 URL 的组成部分
相对复杂的 URL 组成部分
https://www.icodingedu.com:443/search?q=web&type=course
注:
https
协议名称://
协议的固定组成部分,不论什么协议都是必须得有的www.icodingedu.com
主机名(域名):443
端口号(部分情况下可省略不写)/search
路径,以/
开头 与?
之前的部分为 路径。用来定位服务器中的某一个资源?q=web&type=course
查询字符串,是键名、键值的结构,&
为分隔符。用于向服务器传递额外的参数
# 4.5、HTTP 版本号
TIP
常见的 HTTP 版本号
版本号 | 发布时间 |
---|---|
1.0 | 1996 年 |
1.1 | 1999 年 |
2 | 2015 年 |
3 | 2018 年 |
# 5、HTTP 请求报文 - 请求头
TIP
- HTTP 请求报文中的请求头是由一些列的键值对组成的
- 冒号前面是 键名(如:Host),冒号空格 后边是 键值(如:
www.baidu.com
)
关于请求头相关键值的解读
字段 | 解释 |
---|---|
Host | 指定请求的主机名,这里是www.baidu.com 。 |
Connection | 保持连接,值为keep-alive ,表示在完成当前请求后保持连接打开状态。 |
Cache-Control | 控制缓存行为,max-age=0 表示不使用缓存,每次请求都去服务器获取最新的内容。 |
sec-ch-ua | 安全性用户代理字段,包含浏览器类型和版本等信息。 |
sec-ch-ua-mobile | 指示是否为移动设备,这里值为?0 ,表示不是移动设备。 |
sec-ch-ua-platform | 指定操作系统和平台,这里是"Windows"。 |
Upgrade-Insecure-Requests | 指示是否支持将不安全的 HTTP 请求升级为更安全的 HTTPS 请求,这里值为 1,表示支持。 |
User-Agent | 用户代理字符串,包含浏览器类型、版本、操作系统等信息。 |
Accept | 指定客户端可以接受的响应内容类型,例如text/html 表示 HTML 文档。 |
Sec-Fetch-Site | 安全上下文中的站点类型,这里是none 表示没有特定站点上下文。 |
Sec-Fetch-Mode | 安全上下文中的请求模式,这里是navigate 表示导航请求。 |
Sec-Fetch-User | 指示是否为用户的请求,这里值为?1 表示是用户的请求。 |
Sec-Fetch-Dest | 指定请求的目标类型,这里是document 表示请求的是文档。 |
Accept-Encoding | 指定客户端可以接受的压缩编码格式,包括gzip , deflate , 和 br 等。 |
Accept-Language | 指定客户端希望接收的语言内容,这里是简体中文。 |
Cookie | 服务端发送到客户端的 cookie 信息,通常用于保存用户的登录状态或其他相关信息。 |
# 6、HTTP 请求报文 - 请求体
TIP
之前我们在浏览器地址栏中输入百度网址,发送请求时,请求体是空的。
- 请求体的内容格式是非常灵活的,可以设置任意内容
- 只要与后端预定好即可,格式不限
接下来,我们来查看有请求体的请求报文(请求体不为空的情况)
注:
- 请求方式是 “POST”
- 请求体为一个 JSON 字符串(由前后端约定好的数据格式决定)以上为登录时的用户名 和 密码
- 请求体的格式是不限的,可尝试查看不同的平台
也可拿任意网站的 POST 请求,请求体不为空时来作为演示。如:各个平台的表单提交
# 7、HTTP 响应报文
TIP
在浏览器中输入 www.baidu.com
,即可在 Fiddler 中查看 HTTP 响应报文
响应报文结构解读
# 8、HTTP 响应报文 - 响应行
TIP
响应行是由三部分组成
HTTP/1.1 200 OK
HTTP/1.1
HTTP 协议版本号200
响应状态码OK
响应状态的描述
# 8.1、响应状态码
常见的状态码如下
状态码 | 描述 |
---|---|
200 | 请求成功 |
403 | 禁止请求 |
404 | 找不到资源 |
500 | 服务器内部错误 |
注:HTTP 协议对状态码进行了分类,共分为 5 大类。如下
# 8.2、状态码分类
状态码 | 描述 |
---|---|
1xx | 信息响应 |
2xx | 成功响应 |
3xx | 重定向响应 |
4xx | 客户端错误响应 |
5xx | 服务端错误响应 |
# 8.3、响应状态的描述
状态码 | 状态描述 | 含义 |
---|---|---|
200 | OK | 成功 |
403 | Forbidden | 被禁止的 |
404 | Not Found | 不存在 |
500 | Internal Server Error | 内部服务器错误 |
注:
更多状态查阅官方文档:HTTP 响应状态码 (opens new window) ,可在浏览器中 Ctrl + F
搜索对应状态码
# 9、HTTP 响应报文 - 响应头
TIP
响应头:记录了与服务器相关的内容
响应头信息解读
响应头名称 | 响应头值 | 描述 |
---|---|---|
HTTP/1.1 | 200 OK | 表示请求成功,返回的状态码是 200。使用的 HTTP 版本是 1.1。 |
Connection | keep-alive | 建议客户端保持连接,以便后续的请求可以重用同一个 TCP 连接。 |
Content-Security-Policy | frame-ancestors 'self' https://chat.baidu.com http://mirror-chat.baidu.com ... | 限制哪些源可以嵌入当前页面的内容。允许从同源的 URL 加载内容,并列出了一些允许的 URL。 |
Content-Type | text/html; charset=utf-8 | 响应的内容类型是 HTML,字符集是 UTF-8。 |
Date | Fri, 15 Dec 2023 18:10:25 GMT | 响应的日期和时间。 |
Isprivate | 1 | 表示响应是私有的或需要某种特定的处理。 |
Server | BWS/1.1 | 表示服务器类型或版本,可能是百度 Web 服务器的版本。 |
Set-Cookie | H_PS_PSSID=...; path=/; expires=Sat, 14-Dec-24 18:10:25 GMT; domain=.baidu.com | 设置 cookie,通常用于跟踪用户的会话信息或保存一些特定的用户信息。 |
Traceid | 1702663825041124890613909208681376480177 | 可能用于追踪或调试的 ID,帮助开发者识别请求的来源或其他相关信息。 |
X-Ua-Compatible | IE=Edge,chrome=1 | 指示浏览器使用 Edge 模式(针对 Internet Explorer)或 Chrome 模式来渲染页面。 |
Content-Length | 459259 | 表示响应体的长度是 459259 字节。 |
更多响应头信息查阅官方文档:HTTP 标头(header) (opens new window)
# 10、HTTP 响应报文 - 响应体
TIP
响应体 与 请求体类似
注:
响应体的内容格式非常灵活,常见的响应体格式有
- HTML
- CSS
- JavaScript
- 图片
- 视频
- JSON
HTML、CSS、JavaScript 在网络中传输时,都是放在响应报文的响应体中返回给浏览器的
可点击以上不同类型的响应体,查看到不同格式的具体内容
# 二、HTTP 模块
TIP
深入浅出 NodeJS 的 HTTP 模块,创建 HTTP 服务,查看、获取、设置 HTTP 请求和响应报文应用实践,搭建静态资源服务、错误处理等。
# 1、创建 HTTP 服务
TIP
HTTP 模块可用来创建 HTTP 服务,有了 HTTP 服务后可处理浏览器发送的请求,并还可给浏览器返回响应。
从本节开始我们需要转换身份,原来我们前端项目开发的主战区是在 浏览器(HTML、CSS、JavaScript)主要是进行前端开发,即:页面构建、样式的控制、页面的交互这些都是前端的范畴。
现在我们开始进行后端的开发,主要是处理浏览器发送的请求,服务器返回对应的响应结果
在 /src/http-server.js
中,创建 HTTP 服务(需要 3 步)
// 1、导入 http 模块
const http = require("http");
// 2、创建服务对象
// http 对象
// createServer(创建服务)http 对象的 方法
// 返回结果:对象,使用 server 接收
// 接收一个实参:箭头函数、function 函数都可
// request(请求)和 response(响应)为形参,名称可以随意定义
// (request, response) => {} 该箭头函数在内部调用时 会接收两个实参
// request:是对请求报文的一个封装对象,可获取到请求报文中的请求内容(请求行、请求头、请求体 都可获取)
// response:对响应报文的封装对象,借助 response 对象可为浏览器设置响应结果(响应行、响应头、响应体 都可获取)
// 该回调函数执行时间:当服务接收到 HTTP 请求时就会执行
const server = http.createServer((request, response) => {
// 设置响应体
response.end("<h1>Received request</h1>"); // 收到请求
});
// 3、监听端口,启动服务
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
在命令行终端中,通过 node 命令运行
node .\src\http-server.js
# 1.1、HTTP 服务请求响应过程
TIP
当我们在命令行窗口中运行完以上命令后,3000 的端口就会被 NodeJS 脚本占用了
使用 Fiddler 查看请求和响应报文
# 1.2、HTTP 服务注意事项
- ①、命令行
Ctrl + C
停止服务 - ②、当服务启动后,更新代码必须重启服务才能生效
- ③、响应体内容有中文时会乱码,解决方法
// 在 createServer 方法的箭头函数体中新增如下代码
// 设置响应头,设置编码集防止乱码
response.setHeader("content-type", "text/html;charset=utf-8");
// 含义:服务端在返回结果时,添加一个响应头,值为 text/html;charset=utf-8
- ④、端口号被占用
解决办法
- 关闭当前正在运行监听端口的额服务(常用)
- 修改其他端口号
⑤、HTTP 协议默认端口为 80
端口
TIP
HTTP 服务在日常开发中常用的端口有 3000、8080、8090、9000、3128、8081、9080 等
如端口被其他程序占用,可使用 资源监视器 (windows 中) 找到占用端口的程序,然后使用任务管理器关闭对应的程序。
点击 “开始” -> 搜索 “资源监视器” -> 选择网络 “侦听端口” -> 点击按 “端口” 排序 -> 找到对应端口的 PID
打开 “任务管理器” -> 选择 “详细信息” -> 找到对应的 PID -> 点击右键 “结束进程” 即可
# 2、浏览器查看 HTTP 报文
TIP
在 Google 浏览器中,点击右键 “检查” 或 “F12” -> 打开 “NetWork” -> 点击 “当前 URL”
注:
- 点击请求标头,其中所列出的信息都是请求头
- 勾选 Raw(查看源代码)可看到请求行 和 请求头 所有的内容
- 此时,请求体为空
# 2.1、查看请求体内容
TIP
查看请求体,可通过发送 POST 请求来查看
在 /src/index.html
中发送 POST 请求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>查看请求体内容</title>
</head>
<body>
<form action="http://localhost:3000" method="post">
用户名:<input type="text" name="username" /> <br /><br />
密 码:<input type="password" name="password" />
<br /><br />
<input type="submit" value="登 录" />
</form>
</body>
</html>
注:
当请求中有请求体的内容时,NetWork 中就会多一个 Payload(载荷)的选项
- 默认的 Form Data 是格式化后的请求体内容(有键名 和 键值 组成)
- 原始的请求体内容,可点击 “view source” 查看
直接访问 “http://localhost:3000” 时,没有请求体内容,同时 NetWork 中也不会有 Payload(载荷)的选项
# 2.2、查看 URL 中的查询字符串
TIP
也可以通过 Payload(载荷)的选项来查看
- “Query String Parameters” 查询字符串格式化后的键值
- “view source” 查询字符串的原始内容
在浏览器中输入 “http://localhost:3000/?type=1&count=10”
# 3、浏览器查看响应报文
TIP
在 Google 浏览器中,点击右键 “检查” 或 “F12” -> 打开 “NetWork” -> 点击 “当前 URL”
注:
- 点击 “Response Headers” 查看相应头信息
- 勾选 Raw(查看源代码)可看到响应行 和 响应头 所有的内容
- 点击 “Response” 查看响应体的原始内容
通过以上演示,我们借助浏览器控制台即可查看到请求报文 和 响应报文的所有内容,不需要再打开额外的其他软件。
因此,后续项目开发中我们都会使用浏览器的控制台来查看 HTTP 报文。
# 4、获取 HTTP 请求报文
TIP
为什么需要获取 HTTP 请求的报文中的内容 ?
浏览器向服务器发送 HTTP 请求,获取请求报文的目的是为了服务器能给浏览器返回一个正确的结果(本质上就是为了明确服务器返回给浏览器的需求是什么)即想要返回浏览器想要的结果,就必须从请求报文中提取到相关的数据才行。(类似去饭店点餐,你得告诉服务员你的需求,才能迟到你想要的菜品)
获取请求的数据,需要通过 request 对象来实现
方法 | 描述 | 掌握程度 |
---|---|---|
request.method | 请求方法 | *** |
request.httpVersion | 请求版本 | * |
request.url | 请求路径 | *** |
require('url').parse(request.url).pathname | URL 路径 | *** |
require('url').parse(request.url, true).query | URL 查询字符串 | *** |
request.headers | 请求头 | *** |
request.on('data', function() { }) request.on('end', function() { }) | 请求体 | * |
注:
request.url
只能获取路径以及查询字符串,无法获取 URL 中的域名以及协议的内容request.headers
将请求信息转化成一个对象,并将属性名都转化成了 小写- 关于路径:如果访问网站的时候,只填写了 IP 地址或者是域名信息,此时请求的路径为
/
- 关于
favicon.ico
:这个请求是属于浏览器自动发送的请求
# 4.1、获取请求报文的过程
在 /src/http-server.js
中
// 1、导入 http 模块
const http = require("http");
// 2、创建服务对象
const server = http.createServer((request, response) => {
// 设置响应头,设置编码集防止乱码
response.setHeader("content-type", "text/html;charset=utf-8");
// 设置响应体
response.end("<h1>Received request 收到请求</h1>"); // 收到请求
});
// 3、监听端口,启动服务
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
在命令行终端中,通过 node 命令运行
node .\src\http-server.js
此时,在浏览器地址栏输入 http://localhost:3000
访问,在 “NetWork” 中查看请求报文
注:
在浏览器输入 URL 回车后,就会将在 “NetWork” 中看到的原始请求报文 发送给服务端的 9000 端口,由 http.createServer()
中的回调函数 (request, response) => {}
来处理该请求报文,同时还会返回响应报文。
因此,要获取请求报文中的内容,就必须在 回调函数
(request, response) => {}
里边才能获取到。
# 4.2、获取请求报文中的内容
在 /src/http-server.js
中
// 1、导入 http 模块
const http = require("http");
// 2、创建服务对象
const server = http.createServer((request, response) => {
// 设置响应头,设置编码集防止乱码
response.setHeader("content-type", "text/html;charset=utf-8");
// -----------------------------------
// 提取 HTTP 请求报文
// 获取请求方法
// console.log(request.method) // GET GET 控制台会打印两个 GET 是因为浏览器发送了两次请求,其中一个是 favicon.ico 图标请求
// 获取请求的 URL
// console.log(request.url) // 只包含 url 中的路径 与 查询字符串
// 获取 HTTP 协议的版本号
// console.log(request.httpVersion) // 1.1
// 获取 HTTP 的请求头
// console.log(request.headers) // 对象,其中键值是请求头中的内容
// 获取请求头其中的主机名
console.log(request.headers.host); // localhost:3000
// -----------------------------------
// 设置响应体
response.end("<h1>Received request 收到请求</h1>"); // 收到请求
});
// 3、监听端口,启动服务
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
# 5、获取 HTTP 请求报文 - 请求体中的内容
TIP
获取 HTTP 报文的请求体内容
在 /src/request-body.js
中
// 1、导入 http 模块
const http = require("http");
// 2、创建服务对象
const server = http.createServer((request, response) => {
// 设置响应头,设置编码集防止乱码
response.setHeader("content-type", "text/html;charset=utf-8");
// 获取请求体内容,共 3 步
// ①、声明一个变量,用该变量接收请求体的结果
let body = "";
// ②、绑定 data 事件,在可读流对象中,获取可读流对象中的内容,就需要绑定 data 事件,然后一点点将内容取出来(而 request 对象本身就是一个可读流对象,因此可通过可读流的方式将请求体中的内容一点点的取出来)
// chunk 表示每次取出一部分
request.on("data", (chunk) => {
// 将取出的一部分存储在 body 变量中
body += chunk;
});
// ③、绑定 end 事件,当我们把数据取完后所触发的事件(即:将可读流中的数据读完后,就会触发 end 事件)
request.on("end", () => {
// 打印输出
console.log(body);
// 浏览器端的响应结果
response.end("<h1>Received request 收到请求</h1>"); // 收到请求
});
});
// 3、监听端口,启动服务
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
在命令行终端中,通过 node 命令运行
node .\src\request-body.js
在浏览器地址栏输入 http://localhost:3000
回车,此时查看 VSCode 控制台打印输出的结果
为什么打印输出结果为空呢 ?
因为,在浏览器中输入 URL,回车后发送的 HTTP 请求为 GET 请求,而 GET 请求的请求体一般都是空的,所以在 VSCode 的控制台中输出的请求体内容也为空。
要想在控制台输出请求体内容,就必须要发送一个请求体不为空的请求才可以。
在 /src/index.html
中通过 form 表单发送一个 POST 请求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>查看请求体内容</title>
</head>
<body>
<form action="http://localhost:3000" method="post">
用户名:<input type="text" name="username" /> <br /><br />
密 码:<input type="password" name="password" />
<br /><br />
<input type="submit" value="登 录" />
</form>
</body>
</html>
在浏览器中输入 http://127.0.0.1:5500/src/index.html
访问 form 请求页面,点击登录
注:
此时, VSCode 的控制台中就会输出请求体的内容了。以上获取请求体内容的方法了解即可,后边还有更简洁的方法来提取报文的请求体。
# 6、获取 HTTP 请求报文 - 请求路径与查询字符串
TIP
提取 HTTP 请求报文中的 请求路径 和 查询字符串这个两个部分,很重要
注:
很重要的原因在于,服务器端返回不同的结果是根据 URL 路径 或 查询字符串来判定的。
不同的 URL 路径返回不同的结果
- https://baike.baidu.com/vbaike
- https://baike.baidu.com/calendar
- https://baike.baidu.com/museum
不同的查询字符串返回不同的结果,即根据 wd 参数的不同来返回不同结果的
- https://www.baidu.com/s?wd=ibc
- https://www.baidu.com/s?wd=abc
- https://www.baidu.com/s?wd=123
因此,我们作为服务提供者会根据 URL 路径 和 查询字符串来返回用户想要的结果
# 6.2、获取请求路径
在 /src/url-query-string.js
中
// 导入 http 模块
const http = require("http");
// 1、导入 url 模块
const url = require("url");
// 创建服务对象
const server = http.createServer((request, response) => {
// 2、解析 request.url (该结果中包含了 url 路径 和 查询字符串)
// console.log(request.url) // /search/?type=1&count=10
// 只需要 url 路径 或 查询字符串(parse 方法用来解析 url)
let res = url.parse(request.url); // 解析完成后是一个对象
// 3、获取路径(对象中的属性 pathname 为 url 路径)
let pathname = res.pathname;
console.log(pathname); // /search/
// 浏览器端的响应结果
response.end("<h1>URL - QueryString</h1>"); // 收到请求
});
// 监听端口,启动服务
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
在命令行终端中,通过 node 命令运行
node .\src\url-query-string.js
在浏览器的地址栏中输入 http://localhost:3000/search/?type=1&count=10
回车,查看 VSCode 命令行终端的打印输出
# 6.3、获取查询字符串
在 /src/url-query-string.js
中
// 导入 http 模块
const http = require("http");
// 1、导入 url 模块
const url = require("url");
// 创建服务对象
const server = http.createServer((request, response) => {
// 2、解析 request.url (该结果中包含了 url 路径 和 查询字符串)
// 只需要 url 路径 或 查询字符串(parse 方法用来解析 url)
let res = url.parse(request.url);
console.log(res);
// 浏览器端的响应结果
response.end("<h1>URL - QueryString</h1>"); // 收到请求
});
// 监听端口,启动服务
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
启动服务,在浏览器的地址栏中输入 http://localhost:3000/search/?type=1&count=10
回车,查看 VSCode 命令行终端的打印输出
如果我们需要获取查询字符串中
type
或count
的值就会非常麻烦
此时,我们可以使用 url.parse()
方法中的第二个参数 parseQueryString
,将该参数设置为 true
,parse()
方法就会将 查询字符串的值 返回为对象
在 /src/url-query-string.js
中
// 导入 http 模块
const http = require("http");
// 1、导入 url 模块
const url = require("url");
// 创建服务对象
const server = http.createServer((request, response) => {
// 2、解析 request.url (该结果中包含了 url 路径 和 查询字符串)
// 只需要 url 路径 或 查询字符串(parse 方法用来解析 url)
let res = url.parse(request.url, true);
console.log(res);
// 3、获取查询字符串 中的 type 和 count 属性的值
let type = res.query.type;
let count = res.query.count;
console.log(type, count); // 1 10
// 浏览器端的响应结果
response.end("<h1>URL - QueryString</h1>"); // 收到请求
});
// 监听端口,启动服务
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
启动服务,在浏览器的地址栏中输入 http://localhost:3000/search/?type=1&count=10
回车,查看 VSCode 命令行终端的打印输出
注:
url.parse() (opens new window) 在 Node.js V20.x 版本中已弃用,改用 WHATWG URL (opens new window) API
# 7、获取请求路径与查询字符串 - 新方法
TIP
与上边实现同样的需求:获取 HTTP 请求报文中的 请求路径 与 查询字符串的值,本次使用另一种方法来实现。
在 /src/url-query-string-new.js
中
// 导入 http 模块
const http = require("http");
// 创建服务对象
const server = http.createServer((request, response) => {
// 使用新方法 URL 来提取查询字符串
// 实例化 URL 的对象(实例化是可接收一个参数 也可 接收两个参数)
let url = new URL("https://www.ibc.com/search?type=1&count=10");
// 打印输出 url 对象
console.log(url);
// 浏览器端的响应结果
response.end("<h1>URL - QueryString - New</h1>"); // 收到请求
});
// 监听端口,启动服务
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
启动服务,在浏览器的地址栏中输入 http://localhost:3000/search/?type=1&count=10
回车,查看 VSCode 命令行终端的打印输出
注:
在以上控制台输出的 URL 对象中可以看到相关的信息,要获取 请求路径 和 查询字符串就变的容易了
在 /src/url-query-string-new.js
中,使用 实例化 URL 对象,两个参数的情况
// 导入 http 模块
const http = require("http");
// 创建服务对象
const server = http.createServer((request, response) => {
// 使用新方法 URL 来提取查询字符串
// 实例化 URL 的对象(实例化是可接收一个参数 也可 接收两个参数)
let url = new URL("/search?type=1&count=10", "https://www.ibc.com");
// 打印输出 url 对象
console.log(url);
// 浏览器端的响应结果
response.end("<h1>URL - QueryString - New</h1>"); // 收到请求
});
// 监听端口,启动服务
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
启动服务,在浏览器的地址栏中输入 http://localhost:3000/search/?type=1&count=10
回车,查看 VSCode 命令行终端的打印输出,与一个参数输出的结果一样。
在 /src/url-query-string-new.js
中,使用 实例化 URL 对象,两个参数的情况(动态获取 请求路径 和 查询字符串)
// 导入 http 模块
const http = require("http");
// 创建服务对象
const server = http.createServer((request, response) => {
// 使用新方法 URL 来提取查询字符串
// 实例化 URL 的对象(实例化是可接收一个参数 也可 接收两个参数)
// let url = new URL('/search?type=1&count=10', 'https://www.ibc.com')
// 将以上参数替换为 动态获取 request.url
let url = new URL(request.url, "http://127.0.0.1:3000");
// 打印输出 url 对象
console.log(url);
// 浏览器端的响应结果
response.end("<h1>URL - QueryString - New</h1>"); // 收到请求
});
// 监听端口,启动服务
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
启动服务,在浏览器的地址栏中输入 http://localhost:3000/search/?type=1&count=10
回车,查看 VSCode 命令行终端的打印输出,动态获取的 请求路径 和 查询字符串 。
通过 searchParams 属性的
get()
方法获取查询字符串type
和count
的值
在 /src/url-query-string-new.js
中
// 导入 http 模块
const http = require("http");
// 创建服务对象
const server = http.createServer((request, response) => {
// 使用新方法 URL 来提取查询字符串
// 实例化 URL 的对象(实例化是可接收一个参数 也可 接收两个参数)
// let url = new URL('/search?type=1&count=10', 'https://www.ibc.com')
// 将以上参数替换为 动态获取 request.url
let url = new URL(request.url, "http://127.0.0.1:3000");
// 获取 请求路径
console.log(url.pathname); // /search/
// 获取查询字符串 type 和 count 的值
console.log(url.searchParams.get("type"), url.searchParams.get("count")); // 1 10
// 打印输出 url 对象
console.log(url);
// 浏览器端的响应结果
response.end("<h1>URL - QueryString - New</h1>"); // 收到请求
});
// 监听端口,启动服务
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
启动服务,在浏览器的地址栏中输入 http://localhost:3000/search/?type=1&count=10
回车,查看 VSCode 命令行终端的打印输出,动态获取的 请求路径 和 查询字符串 。
# 8、HTTP 请求 - 应用实践
TIP
需求:搭建一个 HTTP 服务,根据不同的请求地址,返回不同的响应结果
- 当浏览器向服务发送请求类型为 get ,请求地址为
/list
,则返回响应结果为 “列表页面” - 当浏览器向服务发送请求类型为 get ,请求地址为
/detail
,则返回响应结果为 “详情页面”
在 /src/http-application.js
中
// 1、导入 http 模块
const http = require("http");
// 2、创建服务对象
const server = http.createServer((request, response) => {
// 设置响应头,设置编码集防止乱码
response.setHeader("content-type", "text/html;charset=utf-8");
// 获取请求类型
let { method } = request;
// 获取请求 URL 路径
let { pathname } = new URL(request.url, "http://127.0.0.1:3000");
console.log(method, pathname);
// 判断请求类型 和 请求 URL 路径
if (method === "GET" && pathname === "/list") {
// 设置响应体 - 返回列表页面
response.end("<h1>列表页面</h1>");
} else if (method === "GET" && pathname === "/detail") {
// 设置响应体 - 返回详情页面
response.end("<h1>详情页面</h1>");
}
});
// 3、监听端口,启动服务
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
启动服务,在浏览器的地址栏中输入 http://localhost:3000/list
和 http://localhost:3000/detail
回车,都可以正常访问,但 favicon.ico
图标请求处于 “待处理” 状态
注:
待处理 状态的请求会一直处于请求待处理状态,服务端也并没有返回资源,这样持续建立连接只会一直占用资源,并不会产生任何效果
解决办法,增加 else
返回响应
// 1、导入 http 模块
const http = require("http");
// 2、创建服务对象
const server = http.createServer((request, response) => {
// 设置响应头,设置编码集防止乱码
response.setHeader("content-type", "text/html;charset=utf-8");
// 获取请求类型
let { method } = request;
// 获取请求 URL 路径
let { pathname } = new URL(request.url, "http://127.0.0.1:3000");
console.log(method, pathname);
// 判断请求类型 和 请求 URL 路径
if (method === "GET" && pathname === "/list") {
// 设置响应体 - 返回列表页面
response.end("<h1>列表页面</h1>");
} else if (method === "GET" && pathname === "/detail") {
// 设置响应体 - 返回详情页面
response.end("<h1>详情页面</h1>");
} else {
response.end("<h1>Not Found</h1>");
}
});
// 3、监听端口,启动服务
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
此时,再次访问
favicon.ico
就没问题了
# 9、设置 HTTP 响应报文
TIP
如何在 Node.js 中设置 HTTP 的响应报文
语法 | 作用 |
---|---|
response.statusCode | 设置响应状态码 |
response.statusMessage | 设置响应状态描述 |
response.setHeader('头名称', '头值') | 设置响应头信息 |
response.write('xxx') response.end('xxx') | 设置响应体 |
在 /src/http-response-message.js
中,设置 HTTP 响应报文
// 导入 http 模块
const http = require("http");
// 创建服务对象
const server = http.createServer((request, response) => {
// 1、设置响应状态码(根据需求设置不同的状态码)
// response.statusCode = 200
// response.statusCode = 500
// 2、响应状态描述(实际开发中,很少用到,默认和状态码同步)
response.statusMessage = "icodingedu";
// 3、响应头(设置编码集防止乱码)
response.setHeader("content-type", "text/html;charset=utf-8");
// 也可以设置其他的头信息(名称可自定义)
response.setHeader("Server", "Node.js");
// 测试
response.setHeader("myHeader", "Test response header settings");
// 响应头也可以设置多个同名的名称,如 set-Cookie
response.setHeader("myTest", ["aaa", "bbb", "ccc"]);
// 4、响应体的设置(可应用 write 也可用 end,设置了 write 就不再设置 end 了)
// write 是可以多次调用的 并 返回响应体
response.write("<p>我是 write 响应体 </p>");
response.write("<p>我是 write 响应体 </p>");
response.write("<p>我是 write 响应体 </p>");
response.write("<p>我是 write 响应体 </p>");
response.write("<p>我是 write 响应体 </p>");
// 注:每一个请求,在执行回调函数的过程中 必须得有一个 end 方法(有且只能有一个 end 方法执行,不能有多个)
response.end("我是 end 响应体 - Received request 收到请求"); // 设置响应体
// response.end('我是 end 响应体 - Received request 收到请求') // 设置响应体
});
// 监听端口,启动服务
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
# 10、HTTP 响应 - 应用实践
TIP
需求:搭建一个 HTTP 服务,响应一个 5 行 4 列的表格,并且要求表格有 隔行换色的效果,并且点击单元格能高亮显示。
在 /src/response-table.js
中
// 导入 http 模块
const http = require("http");
// 创建服务对象
const server = http.createServer((request, response) => {
response.end(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>table 表格</title>
<style>
table {
border-collapse: collapse;
width: 80%;
margin: 50px auto;
}
table tr th,
tbody tr td,
tfoot tr td {
text-align: center;
line-height: 40px;
border: 1px solid #000;
}
/* nth-child(2n) */
table tbody tr:nth-child(even) {
background-color: azure;
}
/* nth-child(2n + 1) */
table tbody tr:nth-child(odd) {
background-color: beige;
}
</style>
</head>
<body>
<table>
<thead>
<tr>
<th>专业</th>
<th>姓名</th>
<th>性别</th>
<th>年龄</th>
<th>所在城市</th>
</tr>
</thead>
<tbody class="box">
<tr>
<td>计算机</td>
<td>arry</td>
<td>男</td>
<td>18</td>
<td>北京</td>
</tr>
<tr>
<td>外语</td>
<td>豆豆</td>
<td>女</td>
<td>21</td>
<td>上海</td>
</tr>
<tr>
<td>市场营销</td>
<td>翠花</td>
<td>19</td>
<td>男</td>
<td>深圳</td>
</tr>
<tr>
<td>软件工程</td>
<td>光头强</td>
<td>21</td>
<td>男</td>
<td>广州</td>
</tr>
</tbody>
<tfoot>
<tr>
<th>备注</th>
<td colspan="4"></td>
</tr>
</tfoot>
</table>
<script>
// 获取所有的 td
let tds = document.querySelectorAll(".box td");
let sel;
// 遍历每一个 td
tds.forEach((item) => {
item.onclick = function (e) {
// 阻止事件冒泡
e.stopPropagation();
if (sel) {
sel.style.background = "";
}
this.style.background = "coral";
sel = this;
};
});
// 点击
document.body.onclick = function () {
sel.style.background = "";
};
</script>
</body>
</html>
`);
});
// 监听端口,启动服务
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
启动服务,在浏览器的地址栏中输入 http://localhost:3000
回车查看效果,点击单元格即可选中
注:
以上虽然已经实现我们想要的效果了,但我们在 JS 的文件中书写 HTML、CSS、前端 JS 是非常痛苦的,既没有代码提示也没有代码高亮,体验非常不好。
思考:有没有一种方法,在 NodeJS 中书写 HTML、CSS、前端 JS 代码时 有语法提示 和 代码高亮呢 ?
# 11、优化 NodeJS 中的前端代码
TIP
在 NodeJS 中书写 HTML、CSS、前端 JS 代码时,让代码有语法提示 和 代码高亮
实现方法:将 前端代码部分提取到一个单独的 HTML 文件中
新建 /src/table.html
文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>table 表格</title>
<style>
table {
border-collapse: collapse;
width: 80%;
margin: 50px auto;
}
table tr th,
tbody tr td,
tfoot tr td {
text-align: center;
line-height: 40px;
border: 1px solid #000;
}
/* nth-child(2n) */
table tbody tr:nth-child(even) {
background-color: azure;
}
/* nth-child(2n + 1) */
table tbody tr:nth-child(odd) {
background-color: beige;
}
</style>
</head>
<body>
<table>
<thead>
<tr>
<th>专业</th>
<th>姓名</th>
<th>性别</th>
<th>年龄</th>
<th>所在城市</th>
</tr>
</thead>
<tbody class="box">
<tr>
<td>计算机</td>
<td>arry</td>
<td>男</td>
<td>18</td>
<td>北京</td>
</tr>
<tr>
<td>外语</td>
<td>豆豆</td>
<td>女</td>
<td>21</td>
<td>上海</td>
</tr>
<tr>
<td>市场营销</td>
<td>翠花</td>
<td>19</td>
<td>男</td>
<td>深圳</td>
</tr>
<tr>
<td>软件工程</td>
<td>光头强</td>
<td>21</td>
<td>男</td>
<td>广州</td>
</tr>
</tbody>
<tfoot>
<tr>
<th>备注</th>
<td colspan="4"></td>
</tr>
</tfoot>
</table>
<script>
// 获取所有的 td
let tds = document.querySelectorAll(".box td");
let sel;
// 遍历每一个 td
tds.forEach((item) => {
item.onclick = function (e) {
// 阻止事件冒泡
e.stopPropagation();
if (sel) {
sel.style.background = "";
}
this.style.background = "coral";
sel = this;
};
});
// 点击
document.body.onclick = function () {
sel.style.background = "";
};
</script>
</body>
</html>
在 /src/response-table.js
中,读取 /src/table.html
文件的内容
// 导入 http 模块
const http = require("http");
const fs = require("fs");
// 创建服务对象
const server = http.createServer((request, response) => {
// 读取 table.html 文件内容
const html = fs.readFileSync(__dirname + "/table.html");
response.end(html); // 设置响应体
});
// 监听端口,启动服务
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
注:
通过将前端代码单独拆分出来,并通过 fs 模块读取进来是可行的。这样更新前端部分的代码会非常的快捷、方便,NodeJS 服务也不用重启就能直接访问。
# 12、网页资源加载的过程
TIP
以京东、小米为例,观察网页相关资源的加载过程
注:
以上是京东 和 小米的网站,在浏览器地址栏中输入 URL 时,浏览器中可以看到
- 京东向服务器发送了 189 次请求,点击查看第一个请求时 301 重定向,第二个请求是用来获取 HTML 内容
- 小米向服务器发送了 60 次请求,点击查看第一个请求时 301 重定向,第二个请求是用来获取 HTML 内容
都是服务器先返回 HTML 内容,然后再去加载 CSS、JS 、图片等内容,整个加载的过程就是网页相关资源的加载过程。
# 13、HTTP 响应实现网页引入外部资源
TIP
将 table.html
中的 CSS、JS 单独提取出来,项目目录结构如下
icoding-http
└─ src
├─ table
│ ├─ response-table.js
│ ├─ table.css
│ ├─ table.html
│ └─ table.js
在 /src/table.html
中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>table 表格</title>
<link rel="stylesheet" href="./table.css" />
</head>
<body>
<table>
<thead>
<tr>
<th>专业</th>
<th>姓名</th>
<th>性别</th>
<th>年龄</th>
<th>所在城市</th>
</tr>
</thead>
<tbody class="box">
<tr>
<td>计算机</td>
<td>arry</td>
<td>男</td>
<td>18</td>
<td>北京</td>
</tr>
<tr>
<td>外语</td>
<td>豆豆</td>
<td>女</td>
<td>21</td>
<td>上海</td>
</tr>
<tr>
<td>市场营销</td>
<td>翠花</td>
<td>19</td>
<td>男</td>
<td>深圳</td>
</tr>
<tr>
<td>软件工程</td>
<td>光头强</td>
<td>21</td>
<td>男</td>
<td>广州</td>
</tr>
</tbody>
<tfoot>
<tr>
<th>备注</th>
<td colspan="4"></td>
</tr>
</tfoot>
</table>
<script src="./table.js"></script>
</body>
</html>
在 /src/table.css
中
table {
border-collapse: collapse;
width: 80%;
margin: 50px auto;
}
table tr th,
tbody tr td,
tfoot tr td {
text-align: center;
line-height: 40px;
border: 1px solid #000;
}
/* nth-child(2n) */
table tbody tr:nth-child(even) {
background-color: azure;
}
/* nth-child(2n + 1) */
table tbody tr:nth-child(odd) {
background-color: beige;
}
在 /src/table.js
中
// 获取所有的 td
let tds = document.querySelectorAll(".box td");
let sel;
// 遍历每一个 td
tds.forEach((item) => {
item.onclick = function (e) {
// 阻止事件冒泡
e.stopPropagation();
if (sel) {
sel.style.background = "";
}
this.style.background = "coral";
sel = this;
};
});
// 点击
document.body.onclick = function () {
sel.style.background = "";
};
在 /src/response-table.js
中
// 导入 http 模块
const http = require("http");
const fs = require("fs");
// 创建服务对象
const server = http.createServer((request, response) => {
// 读取 table.html 文件内容
const html = fs.readFileSync(__dirname + "/table.html");
response.end(html); // 设置响应体
});
// 监听端口,启动服务
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
启动服务,在浏览器的地址栏中输入 http://localhost:3000
回车查看效果,发现并没有 CSS 样式 和 JS 效果
注:
当我们将 table.html
中的 CSS、JS 单独拆分出来后,样式 和 JS 效果都没了,原因是当 <link>
和 <script>
标签被解析时,向服务器发请求,一旦请求到服务端就会给响应 HTML(以上代码中的 response.end(html)
),不论什么请求(以上的 CSS 和 JS 、包括 favicon.ico
图标的请求)到达端口 3000 后,它的返回结果都是 HTML。
因此,主要的问题就在于无论任何请求,响应的结果都是 HTML。要想解决这个问题,就需要调整回调函数中的代码,即:根据请求的 URL 路径来返回 和 响应对应的结果。
在 /src/response-table.js
中
// 导入 http 模块
const http = require("http");
const fs = require("fs");
// 创建服务对象
const server = http.createServer((request, response) => {
// 获取请求 URL 的路径
let { pathname } = new URL(request.url, "http://127.0.0.1");
// 根据请求的 URL 路径来响应对应的结果
if (pathname === "/") {
// 读取 table.html 文件内容
const html = fs.readFileSync(__dirname + "/table.html");
response.end(html); // 设置响应体
} else if (pathname === "/table.css") {
// 读取 table.css 文件内容
const css = fs.readFileSync(__dirname + "/table.css");
response.end(css); // 设置响应体
} else if (pathname === "/table.js") {
// 读取 table.js 文件内容
const js = fs.readFileSync(__dirname + "/table.js");
response.end(js); // 设置响应体
} else {
response.statusCode = 404;
response.end("<h1>404 Not Found</h1>");
}
});
// 监听端口,启动服务
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
启动服务,在浏览器的地址栏中输入 http://localhost:3000
回车查看效果,此时已经 OK
注:
此时,每一个响应的结果就对应上了
localhost
对应 HTML 结构table.css
对应 CSS 样式table.js
对应 JS 代码
以上将每一个单独的资源拆分开后效果出来了,同时也存在问题,在资源很少的情况下 /src/response-table.js
中的可以,但资源多(项目中很多 CSS、JS)的情况下就会非常麻烦了(就需要写很多 if ... else ...
判断)。后边会讲对应解决方案来简化该操作。
# 14、静态资源 和 动态资源
TIP
- 静态资源:内容长时间不发生改变的资源,例如图片、视频、CSS 文件、JS 文件、 HTML 文件、字体文件等(注:这里的静态资源是指项目在运行过程中的图片、视频、CSS 文件、JS 文件、 HTML 文件等 ... 而非在 项目开发中的这些资源)
- 动态资源:内容经常更新的资源,例如天猫首页,京东搜索列表结果页面等
# 15、搭建静态资源服务
TIP
静态资源服务:可以很方便的给客户端浏览器响应静态资源(HTML、CSS、JS、图片、JSON 等)
以下应用中,将会解决以上案例中的痛点(即:为了响应 HTML、CSS、JS 做了很多 if ... else
判断,再复杂的还需要写更多的 if ... else
)
需求:创建一个 HTTP 服务,端口为 3000,满足如下需求
- 浏览器发送 GET 请求 ,请求路径为
/index.html
,服务端响应page/index.html
的文件内容 - 浏览器发送 GET 请求,请求路径为
/css/index.css
,服务端响应page/css/index.css
的文件内容 - 浏览器发送 GET 请求,请求路径为
/images/banner.png
,服务端响应page/images/banner.png
的文件内容
项目目录结构
icoding-http
└─ src
├─ server
│ ├─ page
│ │ ├─ css
│ │ │ └─ index.css
│ │ ├─ images
│ │ │ └─ banner.png
│ │ ├─ index.html
│ │ └─ js
│ │ └─ index.js
│ └─ static-services.js
在 /src/server/page/index.html
中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>搭建静态资源服务</title>
</head>
<body>
<h1>HTML 静态页面</h1>
</body>
</html>
在 /src/server/page/css/index.css
中
html {
color: #000;
overflow-y: scroll;
overflow: -moz-scrollbars;
}
在 /src/server/page/js/index.js
中
console.log("JS 输出的内容 !");
在 /src/server/static-services.js
中
// 导入 http 模块
const http = require("http");
const fs = require("fs");
// 创建服务对象
const server = http.createServer((request, response) => {
// 获取请求 URL 的路径
let { pathname } = new URL(request.url, "http://127.0.0.1");
// 根据请求的 URL 路径来响应对应的结果
if (pathname === "/index.html") {
// 读取文件内容
const html = fs.readFileSync(__dirname + "/page/index.html");
response.end(html); // 设置响应体
} else if (pathname === "/css/index.css") {
// 读取文件内容
const css = fs.readFileSync(__dirname + "/page/css/index.css");
response.end(css); // 设置响应体
} else if (pathname === "/images/banner.png") {
// 读取文件内容
const images = fs.readFileSync(__dirname + "/page/images/banner.png");
response.end(images); // 设置响应体
} else {
response.statusCode = 404;
response.end("<h1>404 Not Found</h1>");
}
});
// 监听端口,启动服务
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
启动服务,在浏览器的地址栏中输入对应的请求地址,回车查看效果
以上效果已经实现了,但有一个问题 !以上项目中有一个 /src/server/page/js/index.js
如果直接给服务端发请求路径为 http://localhost:3000/js/index.js
能否响应 index.js
中的内容 ?
注:
显然不能获取 index.js
中的内容,原因是并没有对应 JS 的 if ... else
判断 ,但我们又需要得到 JS 文件中的效果,如果再使用 if ... else
判断就重蹈覆辙了,并没有意义。有没有简化操作的方法呢 ?
# 16、搭建静态资源服务 - 优化
TIP
观察 URL 的请求 和 最终响应的 文件路径是有关联的(即:找规律)将相同的路径部分提取出来
在 /src/server/static-services.js
中
// 导入 http 模块
const http = require("http");
const fs = require("fs");
// 创建服务对象
const server = http.createServer((request, response) => {
// 获取请求 URL 的路径
let { pathname } = new URL(request.url, "http://127.0.0.1");
// 拼接文件路径(提取相同的路径部分)
let filePath = __dirname + "/page" + pathname;
// 读取文件(使用 fs 异步 API)
fs.readFile(filePath, (err, data) => {
if (err) {
response.statusCode = 500;
response.end("文件读取失败 !");
return;
}
// 响应文件内容
response.end(data);
});
// 根据请求的 URL 路径来响应对应的结果
// if (pathname === '/index.html') {
// // 读取文件内容
// const html = fs.readFileSync(__dirname + '/page/index.html')
// response.end(html) // 设置响应体
// } else if (pathname === '/css/index.css') {
// // 读取文件内容
// const css = fs.readFileSync(__dirname + '/page/css/index.css')
// response.end(css) // 设置响应体
// } else if (pathname === '/images/banner.png') {
// // 读取文件内容
// const images = fs.readFileSync(__dirname + '/page/images/banner.png')
// response.end(images) // 设置响应体
// } else {
// response.statusCode = 404
// response.end('<h1>404 Not Found</h1>')
// }
});
// 监听端口,启动服务
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
启动服务,在浏览器的地址栏中输入对应的请求地址,回车查看效果,即便再往 js 文件夹中新增文件也能同样访问(乱码问题后边再解决)
# 17、设置 MIME 类型
TIP
通过设置 MIME 类型来完善以上的静态资源服务
媒体类型:Multipurpose Internet Mail Extensions(直译:多用途的邮件扩展,一般不会用直译)会称之为 “媒体类型”、“资源类型” 或 MIME 类型
它是一种标准,用来表示文档、文件 或 字节流的性质和格式。
mime 类型结构:[type] / [subType] 即:[主类型] / [子类型]
如:text/html text/css image/jpeg image/png application/json
HTTP 服务可以设置响应头 Content-Type 来表明响应体的 MIME 类型,浏览器会根据该类型决定如何处理资源。如下是常见文件对应的 MIME 类型
html: 'text/html',
css: 'text/css',
js: 'text/javascript',
png: 'image/png',
jpg: 'image/jpeg',
gif: 'image/gif',
mp4: 'video/mp4',
mp3: 'audio/mpeg',
json: 'application/json'
对未知的资源类型,可以选择 application/octet-stream
类型,浏览器在遇到该类型响应时,会对响应体内容进行独立存储,也就是我们常见的 下载 效果
在 /src/server/static-services.js
中
// 导入 http 模块
const http = require("http");
const fs = require("fs");
const path = require("path");
// 在实际项目开发中不会这么做,会有单独的模块来实现 MIME 类型的设置
// 声明一个变量,来保存自定义的 MIME 类型
let mimes = {
html: "text/html",
css: "text/css",
js: "text/javascript",
png: "image/png",
jpg: "image/jpeg",
gif: "image/gif",
mp4: "video/mp4",
mp3: "audio/mpeg",
json: "application/json",
};
// 创建服务对象
const server = http.createServer((request, response) => {
// 获取请求 URL 的路径
let { pathname } = new URL(request.url, "http://127.0.0.1");
// 拼接文件路径
let filePath = __dirname + "/page" + pathname;
// 读取文件(使用 fs 异步 API)
fs.readFile(filePath, (err, data) => {
if (err) {
response.statusCode = 500;
response.end("文件读取失败 !");
return;
}
// 2、获取文件的后缀名
// let ext = path.extname(filePath) // .html .css .js .png ......
let ext = path.extname(filePath).slice(1); // 截取文件后缀名中的 点 .
console.log(ext); // html css js png ......
// 3、获取后缀对应的类型
let type = mimes[ext];
// 有可能获取到的类型不存在,需要判断
if (type) {
// 存在(匹配到了)
response.setHeader("content-type", type);
} else {
// 不存在(没有匹配到)
response.setHeader("content-type", "application/octet-stream");
}
// ------------------------
// 1、在读取文件内容后,设置响应头
// 当前位置的响应头显然不能写死,因为不确定它响应的是 HTML、CSS 还是 JS
// 因此,我们可以根据请求文件的后缀来决定 MIME 类型
// response.setHeader('content-type', '')
// 响应文件内容
response.end(data);
});
});
// 监听端口,启动服务
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
启动服务,在浏览器的地址栏中输入对应的请求地址,回车查看效果,在 NetWork 中查看响应头信息 content-type
的值
注:
你会发现以上 MIME 类型设置部分的代码,添加 和 不添加 貌似对最终结果显示并没有任何影响,但添加上会更为规范。
不添加也能正常显示的原因是:浏览器有一个能力它会根据响应回来的内容来判定该资源的类型,因此不添加类型也是可以的。但添加上更为规范 !
# 18、乱码问题
TIP
在添加完 MIME 类型后,会出现中文乱码问题(在 CSS、JS 出现中文都会乱码)
解决方法:只需要在设置 MIME 类型后边,添加上
urf-8
的字符集即可
在 /src/server/static-services.js
中,添加字符集编码
const http = require("http");
const fs = require("fs");
const path = require("path");
let mimes = {
html: "text/html",
css: "text/css",
js: "text/javascript",
png: "image/png",
jpg: "image/jpeg",
gif: "image/gif",
mp4: "video/mp4",
mp3: "audio/mpeg",
json: "application/json",
};
const server = http.createServer((request, response) => {
let { pathname } = new URL(request.url, "http://127.0.0.1");
let filePath = __dirname + "/page" + pathname;
fs.readFile(filePath, (err, data) => {
if (err) {
response.statusCode = 500;
response.end("文件读取失败 !");
return;
}
let ext = path.extname(filePath).slice(1);
console.log(ext);
let type = mimes[ext];
if (type) {
// 在设置 MIME 类型后边,添加上 urf-8 的字符集即可
response.setHeader("content-type", type + ";charset=utf-8");
} else {
response.setHeader("content-type", "application/octet-stream");
}
response.end(data);
});
});
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
启动服务,在浏览器的地址栏中输入对应的请求地址,回车查看效果
注:
添加完 utf-8
字符集后,CSS 和 JS 里边的中文乱码问题已解决,如果在不设置响应头中的编码集时,CSS 和 JS 中出现中文会乱码,但 /index.html
中的中文是不会乱码的
原因是:在 HTML 文件中有一个 <meta charset="UTF-8" />
标签,已经设置了 HTML 网页的字符集,因此 HTML 文件不会乱码。
由此,我们可以看出设置网页字符集的方式有两种:一种是 设置响应头,一种是 <meta charset="UTF-8" />
标签。那么他们的优先级谁更高呢 ?
# 18.1、测试优先级
TIP
调换以上代码 <meta charset="UTF-8" />
标签 和 response.setHeader('content-type', type + ';charset=utf-8')
中 charset
值,将其设置为 gbk
或 utf-8
互换,测试
由此,可得出结论 设置为响应头 response.setHeader('content-type', type + ';charset=utf-8')
中的字符集优先级更高
在绝大多数情况下,两者会保持统一(两者的值一致),不会搞差异化
# 18.2、网页外部资源的字符集
TIP
对于网页外部资源(CSS、JS、图片)在设置响应时,是没有必要设置字符集的。这些资源在进入到网页中后,都会根据网页的字符集来对响应结果做处理
注:
以上京东 www.jd.com
请求中 content-type: text/html;charset=utf-8
是有字符集的,在 CSS、JS 中没有字符集。
而这些资源在进入到网页中后,都是按照网页的字符集 即:utf-8
的字符集来对最终的结果做处理,因此不会产生问题。
当然,在没有设置字符集的 CSS、JS 中有中文出现时也会乱码,即便是乱码也不会影响在网页中的执行。因为这些资源在回到网页中后它会以网页的字符集来解析,这样就不会出现问题。
做到跟京东一模一样的字符集设置,在 /src/server/static-services.js
中进行判断
const http = require("http");
const fs = require("fs");
const path = require("path");
let mimes = {
html: "text/html",
css: "text/css",
js: "text/javascript",
png: "image/png",
jpg: "image/jpeg",
gif: "image/gif",
mp4: "video/mp4",
mp3: "audio/mpeg",
json: "application/json",
};
const server = http.createServer((request, response) => {
let { pathname } = new URL(request.url, "http://127.0.0.1");
let filePath = __dirname + "/page" + pathname;
fs.readFile(filePath, (err, data) => {
if (err) {
response.statusCode = 500;
response.end("文件读取失败 !");
return;
}
let ext = path.extname(filePath).slice(1);
console.log(ext);
let type = mimes[ext];
if (type) {
// ---------------------------
// 判断文件是否是 HTML 文件(添加 utf-8 字符集) 达到京东的效果
if (ext === "html") {
response.setHeader("content-type", type + ";charset=utf-8");
} else {
// 如果不是 HTML,就不添加 字符集
response.setHeader("content-type", type);
}
} else {
response.setHeader("content-type", "application/octet-stream");
}
response.end(data);
});
});
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
# 19、静态资源错误处理
TIP
以上我们都是将错误处理统一为 500 错误,即 服务器内部错误。其实并不合适 !我们应该不同的错误来返回不同的错误编号 和 错误提示
在 /src/server/static-services.js
中,先打印输出 err
const http = require("http");
const fs = require("fs");
const path = require("path");
let mimes = {
html: "text/html",
css: "text/css",
js: "text/javascript",
png: "image/png",
jpg: "image/jpeg",
gif: "image/gif",
mp4: "video/mp4",
mp3: "audio/mpeg",
json: "application/json",
};
const server = http.createServer((request, response) => {
let { pathname } = new URL(request.url, "http://127.0.0.1");
let filePath = __dirname + "/page" + pathname;
fs.readFile(filePath, (err, data) => {
if (err) {
// ---------------
// 为了清晰看出错误信息,先打印输出 err
console.log(err);
response.statusCode = 500;
response.end("文件读取失败 !");
return;
}
let ext = path.extname(filePath).slice(1);
console.log(ext);
let type = mimes[ext];
if (type) {
// 判断文件是否是 HTML 文件(添加 utf-8 字符集) 达到京东的效果
if (ext === "html") {
response.setHeader("content-type", type + ";charset=utf-8");
} else {
// 如果不是 HTML,就不添加 字符集
response.setHeader("content-type", type);
}
} else {
response.setHeader("content-type", "application/octet-stream");
}
response.end(data);
});
});
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
启动服务,在浏览器的地址栏中输入一个不存在的请求地址,回车查看效果
在 VSCode 控制台中,查看错误信息
报错信息 Error: ENOENT: no such file or directory
找不到文件 或 文件夹,该错误信息是一个对象
{
// 错误编号
"errno": -4058,
// 错误代码
"code": "ENOENT",
// 系统调用:打开文件时出错
"syscall": "open",
// 文件路径:寻找该文件时报错
"path": "D:\\web\\node\\icoding-http\\src\\server\\page\\111.html"
}
以上报错即:文件找不到的错误 !服务端应该给客户端返回一个 404 的响应
在 /src/server/static-services.js
中 err 部分做判断(根据 err.code
的值判断 给出对应的响应)
const http = require("http");
const fs = require("fs");
const path = require("path");
let mimes = {
html: "text/html",
css: "text/css",
js: "text/javascript",
png: "image/png",
jpg: "image/jpeg",
gif: "image/gif",
mp4: "video/mp4",
mp3: "audio/mpeg",
json: "application/json",
};
const server = http.createServer((request, response) => {
let { pathname } = new URL(request.url, "http://127.0.0.1");
let filePath = __dirname + "/page" + pathname;
fs.readFile(filePath, (err, data) => {
if (err) {
// ---------------
// 统一设置字符集
response.setHeader("content-type", "text/html;charset=utf-8");
// 根据 err.code 的值判断 给出对应的响应
// 判断错误的代号
switch (err.code) {
case "ENOENT":
response.statusCode = 404;
response.end("<h1>404 Not Found</h1>");
}
return;
}
let ext = path.extname(filePath).slice(1);
console.log(ext);
let type = mimes[ext];
if (type) {
// 判断文件是否是 HTML 文件(添加 utf-8 字符集) 达到京东的效果
if (ext === "html") {
response.setHeader("content-type", type + ";charset=utf-8");
} else {
// 如果不是 HTML,就不添加 字符集
response.setHeader("content-type", type);
}
} else {
response.setHeader("content-type", "application/octet-stream");
}
response.end(data);
});
});
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
启动服务,在浏览器的地址栏中输入一个不存在的请求地址,回车查看效果
# 19.1、权限错误 - 403
在 /src/server/static-services.js
中打印输出 err
const http = require("http");
const fs = require("fs");
const path = require("path");
let mimes = {
html: "text/html",
css: "text/css",
js: "text/javascript",
png: "image/png",
jpg: "image/jpeg",
gif: "image/gif",
mp4: "video/mp4",
mp3: "audio/mpeg",
json: "application/json",
};
const server = http.createServer((request, response) => {
let { pathname } = new URL(request.url, "http://127.0.0.1");
let filePath = __dirname + "/page" + pathname;
fs.readFile(filePath, (err, data) => {
if (err) {
// 打印输出 err
console.log(err);
// ---------------
// 统一设置字符集
response.setHeader("content-type", "text/html;charset=utf-8");
// 根据 err.code 的值判断 给出对应的响应
// 判断错误的代号
switch (err.code) {
case "ENOENT":
response.statusCode = 404;
response.end("<h1>404 Not Found</h1>");
}
return;
}
let ext = path.extname(filePath).slice(1);
console.log(ext);
let type = mimes[ext];
if (type) {
// 判断文件是否是 HTML 文件(添加 utf-8 字符集) 达到京东的效果
if (ext === "html") {
response.setHeader("content-type", type + ";charset=utf-8");
} else {
// 如果不是 HTML,就不添加 字符集
response.setHeader("content-type", type);
}
} else {
response.setHeader("content-type", "application/octet-stream");
}
response.end(data);
});
});
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
修改 /src/server/page/index.html
文件的权限,选择 拒绝 “读取 和 执行”
启动服务,在浏览器的地址栏中输入请求地址 http://localhost:3000/index.html
,回车查看效果
在 VSCode 控制台中查看错误信息 operation not permitted
即:不允许操作
查看错误信息,可在 NodeJS 官方文档 - 常见系统错误 (opens new window) 搜索 "错误代码“:EPERM
可看到
在 /src/server/static-services.js
中打印响应该错误信息
const http = require("http");
const fs = require("fs");
const path = require("path");
let mimes = {
html: "text/html",
css: "text/css",
js: "text/javascript",
png: "image/png",
jpg: "image/jpeg",
gif: "image/gif",
mp4: "video/mp4",
mp3: "audio/mpeg",
json: "application/json",
};
const server = http.createServer((request, response) => {
let { pathname } = new URL(request.url, "http://127.0.0.1");
let filePath = __dirname + "/page" + pathname;
fs.readFile(filePath, (err, data) => {
if (err) {
console.log(err);
// ---------------
// 统一设置字符集
response.setHeader("content-type", "text/html;charset=utf-8");
// 根据 err.code 的值判断 给出对应的响应
// 判断错误的代号
switch (err.code) {
case "ENOENT":
response.statusCode = 404;
response.end("<h1>404 Not Found</h1>");
// ------------------------
// 响应权限错误信息
case "EPERM":
response.statusCode = 403;
response.end("<h1>403 Forbidden 被禁止的 !</h1>");
}
return;
}
let ext = path.extname(filePath).slice(1);
console.log(ext);
let type = mimes[ext];
if (type) {
// 判断文件是否是 HTML 文件(添加 utf-8 字符集) 达到京东的效果
if (ext === "html") {
response.setHeader("content-type", type + ";charset=utf-8");
} else {
// 如果不是 HTML,就不添加 字符集
response.setHeader("content-type", type);
}
} else {
response.setHeader("content-type", "application/octet-stream");
}
response.end(data);
});
});
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
启动服务,在浏览器的地址栏中输入请求地址 http://localhost:3000/index.html
,回车查看效果
此时,再次恢复 /src/server/page/index.html
文件的权限,去掉 拒绝 “读取 和 执行” 的选项,在浏览器的地址栏中输入请求地址 http://localhost:3000/index.html
,回车查看效果
# 19.2、请求方式错误
TIP
以上创建服务的请求方式必须是 GET 请求,不能用 POST 发送请求获取资源,即:如果请求方式错误也需要返回一个错误信息。
因此,需要对请求方式做判断 !
在 /src/server/static-services.js
中判断请求方式
const http = require("http");
const fs = require("fs");
const path = require("path");
let mimes = {
html: "text/html",
css: "text/css",
js: "text/javascript",
png: "image/png",
jpg: "image/jpeg",
gif: "image/gif",
mp4: "video/mp4",
mp3: "audio/mpeg",
json: "application/json",
};
const server = http.createServer((request, response) => {
// ------------------------------
// 判断请求方式
if (request.method !== "GET") {
response.statusCode = 405;
response.end("<h1>405 Method Not Allowed</h1>"); // 不允许的方法 !
return;
}
let { pathname } = new URL(request.url, "http://127.0.0.1");
let filePath = __dirname + "/page" + pathname;
fs.readFile(filePath, (err, data) => {
if (err) {
console.log(err);
// 统一设置字符集
response.setHeader("content-type", "text/html;charset=utf-8");
// 根据 err.code 的值判断 给出对应的响应
// 判断错误的代号
switch (err.code) {
case "ENOENT":
response.statusCode = 404;
response.end("<h1>404 Not Found</h1>");
// 响应权限错误信息
case "EPERM":
response.statusCode = 403;
response.end("<h1>403 Forbidden 被禁止的 !</h1>");
}
return;
}
let ext = path.extname(filePath).slice(1);
console.log(ext);
let type = mimes[ext];
if (type) {
// 判断文件是否是 HTML 文件(添加 utf-8 字符集) 达到京东的效果
if (ext === "html") {
response.setHeader("content-type", type + ";charset=utf-8");
} else {
// 如果不是 HTML,就不添加 字符集
response.setHeader("content-type", type);
}
} else {
response.setHeader("content-type", "application/octet-stream");
}
response.end(data);
});
});
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
启动服务,在 /src/server/page/index.html
表单中发送 POST 请求,进行测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>查看请求体内容</title>
</head>
<body>
<!-- 发送 POST 请求至 http://localhost:3000/index.html -->
<form action="http://localhost:3000/index.html" method="post">
用户名:<input type="text" name="username" /> <br /><br />
密 码:<input type="password" name="password" />
<br /><br />
<input type="submit" value="登 录" />
</form>
</body>
</html>
在 VSCode 中通过 Open with Live Server
打开 http://127.0.0.1:5500/src/index.html
并发送 POST 请求测试,查看状态码 和 响应内容
# 19.3、其他未知错误
TIP
将其他未知错误归为 default 中即可
在 /src/server/static-services.js
中
const http = require("http");
const fs = require("fs");
const path = require("path");
let mimes = {
html: "text/html",
css: "text/css",
js: "text/javascript",
png: "image/png",
jpg: "image/jpeg",
gif: "image/gif",
mp4: "video/mp4",
mp3: "audio/mpeg",
json: "application/json",
};
const server = http.createServer((request, response) => {
// 判断请求方式
if (request.method !== "GET") {
response.statusCode = 405;
response.end("<h1>405 Method Not Allowed</h1>"); // 不允许的方法 !
return;
}
let { pathname } = new URL(request.url, "http://127.0.0.1");
let filePath = __dirname + "/page" + pathname;
fs.readFile(filePath, (err, data) => {
if (err) {
console.log(err);
// 统一设置字符集
response.setHeader("content-type", "text/html;charset=utf-8");
// 根据 err.code 的值判断 给出对应的响应
// 判断错误的代号
switch (err.code) {
case "ENOENT":
response.statusCode = 404;
response.end("<h1>404 Not Found</h1>");
// 响应权限错误信息
case "EPERM":
response.statusCode = 403;
response.end("<h1>403 Forbidden 被禁止的 !</h1>");
// ---------------
// 其他未知错误
default:
response.statusCode = 500;
response.end("<h1>500 Internal Server Error !</h1>"); // 服务器内部错误
}
return;
}
let ext = path.extname(filePath).slice(1);
console.log(ext);
let type = mimes[ext];
if (type) {
// 判断文件是否是 HTML 文件(添加 utf-8 字符集) 达到京东的效果
if (ext === "html") {
response.setHeader("content-type", type + ";charset=utf-8");
} else {
// 如果不是 HTML,就不添加 字符集
response.setHeader("content-type", type);
}
} else {
response.setHeader("content-type", "application/octet-stream");
}
response.end(data);
});
});
server.listen(3000, () => {
console.log(
"服务已启动,http 请求已经被监听,3000 端口,请访问 http://localhost:3000"
);
});
# 20、GET 和 POST 请求的应用场景
GET 请求
GET 方法通常用于数据检索,即获取资源的请求。适用于无副作用的操作,例如展示网页内容、读取数据、搜索等。它常用于浏览器 URL 的地址栏中。
- 在地址栏直接输入 url 访问
- 点击 a 链接
- link 标签引入 css
- script 标签引入 js
- video 与 audio 引入多媒体
- img 标签引入图片
- form 标签中的 method 为 get(不区分大小写)
- ajax 中的 get 请求
POST 请求
POST 方法通常用于数据提交,即向服务器发送数据。适用于有副作用的操作,例如提交表单、处理用户注册、发送邮件等。它常用于表单的提交。
- form 标签中的 method 为 post (不区分大小写)
- Ajax 的 POST 请求
# 21、GET 和 POST 请求区别
TIP
GET 和 POST 是 HTTP 协议请求的两种方式,主要区别如下
- 作用: GET 主要用来获取数据,POST 主要用来提交数据
- 数据传输位置: GET 带参数请求是将参数缀到 URL 之后,POST 带参数请求是将参数放到请求体中。GET 方法将请求参数附加在 URL 的查询字符串中,可见于请求 URL 中;而 POST 方法将请求参数作为 HTTP 消息的实体部分发送给服务器,不可见于请求 URL。
- 安全性: POST 请求相对 GET 安全一些,因为在浏览器中参数会暴露在地址栏。GET 方法的参数通过 URL 传输,容易被拦截和嗅探;而 POST 方法将参数放在请求体中,相对安全,但仍需使用 HTTPS 等方式进行加密保护。
- 数据传输限制: 由于 URL 长度限制或浏览器的限制,GET 方法对数据传输的长度有限制(一般为 2K);而 POST 方法对数据传输的长度没有严格限制(没有大小限制)。
- 对数据的操作: GET 方法用于获取资源,不应产生副作用;而 POST 方法用于提交数据,可能对服务器和资源产生影响,如存储、更新或删除数据。
- 传送方式: GET 通过地址栏传输,产生一个 TCP 数据包;而 POST 通过报文传输,产生两个数据包。
- 执行效率: GET 安全性低,但执行效率高;而 POST 安全性较高,但执行效率比 GET 方法稍低。
- 数据格式: 使用 GET 方式提交的数据会显示在地址栏上,因此一般对数据的格式有所限制(例如只接受 ASCII 字符)。而 POST 方式提交的数据不会显示在地址栏上,因此对数据的格式没有特别限制。
大厂最新技术学习分享群
微信扫一扫进群,获取资料
X