文章目錄
- 前言
- 一、什么是ngify?
- 二、npm安裝
- 三、發起請求
- 3.1 獲取 JSON 數據
- 3.2 獲取其他類型的數據
- 3.3 改變服務器狀態
- 3.4 設置 URL 參數
- 3.5 設置請求標頭
- 3.6 與服務器響應事件交互
- 3.7 接收原始進度事件
- 3.8 處理請求失敗
- 3.9 Http Observables
- 四、更換 HTTP 請求實現
- 五、XSRF/CSRF 防護
- 六、全局配置
- 七、基本用法
- 八、攔截請求和響應
- 九、測試請求
- 十、支持
前言
這篇文章主要介紹了 @ngify/http
這一響應式 HTTP 客戶端,包括其與 axios
的關聯、功能特點、基本用法(如獲取不同類型數據、設置參數和標頭等)、攔截器的使用、更換請求實現、防護機制、全局配置及測試請求等,還對比了它與其他庫的差異,強調了其穩定性和便捷性。
一、什么是ngify?
在前端開發中,使用最廣泛的 HTTP 客戶端為 axios
,它是一個用于瀏覽器和 Node.js 的、基于 Promise 的 HTTP 客戶端。
axios
的前身其實是 AngularJS 的 $http
服務。axios
深受 AngularJS 中提供的 $http
服務的啟發,將 $http
服務從 AngularJS 中剝離,提供一個獨立的服務,以便在 AngularJS 之外使用。
注:AngularJS 特指 AngularJS v1,而非 Angular 2+。
Angular2+ 拋棄了原有的 $http
服務,轉而與 RxJS 深度集成,打造了一個更加先進、現代化的響應式 HTTP 客戶端。這個新的客戶端充分利用了 RxJS 的強大功能,提供了更靈活、更易于理解的異步操作方式。然而,由于其與 Angular 的依賴注入和 SSR 功能緊密耦合,使得它無法直接應用于 Angular 生態之外。
@ngify/http
是一個基于 RxJS 的響應式 HTTP 客戶端,提供了與 Angular HttpClient 高度一致的 API,主要包含以下功能:
- 請求類型化響應對象的能力。
- 簡化的錯誤處理。
- 請求和響應的攔截機制。
- 輕松處理請求超時、重試、并發、緩存等等。
- 支持多種平臺:Web、Node.js、微信小程序、uni-app、taro 等等。
@ngify/http
的目標與 axios
一致:提供一個獨立的 HTTP 服務,以便在 Angular2+ 之外使用。
💯無需擔心 @ngify/http
的穩定性,@ngify/http
采用了與 Angular HttpClient 相同的嚴格單元測試,確保了其代碼質量和可靠性,在各種場景下保持足夠的穩定性。
二、npm安裝
npm install @ngify/http
三、發起請求
HttpClient
具有與用于發出請求的不同 HTTP
動詞相對應的方法,這些方法既可以加載數據,也可以在服務器上應用變更。每個方法都返回一個 RxJS Observable
,訂閱后會發送請求,然后在服務器響應時發出結果。
由 HttpClient 創建的 Observable 可以被訂閱任意多次,并且每次訂閱都會發出一個新的后端請求。
通過傳遞給請求方法的選項對象,可以調整請求的各種屬性和返回的響應類型。
3.1 獲取 JSON 數據
從后端獲取數據通常需要使用 HttpClient.get()
方法發出 GET
請求。此方法采用兩個參數:要從中獲取的字符串端點 URL
,以及用于配置請求的可選選項對象。
例如,要使用 HttpClient.get()
方法從假設的 API
獲取配置數據:
http.get<Config>('/api/config').subscribe(config => {// process the configuration.
});
請注意泛型類型參數,它指定服務器返回的數據的類型為 Config
。該參數是可選的,如果省略它,則返回的數據將具有 any
類型。
🎯 注意: 如果數據具有未知形狀的類型,那么更安全的方法是使用 unknown 作為響應類型。
請求方法的通用類型是關于服務器返回的數據的類型斷言。 HttpClient
不會驗證實際返回數據是否與該類型匹配。
3.2 獲取其他類型的數據
默認情況下, HttpClient
假定服務器將返回 JSON
數據。與非 JSON API 交互時,您可以告訴 HttpClient
在發出請求時期望并返回什么響應類型。這是通過 responseType
選項完成的。
Response type | Returned response type |
---|---|
‘json’ (默認) | 給定泛型類型的 JSON 數據 |
‘text’ | 字符串文本 |
‘blob’ | Blob 實例 |
‘arraybuffer’ | 包含原始響應字節的 ArrayBuffer |
例如,您可以要求 HttpClient 將 .jpeg 圖像的原始字節下載到 ArrayBuffer 中:
http.get('/images/dog.jpg', { responseType: 'arraybuffer' }).subscribe(buffer => {console.log('The image is ' + buffer.byteLength + ' bytes large');
});
3.3 改變服務器狀態
執行修改的服務器 API
通常需要發出 POST
請求,并在請求正文中指定新狀態或要進行的更改。
HttpClient.post()
方法的行為與 get()
類似,只是第二位參數變為 body
參數:
http.post<Config>('/api/config', newConfig).subscribe(config => {console.log('Updated config:', config);
});
可以提供許多不同類型的值作為請求的 body
,并且 HttpClient
將相應地序列化它們:
Body | typeSerialization as |
---|---|
string | 純文本 |
number、boolean、Array 或 object | JSON 字符串 |
ArrayBuffer | 來自 buffer 的原始數據 |
Blob | 具有 Blob 內容類型的原始數據 |
FormData | multipart/form-data 表單數據 |
HttpParams 或 URLSearchParams | application/x-www-form-urlencoded 格式化字符串 |
3.4 設置 URL 參數
使用 params
選項指定應包含在請求 URL
中的請求參數。
傳遞字面量對象是配置 URL
參數的最簡單方法:
http.get('/api/config', {params: { filter: 'all' },
}).subscribe(config => {// ...
});http.post('/api/config', body, {params: { filter: 'all' },
}).subscribe(config => {// ...
});
或者,如果您需要對參數的構造或序列化進行更多控制,則可以傳遞 HttpParams
的實例。
🎯注意: HttpParams 的實例是不可變的,不能直接更改。相反,諸如 append() 之類的可變方法會返回應用了變更的 HttpParams 的新實例。
const baseParams = new HttpParams().set('filter', 'all');http.get('/api/config', {params: baseParams.set('details', 'enabled')
}).subscribe(config => {// ...
});http.post('/api/config', body, {params: baseParams.set('details', 'enabled')
}).subscribe(config => {// ...
});
您可以使用自定義 HttpParameterCodec
實例化 HttpParams(構造方法中的第二個參數),該自定義
HttpParameterCodec確定
HttpClient` 如何將參數編碼到 URL 中。
3.5 設置請求標頭
使用 headers
選項指定應包含在請求中的請求標頭。
傳遞字面量對象是配置請求標頭的最簡單方法:
http.get('/api/config', {headers: {'X-Debug-Level': 'verbose',}
}).subscribe(config => {// ...
});
或者,如果您需要對標頭的構造進行更多控制,請傳遞 HttpHeaders
的實例。
🎯注意: HttpHeaders 的實例是不可變的,不能直接更改。相反,諸如 append() 之類的可變方法會返回應用了變更的 HttpHeaders 的新實例。
const baseHeaders = new HttpHeaders().set('X-Debug-Level', 'minimal');http.get<Config>('/api/config', {headers: baseHeaders.set('X-Debug-Level', 'verbose'),
}).subscribe(config => {// ...
});
3.6 與服務器響應事件交互
為了方便起見,HttpClient
默認返回服務器返回的數據的 Observable(body)
。有時需要檢查實際響應,例如檢索特定的響應標頭。
要訪問整個響應,請將 observe
選項設置為 'response'
:
http.get<Config>('/api/config', { observe: 'response' }).subscribe(res => {console.log('Response status:', res.status);console.log('Body:', res.body);
});
3.7 接收原始進度事件
除了響應正文或響應對象之外,HttpClient
還可以返回與請求生命周期中的特定時刻相對應的原始事件流。這些事件包括何時發送請求、何時返回響應標頭以及何時完成正文。這些事件還可以包括報告大型請求或響應正文的上傳和下載狀態的進度事件。
默認情況下,進度事件處于禁用狀態(因為它們會產生性能成本),但可以使用 reportProgress
選項來啟用。
🎯注意:
HttpClient
的fetch
實現不支持報告上傳進度事件。
要觀察事件流,請將 observe
選項設置為 'events'
:
http.post('/api/upload', myData, {reportProgress: true,observe: 'events',
}).subscribe(event => {switch (event.type) {case HttpEventType.UploadProgress:console.log('Uploaded ' + event.loaded + ' out of ' + event.total + ' bytes');break;case HttpEventType.Response:console.log('Finished uploading!');break;}
});
事件流中報告的每個 HttpEvent
都有一個 type
來區分事件所代表的內容:
3.8 處理請求失敗
HTTP
請求失敗有兩種情況:
- 網絡或連接錯誤可能會阻止請求到達后端服務器。
- 后端可以收到請求但無法處理,并返回錯誤響應。
HttpClient
在 HttpErrorResponse 中捕獲這兩種錯誤,并通過 Observable
的錯誤通道返回該錯誤。網絡錯誤的 status
代碼為 0,error
是 ProgressEvent 的實例。
后端錯誤具有后端返回的失敗 status
代碼,以及錯誤響應 error
。檢查響應以確定錯誤的原因以及處理錯誤的適當操作。
RxJS 提供了多個可用于錯誤處理的運算符。
您可以使用 catchError
運算符將錯誤響應轉換為 UI
的值。該值可以告訴 UI
顯示錯誤頁面或值,并在必要時捕獲錯誤原因。
有時,諸如網絡中斷之類的暫時性錯誤可能會導致請求意外失敗,只需重試請求即可使其成功。RxJS
提供了幾個重試運算符,它們在某些條件下自動重新訂閱失敗的 Observable
。例如,retry()
運算符將自動嘗試重新訂閱指定的次數。
3.9 Http Observables
HttpClient
上的每個請求方法都會構造并返回所請求響應類型的 Observable。使用 HttpClient
時,了解這些 Observable 的工作原理非常重要。
HttpClient
生成 RxJS 所謂的 “冷” Observable,這意味著在訂閱 Observable之前不會發生任何實際請求。只有這樣,請求才真正發送到服務器。多次訂閱同一個 Observable會觸發多個后端請求。每個訂閱都是獨立的。
一旦響應返回,來自 HttpClient
的 Observable通常會自動完成(盡管攔截器會影響這一點)。
由于自動完成,如果不清理 HttpClient
訂閱,通常不存在內存泄漏的風險。
四、更換 HTTP 請求實現
@ngify/http
內置了以下 HTTP
請求實現:
HTTP請求實現 | 包 | 描述 |
---|---|---|
withXhr | @ngify/http | 使用 XMLHttpRequest 進行 HTTP 請求 |
withFetch | @ngify/http | 使用 Fetch API 進行 HTTP 請求 |
withWx | @ngify/http-wx | 在 微信小程序 中進行 HTTP 請求 |
withTaro | @ngify/http-taro | 在 Taro 中進行 HTTP 請求 |
withUni | @ngify/http-uni | 在 Uni-app 中進行 HTTP 請求 |
HttpClient
默認使用 withXhr
,您可以自行切換到其他實現:
import { withXhr, withFetch } from '@ngify/http';
import { withWx } from '@ngify/http-wx';const xhrHttp = new HttpClient(withXhr()
);
const fetchHttp = new HttpClient(withFetch()
);
const wxHttp = new HttpClient(withWx()
);
你還可使用自定義的 HttpBackend
實現:
// 需要實現 HttpBackend 接口
class CustomHttpBackend implements HttpBackend {handle(request: HttpRequest<any>): Observable<HttpEvent<any>> {// ...}
}const customHttp = new HttpClient({kind: HttpFeatureKind.Backend,value: new CustomHttpBackend()}
);
五、XSRF/CSRF 防護
HttpClient
支持用于防止 XSRF 攻擊的通用機制。執行 HTTP
請求時,攔截器從 cookie
中讀取令牌(默認為 XSRF-TOKEN),并將其設置為 HTTP
標頭(默認為 X-XSRF-TOKEN)。
由于只有在您的域上運行的代碼才能讀取 cookie
,因此后端可以確定 HTTP
請求來自您的客戶端應用程序而不是攻擊者。
要啟用 XSRF
防護,請在 HttpClient
實例化時或在 setupHttpClient
中傳遞 withXsrfProtection()
:
const http = new HttpClient(withXsrfProtection()
);
如果您的后端服務對 XSRF
令牌 cookie
或標頭使用不同的名稱,請自定義 cookie
名稱與標頭名稱:
withXsrfProtection({cookieName: 'CUSTOM_XSRF_TOKEN',headerName: 'X-Custom-Xsrf-Header',
});
默認情況下,攔截器會在除 GET/HEAD
外的所有請求(例如 POST)上將此標頭發送到相對 URL
,但不會在具有絕對 URL
的請求上發送此標頭。
為什么不保護 GET 請求?
僅對于可以更改后端狀態的請求才需要 CSRF 保護。就其本質而言,CSRF 攻擊跨越域邊界,并且 Web 的同源策略將阻止攻擊頁面檢索經過身份驗證的 GET 請求的結果。
為了利用這一點,您的服務器需要在頁面加載或第一個 GET
請求時在名為 XSRF-TOKEN
的 cookie
中設置一個令牌。
在后續請求中,服務器可以驗證 cookie
是否與 X-XSRF-TOKEN HTTP
標頭匹配,因此確保只有在您的域上運行的代碼才能發送請求。
令牌對于每個用戶必須是唯一的,并且必須可由服務器驗證;這可以防止客戶端創建自己的令牌。
HttpClient 僅支持 XSRF 保護方案的客戶端部分
您的后端服務必須配置為為您的頁面設置 cookie,并驗證標頭是否存在于所有符合條件的請求中。如果不這樣做,HttpClient 的 XSRF 保護就會失效。
六、全局配置
您可以使用 setupHttpClient
函數進行全局配置:
setupHttpClient(withFetch(),withXsrfProtection(),
);
七、基本用法
待續
八、攔截請求和響應
請移步:ngify 攔截請求和響應
九、測試請求
參考 angular.dev/guide/http/…
十、支持
對 @ngify/http 感興趣?為該項目點星?!@ngify/http