如何理解浏览器的 user agent 用户代理的含义?
# 0.前言
本篇博客对应 issue
:https://github.com/wangjs-jacky/examples-collection/issues/2 (opens new window)
user agenet
顾名思义为 用户代理,能帮用户发送网络请求的工具很多,如 VSCode
插件 REST client
、客户端 postman
、国产客户端 apifox
等。
这里 用户代理 在网络层面主要值的是两大核心能力:
- 发的能力:代替用户发送
http
请求。 - 收的能力:根据
Content-Type
解析响应结果。
# 1. 浏览器的自动发送请求功能
浏览器的发送方式有多种多样,大致分为两类:其一、地址栏行为。其二、原生 html
标签内置行为。
# 1.1 地址栏行为
当用户在地址栏输入了一个 url
地址,浏览器会自动解析 URL
地址,并发出一个 GET
请求。
对于 GET
请求中, URL
只允许出现 ASCII
字符,默认对中文字符进行 url
编码。
举例:百度搜索 灌篮高手
decodeURIComponent("%E7%81%8C%E7%AF%AE%E9%AB%98%E6%89%8B");
"灌篮高手";
encodeURIComponent("灌篮高手");
"%E7%81%8C%E7%AF%AE%E9%AB%98%E6%89%8B";
2
3
4
# 1.2 原生 HTML
标签行为
a
标签 【GET
请求】<a href="”www.baidu.com“">跳转百度</a>
1会自动解析
href
属性,并发送一个GET
请求。注意
href
其实存在多重省略写法- 省略
协议
写法:<a href="//localhost:3000/abc">
- 省略
协议
、域名
、端口
写法。<a href="/abc">
- 省略
<link> <img> <script> <video> <audio>
等元素 【GET
请求】浏览器会拿到对应的地址,发出
GET
请求。表单提交行为
早期让浏览器发送
POST
请求的唯一方案。<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Form表单的示例</title> </head> <body> <form action="https://www.baidu.com/" method="post"> <p>账号:<input type="text" name="loginId" /></p> <p>密码: <input type="text" name="password" /></p> <button type="submit">登录</button> </form> </body> </html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16观察
NetWork
面板对应等价的
REST Client
模拟发送HTTP
请求POST / HTTP/1.1 HOST: www.baidu.com Content-Type: application/x-www-form-urlencoded loginId=admin&loginPwd=12345
1
2
3
4
5虽然现在可以使用
ajax
技术进行post
请求,原生form
依旧存在价值,如监听enter
键盘回车事件。在
antd
中,通过拦截form
的submit
事件对表单能力进行扩展<script> /* 在 antd 等表单组件中,会对 form 的提交行为拦截 */ const form = document.querySelector("form"); form.onsubmit = (e) => { e.preventDefault(); console.log("任意逻辑代码"); }; </script>
1
2
3
4
5
6
7
8
9
# 2. 根据 Content-Type
响应结果
text/plain
: 普通的纯文本,浏览器通常会将响应体原封不动的显示到页面上text/html
:html 文档,浏览器通常会将响应体作为页面进行渲染text/javascript
或application/javascript
:js 代码,浏览器通常会使用 JS 执行引擎将它解析执行text/css
:css 代码,浏览器会将它视为样式image/jpeg
:浏览器会将它视为 jpg 图片application/octet-stream
:二进制数据,会触发浏览器下载功能attachment
:附件,会触发下载功能
# 3. 综合案例实践
使用三种方式发送网络请求:
REST client
发送POST
请求。
POST /d HTTP/1.1
HOST: localhost:7000
Content-Type: application/x-www-form-urlencoded
loginId=admin&loginPwd=12345
2
3
4
5
- 使用原生表单发送
urlencode
请求,发送POST
请求。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Form表单的示例</title>
</head>
<body>
<form action="//localhost:7000/d" method="post">
<p>账号:<input type="text" name="loginId" /></p>
<p>密码: <input type="text" name="loginPwd" /></p>
<button type="submit">登录</button>
</form>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 使用
url
发送GET
请求。 直接地址栏输入url
- 输入
localhost:7000/a
,浏览器以纯文本
形式显示。 - 输入
localhost:7000/b
,浏览器以html
形式显示。 - 输入
localhost:7000/c
,浏览器以附件
形式显示。
以 express
框架示例,监听 7000
端口。
const app = require("express")();
const bodyParser = require("body-parser");
const text = `<h1>两只老虎爱跳舞</h1>
小兔子乖乖拔萝卜
我和小鸭子学走路
童年是最美的礼物`;
const PORT = 7000;
// 配置 body-parser 中间件
// Content-Type: application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }));
// 配置 body-parser 中间件
// 解析: Content-Type: application/json
app.use(bodyParser.json());
/* 解析响应体:text/plain */
app.get("/a", (req, res) => {
res.set("Content-Type", "text/plain; charset=utf-8");
res.end(text);
});
/* 解析响应体:text/plain */
app.get("/b", (req, res) => {
res.set("Content-Type", "text/html; charset=utf-8");
res.end(text);
});
/* 下载文件 */
app.get("/c", (req, res) => {
res.set("Content-Type", "text/plain; charset=utf-8");
res.set("Content-Disposition", "attachment; filename=song.txt");
res.end(text);
});
/* POST请求测试 */
app.post("/d", (req, res) => {
console.log("sss", req.body);
/* console.log("loginId", req.body.loginId); // 输出 "admin"
console.log("loginPwd", req.body.loginPwd); // 输出 "123123" */
res.end("success");
});
app.listen(PORT, () => {
console.log(`server started: ${PORT}`);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
观察响应结果如下:
访问:
localhost:7000/a
,以纯文本形式显示。访问:
localhost:7000/b
,以html
形式显示。访问:
localhost:7000/c
,以附件形式下载,附件名称为song.txt
以
REST Client
发送POST
请求使用
html
元素form
发送POST
请求
# 4.常见错误
对于 http
协议头的简写错误示例:
localhost:7000/d
→ //localhost:7000/d
<body>
<!-- ❎ 错误写法 -->
<form action="localhost:7000/d" method="post">
<!-- ❎ 正确写法 -->
<form action="//localhost:7000/d" method="post">
<p>
账号:<input type="text" name="loginId">
</p>
<p>
密码: <input type="text" name="loginPwd">
</p>
<button type="submit">登录</button>
</form>
</body>
2
3
4
5
6
7
8
9
10
11
12
13
14
控制台报错:缺少协议头