Content-Type 用于规定客户端经过http或https协议向服务器发起请求时,传递的请求体中数据的编码格式。由于get请求是直接将请求数据以键值对经过&号链接(key1=value1&key2=value2
)的方式附加到url地址后面,不在请求体中,因此get请求中不须要设置Content-Type。经过浏览器抓取get请求数据能够发现其请求头中并无Content-Type这一属性。vue
当咱们经过<form>表单标签提交数据的时候,若是将其method设置为get,那么是属于get请求,全部表单数据会被附加到url地址后面,如:node
<form method="get"><!--属于get方式提交--> <input name="user"/> <button type="submit">提交</button> </form>
在浏览器中输入localhost:8080,显示出页面后,在表单中填入数据lihb
后,浏览器地址会变为http://localhost:8080/?user=lihb
,说明其使用的是get的提交方式。ios
POST请求中常见的几种Content-Type形式: ajax
① application/x-www-form-urlencoded
这是最多见的POST请求提交方式,咱们经过<form>表单标签提交数据的时候,若是将其method设置为post,那么就是属于post请求,表单提交的数据会被放到请求体中,而且表单提交的数据也是采用键值对经过&号链接(key1=value1&key2=value2
)的方式,也就是说,该Content-Type下,get和post请求提交的数据格式是同样的,只不过post请求是将数据放到了请求体中。vue-cli
<form method="post"><!--属于post方式提交--> <input name="user"/> <input name="age"/> <button type="submit">提交</button> </form>
在浏览器中输入localhost:8080,显示出页面后,在表单中填入数据lihb
和18
后,浏览器地址仍然是http://localhost:8080,可是在请求头中,咱们能够看到Content-Type:application/x-www-form-urlencoded
,请求体中能够看到user=lihb&age=18
说明其使用的是post的提交方式。express
② multipart/form-data
这种一般用于文件上传,咱们经过<form>表单标签提交数据的时候,须要将其method设置为post,同时还要将其enctype设置为multipart/form-data,那么这个时候就是属于post数据提交,只不过其数据会编码为一条一条的消息。须要注意的是文件上传必须设置为post请求,若是method为get,即便设置了enctype为multipart/form-data,那么仍然为get请求。npm
<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
json
------WebKitFormBoundarysa9SPcvOKqKEDdBb Content-Disposition: form-data; name="user" lihb ------WebKitFormBoundarysa9SPcvOKqKEDdBb--
浏览器会自动生成一个boundary分界线,用于将每条消息数据分割开。axios
③ 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 是一个基于 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的封装?封装是经过更少的调用代码覆盖更多的调用场景。在浏览器端他经过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); }
⑥ 请求拦截器,咱们发送请求的时候一般须要到服务器验证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接口进行模块化,首先在src目录下新建一个api目录,而后新建一个index.js,做为全部api接口的输出口,而后在index.js中分别引入各个模块对应的api,在index.js中导出便可。一样根据不一样的模块,在src/api目录下建立便可,不一样模块下的api都引入上面封装好的axios进行发送请求便可。
在跨域请求中,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"); });