【Angular中的HTTP请求】- HttpClient 详解

        大多数前端应用都要通过 HTTP 协议与服务器通讯,才能下载或上传数据并访问其它后端服务。Angular 给应用提供了一个 HTTP 客户端 API,也就是 @angular/common/http 中的 HttpClient 服务类。

1、HttpClient的使用

        要想使用 HttpClient,就要先导入 Angular 的 HttpClientModule。一般都是在根模块 AppModule 中进行导入。

        app/app.module.ts:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  imports: [
    BrowserModule,

    HttpClientModule,
    // ... other modules ...
  ],
  declarations: [
    AppComponent,
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule {}

        然后把 HttpClient 服务注入成一个服务类的依赖项,在服务类的方法中进行使用:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class ApiService {
  constructor(private http: HttpClient) { }
  
   getUserList(){
       const url = "/api/user/list";
       return this.http.get<any>(url);   
   }
}

        然后在组件中调用服务获取数据进行处理:

import { Component } from '@angular/core';
import { ApiService } from '../services/api.service';

@Component({
  selector: 'app-user-list',
  templateUrl: './user-list.component.html',
  styleUrls: ['./user-list.component.css']
})
export class UserListComponent {
    
   userList:Array<any> = []; 
   constructor(private api: ApiService) { }
   
   getUserList(){
       this.api. getUserList.subscribe(data=>{
           this.userList = data;       
       });
   }
}

2、常用请求方法

        HttpClient 类包含了 get post delete put 等常见的 HTTP 请求方法。主要的方法声明如下:

    get<T>(url: string, options?: {
        headers?: HttpHeaders | { [header: string]: string | string[]; };
        observe?: 'body';
        params?: HttpParams | { [param: string]: string | string[]; };
        reportProgress?: boolean;
        responseType?: 'json';
        withCredentials?: boolean;
    }): Observable<T>;
    
    post<T>(url: string, body: any | null, options: {
        headers?: HttpHeaders | { [header: string]: string | string[]; };
        observe: 'response';
        params?: HttpParams | { [param: string]: string | string[]; };
        reportProgress?: boolean;
        responseType?: 'json';
        withCredentials?: boolean;
    }): Observable<HttpResponse<T>>;
    
    delete<T>(url: string, options?: {
        headers?: HttpHeaders | { [header: string]: string | string[]; };
        observe?: 'body';
        params?: HttpParams | { [param: string]: string | string[]; };
        reportProgress?: boolean;
        responseType?: 'json';
        withCredentials?: boolean;
    }): Observable<T>;
    
    put<T>(url: string, body: any | null, options?: {
        headers?: HttpHeaders | { [header: string]: string | string[]; };
        observe?: 'body';
        params?: HttpParams | { [param: string]: string | string[]; };
        reportProgress?: boolean;
        responseType?: 'json';
        withCredentials?: boolean;
    }): Observable<T>;

        这里只主要列举了四个常用的方法,实际上还有 request, head, options, patch, jsonp 等方法,并且相同的方法名也存在多个重载方法。项目中可以根据实际需求调用合适的方法。

        可以看到所有请求的方法返回值都是 Observable 可观察对象。

3、调用关系

        查阅 HttpClient 类的源代码,可以发现全部的请求方法都是通过传递不同参数来调用 request方法:

    get(url, options = {}) {
        return this.request('GET', url, (/** @type {?} */ (options)));
    }
    
    post(url, body, options = {}) {
        return this.request('POST', url, addBody(options, body));
    }    
    
    delete(url, options = {}) {
        return this.request('DELETE', url, (/** @type {?} */ (options)));
    }    
    
    put(url, body, options = {}) {
        return this.request('PUT', url, addBody(options, body));
    }   

        request 方法关键代码如下:

    // Start with an Observable.of() the initial request, and run the handler (which
    // includes all interceptors) inside a concatMap(). This way, the handler runs
    // inside an Observable chain, which causes interceptors to be re-run on every
    // subscription (this also makes retries re-run the handler, including interceptors).
    /** @type {?} */
    const events$ = of (req).pipe(concatMap((req) => this.handler.handle(req)));
    ......
    // The requested stream contains either the full response or the body. In either
    // case, the first step is to filter the event stream to extract a stream of
    // responses(s).
    /** @type {?} */
    const res$ = ( /** @type {?} */ (events$.pipe(filter((event) => event instanceof HttpResponse))));
    ......

        可以看到主要的步骤包括:

        1、of (req) : 使用 RxJS 的 of 操作符基于请求信息新建的一个单播的 Cold Observable (数据流,原始的数据就是请求信息) 。

        2、pipe(concatMap((req) => this.handler.handle(req))) 经过 concatMap 操作符对数据流进行处理。处理的方法就是 (req) => this.handler.handle(req);实际的请求的发送就是在this.handler.handle(req)方法中。

        3、events$.pipe(filter((event) => event instanceof HttpResponse)) 过滤数据流中的 HttpResponse 数据。

        完成以上步骤后,request 方法返回操作符处理后的 Observable。因为返回的是 单播的 Cold Observable,所以只有在 subscribe 的时候才会开启 Observable 的数据通知;发出数据通知后才会调用 concatMap 中的处理方法,才会发起实际的HTTP请求。所以只有在 Observable.subscribe() 方法执行的时候,浏览器才会发起HTTP请求。

4、handle() 方法跟踪

        上文已指出实际的请求是在 this.handler.handle(req) 方法中完成的,实际上在 handle() 方法中不仅只是发起请求。

        查看代码找到 this.handler 定义,先看声明:

export declare class HttpClient {
    private handler;
    constructor(handler: HttpHandler);
    ...
}

        再看源代码:

class HttpClient {
    constructor(handler) {
        this.handler = handler;
    }
    ...
}

        可以发现 this.handler 为一个 HttpHandler 对象。

        查看声明可以发现 HttpHandler 是一个抽象类:

/**
 * Transforms an `HttpRequest` into a stream of `HttpEvent`s, one of which will likely be a
 * `HttpResponse`.
 *
 * `HttpHandler` is injectable. When injected, the handler instance dispatches requests to the
 * first interceptor in the chain, which dispatches to the second, etc, eventually reaching the
 * `HttpBackend`.
 *
 * In an `HttpInterceptor`, the `HttpHandler` parameter is the next interceptor in the chain.
 *
 * @publicApi
 */
export declare abstract class HttpHandler {
    abstract handle(req: HttpRequest<any>): Observable<HttpEvent<any>>;
}

        百度翻译一下注释:将一个 “Http 请求” 转换为 “HttpEvent” 事件流,事件流中可能就存在“HttpResponse”。HttpHandler 是可注入的。 注入时,处理程序实例将请求分派到链中的第一个拦截器,然后分派到第二个拦截器,第三个拦截器,依次分派,最终到达 HttpBackend。在 HttpInterceptor 中,HttpHandler 参数是链中的下一个拦截器。

        通过注释可以发现 HttpHandler 通过 handle() 方法对请求进行了一个链式的处理流程:主要包括不定数量的拦截器的处理加上最后的 HttpBackend 的处理。

         查看代码,找到 HttpHandler 的实现类:

/**
 * An injectable `HttpHandler` that applies multiple interceptors
 * to a request before passing it to the given `HttpBackend`.
 *
 * The interceptors are loaded lazily from the injector, to allow
 * interceptors to themselves inject classes depending indirectly
 * on `HttpInterceptingHandler` itself.
 * @see `HttpInterceptor`
 */
export declare class ɵHttpInterceptingHandler implements HttpHandler {
    private backend;
    private injector;
    private chain;
    constructor(backend: HttpBackend, injector: Injector);
    handle(req: HttpRequest<any>): Observable<HttpEvent<any>>;
}

        百度翻译一下注释:一个可注入的 HttpHandler,在将请求传递给给定的 HttpBackend 之前,将多个拦截器应用于请求。拦截器从注入器延迟加载,以允许拦截器自己注入类,这间接依赖于 HttpInterceptingHandler 本身。

        查看源代码,发现 ɵHttpInterceptingHandler 只是 HttpInterceptingHandler 的别名:

export {
    ... 
    HttpBackend, HttpHandler, HttpClient, HttpHeaders, 
    HTTP_INTERCEPTORS, JsonpClientBackend, JsonpInterceptor, 
    ... 
    HttpInterceptingHandler as ɵHttpInterceptingHandler,
    ... 
};

        找到 HttpInterceptingHandler 的源代码:

class HttpInterceptingHandler {
    /**
     * @param {?} backend
     * @param {?} injector
     */
    constructor(backend, injector) {
        this.backend = backend;
        this.injector = injector;
        this.chain = null;
    }
    /**
     * @param {?} req
     * @return {?}
     */
    handle(req) {
        if (this.chain === null) {
            /** @type {?} */
            const interceptors = this.injector.get(HTTP_INTERCEPTORS, []);
            this.chain = interceptors.reduceRight((/**
             * @param {?} next
             * @param {?} interceptor
             * @return {?}
             */
            (next, interceptor) => new HttpInterceptorHandler(next, interceptor)), this.backend);
        }
        return this.chain.handle(req);
    }
}

        从handle() 方法的代码中可以看出,的确如上述注释描述的那样:

        通过 const interceptors = this.injector.get(HTTP_INTERCEPTORS, []); 获取模块中注册使用的拦截器。

        通过 reduceRight() 方法,将 拦截器和 HttpBackend 组成一个链式结构,对请求进行处理。

5、HttpBackend 介绍

        拦截器(HttpInterceptor)的介绍请参考:Angular请求拦截器HttpInterceptor详解

        这里直接看链式处理流程的最后一个环节,HttpBackend 的处理。先看 HttpBackend 的声明:

/**
 * A final `HttpHandler` which will dispatch the request via browser HTTP APIs to a backend.
 *
 * Interceptors sit between the `HttpClient` interface and the `HttpBackend`.
 *
 * When injected, `HttpBackend` dispatches requests directly to the backend, without going
 * through the interceptor chain.
 *
 * @publicApi
 */
export declare abstract class HttpBackend implements HttpHandler {
    abstract handle(req: HttpRequest<any>): Observable<HttpEvent<any>>;
}

        注释说明 HttpBackend 就是最终将请求通过浏览器HTTP API 发送到后端 的 处理程序。拦截器位于 HttpClientHttpBackend 之间,HttpBackend 直接发送请求到后端,不会再经过拦截器的链式处理流程。这里的注释进一步印证了前述的流程说明。

        HttpBackend 也是一个抽象类,查找 HttpBackend 的实现类,会发现有两个,一个是 HttpXhrBackend,另一个是 JsonpClientBackend, 通过代码注释可以知道 HttpXhrBackend 是使用 XMLHttpRequest 发送请求的,而 JsonpClientBackend 是使用 JSONP 方法发送请求的。

        而 HttpClientModule 中使用的是 HttpXhrBackend

         我们看 HttpXhrBackend 的声明:

/**
 * Uses `XMLHttpRequest` to send requests to a backend server.
 * @see `HttpHandler`
 * @see `JsonpClientBackend`
 *
 * @publicApi
 */
export declare class HttpXhrBackend implements HttpBackend {
    private xhrFactory;
    constructor(xhrFactory: XhrFactory);
    /**
     * Processes a request and returns a stream of response events.
     * @param req The request object.
     * @returns An observable of the response events.
     */
    handle(req: HttpRequest<any>): Observable<HttpEvent<any>>;
}

        再看 HttpXhrBackend 的源代码:

      handle(req) {
        ......
        return new Observable((
        (observer) => {
            const xhr = this.xhrFactory.build();
            xhr.open(req.method, req.urlWithParams);
            ......
            const partialFromXhr = (() => { ... });
            const onLoad =  (() => { ... }); 
            const onError = ( (error) => { ... });
            const onDownProgress = ( (event) => {
             ... 
              observer.next(progressEvent);
             });
            const onUpProgress = ( (event) => {
             ... 
              observer.next(progressEvent);
             });
            xhr.addEventListener('load', onLoad);
            xhr.addEventListener('error', onError);
            if (req.reportProgress) {
                // Download progress is always enabled if requested.
                xhr.addEventListener('progress', onDownProgress);
                // Upload progress depends on whether there is a body to upload.
                if (reqBody !== null && xhr.upload) {
                    xhr.upload.addEventListener('progress', onUpProgress);
                }
            }
            // Fire the request, and notify the event stream that it was fired. 
            xhr.send((/** @type {?} */ (reqBody)));  // 实际发出请求的代码
            observer.next({ type: HttpEventType.Sent });
            
            return (
            () => {
                // On a cancellation, remove all registered event listeners.
                xhr.removeEventListener('error', onError);
                xhr.removeEventListener('load', onLoad);
                if (req.reportProgress) {
                    xhr.removeEventListener('progress', onDownProgress);
                    if (reqBody !== null && xhr.upload) {
                        xhr.upload.removeEventListener('progress', onUpProgress);
                    }
                }
                // Finally, abort the in-flight request.
                xhr.abort();
            });
        }));
    }

        其中  const xhr = this.xhrFactory.build();  xhrXMLHttpRequest,对应源代码:

/**
 * A factory for `HttpXhrBackend` that uses the `XMLHttpRequest` browser API.
 *
 */
class BrowserXhr {
    constructor() { }
    /**
     * @return {?}
     */
    build() { return (/** @type {?} */ ((new XMLHttpRequest()))); }
}

        使用 JSONP 方法发送请求时使用的 HttpBackendJsonpClientBackend,想要使用JSONP 发送请求需要导入 HttpClientJsonpModule,请参考:【Angular中的HTTP请求】- JSONP 详解 

6、总结       

        综上, HttpClient 就是集成了 RxJS 和 XMLHttpRequest 的封装;所有的请求都是通过 HttpClient.request() 进行调用,方法返回 Cold Observable, 在 Observable 被订阅的时候,请求先经过拦截器的处理,最后通过浏览器 HTTP API 发出。

        不同版本的Angular,以上代码位置行数不同,但是文件位置都是一样的,大家可以通过以上关键字搜索就能找到相关代码。

        类的声明文件路径:

node_modules/@angular/common/http/http.d.ts

        代码源文件路径:

node_modules/@angular/common/fesm2015/http.js

  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_老杨_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值