一比一還原axios原始碼(八)—— 其他功能

Zaking發表於2022-03-18

  到此,我們完成了axios的絕大部分的功能,接下來我們來補全一下其他的小功能。

一、withCredentials

   這個引數可以可以表明是否是一個跨域的請求。那這個的使用場景是啥呢?就是我們在同域的請求的情況下,是會預設攜帶cookie的,跨域的話就不會攜帶cookie,如果我們想要跨域請求並攜帶cookie,那麼就需要這個引數了。當然這個實現非常簡單:

    if (!utils.isUndefined(config.withCredentials)) {
      request.withCredentials = !!config.withCredentials;
    }

  嗯,就這樣就完了。

  然後我們需要建立一個作為接受跨域請求的server2.js作為跨域訪問的伺服器。程式碼可以在gitHub上看哦。就不多說了。

二、XSRF 防禦

  就是跨站請求偽造,登入信任的A網站後會產生該使用者的信任cookie,由於瀏覽器在傳送請求的時候會自動攜帶cookie,如果使用者在沒有登出的情況下登入黑客網站,那麼就會把帶有信任的cookie傳遞給黑客網站,此時黑客網站拿到你的使用者cookie就可以偽造使用者登入A網站了。

  XSRF的防禦手段有很多,比如referer,但是referer也是可以偽造的,所以杜絕此類攻擊的一種方式是伺服器端要求每次請求都包含一個token,這個token不在前端生成,而是在我們每次訪問站點的時候生成,並通過set-cookie的方式種到客戶端,然後客戶端傳送請求的時候,從cookie中對應的欄位讀取出token,然後新增到請求headers中。這樣服務端就可以從請求headers中讀取這個token並驗證,由於這個token 是很難偽造的,所以就能區分這個請求是否是使用者正常發起的。

  所以在axios中,我們需要自動把這些事情做了,每次傳送請求的時候,從cookie中讀取對應的token值,然後新增到請求headers中。我們允許使用者配置xsrfCookieNamexsrfHeaderName,其中xsrfCookieName表示儲存tokencookie名稱,xsrfHeaderName表示請求headerstoken對應的header名稱。

   例子就像這樣:

const instance = axios.create({
  xsrfCookieName: 'XSRF-TOKEN-D',
  xsrfHeaderName: 'X-XSRF-TOKEN-D'
})

instance.get('/more/get').then(res => {
  console.log(res)
})

  那麼接下來我們來看下程式碼的實現:

  首先,我們在defaults中新增兩個預設引數:

var defaults = {
// ...
  xsrfCookieName: "XSRF-TOKEN",
  xsrfHeaderName: "X-XSRF-TOKEN",
// ...

}

  首先啊,我們要做一些判斷,首先要判斷如果是配置 withCredentials 為 true 或者是同域請求,我們才會請求 headers 新增 xsrf 相關的欄位,然後我們在helpers資料夾下建立一個isURLSameOrigin檔案,用來判斷是否是同源,我直接從axios複製過來的,哈哈。程式碼大家自己去看註釋哦。它的核心其實就是建立個a標籤,然後設定屬性為我們傳入的url地址,通過這個DOM,我們就可以獲取到對應url的protocol、host等屬性,然後我們判斷這兩個是否相同就可以了。

  然後,我們在建立個cookies檔案,也是在helpers資料夾中,這個cookies主要封裝了一些cookie的讀寫操作。

  最後,就是我們的核心邏輯程式碼了,其實很簡單:

    if (utils.isStandardBrowserEnv()) {
      // Add xsrf header
      var xsrfValue =
        (config.withCredentials || isURLSameOrigin(config.url)) &&
        config.xsrfCookieName
          ? cookies.read(config.xsrfCookieName)
          : undefined;
      if (xsrfValue) {
        requestHeaders[config.xsrfHeaderName] = xsrfValue;
      }
    }

  首先判斷是否是瀏覽器環境,如果是的話,就再判斷是否有withCredentials或者是同源的,並且有xsrfCookieName,然後讀取cookie中的xsrfCookieName的值,然後設定給headers即可。其實核心邏輯並不複雜,複雜的是XSRF的概念,和一些它的判斷條件中的方法。

  最後,在我們的demo裡,我們還要設定下server.js:

app.use(express.static(__dirname, {
  setHeaders (res) {
    res.cookie('XSRF-TOKEN-D', '1234abc')
  }
}))

  這樣才可以。

三、上傳下載進度監控

  其實上傳和下載的進度監控,都可以通過xhr的原生的物件來進行獲取。這是axios的文件:

   我們先來看實現吧,實現起來非常簡單,其實就是傳了個引數,xhr裡呼叫一下:

    // Handle progress if needed
    if (typeof config.onDownloadProgress === 'function') {
      request.addEventListener('progress', config.onDownloadProgress);
    }

    // Not all browsers support upload events
    if (typeof config.onUploadProgress === 'function' && request.upload) {
      request.upload.addEventListener('progress', config.onUploadProgress);
    }

  就這麼簡單,完事了。。。。demo的話我就不在這裡佔篇幅了,大家去專案裡看下。

四、Authorization

  HTTP 協議中的 Authorization 請求 header 會包含伺服器用於驗證使用者代理身份的憑證,通常會在伺服器返回 401 Unauthorized 狀態碼以及 WWW-Authenticate 訊息頭之後在後續請求中傳送此訊息頭。

  axios 庫也允許你在請求配置中配置 auth 屬性,auth 是一個物件結構,包含 username 和 password 2 個屬性。一旦使用者在請求的時候配置這倆屬性,我們就會自動往 HTTP 的 請求 header 中新增 Authorization 屬性,它的值為 Basic 加密串。 這裡的加密串是 username:password base64 加密後的結果。

  axios文件中是這樣說明的:

 

   我們來看下程式碼實現:

// HTTP basic authentication
if (config.auth) {
  var username = config.auth.username || "";
  var password = config.auth.password
    ? unescape(encodeURIComponent(config.auth.password))
    : "";
  requestHeaders.Authorization = "Basic " + btoa(username + ":" + password);
}

  就這麼簡單~~。

五、自定義合法狀態碼

  首先,我們還是來看官方的文件:
  也就是說,我們可以通過validateStatus引數,自定義哪些狀態碼是合法的。那,我們需要給defaults物件一個預設的validateStatus:

  額。。。跟文件一模一樣。。。。那我們在判斷promise是走resolve還是reject的時候,就要額外的處理下了,對了,大家還記得我們實在哪裡執行的了不?還記不記得有個settle檔案?

export default function settle(resolve, reject, response) {
  var validateStatus = response.config.validateStatus;
  if (!response.status || !validateStatus || validateStatus(response.status)) {
    resolve(response);
  } else {
    reject(
      createError(
        "Request failed with status code " + response.status,
        response.config,
        null,
        response.request,
        response
      )
    );
  }
}

  嗨(四聲)!就是個判斷!

六、自定義引數序列化

  我們先來看文件:

 

   這個東西呢,允許我們自己定義規則解析url後的query引數,大多數情況下用不到吼。

七、baseURL

  有些時候,我們會請求某個域名下的多個介面,我們不希望每次傳送請求都填寫完整的 url,希望可以配置一個 baseURL,之後都可以傳相對路徑。我們一旦配置了 baseURL,之後請求傳入的 url 都會和我們的 baseURL 拼接成完整的絕對地址,除非請求傳入的 url 已經是絕對地址。

          

   我們需要兩個輔助函式:combineURLs和isAbsoluteURL,然後再通過這兩個輔助函式,構建一個buildFullPath核心方法。然後在xhr中直接呼叫buildFullPath即可:
var fullPath = buildFullPath(config.baseURL, config.url);
request.open(
  config.method.toUpperCase(),
  buildURL(fullPath, config.params, config.paramsSerializer),
  true
);

八、靜態方法擴充套件

  額。。。直接看程式碼吧,懶得說了~~~

  首先,擴充套件幾個靜態方法:

// Expose all/spread
axios.all = function all(promises) {
  return Promise.all(promises);
};
axios.spread = function spread(callback) {
  return function wrap(arr) {
    return callback.apply(null, arr);
  };
};

// Expose isAxiosError
axios.isAxiosError = function isAxiosError(payload) {
  return utils.isObject(payload) && payload.isAxiosError === true;
};

  然後,擴充套件個getUri方法:

Axios.prototype.getUri = function getUri(config) {
  config = mergeConfig(this.defaults, config);
  return buildURL(config.url, config.params, config.paramsSerializer).replace(
    /^\?/,
    ""
  );
};

  撒花,完結~

相關文章