axios封装和api管理实践笔记

一、Content-Type

Content-Type 用于规定客户端通过http或https协议向服务器发起请求时,传递的请求体中数据的编码格式。因为get请求是直接将请求数据以键值对通过&号连接(key1=value1&key2=value2)的方式附加到url地址后面,不在请求体中,所以get请求中不需要设置Content-Type。通过浏览器抓取get请求数据可以发现其请求头中并没有Content-Type这一属性。

当我们通过<form>表单标签提交数据的时候,如果将其method设置为get,那么是属于get请求,所有表单数据会被附加到url地址后面,如:

<form method="get"><!--属于get方式提交-->

<input name="user"/>

<button type="submit">提交</button>

</form>

在浏览器中输入localhost:8080,显示出页面后,在表单中填入数据lihb后,浏览器地址会变为http://localhost/:8080/?user=lihb,说明其使用的是get的提交方式。

POST请求中常见的几种Content-Type形式:

① application/x-www-form-urlencoded

这是最常见的POST请求提交方式,我们通过<form>表单标签提交数据的时候,如果将其method设置为post,那么就是属于post请求,表单提交的数据会被放到请求体中,并且表单提交的数据也是采用键值对通过&号连接(key1=value1&key2=value2)的方式,也就是说,该Content-Type下,get和post请求提交的数据格式是一样的,只不过post请求是将数据放到了请求体中。

<form method="post"><!--属于post方式提交-->

<input name="user"/>

<input name="age"/>

<button type="submit">提交</button>

</form>

在浏览器中输入localhost:8080,显示出页面后,在表单中填入数据lihb18后,浏览器地址仍然是http://localhost/:8080,但是在请求头中,我们可以看到Content-Type:application/x-www-form-urlencoded,请求体中可以看到user=lihb&age=18说明其使用的是post的提交方式。

② multipart/form-data

这种通常用于文件上传,我们通过<form>表单标签提交数据的时候,需要将其method设置为post,同时还要将其enctype设置为multipart/form-data,那么这个时候就是属于post数据提交,只不过其数据会编码为一条一条的消息。需要注意的是文件上传必须设置为post请求,如果method为get,即使设置了enctype为multipart/form-data,那么仍然为get请求。

<form method="post" enctype="multipart/form-data"><!--属于post方式提交,并且数据会被编码为一条一条的消息-->

<input name="user"/>

<button type="submit">提交</button>

</form>

在浏览器中输入localhost:8080,显示出页面后,在表单中填入数据lihb后,浏览器地址仍然是http://localhost/:8080,但是在请求头中,我们可以看到

Content-Type:multipart/form-data; boundary=----WebKitFormBoundarysa9SPcvOKqKEDdBb

------WebKitFormBoundarysa9SPcvOKqKEDdBb

Content-Disposition: form-data; name="user"

lihb

------WebKitFormBoundarysa9SPcvOKqKEDdBb--

浏览器会自动生成一个boundary分界线,用于将每条消息数据分割开。

③ application/json

这种通常会采用json的格式进行数据传递,主要用于一些比较复杂的数据的传递,如果仍然采用application/x-www-form-urlencoded的方式,解析起来就会变得非常复杂,所以可以利用该类型直接传递json数据到服务器,需要注意的是,浏览器中通过设置<form>的enctype为application/json是不会起作用的,需要通过ajax或axios等第三方库进行设置,即form表单只支持application/x-www-form-urlencoded 和 multipart/form-data 这两种。 如:

// 以下数据如果通过键值对和&号进行连接的方式就会变得很复杂

{

name: [

{

first: "Li",

last: "hb"

}

]

}

二、服务器端数据解析

由于发送post请求的时候,传递的数据有多种编码格式,所以服务端也会对应不同的方式进行解析。我们以node + express服务器为例

① 对于get请求

对于get请求传递的数据,我们可以直接通过req的query属性即可获取到,如:

app.get("/", (req, res) => {

console.log(`req.query: ${JSON.stringify(req.query)}`);

});

② 对于post请求,编码方式为application/x-www-form-urlencoded

对于键值对形式,我们可以通过body-parser进行解析,如:

const bodyParser = require("body-parser");

// 处理x-www-form-urlencoded编码后的数据

app.use(bodyParser.urlencoded({extended: false}));

// 处理json编码后的数据

app.use(bodyParser.json());

③ 对于post请求,编码方式为multipart/form-data

body-parser是无法解析消息数据的,这个时候可以通过formidable进行解析。如:

var formidable = require('formidable');

app.post("/", (req, res) => {

var form = new formidable.IncomingForm();

form.parse(req, function(err, fields, files) {

console.log(fields);

});

}

三、axios基本用法

axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。

① axios(config)

安装axios模块后,引入的axios是创建好的是一个Axios实例,可以直接用于发请求,axios其实是一个函数,可以直接传递一个请求配置对象,常见配置参数如下:

baseURL:用于设置请求的域名和端口号,axios中不支持host和port的设置,是直接通过baseURL一起设置的。

method:用于设置请求的方式。

url:用于设置服务器的相对地址或者绝对地址,如果是相对地址,那么是直接将相对地址附加到baseURL后面,如果是绝对地址,那么用该绝对地址覆盖掉baseURL的地址,即url比baseURL优先级更高。

headers:用于设置请求头数据,如Content-Type

axios({

headers: {

'Content-Type':'application/x-www-form-urlencoded'

},

});

transformRequest: 用于在请求发送到服务器之前对请求的数据做出一些改动,其是一个函数,会接收传递的data数据和headers请求头作为参数,并且函数必须返回(处理后的)data,如:

transformRequest(data, headers) {

console.log(data);

headers["Content-Type"] = "application/x-www-form-urlencoded";

return qs.stringify(data);

}

params:用于设置get请求时的数据,其必须是一个JS对象或者是URLSearchParams对象,其中的数据会被解析为键值对附加到url地址后面。

data:用于设置post请求时的数据,需要注意的是,默认情况下,如果传递的是一个普通的JS对象,那么data数据会被JSON.stringify(data)处理,并且Content-Type会被设置为application/json;charset=utf-8,即以json的格式进行提交,后端解析的时候必须支持json的解析才能正常拿到数据;如果传递是字符串,并且是符合application/x-www-form-urlencoded格式的字符串,那么才会以application/x-www-form-urlencoded的方式提交到服务器;如果data数据是一个URLSearchParams对象,那么服务器可以正常获取到该数据,即data支持URLSearchParams对象,其默认源码如下:

if (utils.isURLSearchParams(data)) { // 如果传递的data是一个URLSearchParams对象

setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');

return data.toString(); // 这里就会变成&号连接的键值对字符串

}

if (utils.isObject(data)) { // 如果传递的data是一个JS对象

setContentTypeIfUnset(headers, 'application/json;charset=utf-8');

return JSON.stringify(data);

}

当然,这个是默认的情况,我们可以通过配置transformRequest重写改方法进行headers的设置和data数据的修改,如:

import QS from "qs";

axios({

data: {

user: "lihb"

},

transformRequest(data, headers) {

// data为JS对象的时候,强制修改Content-Type和data

headers["Content-Type"] = "application/x-www-form-urlencoded";

return QS.stringify(data);

}

})

transformResponse:用于我们在数据传送到then/catch方法之前对数据进行改动,其是一个函数,接收响应数据作为参数,可以在其中对响应数据进行修改。

timeout:用于设置请求超时时间,单位为毫秒,到了超时时间,请求将会被终止。

onUploadProgress:用于监听上传进度事件,值为一个函数。

onDownloadProgress:用于监听下载进度事件,值为一个函数

② axios.get(url, {params: getRequestObj})

axios.get是对axios()的封装,其第一个参数为请求的url地址,第二个参数为一个对象,对象中有一个params属性,属性值为get请求发送的数据,是一个JS对象或者URLSearchParams对象,如:

axios.get("http://localhost/:3000/", {

params: { // 必须要有params属性

user: "lihb" // 传递JS对象

}

});

const params = new URLSearchParams();

params.append("user", "lihb");

axios.get("http://localhost/:3000/", {

params // 传递URLSearchParams对象

})

③ axios.post(url, postRequestObj, config)

axios.post是对象axios()的封装,其第一个参数为请求的url地址,第二个参数为一个对象,即post请求发送的对象,第三个是config配置对象,同axios的config对象,如:

import QS from "qs";

// 由于直接传递JS对象默认会被转换为application/json,所以需要通过QS解析为符合urlencoded的参数字符串

axios.post("http://localhost/:3000/", QS.stringify({user: "lihb"}));

// 二者等价,重写transformRequest方法,去除默认修改

axios.post("http://localhost/:3000/", {user: "lihb"}, {

transformRequest(data, headers) {

return QS.stringify(data);

}

});

四、axios封装

为什么要进行axios的封装?封装是通过更少的调用代码覆盖更多的调用场景。在浏览器端他通过xhr方式创建ajax请求。在node环境下,通过http库创建网络请求。在实际开发中,一个项目有很多组件,每个组件中又有很多请求,如果每个请求在发送前都进行设置,比如baseURL、请求头、超时时间、跨域、token、响应处理等等,就会造成大量代码的重复。所以我们需要对这些通用的操作进行封装。

首先在src目录下新建一个http目录,并在其中新建一个index.js作为axios的封装。

① 创建一个独立的axios实例,我们安装好axios之后,默认拿到的全局的axios实例,为了避免对全局axios的污染,以及某个请求修改后影响到其他请求,所以我们需要创建一个单独的axios实例对象,如:

import axios from "axios";

const instance = axios.create({ // 创建一个独立的axios实例

});

export default instance;

② baseURL,接下来我们需要给这个实例配置一个baseURL,以便让我们可以根据不同的环境切换不同的baseURL,如:

import axios from "axios";

const instance = axios.create({ // 创建一个独立的axios实例

baseURL: process.env.NODE_ENV === "production" ? "http://www.lihb.com" : "http://localhost/:3000"

});

export default instance;

vue项目初始化后,我们通过npm run serve启动的项目默认模式为development,当我们通过npx vue-cli-service serve --mode production启动项目后模式将切换未production,此时我们请求的baseURL也会跟着进行相应的变化。

③ 统一设置请求头,我们可以将一些通用的请求头事先设置好,如post请求一般都是采用application/x-www-form-urlencoded编码,如:

import axios from "axios";

const instance = axios.create({ // 创建一个独立的axios实例

baseURL: process.env.NODE_ENV === "production" ? "http://www.lihb.com" : "http://localhost/:3000",

headers: { // 定义统一的请求头部

post: {

"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"

}

}

});

export default instance;

④超时、响应码处理,我们可以给每个请求都设置一个超时时间,默认情况下,axios只会将状态码为2系列或者304的请求设为resolve状态,其余为reject状态,如果我们在代码里面使用了async-await,而众所周知,async-await捕获catch的方式是极为麻烦的,所以在此处,我们需要将所有响应都设为resolve状态,统一在then中处理。可以通过配置validateStatus,其是一个函数,让其返回true,即可实现所有http状态码都被resolve,即在then中接收,如:

import axios from "axios";

const instance = axios.create({ // 创建一个独立的axios实例

baseURL: process.env.NODE_ENV === "production" ? "http://www.lihb.com" : "http://localhost/:3000",

headers: { // 定义统一的请求头部

post: {

"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"

}

},

timeout: 20 * 1000, // 配置请求超时时间

validateStatus: () => {

return true; // 使用async-await,处理reject情况较为繁琐,所以全部返回resolve,在业务代码中处理异常

}

});

export default instance;

⑤ 跨域,在跨域请求的时候,如果想要将客户端cookie也一起传到服务器端,那么我们就需要将withCredentials设置为true,这里需要注意的是,withCredentials设置为true后,服务端就不能将Access-Control-Allow-Origin设置为*,必须设置为指定的域名地址,如: "http://localhost/:3000",同时还需要将Access-Control-Allow-Credentials设置为true,如:

import axios from "axios";

const instance = axios.create({ // 创建一个独立的axios实例

baseURL: process.env.NODE_ENV === "production" ? "http://www.lihb.com" : "http://localhost/:3000",

headers: { // 定义统一的请求头部

post: {

"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"

}

},

timeout: 20 * 1000, // 配置请求超时时间

validateStatus: () => {

return true; // 使用async-await,处理reject情况较为繁琐,所以全部返回resolve,在业务代码中处理异常

},

withCredentials: true, // 跨域请求的时候允许带上cookie发送到服务器

});

export default instance;

这里简单介绍一下cookie在vue中的使用,vue中使用cookie,可以通过vue-cookies这个模块,引入之后通过Vue.use()进行安装,安装之后会产生一个全局的$cookies对象,就可以进行cookie的操作了,如:

import VueCookies from 'vue-cookies';

Vue.use(VueCookies);

$cookies.set("user", "lihb");

console.log($cookies.get("user"));

服务端可以通过cookie-parser模块进行解析,然后交给express中间件处理,就会在req对象中添加一个cookies属性,可以拿到传递给服务器的cookie,如:

var cookieParser = require("cookie-parser");

app.use(cookieParser());

app.post("/login", (req, res) => {

console.log(req.cookies);

}

为什么将withCredentials设置为true就能够实现cookie的跨域访问呢?

因为withCredentials本身是XMLHttpRequest对象的属性,通过将XMLHttpRequest对象的withCredentials属性设置为true就可以实现cookie的跨域访问了,如:

  • 对于XMLHttpRequest的Ajax请求

var xhr = new XMLHttpRequest(); 

xhr.open('GET', url);

xhr.withCredentials = true; // 携带跨域cookie

xhr.send();

  • 对于JQuery的Ajax请求

$.ajax({ 

type: "GET",

url: url,

xhrFields: {

withCredentials: true // 携带跨域cookie

},

processData: false,

success: function(data) { console.log(data); }

});

如何禁止客户端通过JS访问cookie?

要想禁止客户端通过JS访问cookie,那么我们需要给cookie添加上httpOnly属性,如:

app.post("/login", (req, res) => {

res.cookie("user", "lihb", {httpOnly: true}); // 静止客户端通过JS操作cookie

});

此时客户端就无法通过JS获取到user的值了。

⑥ 请求拦截器,我们发送请求的时候通常需要到服务器验证token有没有过期,所以每个请求都会带上token到服务器验证,我们可以通过在请求拦截器中设置,避免每个请求都设置一遍,如:

// 请求拦截器

instance.interceptors.request.use(config => {

const token = localStorage.getItem("token");

token && (config.headers.Authorization = token);

return config;

}, error => {

error.data = {}

error.data.msg = '服务器异常,请联系管理员!'

return Promise.resolve(error)

});

⑦ 响应拦截器,我们发送的每个请求都会有对应的响应,对于一些通用的错误码,我们可以进行统一处理,同时将错误resolve回去,以便await的时候能够获取到错误信息。如:

// 响应拦截器  

instance.interceptors.response.use((response) => {

errorHandle(response.status, response.data);

return response

}, (error) => {

// 错误抛到业务代码

error.data = {};

if (!window.navigator.onLine) { // 如果网络已断开

alert("网络已断开");

error.data.msg = '网络异常,请检查网络是否正常连接';

} else if (error.code === "ECONNABORTED") {

alert("请求超时");

error.data.msg = '请求超时';

} else {

alert("服务器异常");

error.data.msg = '服务器异常,请联系管理员';

}

return Promise.resolve(error); // 将错误resolve出去

});

const toTargetView = (targetUrl) => {

history.pushState({}, '', targetUrl);

}

const errorHandle = (status, message) => {

switch(status) {

case 401:

toTargetView("/login");

break;

case 403:

// 处理token过期等禁止访问问题

localStorage.removeItem("token");

、 setTimeout(() => {

toTargetView("/login");

}, 1000);

break;

case 404:

// 处理404问题

toTargetView("/404");

break;

default:

console.log(message);

}

}

五、api管理

为了更好的管理api接口以及方便模块化开发,我们需要对我们的api接口进行模块化,首先在src目录下新建一个api目录,然后新建一个index.js,作为所有api接口的输出口,然后在index.js中分别引入各个模块对应的api,在index.js中导出即可。同样根据不同的模块,在src/api目录下创建即可,不同模块下的api都引入上面封装好的axios进行发送请求即可。

六、options请求

在跨域请求中,options请求是浏览器自发起的preflight request(预检请求),以检测实际请求是否可以被浏览器接受。

当跨域请求是简单请求时不会进行preflight request, 只有复杂请求才会进行preflight request。

符合以下任一情况的就是复杂请求:

a.使用方法put或者delete;

b.发送json格式的数据(content-type: application/json)

c.请求中带有自定义头部,比如Authorization

当跨域遇到这些复杂请求的时候,浏览器会自动发送options请求,所以我们必须对这些options请求进行处理,否则浏览器拿不到对应ok状态码,就会拒绝发送请求了,即跨域请求失败。

所以服务器端完整的跨域处理为:

app.all('*', (req, res, next) => {

res.header("Access-Control-Allow-Origin", "http://localhost/:8080");

res.header('Access-Control-Allow-Headers', "*");

res.header("Access-Control-Allow-Credentials", true);

res.header("Access-Control-Allow-Methods", "*");

next();

});

app.options("*", (req, res, next) => { // 处理浏览器options请求

console.log("预检"+req.path);

res.statusCode = 200;

res.send("ok");

});

跨域的复杂请求之所以需要preflight request是因为复杂请求可能对服务器数据产生副作用。例如delete或者put,都会对服务器数据进行修改,所以在请求之前都要先询问服务器,当前网页所在域名是否在服务器的许可名单中,服务器允许后,浏览器才会发出正式的请求,否则不发送正式请求。

以上是 axios封装和api管理实践笔记 的全部内容, 来源链接: utcz.com/a/13844.html

回到顶部