axios的封裝和api的管理

1、Content-Type

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,顯示出頁面後,在表單中填入數據lihb18後,瀏覽器地址仍然是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=----WebKitFormBoundarysa9SPcvOKqKEDdBbjson

------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"
      }
    ]
}

2、服務器端數據解析

因爲發送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);
    });
}

3、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);
  }
});

4、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);
}

⑥ 請求攔截器,咱們發送請求的時候一般須要到服務器驗證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);
    }
}

5、api管理

爲了更好的管理api接口以及方便模塊化開發,咱們須要對咱們的api接口進行模塊化,首先在src目錄下新建一個api目錄,而後新建一個index.js,做爲全部api接口的輸出口,而後在index.js中分別引入各個模塊對應的api,在index.js中導出便可。一樣根據不一樣的模塊,在src/api目錄下建立便可,不一樣模塊下的api都引入上面封裝好的axios進行發送請求便可。

屏幕快照 2020-03-14 下午5.13.30.png

6、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");
});
相關文章
相關標籤/搜索