专栏名称: 前端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) {







请到「今天看啥」查看全文


推荐文章
澄泓财经  ·  窥破白马王调整的战略意图
7 年前
知识分子  ·  肠道菌群的产业创新之路
7 年前
theLittleSarah  ·  伤口,是光进入你内心的地方。
7 年前