[HTTP request in Angular]-HttpClient detailed explanation

        Most front-end applications communicate with servers via the HTTP protocol in order to download or upload data and access other back-end services. Angular provides an HTTP client API for applications, which is the HttpClient service class in @angular/common/http .

1. Use of HttpClient

        To use HttpClient , you must first import Angular's HttpClientModule . Generally, it is imported in the root module 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 {}

        Then inject the HttpClient service as a dependency of the service class and use it in the method of the service class:

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);   
   }
}

        Then call the service in the component to get the data for processing:

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. Common request methods

        The HttpClient class includes common HTTP request methods such as get post delete put . The main method declaration is as follows:

    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>;

        Only four commonly used methods are listed here. In fact, there are request , head , options , patch , jsonp and other methods, and there are multiple overloaded methods with the same method name. Appropriate methods can be called in the project according to actual needs.

        You can see that the return values ​​of all requested methods are Observable observable objects.

3. Call relationship

        Looking at the source code of the HttpClient class, you can find that all request methods call the request method by passing different parameters:

    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));
    }   

        The key code of the request method is as follows:

    // 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))));
    ......

        It can be seen that the main steps include:

        1. of (req) : Use the of operator of RxJS to create a new unicast Cold Observable (data stream, the original data is the request information) based on the request information.

        2. pipe(concatMap((req) => this.handler.handle(req))) processes the data stream through the concatMap operator. The processing method is (req) => this.handler.handle(req) ; the actual request is sent in the this.handler.handle(req) method.

        3. events$.pipe(filter((event) => event instanceof HttpResponse)) filters the HttpResponse data in the data stream .

        After completing the above steps, the request method returns the Observable processed by the operator . Because the return is a unicast Cold Observable, the Observable 's data notification will only be enabled when subscribed ; the processing method in concatMap will be called after the data notification is issued , and the actual HTTP request will be initiated. So only when the Observable.subscribe() method is executed, the browser will initiate an HTTP request.

4. handle() method tracking

        It has been pointed out above that the actual request is completed in the this.handler.handle(req) method. In fact, the handle() method is not just a request.

        Look at the code to find the definition of this.handler , first look at the declaration:

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

        Look at the source code again:

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

        It can be found that this.handler is an HttpHandler object.

        Looking at the declaration, you can find that HttpHandler is an abstract class:

/**
 * 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>>;
}

        Baidu translates the note: Convert an "Http Request" into an "HttpEvent" event stream, and there may be "HttpResponse" in the event stream. HttpHandler is injectable. When injected, the handler instance dispatches the request to the first interceptor in the chain, which then dispatches to the second interceptor, which then dispatches to the third interceptor, which in turn dispatches, and finally reaches the HttpBackend . In HttpInterceptor, the HttpHandler parameter is the next interceptor in the chain.

        Through the comments, it can be found that HttpHandler performs a chain processing process on the request through the handle() method: mainly including the processing of an indefinite number of interceptors plus the final processing of HttpBackend .

         Look at the code and find the implementation class of 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>>;
}

        Baidu translates the note: an injectable HttpHandler that applies multiple interceptors to a request before passing it to a given HttpBackend . Interceptors are lazily loaded from the injector to allow the interceptor to inject classes itself, which indirectly relies on the HttpInterceptingHandler itself.

        Looking at the source code, I found that ɵHttpInterceptingHandler is just an alias for HttpInterceptingHandler :

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

        Find the source code of 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);
    }
}

        As can be seen from the code of the handle() method, it is indeed as described in the above comments:

        Pass const interceptors = this.injector.get(HTTP_INTERCEPTORS, []); to get the interceptors registered in the module.

        Through the reduceRight() method, the interceptor and HttpBackend form a chain structure to process the request.

5. Introduction to HttpBackend

        For an introduction to the interceptor ( HttpInterceptor ), please refer to: Detailed Explanation of the Angular Request Interceptor HttpInterceptor

        Here we directly look at the last link of the chain processing flow, the processing of HttpBackend . First look at the declaration of 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>>;
}

        The comment indicates that HttpBackend is the handler that finally sends the request to the backend through the browser HTTP API. The interceptor is located between HttpClient and HttpBackend , and HttpBackend sends the request directly to the backend without going through the chain processing process of the interceptor. The comments here further corroborate the preceding flow descriptions.

        HttpBackend is also an abstract class. If you search for the implementation class of HttpBackend , you will find two, one is HttpXhrBackend and the other is JsonpClientBackend . Through code comments, you can know that HttpXhrBackend uses XMLHttpRequest to send requests, while JsonpClientBackend uses JSONP method to send requests.

        And HttpXhrBackend is used in HttpClientModule :

         Let's look at the statement of 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>>;
}

Look at the source code of HttpXhrBackend         again :

      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();
            });
        }));
    }

        Where  const xhr = this.xhrFactory.build(); xhr is XMLHttpRequest , corresponding to the source code:

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

The HttpBackend         used when sending requests using the JSONP method is JsonpClientBackend . If you want to use JSONP to send requests, you need to import HttpClientJsonpModule . Please refer to: [HTTP Requests in Angular] - JSONP Details 

6. Summary       

        In summary, HttpClient is a package that integrates RxJS and XMLHttpRequest ; all requests are called through HttpClient.request() , and the method returns Cold Observable. When the Observable is subscribed, the request is first processed by the interceptor, and finally through the browser Issued by the server HTTP API.

        For different versions of Angular, the number of lines of the above code location is different, but the file location is the same. You can find the relevant code by searching the above keywords.

        Class declaration file path:

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

        Code source file path:

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

Guess you like

Origin blog.csdn.net/evanyanglibo/article/details/122196426