如何发送 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) {
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
设置 status、statusText 值
获取 HTTP 响应体:_xhr.responseText (IE 8 & IE 9) 或 _xhr.response (IE 10)
解析响应头创建 Headers 对象:Headers.fromResponseHeaderString(_xhr.getAllResponseHeaders())
基于 status、status、headers、body 等信息创建响应对象
通知观察者 (根据请求状态,调用观察者的 next 或 error 方法)
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 模块的核心功能,终于分析完了。最后我们来总结一下:
json(): any - 转换为 JSON 对象
text(): string -
arrayBuffer(): ArrayBuffer - 转换为 ArrayBuffer 对象
blob(): Blob - 转化为 Blob 对象
转自: https://segmentfault.com/a/1190000009028150#articleHeader10
作者: semlinker
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
==========阅读原文==========