前言
记录一下自己在 nodejs 中使用 http 请求库 axios 中的一些坑(针对 Cookie 操作)
不敢说和别人封装的 axios 相比有多好,但绝对是你能收获到 axios 的一些知识,话不多说,开始
封装
一般而言,很少有裸装使用 axios 的,就我涉及的项目来说,我都会将 axios 的 request 封装成一个函数使用,接着在 api 目录下,引用该文件。项目结构一般是这样的:
|-- src
|-- api
|-- user.js
|-- goods.js
|-- utils
|-- request.js
request.js
import axios from 'axios'
var instance = axios.create({
baseURL: process.env.API, // node环境变量获取的Api地址
withCredentials: true, // 跨域携带Cookies
timeout: 5000,
})
// 设置请求拦截器
instance.interceptors.request.use(
config => {
// 在config可以添加自定义协议头 例如token
config.headers['x-token'] = 'xxxxxxxx'
return config
},
error => {
Promise.error(error)
},
)
instance.interceptors.response.use(
response => {
const res = response.data
// 根据对应的业务代码 对返回数据进行处理
return res
},
error => {
const { response } = error
// 状态码为4或5开头则会报错
// 根据根据对应的错误,反馈给前端显示
if (response) {
if (response.status == 404) {
console.log('请求资源路径不存在')
}
return Promise.reject(response)
} else {
// 断网......
}
},
)
export default instance
实际上,上面那样的封装就够了,相对于的业务代码就不补充了,如果你的宿主环境是浏览器的话,很多东西你就没必要在折腾的,甚至下面的文章都没必要看(不过还是推荐你看看,会有帮助的)。不过没完,再看看 api 里怎么使用的
api/user.js
import request from '@/utils/request'
export function login(data) {
return request({
url: '/user/login',
method: 'post',
data,
})
}
export function info() {
return request({
url: '/user/info',
method: 'get',
})
}
export function logout() {
return request({
url: '/user/logout',
method: 'post',
})
}
看来很简单,没错, 就是这么简单,由于是运行在浏览器内的,所以像 cookies,headers 等等都没必要设置,浏览器会自行携带该有的设置,其实想设置也设置不了,主要就是浏览器内置跨域问题。XMLHttpRequest
就这?感觉你写的跟别人没什么区别啊
别急,下面才是重头戏。也是我为啥标题只写 axios,而不写 vue-axios 或者 axios 封装的原因。
踩坑 Cookies 获取与设置
浏览器
运行环境在浏览器中,axios 是无法设置与获取 cookie,获取不到 set-cookies 这个协议头的(即使服务器设置了也没用),先看代码与输出
instance.interceptors.request.use(config => {
config.headers['cookie'] = 'cookie=this_is_cookies;username=kuizuo;'
console.log('config.headers', config.headers)
return config
})
instance.interceptors.response.use(response => {
console.log('response.headers', response.headers)
return res
})
控制台结果:
首先,就是圈的这个,浏览器是不许允许设置一些不安全的协议头,例如 Cookie,Orgin,Referer 等等,即便你看到控制台 config.headers 确实有刚刚设置 cookie,但我们输出的也只是 headers 对象,在 Network 中找到这个请求,也同样看不到 Cookie 设置的(这就不放图了)。
同 样的,通过响应拦截器中输出的 headers 中也没有 set-cookies 这个字样。网络上很多都是说,添加这么一行代码 withCredentials: true
,确实,但是没说到重点,都没讲述到怎么获取 cookies 的,因为在浏览器环境中 axios 压根就获取不到 set-cookies 这个协议头,实际上 axios 就没必要,因为浏览器会自行帮你获取服务器返回的 Cookies,并将其写入在 Storage 里的 Cookies 中,再下次请求的时候根据同源策略携带上对应的 Cookie。
要获取也很简单,vue 中通过js-cookie
模块即可,而在 electron 中通过const { session } = require('electron').remote
(electron 可以设置允许跨域,好用)有关更多可以自行查看文档。
那我就是想要设置 Cookies,来跳过登录等等咋办,我的建议是别用浏览器来伪装 http 请求。跨域是浏览器内不可少的一部分,并且要允许跨域过于麻烦。有关跨域,我推一篇文章10 种跨域解决方案(附终极大招)
完整封装代码
import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
withCredentials: true,
timeout: 5000,
})
service.interceptors.request.use(
config => {
if (store.getters.token) {
config.headers['x-token'] = getToken()
}
return config
},
error => {
Message.error(error)
return Promise.reject(error)
},
)
service.interceptors.response.use(
response => {
const res = response.data
if (res.code !== 200) {
Message.error(res.msg || 'Error')
return Promise.reject(new Error(res.msg || '未知错误'))
} else {
return res
}
},
error => {
if (error.response) {
let res = error.response
switch (res.status) {
case 400:
Message.error(res.msg || '非法请求')
break
case 401:
MessageBox.alert('当前登录已过期,请重新登录', '提示', {
confirmButtonText: '重新登录',
type: 'warning',
}).then(() => {
store.dispatch('user/logout').then(() => {
location.reload()
})
})
case 403:
Message.error(res.msg || '非法请求')
router.push('/401')
case 404:
Message.error(res.msg || '请求资源不存在')
break
case 500:
Message.error(res.msg || '服务器开小差啦')
break
default:
Message.error(res.msg || res.statusText)
}
} else {
Message.error(res.msg || '请检查网络连接状态')
}
return Promise.reject(error)
},
)
export default service