用户代理
浏览器可以代替用户完成http请求,代替用户解析响应结果,所以我们称之为:用户代理 user agent
在网络层面,对于前端开发者,必须要知道浏览器拥有的两大核心能力
- 自动发出请求的能力
- 自动解析响应的能力
自动发出请求的能力
当一些事情发生的时候,浏览器会代替用户自动发出http请求,常见的包括:
- 用户在地址栏输入了一个url地址,并按下了回车
浏览器会自动解析URL,并发出一个GET
请求,同时抛弃当前页面 - 当用户点击了页面中的a元素
当用户会拿到a元素的href地址,并发出一个GET
请求,同时抛弃当前页面 - 当用户点击了提交按钮
<button type="submit">...</button>
浏览器会获取按钮所在的<form>
元素,拿到它的action
属性地址,同时拿到它的method
属性值,然后把表单中的数据组织到请求体中,发出指定方法
的请求,同时抛弃当前页面
这种方式的提交现在越来越少了
-
当解析了HTML时遇到了
<link> <img> <script> <video> <audio>
等元素
浏览器会拿到对应的地址,发出GET
请求 -
当浏览器点击了刷新
浏览器会拿到当前页面的地址,以及当前页面的请求方法,重新发一次请求,同时抛弃当前页面
浏览器在发出请求时,会自动附带一些请求头
重点
一直以来,浏览器都有一个约定
当发送GET请求时,浏览器不会附带请求体
这个约定深刻的影响着后续的前后端各种应用,现在,几乎所有人都在潜意识中认同了这一点,无论是前端开发人员还是后端开发人员。
由于前后端程序的默认行为,逐渐造成了GET和POST的各种差异
1. 浏览器发送GET请求时,不会附带请求体
2. GET请求的传递信息量有限,适合传递少量数据;POST请求的传递信息量是没有限制的,适合传输大量数据。
3. GET请求传递的数据都附带在path参数中,能够通过分享地址完整的重现页面,但同时也暴露了数据,若有敏感数据传递,不应该使用GET请求,至少不应该放到path中
4. GET请求只能传递ASCII数据,遇到非ASCII数据需要进行编码;POST请求没有限制
5. POST不会保存到浏览器的历史记录中
6. 刷新页面是,如果当前的页面是通过POST请求得到的,则浏览器会提示用户是否重新提交。若是GET请求得到的页面则没有提示
自动解析响应的能力
浏览器不仅能发送请求,还能够针对服务器的各种响应结果做出不同的自动处理
常见的处理有:
- 识别响应码
浏览器能够自动识别响应码,当出现一些特殊的响应码时浏览器会自动完成处理,比如301,302
- 根据响应结果做不同的处理
浏览器能够自动分析响应头中的content-type
,根据不同的值进行不同的处理,比如text/plain
:普通纯文本,浏览器通常会将响应体原封不动的显示到页面上text/html
: html文档,浏览器通常会将响应体作为页面进行渲染text/JavaScript
或application/javascript
:js代码,浏览器通常会使用JS引擎将他解析执行text/css
: CSS代码。浏览器会将他视为样式image/jpeg
:浏览器会将他视作图片application/octet-stream
: 二进制数据,会触发浏览器下载功能attachment
:附件,会触发下载功能- 该值和其他值不同,应该放到
Content-Disposition
头中。
- 该值和其他值不同,应该放到
基本流程
访问https://oss.duyiedu.com/test/index.html
具体细节如下图所示:
接口开发
浏览器本身就具备网络通信的能力,但在早期,浏览器并没有把这个功能开放给JS
最早时微软在IE浏览器把这一能力想JS开放(所以别再吐槽ie啦),让JS可以在代码中发送请求,这项技术在2005年被正式命名为AJAX
IE使用了一套API来完成请求的发送,这套API主要依靠一个构造函数完成。该构造函数的名称为XMLHttpRequest
简称为XHR
,所以这套API又称为XHR API
由于XHR API
有许多缺陷,在HTML5和ES6发布之后,产生了一套更完善的API来发送请求,这个API主要使用fetch
函数,因此这套API又称之为Fetch API
无论是XHR还是Fetch,他们都是实现ajax的技术手段,知识API不同。
XHR API
var xhr = new XMLHttpRequest(); //创建发送请求的对象
xhr.onreadystatechange = function(){ //当请求状态发生改变时运行的函数
// xhr.readyState: 一个数字,用于判断请求到了哪个阶段
// 0: 刚刚创建好了请求对象,但还未配置请求(未调用open方法)
// 1: open方法已被调用
// 2: send方法已被调用
// 3: 正在接收服务器的响应消息体
// 4: 服务器响应的所有内容均已接收完毕
// xhr.responseText: 获取服务器响应的消息体文本
// xhr.getResponseHeader("Content-Type") 获取响应头Content-Type
}
xhr.open("请求方法", "url地址"); //配置请求
xhr.setRequestHeader("Content-Type", "application/json"); //设置请求头
xhr.send("请求体内容"); //构建请求体,发送到服务器,如果没有请求体,传递null
Fetch API
const resp = await fetch('url地址', { // 请求配置对象,可省略,省略则所有配置为默认值
method: '请求方法', // 默认为GET
headers: { // 请求头配置
'Content-Type': 'application/json',
'a': 'abc'
},
body: '请求体内容' // 请求体
}); // fetch会返回一个Promise,该Promise会在接收完响应头后变为fulfilled
resp.headers; // 获取响应头对象
resp.status; // 获取响应状态码,例如200
resp.statusText; // 获取响应状态码文本,例如OK
resp.json(); // 用json的格式解析即将到来的响应体,返回Promise,解析完成后得到一个对象
resp.text(); // 用纯文本的格式解析即将到来的响应体,返回Promise,解析完成后得到一个字符串
实战 => token令牌
token令牌的定义: token定义
token令牌常出现在登录场景.
由于HTTP协议的特点,每次【请求-响应】都是独立的,这就会导致身份信息丢失的问题
这个问题可以使用token令牌解决
在使用AJAX时
可以按照这样一种通用模式进行处理
- 在初六响应结果时,只要服务器给我的响应头中包含了
token
,就将其保存在localStorage
中 - 在请求时,只要
localStorage
中有token
,就将其代入到响应头发送到服务器。
代码封装
封装AJAX请求方法。提供一个request
函数:
request(method, url, [options]):promise
method
, Stringurl
: Stringoptions
, 可选参数,object,请求配置,和fetch的配置相同return
,返回一个Promise,Promise完成后会得到一个对象
{
headers: { ... } // 响应头,
data: { ... } // 响应体
}
request
函数能够自动处理token
同时,它提供了request.get(url, [options])
和request.post(url, data, [options])
的简约调用方式
封装的代码如下
// 当浏览器发送请求时,触发自调用函数
window.request = (() => {
const TOKEN_KEY = '__token__';
const request = async (method, url, options = {}) => {
options.headers = options.headers || {};
// 格式化options
if (options.body && typeof options.body === 'object') {
options.body = JSON.stringify(options.body);
options.headers['content-type'] = 'application/json';
}
// 是否携带token
const token = localStorage.getItem(TOKEN_KEY);
if (token) {
// 向请求头中加入内容
options.headers.authorization = `Bearer ${token}`;
}
const resp = await fetch(url, {
...options,
method: method.toUpperCase(),
});
const headers = Object.fromEntries(resp.headers.entries());
// 检查头中的token
if (headers.authorization) {
localStorage.setItem(TOKEN_KEY, headers.authorization);
}
const body = await resp.json();
return {
headers,
body,
};
};
return request;
})();