如何发送 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) { ... }
是时候分析以下代码的执行过程:
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) {