這一章主要完善接口
目錄結構沒有進行修改
withCredentials
這個功能是可以攜帶跨越請求的,默認情況下是自動攜帶同源 cookie
的 ,但是跨域的時候是不可以進行攜帶的,將 withCredentials
設置成 true
就可以進行攜帶了。
types/index.ts
export interface AxiosRequestConfig {
// ...
withCredentials?:boolean,
//...
}
core/xhr.ts
// ...
if(timeout) request.timeout = timeout;
if(withCredentials) {
request.withCredentials = withCredentials;
}
// ...
接改變這點就夠了
XSRF
其實就是在 header
中添加 token
(後端生成返回的標識)
types/index.ts
export interface AxiosRequestConfig {
// ...
xsrfCookieName?:string,
xsrfHeaderName?:string,
// ...
}
defaults.ts
const defaults: AxiosRequestConfig = {
// ...
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
// ...
}
helpers/url.ts
interface URLOrigin{
protocol:string,
host: string,
}
export function isURLsameOrigin(requestURL: string):boolean {
const parseOrigin = resolverURL(requestURL);
return (parseOrigin.protocol === currentOrigin.protocol && parseOrigin.host === currentOrigin.host)
}
const urlParsingNode = document.createElement('a');
const currentOrigin = resolverURL(window.location.href);
function resolverURL(url: string): URLOrigin {
urlParsingNode.setAttribute('href',url);
const {protocol,host} = urlParsingNode;
return {
protocol,
host,
}
}
通過 a
標籤拿到 protocol
(協議)和 host
(主機地址)
新建 helpers/cookie.ts
const cookie = {
read(name: string) : string | null {
const match = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)'));
return match ? decodeURIComponent(match[3]) : null;
}
}
export default cookie;
讀取 cookie
core/xhr.ts
// ...
request.ontimeout = function handleTimeout() {
reject(createError(`Timeout of ${timeout} ms exceeded`,config,'ECONNABORTED',request));
}
// 添加的部分
if((withCredentials || isURLsameOrigin(url)) && xsrfCookieName){
const xsrfValue = cookie.read(xsrfCookieName);
if(xsrfValue && xsrfHeaderName) {
headers[xsrfHeaderName] = xsrfValue;
}
};
// 添加的部分
// ...
自動設置 cokie
,個人感覺沒什麼用
下載 和 上傳監控
types/index.ts
// ...
export interface AxiosRequestConfig {
// ...
onDownloadProgress?:(e: ProgressEvent) => void,
onUploadProgress?: (e: ProgressEvent) => void,
// ...
}
// ...
helpers/util.ts
export function isFormData(val: any): val is FormData {
return typeof val !== 'undefined' && val instanceof FormData
}
判斷對象是否是 FormData
core/xhr.ts
// ... 錨點
request.ontimeout = function handleTimeout() {
reject(createError(`Timeout of ${timeout} ms exceeded`,config,'ECONNABORTED',request));
}
// 添加的部分
if(onDownloadProgress) {
request.onprogress = onDownloadProgress;
}
if(onUploadProgress) {
request.upload.onprogress = onUploadProgress;
}
if(isFormData(data)) {
delete headers['Content-Type']
}
// 添加的部分
// ...
將 上傳 和 下載的函數掛載到 request 上;如果 data
是 Formdata
類型的數據就將 Content-Type
刪除,瀏覽器會默認添加的
整理核心庫 xhr
import {AxiosRequestConfig,AxiosPromise,AxiosResponse} from '../types';
import {parseHeaders} from '../helpers/header';
import {createError} from '../helpers/error';
import {isURLsameOrigin} from '../helpers/url';
import {isFormData} from '../helpers/util'
import cookie from '../helpers/cookie';
export default function xhr(config:AxiosRequestConfig) : AxiosPromise {
return new Promise((reslove,reject)=> {
const {
data = null,
url,
method='get',
timeout,
headers,
responseType,
cancleToken,
withCredentials,
xsrfCookieName,
xsrfHeaderName,
onDownloadProgress,
onUploadProgress
} = config;
const request = new XMLHttpRequest();
request.open(method.toUpperCase(),url!,true);
configureRequest();
addEvents();
processHeaders();
proccessCancel();
request.send(data);
function configureRequest(): void{
if(responseType) request.responseType = responseType;
// 超時操作
if(timeout) request.timeout = timeout;
if(withCredentials) request.withCredentials = withCredentials;
}
function addEvents():void{
request.onreadystatechange = function handleLoad() {
if(request.readyState !== 4) {
return;
}
if(request.status === 0) {
// 網絡錯誤和超時錯誤時 status爲 0
return;
}
const responseHeaders = parseHeaders(request.getAllResponseHeaders());
const responseDate = responseType !== 'text' ? request.response : request.responseText;
const response:AxiosResponse = {
data:responseDate,
status:request.status,
statusText: request.statusText,
headers: responseHeaders,
config,
request,
}
handleResponse(response);
}
// 請求錯誤
request.onerror = function handleError() {
reject(createError('Network Error',config,null,request));
}
// 超時錯誤
request.ontimeout = function handleTimeout() {
reject(createError(`Timeout of ${timeout} ms exceeded`,config,'ECONNABORTED',request));
}
if(onDownloadProgress) {
request.onprogress = onDownloadProgress;
}
if(onUploadProgress) {
request.upload.onprogress = onUploadProgress;
}
}
function processHeaders():void {
if(isFormData(data)) {
delete headers['Content-Type']
}
if((withCredentials || isURLsameOrigin(url!)) && xsrfCookieName){
console.log('執行了',xsrfCookieName);
const xsrfValue = cookie.read(xsrfCookieName);
console.log(xsrfValue);
if(xsrfValue && xsrfHeaderName) {
headers[xsrfHeaderName] = xsrfValue;
}
};
// 錯誤結束
Object.keys(headers).forEach((name) => {
if(data === null && name.toLocaleLowerCase() === 'content-type') {
delete headers[name];
} else {
request.setRequestHeader(name,headers[name]);
}
})
}
function proccessCancel():void {
if(cancleToken) {
cancleToken.promise.then(reason => {
request.abort()
reject(reason)
})
}
}
function handleResponse(response: AxiosResponse) : void{
if(response.status >= 200 && response.status < 300) {
reslove(response);
} else {
reject(createError(`Request failed with status code ${response.status}`,config,null,request,response));
}
}
})
}
從以上代碼可以看出,我將以前的代碼簡單的歸了類, 依次執行 configureRequest
、addEvents
、processHeaders
、proccessCancel
這四個函數,組合在進行發送
HTTP auth
HTTP協議中的 Authorization 請求消息頭含有服務器用於驗證用戶代理身份的憑證,通常會在服務器返回401
Unauthorized
狀態碼以及WWW-Authenticate
消息頭之後在後續請求中發送此消息頭。
axios 中可以通過 auth
來設置 Authorization ,個人感覺沒太大用。
types/index.ts
// ...
export interface AxiosRequestConfig {
// ...
auth?:AxiosBasicCredentials,
// ...
}
// ...
export interface AxiosBasicCredentials {
username: string,
password: string,
}
core/xhr.ts
if(auth) {
headers['Authorization'] = 'Basic ' + `${btoa(auth.username)}:${btoa(auth.password)}`;
}
就這樣就行了,不過使用的話還是要配合後臺的。
自定義合法狀態碼
我們默認的合法狀態碼( status )是 200 - 300,我們可以通過 validateStatus 函數來自定義合法狀態
types/index.ts
export interface AxiosRequestConfig {
// ...
validateStatus?:(status:number) => boolean,
// ...
}
core/xhr.ts
function processHeaders():void {
// ...
if(auth) {
headers['Authorization'] = 'Basic ' + `${btoa(auth.username)}:${btoa(auth.password)}`;
}
// ...
}
使用
axios.get('/more/304',{
validateStatus(status){
return status >= 200 && status < 400
}
}).then(res => {
console.log(res)
}).catch((e) =>{
console.log(e.message);
})
自定義參數解析規則
看名字應該理解,用戶自己定義 url 產生的解析規則
types/index.ts
export interface AxiosRequestConfig {
// ...
paramsSerializer?:(params: any) => string,
// ...
}
helpers/url.ts
export function buildURL(url: string, params?: any, paramsSerializer?:(params:any) => string):string {
if(!params) return url;
let serializedParams;
if(paramsSerializer) {
serializedParams = paramsSerializer(params);
} else if(isURLSearchParams(params)){
serializedParams = params.toString()
}else {
const parts:string[] = [];
Object.keys(params).forEach((key) => {
const val = params[key];
if(val === null || typeof val === 'undefined') {
return
}
let values = [];
if(Array.isArray(val)) {
values = val;
key += '[]';
}else {
values = [val];
}
values.forEach((val) => {
if(isDate(val)){
val = val.toISOString();
}else if(isPlainObject(val)) {
val = JSON.stringify(val);
}
parts.push(`${encode(key)}=${encode(val)}`);
})
})
serializedParams = parts.join('&');
}
if(serializedParams) {
const markIndex = url.indexOf('#');
if(markIndex !== -1) {
url = url.slice(0,markIndex);
}
url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;
}
return url;
}
這個函數分三種情況:paramsSerializer
有的話,就進行自定義的參數解析; params
是否是 URLSearchParams
;默認解析情況
helpers/util.ts
export function isURLSearchParams(val: any): val is URLSearchParams{
return typeof val !== 'undefined' && val instanceof URLSearchParams
}
core/dispatchRequest.ts
function transformURL (config: AxiosRequestConfig) :string {
const {url,params,paramsSerializer} = config;
return buildURL(url!,params,paramsSerializer);
}
baseURL
就是我們訪問同一個域名下的多個接口時,我們不希望每次發送請求都填寫完整的 url,可以用這個參數進行配合
types/index.ts
export interface AxiosRequestConfig {
baseURL?:string,
}
helpers/url.ts
export function isAbsoluteURL(url:string): boolean {
return /(^[a-z][a-z\d\+\-\.]*:])?\/\//i.test(url)
}
export function combineURL(baseURL:string, relativeURL?:string):string {
return relativeURL ? baseURL.replace(/\/+$/,'') + '/' +relativeURL.replace(/^\/+/, ''): baseURL
}
前一個函數用來判斷是否是絕對url,後一個函數進行url地址連接
core/dispatchRequest.ts
function transformURL (config: AxiosRequestConfig) :string {
let {url,params,paramsSerializer,baseURL} = config;
if(baseURL && !isAbsoluteURL(url)) {
url = combineURL(baseURL,url);
}
return buildURL(url!,params,paramsSerializer);
}
這裏進行完整的url地址拼接
我覺得這些函數已經夠用了,其他的一些很小衆的函數我就不寫了。