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"); });