专栏名称: 前端JavaScript
分享 | 学习 | 交流 | 原创 分享是学习的开始;学习不必要从头开始,是从现在开始;交流能沟通你我,提高你的学识;期待你的加入!!! web前端技术交流,JavaScript,HTML5,CSS3……
目录
相关文章推荐
51好读  ›  专栏  ›  前端JavaScript

HttpModule 揭秘

前端JavaScript  · 公众号  · Javascript  · 2017-05-02 07:27

正文

如何发送 GET 请求

this.http.get(`https://api.github.com/orgs/angular/members?page=1&per_page=5`)

Http 类


// angular2/packages/http/src/http.ts 片段

@Injectable()

export class Http {

      // 构造函数

    constructor(protected _backend: ConnectionBackend, 

      protected _defaultOptions: RequestOptions) {}

      

    // 发送任意类型的请求,返回Observable对象

    request(url: string|Request, options?: RequestOptionsArgs): Observable {

      let responseObservable: any;

      if (typeof url === 'string') {

        responseObservable = httpRequest(

            this._backend,

            new Request(mergeOptions(this._defaultOptions, options, 

                RequestMethod.Get, url)));

      } else if (url instanceof Request) {

        responseObservable = httpRequest(this._backend, url);

      } else {

        throw new Error('First argument must be a url string or Request instance.');

      }

      return responseObservable;

    }

  

    // 发送GET请求

    get(url: string, options?: RequestOptionsArgs): Observable {

        return this.request(

            new Request(mergeOptions(this._defaultOptions, options, 

                RequestMethod.Get, url)));

    }

  

    // 发送POST请求

    post(url: string, body: any, options?: RequestOptionsArgs): Observable {

        return this.request(new Request(mergeOptions(

            this._defaultOptions.merge(new RequestOptions({body: body})), options, 

                   RequestMethod.Post, url)));

    }

    ... 

}

发送 GET 请求


/**

 * url: 请求地址

 * options: 可选的请求参数

 */

get(url: string, options?: RequestOptionsArgs): Observable {

    return this.request(

            new Request(mergeOptions(this._defaultOptions, options, 

                RequestMethod.Get, url)));

}

this.http.get('remoteUrl') 方法执行主要过程:


this.get('https://api.github.com/orgs/angular/members?page=1&per_page=5') 

   this.request(new Request(mergeOptions(...,options,RequestMethod.Get, url))

     httpRequest(this._backend, new Request(...))

        backend.createConnection(request)

request() 方法


// 发送请求

request(url: string|Request, options?: RequestOptionsArgs): Observable {

      let responseObservable: any;

      if (typeof url === 'string') { // url类型是字符串

        responseObservable = httpRequest( // 调用httpRequest() 方法

            this._backend, // ConnectionBackend 对象

            new Request(mergeOptions(this._defaultOptions, // 创建Request对象

                  options, RequestMethod.Get, url)));

      } else if (url instanceof Request) { // 若url是Request对象的实例

        responseObservable = httpRequest(this._backend, url);

      } else {

        throw new Error('First argument must be a url string or Request instance.');

      }

      return responseObservable; // 返回Observable对象

}

httpRequest() 方法


function httpRequest(backend: ConnectionBackend, request: Request): 

  Observable {

  return backend.createConnection(request).response;

}

前面我们已经分析了 ConnectionBackend 对象,接下来我们来分析一下 Request 对象。


如何创建 Request 对象

new Request({

  method: RequestMethod.Get,

  url: 'https://google.com'

});

Request 类


// angular2/packages/http/src/static_request.ts 片段

export class Request extends Body {

  method: RequestMethod; // 请求方法

  headers: Headers; // 请求头

  url: string; // 请求URL地址

  private contentType: ContentType; // 请求体的类型

  withCredentials: boolean; // 是否开启withCredentials(不会影响same-site请求)

  responseType: ResponseContentType; // 设置该值能够改变响应类型,就是告诉服务器你期望的响应格式


  constructor(requestOptions: RequestArgs) {

    super();

    const url = requestOptions.url;

    this.url = requestOptions.url;

    if (requestOptions.params) { // 处理请求参数

      const params = requestOptions.params.toString();

      if (params.length > 0) {

        let prefix = '?';

        if (this.url.indexOf('?') != -1) { // 判断url是否已包含?字符

          prefix = (this.url[this.url.length - 1] == '&') ? '' : '&';

        }

        // TODO: just delete search-query-looking string in url?

        this.url = url + prefix + params;

      }

    }

    this._body = requestOptions.body; // 设置请求体

    this.method = normalizeMethodName(requestOptions.method); // 标准化请求方法

    this.headers = new Headers(requestOptions.headers); // 设置请求头

    this.contentType = this.detectContentType(); 

    this.withCredentials = requestOptions.withCredentials;

    this.responseType = requestOptions.responseType;

  }

}

Body 类


// angular2/packages/http/src/body.ts 片段

export abstract class Body {

  protected _body: any;


  json(): any { // 转化为JSON对象 - 具体应用:map(res => res.json())

    if (typeof this._body === 'string') {

      return JSON.parse(this._body);

    }

    if (this._body instanceof ArrayBuffer) {

      return JSON.parse(this.text());

    }

    return this._body;

  }


  // 转换为Text文本

  text(): string { ... }


  // 转换为ArrayBuffer对象

  arrayBuffer(): ArrayBuffer { ... }


  // 转换为Blob对象

  blob(): Blob { ... }

}

分析完如何创建请求对象,我们马上要进入最核心的部分,如何创建连接发送请求及创建响应对象。


如何创建连接

backend.createConnection(request)

httpRequest() 方法


function httpRequest(backend: ConnectionBackend, request: Request): Observable {

  return backend.createConnection(request).response; // 创建连接

}

XHRBackend 类


@Injectable()

export class XHRBackend implements ConnectionBackend {

  constructor(

      private _browserXHR: BrowserXhr, private _baseResponseOptions: ResponseOptions,

      private _xsrfStrategy: XSRFStrategy) {}


  // 用于创建XHRConnection,此外还有JSONPConnection

  createConnection(request: Request): XHRConnection {

    this._xsrfStrategy.configureRequest(request);

    return new XHRConnection(request, this._browserXHR, this._baseResponseOptions);

  }

}

如何创建 XHRConnection 对象

new XHRConnection(request, this._browserXHR, this._baseResponseOptions);

XHRConnection 类


// angular2/packages/http/src/backends/xhr_backend.ts 完整代码

export class XHRConnection implements Connection {

  request: Request; // 请求对象

  response: Observable; // 响应的Observable对象

  readyState: ReadyState; // 请求状态

  

  constructor(req: Request, browserXHR: BrowserXhr, baseResponseOptions?: ResponseOptions) {

    this.request = req;

    // 创建响应的Observable对象

    this.response = new Observable(

      responseObserver: Observer) => {

      

      // build(): any { return (new XMLHttpRequest()); }

      // 创建XMLHttpRequest对象

      const _xhr: XMLHttpRequest = browserXHR.build();

      

      // void open( DOMString method, DOMString url, optional boolean async,...);

      _xhr.open(RequestMethod[req.method].toUpperCase(), req.url);

      if (req.withCredentials != null) { // 是否开启withCredentials

        _xhr.withCredentials = req.withCredentials;

      }

      

      // load event handler

      // 请求成功处理函数

      const onLoad = () => {

        // normalize IE9 bug (http://bugs.jquery.com/ticket/1450)

        // 获取xhr状态,需处理IE9下的bug

        let status: number = _xhr.status === 1223 ? 204 : _xhr.status;


        let body: any = null;


        // HTTP 204 means no content

        // HTTP 204 表示没有内容,即不用处理响应体

        if (status !== 204) {

          // responseText is the old-school way of retrieving response 

          // (supported by IE8 & 9)

          // response/responseType properties were introduced in 

          // ResourceLoader Level2 spec

          // (supported by IE10)

          

          /**获取响应体方式:

           * 1. responseText 兼容IE8与IE9

           * 2. response/responseType XMLHttpRequest Level 2 规范中引入,IE10支持

           */

          body = (typeof _xhr.response === 'undefined') ? 

                  _xhr.responseText : _xhr.response;


          // Implicitly strip a potential XSSI prefix.

          if (typeof body === 'string') {

            body = body.replace(XSSI_PREFIX, '');

          }

        }


        // fix status code when it is 0 (0 status is undocumented).

        // Occurs when accessing file resources or on Android 4.1 stock browser

        // while retrieving files from application cache.

        

        /**

        * 当访问本地文件资源或在 Android 4.1 stock browser 中从应用缓存中获取文件时,        

        * XMLHttpRequest 的 status 值也会为0。因此要对返回的状态码做处理。

        */

        if (status === 0) {

          status = body ? 200 : 0;

        }


        // 解析响应头,创建Headers对象

        // 注意:使用该方法获取的响应头与在开发者工具Network面板中看到的响应头不一致

        const headers: Headers = Headers.

            fromResponseHeaderString(_xhr.getAllResponseHeaders());

        // IE 9 does not provide the way to get URL of response

        // IE 9 没有提供获取响应URL的方式

        const url = getResponseURL(_xhr) || req.url;

        // 设置状态码

        const statusText: string = _xhr.statusText || 'OK';


        // 创建ResponseOptions对象

        let responseOptions = new ResponseOptions({body, status, 

            headers, statusText, url});

        if (baseResponseOptions != null) {

          responseOptions = baseResponseOptions.merge(responseOptions);

        }

        

        // 创建响应对象

        const response = new Response(responseOptions);

        

        // const isSuccess = (status: number): boolean => (status >= 200 && status

        response.ok = isSuccess(status);

        if (response.ok) {

          responseObserver.next(response); // 请求成功,调用next()方法,传递响应对象

          // TODO(gdi2290): defer complete if array buffer until done

          responseObserver.complete();

          return;

        }

        responseObserver.error(response); // 发生异常,调用error()方法,传递响应对象

      };

      

      // error event handler

      // 异常处理函数

      const onError = (err: ErrorEvent) => {

        let responseOptions = new ResponseOptions({

          body: err,

          type: ResponseType.Error,

          status: _xhr.status,

          statusText: _xhr.statusText,

        });

        if (baseResponseOptions != null) {

          responseOptions = baseResponseOptions.merge(responseOptions);

        }

        responseObserver.error(new Response(responseOptions));

      };


      // 根据 req.contentType 类型,设置请求头content-type信息

      this.setDetectedContentType(req, _xhr); 


      if (req.headers == null) { // 创建headers对象

        req.headers = new Headers();

      }

      if (!req.headers.has('Accept')) { // 若设置Accept请求头,则设置默认的值

        req.headers.append('Accept', 'application/json, text/plain, */*');

      }

      req.headers.forEach((values, name) => 

          _xhr.setRequestHeader(name, values.join(',')));


      // Select the correct buffer type to store the response

      // 根据req.responseType类型设置xhr.responseType

      if (req.responseType != null && _xhr.responseType != null) {

        switch (req.responseType) {

          case ResponseContentType.ArrayBuffer:

            _xhr.responseType = 'arraybuffer';

            break;

          case ResponseContentType.Json:

            _xhr.responseType = 'json';

            break;

          case ResponseContentType.Text:

            _xhr.responseType = 'text';

            break;

          case ResponseContentType.Blob:

            _xhr.responseType = 'blob';

            break;

          default:

            throw new Error('The selected responseType is not supported');

        }

      }


      // 当资源完成加载时,将触发load事件

      _xhr.addEventListener('load', onLoad); 

      // 当资源加载失败时,将触发 error 事件

      _xhr.addEventListener('error', onError);


      // 发送请求

      // void send();

      // void send(ArrayBuffer data);

      // void send(Blob data);

      // void send(Document data);

      // void send(DOMString? data);

      // void send(FormData data);

      _xhr.send(this.request.getBody()); 


      // 返回函数对象,用于移除事件监听及终止请求

      return () => {

        _xhr.removeEventListener('load', onLoad);

        _xhr.removeEventListener('error', onError);

        _xhr.abort();

      };

    });

  }


  setDetectedContentType(req: any /** TODO Request */, _xhr: any /** XMLHttpRequest */) {

    // Skip if a custom Content-Type header is provided

    if (req.headers != null && req.headers.get('Content-Type') != null) {

      return;

    }


    // Set the detected content type

    switch (req.contentType) {

      case ContentType.NONE:

        break;

      case ContentType.JSON:

        _xhr.setRequestHeader('content-type', 'application/json');

        break;

      case ContentType.FORM:

        _xhr.setRequestHeader('content-type', 

            'application/x-www-form-urlencoded;charset=UTF-8');

        break;

      case ContentType.TEXT:

        _xhr.setRequestHeader('content-type', 'text/plain');

        break;

      case ContentType.BLOB:

        const blob = req.blob();

        if (blob.type) {

          _xhr.setRequestHeader('content-type', blob.type);

        }

        break;

    }

  }

}

是不是有点晕了,我们赶紧来梳理一下创建 XHRConnection 对象的内部流程:


调用 XHRConnection 构造函数,创建 XHRConnection 对象


constructor(req: Request, browserXHR: BrowserXhr, 

   baseResponseOptions?: ResponseOptions) { ... }

  • 设置请求对象

  • 设置Observable响应对象 - new Observable((responseObserver: Observer) => { … })

是时候分析以下代码的执行过程:


ngOnInit() {

  this.http.get(`https://api.github.com/orgs/angular/members?

    page=1&per_page=5`) // (1)

      .map(res => res.json()) // (2)

      .subscribe(data => { // (3)

        if (data) this.members = data; 

  });

}

1.调用 Http 对象的 get() 方法


get(url: string, options?: RequestOptionsArgs): Observable {

    return this.request(

        new Request(mergeOptions(this._defaultOptions, options, RequestMethod.Get, url)));

}


request(url: string|Request, options?: RequestOptionsArgs): Observable {

    let responseObservable: any;

    if (typeof url === 'string') {

      responseObservable = httpRequest(this._backend,new Request(...);

    } else if (url instanceof Request) {

      responseObservable = httpRequest(this._backend, url);

    } 

    ...

    return responseObservable;

  }

2.调用 httpRequest() 方法,返回 Observable 对象


function httpRequest(backend: ConnectionBackend, request: Request): 

  Observable {

  return backend.createConnection(request).response;

}

3.调用 RxJS 中的 map() 操作符,对响应 Response 对象进行处理,即转换为 JSON 对象


public map(project: function(value: T, index: number): R, thisArg: any): Observable

4.订阅返回的 Observable 对象,即正式发送 HTTP 请求


5.创建 XMLHttpRequest 对象 — _xhr


  • 调用 _xhr 对象的 open() 方法,设置请求方法和请求地址

  • 监听 _xhr load 事件,设置 onLoad 处理函数,onLoad 函数内部处理流程:

    • 设置 status、statusText 值

    • 获取 HTTP 响应体:_xhr.responseText (IE 8 & IE 9) 或 _xhr.response (IE 10)

    • 解析响应头创建 Headers 对象:Headers.fromResponseHeaderString(_xhr.getAllResponseHeaders())

    • 基于 status、status、headers、body 等信息创建响应对象

    • 通知观察者 (根据请求状态,调用观察者的 next 或 error 方法)

  • 监听 _xhr error 事件,设置 onError 处理函数

  • 返回一个用于移除监听(load、error事件)和终止 HTTP 请求的函数

Angular HttpModule 中核心的内容,我们已经分析完了,最后在补充一下如何创建 Response 响应对象。


如何创建 Response 对象

new Response({ body: '{"name":"Jeff"}', url: 'https://google.com' })

Response 类


export class Response extends Body {

  type: ResponseType; // "basic", "cors", "default", "error", or "opaque",默认"default"

  ok: boolean; // 当status在200-299范围内,该值为true

  url: string; // 响应的URL地址,默认为空字符串

  status: number; // 服务器返回的状态,默认为200

  statusText: string; // 请求的响应状态信息,默认值是"OK"

  bytesLoaded: number; // 非标准属性:用于表示已加载响应体的字节数

  totalBytes: number; // 非标准属性:表示响应体总字节数

  headers: Headers; // 响应头对象


  constructor(responseOptions: ResponseOptions) {

    super();

    this._body = responseOptions.body;

    this.status = responseOptions.status;

    this.ok = (this.status >= 200 && this.status

    this.statusText = responseOptions.statusText;

    this.headers = responseOptions.headers;

    this.type = responseOptions.type;

    this.url = responseOptions.url;

  }


  toString(): string {

    return `Response with status: ${this.status} ${this.statusText} for URL: ${this.url}`;

  }

}

总结

Angular HttpModule 模块的核心功能,终于分析完了。最后我们来总结一下:


  • 当调用 Http 对象的 get()、post()、put() 等方法时,会返回一个 Observable 对象,仅当我们订阅该 Observable 对象时,才会正式发起 HTTP 请求。

  • Angular 内部使用 Request 和 Response 对象来封装请求信息和响应信息。Request 类和 Response 类都是继承于 Body 类,Body 类中提供了四个方法用于数据转换:

    • json(): any - 转换为 JSON 对象

    • text(): string -

    • arrayBuffer(): ArrayBuffer - 转换为 ArrayBuffer 对象

    • blob(): Blob - 转化为 Blob 对象

  • 订阅 Observable 对象后,返回一个函数对象。调用该函数对象,我们可以移除 load、error 事件监听及取消 HTTP 请求。


转自: https://segmentfault.com/a/1190000009028150#articleHeader10

作者: semlinker

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


==========阅读原文==========