众所周知,在浏览器端和 Node.js 端使用最广泛的 HTTP 客户端为 axios 。想必大家都对它很熟悉,它是一个用于浏览器和 Node.js 的、基于 Promise
的 HTTP 客户端,但这次的主角不是它。
起源
axios
的前身其实是 AngularJS 的 $http
服务。
为了避免混淆,这里需要澄清一下:
AngularJS
并不等于Angular
,AngularJS
是特指 angular.js v1.x 版本,而Angular
特指 angular v2+ (没有 .js)和其包含的一系列工具链。
这样说可能不太严谨,但 axios
深受 AngularJS
中提供的$http
服务的启发。归根结底,axios
是为了提供一个类似独立的服务,以便在 AngularJS
之外使用。
发展
但在 Angular
中,却没有继续沿用之前的 $http
服务,而是与 rxjs 深度结合,提供了一个比 $http
服务更先进的、现代化的,响应式的 HTTP 客户端。
在这个响应式的 HTTP Client 中,发送请求后接收到的不再是一个 Promise
,而是来自 rxjs
的 Observable,我们可以订阅它,从而侦听到请求的响应:
const observable = http.get('url');
observable.subscribe(o => console.log(o));
有关它的基本形态及详细用法,请参考官方文档。
正文
@ngify/http 是一个形如 Angular HttpClient
的响应式 HTTP 客户端。@ngify/http
的目标与 axios
相似:提供一个类似独立的服务,以便在 Angular
之外使用。
@ngify/http
提供了以下主要功能:
先决条件
在使用 @ngify/http
之前,您应该对以下内容有基本的了解:
- JavaScript / TypeScript 编程。
- HTTP 协议的用法。
- RxJS Observable 相关技术和操作符。请参阅 Observables 指南。
API
有关完整的 API 定义,请访问 ngify.github.io/ngify.
可靠性
@ngify/http
使用且通过了 Angular HttpClient
的单元测试(测试代码根据 API 的细微差异做出了相应的更改)。
安装
npm i @ngify/http
基本用法
import { HttpClient, HttpContext, HttpContextToken, HttpHeaders, HttpParams } from '@ngify/http';
import { filter } from 'rxjs';
const http = new HttpClient();
http.get<{ code: number, data: any, msg: string }>('url', 'k=v').pipe(
filter(({ code }) => code === 0)
).subscribe(res => console.log(res));
http.post('url', { k: 'v' }).subscribe(res => console.log(res));
const HTTP_CACHE_TOKEN = new HttpContextToken(() => 1800000);
http.put('url', null, {
context: new HttpContext().set(HTTP_CACHE_TOKEN)
}).subscribe(res => console.log(res));
http.patch('url', null, {
params: { k: 'v' }
}).subscribe(res => console.log(res));
http.delete('url', new HttpParams('k=v'), {
headers: new HttpHeaders({ Authorization: 'token' })
}).subscribe(res => console.log(res));
拦截请求和响应
借助拦截机制,你可以声明一些拦截器,它们可以检查并转换从应用中发给服务器的 HTTP 请求。这些拦截器还可以在返回应用的途中检查和转换来自服务器的响应。多个拦截器构成了请求/响应处理器的双向链表。
@ngify/http
会按照您提供拦截器的顺序应用它们。
import { HttpClient, HttpHandler, HttpRequest, HttpEvent, HttpInterceptor, HttpEventType } from '@ngify/http';
import { Observable, tap } from 'rxjs';
const http = new HttpClient([
new class implements HttpInterceptor {
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
// 克隆请求以修改请求参数
request = request.clone({
headers: request.headers.set('Authorization', 'token')
});
return next.handle(request);
}
},
{
intercept(request: HttpRequest<unknown>, next: HttpHandler) {
request = request.clone({
params: request.params.set('k', 'v')
});
console.log('拦截后的请求', request);
return next.handle(request).pipe(
tap(response => {
if (response.type === HttpEventType.Response) {
console.log('拦截后的响应', response);
}
})
);
}
}
]);
虽然拦截器有能力改变请求和响应,但 HttpRequest
和 HttpResponse
实例的属性是只读的,因此让它们基本上是不可变的。
有充足的理由把它们做成不可变对象:应用可能会重试发送很多次请求之后才能成功,这就意味着这个拦截器链表可能会多次重复处理同一个请求。 如果拦截器可以修改原始的请求对象,那么重试阶段的操作就会从修改过的请求开始,而不是原始请求。 而这种不可变性,可以确保这些拦截器在每次重试时看到的都是同样的原始请求。
如果你需要修改一个请求,请先将它克隆一份,修改这个克隆体后再把它传递给 next.handle()
。
替换 HTTP 请求类
@ngify/http
内置了以下 HTTP 请求类:
HTTP 请求类 | 描述 |
---|---|
HttpXhrBackend | 使用 XMLHttpRequest 进行 HTTP 请求 |
HttpFetchBackend | 使用 Fetch API 进行 HTTP 请求 |
HttpWxBackend | 在 微信小程序 中进行 HTTP 请求 |
默认使用 HttpXhrBackend
,可以通过修改配置切换到其他的 HTTP 请求类:
import { HttpFetchBackend, HttpWxBackend, setupConfig } from '@ngify/http';
setupConfig({
backend: new HttpFetchBackend()
});
你还可使用自定义的 HttpBackend
实现类:
import { HttpBackend, HttpClient, HttpRequest, HttpEvent, setupConfig } from '@ngify/http';
import { Observable } from 'rxjs';
// 需要实现 HttpBackend 接口
class CustomHttpBackend implements HttpBackend {
handle(request: HttpRequest<any>): Observable<HttpEvent<any>> {
// ...
}
}
setupConfig({
backend: new CustomHttpBackend()
});
如果需要为某个 HttpClient
单独配置 HttpBackend
,可以在 HttpClient
构造方法中传入:
const http = new HttpClient(new CustomHttpBackend());
// 或者
const http = new HttpClient({
interceptors: [/* 一些拦截器 */],
backend: new CustomHttpBackend()
});
在 Node.js 中使用
@ngify/http
默认使用浏览器实现的 XMLHttpRequest
与 Fetch API
。要在 Node.js 中使用,您需要进行以下步骤:
XMLHttpRequest
如果需要在 Node.js 环境下使用 XMLHttpRequest
,可以使用 xhr2,它在 Node.js API 上实现了 W3C XMLHttpRequest 规范。
要使用 xhr2 ,您需要创建一个返回 XMLHttpRequest
实例的工厂函数,并将其作为参数传递给 HttpXhrBackend
构造函数:
import { HttpXhrBackend, setupConfig } from '@ngify/http';
import * as xhr2 from 'xhr2';
setupConfig({
backend: new HttpXhrBackend(() => new xhr2.XMLHttpRequest())
});
Fetch API
如果需要在 Node.js 环境下使用 Fetch API
,可以使用 node-fetch 和 abort-controller。
要应用它们,您需要分别将它们添加到 Node.js
的 global
:
import fetch from 'node-fetch';
import AbortController from 'abort-controller';
import { HttpFetchBackend, HttpWxBackend, setupConfig } from '@ngify/http';
global.fetch = fetch;
global.AbortController = AbortController;
setupConfig({
backend: new HttpFetchBackend()
});
传递额外参数
为保持 API 的统一,需要借助 HttpContext
来传递一些额外参数。
Fetch API 额外参数
import { HttpContext, FETCH_TOKEN } from '@ngify/http';
// ...
// Fetch API 允许跨域请求
http.get('url', null, {
context: new HttpContext().set(FETCH_TOKEN, {
mode: 'cors',
// ...
})
});
微信小程序额外参数
import { HttpContext, WX_UPLOAD_FILE_TOKEN, WX_DOWNLOAD_FILE_TOKEN, WX_REQUSET_TOKEN } from '@ngify/http';
// ...
// 微信小程序开启 HTTP2
http.get('url', null, {
context: new HttpContext().set(WX_REQUSET_TOKEN, {
enableHttp2: true,
})
});
// 微信小程序文件上传
http.post('url', null, {
context: new HttpContext().set(WX_UPLOAD_FILE_TOKEN, {
filePath: 'filePath',
fileName: 'fileName'
})
});
// 微信小程序文件下载
http.get('url', null, {
context: new HttpContext().set(WX_DOWNLOAD_FILE_TOKEN, {
filePath: 'filePath'
})
});
更多
有关更多用法,请访问 angular.cn。