有点小鸡冻,咱们 HttpModule 系列的主角终于要出场了。此时忽然想起了一句诗:javascript
千呼万唤始出来,犹抱琵琶半遮面。 —— 白居易 <<琵琶行>>php
为了写好这篇文章 (写得很差的话,你们请见谅),考虑了一番,最终仍是打算先写相关的基础文章:html
其中 HTTP 最强资料大全及你不知道的 XMLHttpRequest 内容比较全面,但对于咱们揭秘 Angular 4.x HttpModule 模块,咱们只需了解其中的一些相关知识便可。所以下面我也仅会介绍相关的知识点,若想了解详细信息,你们能够查看原文。java
直接略过基础部分,直达 HttpModulejquery
超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是互联网上应用最为普遍的一种网络协议。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。经过HTTP或者HTTPS协议请求的资源由统一资源标识符(Uniform Resource Identifiers,URI)来标识。—— 维基百科git
HTTP 协议是基于请求与响应,具体以下图所示:github
HTTP 请求报文由请求行、请求头、空行 和 请求体(请求数据) 4 个部分组成,以下图所示:web
GET / HTTP/1.1
Host: www.baidu.com
Connection: keep-alive
Cache-Control: max-age=0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8复制代码
HTTP响应报文由状态行、响应头、空行和响应体4 个部分组成,以下图所示:typescript
HTTP/1.1 200 OK
Server: bfe/1.0.8.18
Date: Thu, 30 Mar 2017 12:28:00 GMT
Content-Type: text/html; charset=utf-8
Connection: keep-alive
Cache-Control: private
Expires: Thu, 30 Mar 2017 12:27:43 GMT
Set-Cookie: BDSVRTM=0; path=/复制代码
XMLHttpRequest 是一个 API, 它为客户端提供了在客户端和服务器之间传输数据的功能。它提供了一个经过 URL 来获取数据的简单方式,而且不会使整个页面刷新。这使得网页只更新一部分页面而不会打扰到用户。XMLHttpRequest 在 AJAX 中被大量使用。shell
XMLHttpRequest 是一个 JavaScript 对象,它最初由微软设计,随后被 Mozilla、Apple 和 Google 采纳. 现在,该对象已经被 W3C组织标准化. 经过它,你能够很容易的取回一个 URL 上的资源数据. 尽管名字里有 XML, 但 XMLHttpRequest 能够取回全部类型的数据资源,并不局限于 XML。 并且除了HTTP ,它还支持 file
和 ftp
协议。
用于初始化一个 XMLHttpRequest 对象,必须在全部其它方法被调用前调用构造函数。使用示例以下:
var req = new XMLHttpRequest();复制代码
值 | 状态 | 描述 |
---|---|---|
0 | UNSENT (未打开) | 表示已建立 XHR 对象,open() 方法还未被调用 |
1 | OPENED (未发送) | open() 方法已被成功调用,send() 方法还未被调用 |
2 | HEADERS_RECEIVED (已获取响应头) | send() 方法已经被调用,响应头和响应状态已经返回 |
3 | LOADING (正在下载响应体) | 响应体下载中,responseText中已经获取了部分数据 |
4 | DONE (请求完成) | 整个请求过程已经完毕 |
值 | 响应数据类型 |
---|---|
"" | 字符串(默认值) |
"arraybuffer" | ArrayBuffer |
"blob" | Blob |
"document" | Document |
"json" | JSON |
"text" | 字符串 |
void open(
DOMString method,
DOMString url,
optional boolean async,
optional DOMString user,
optional DOMString password
);复制代码
void send();
void send(ArrayBuffer data);
void send(Blob data);
void send(Document data);
void send(DOMString? data);
void send(FormData data);复制代码
void setRequestHeader(
DOMString header,
DOMString value
);复制代码
var xhr = new XMLHttpRequest(); // 建立xhr对象
xhr.open( method, url ); // 设置请求方法和URL
xhr.onreadystatechange = function () { ... }; // 监听请求状态变化
xhr.setRequestHeader( ..., ... ); // 设置请求头信息
xhr.send( optionalEncodedData ); // 设置请求体并发送请求复制代码
app.component.ts
import { Component, OnInit } from '@angular/core';
import { Http } from '@angular/http'; // (1)
import 'rxjs/add/operator/map'; // (2)
interface Member {
id: string;
login: string;
avatar_url: string;
}
@Component({
selector: 'exe-app',
template: ` <h3>Angular Orgs Members</h3> <ul *ngIf="members"> <li *ngFor="let member of members;"> <p> <img [src]="member.avatar_url" width="48" height="48"/> ID:<span>{{member.id}}</span> Name: <span>{{member.login}}</span> </p> </li> </ul> `
})
export class AppComponent implements OnInit {
members: Member[];
constructor(private http: Http) { } // (3)
ngOnInit() {
this.http.get(`https://api.github.com/orgs/angular/members?page=1&per_page=5`) // (4)
.map(res => res.json()) // (5)
.subscribe(data => {
if (data) this.members = data; // (6)
});
}
}复制代码
示例说明:
(1) 从 @angular/http
模块中导入 Http 类
(2) 导入 RxJS 中的 map
操做符
(3) 使用 DI 方式注入 http 服务
(4) 调用 http 服务的 get()
方法,设置请求地址并发送 HTTP 请求
(5) 调用 Response 对象的 json()
方法,把响应体转成 JSON 对象
(6) 把请求的结果,赋值给 members
属性
是否是感受上面的示例太简单了,请深吸一口气,咱们再来看一下若是没有使用 HttpModule
,应该如何实现上述的功能。
app.component.ts
import { Component, OnInit } from '@angular/core';
interface Member {
id: string;
login: string;
avatar_url: string;
}
@Component({
selector: 'exe-app',
template: ` <h3>Angular Orgs Members</h3> <ul *ngIf="members"> <li *ngFor="let member of members;"> <p> <img [src]="member.avatar_url" width="48" height="48"/> ID:<span>{{member.id}}</span> Name: <span>{{member.login}}</span> </p> </li> </ul> `
})
export class AppComponent implements OnInit {
members: Member[];
getMembers() {
let MEMBERS_URL = `https://api.github.com/orgs/angular/members?page=1&per_page=5`;
let xhr = new XMLHttpRequest(); // (1)
xhr.open("GET", MEMBERS_URL); // (2)
xhr.onreadystatechange = () => { // (3)
if (xhr.readyState == 4 && xhr.status == 200) { // (4)
if (xhr.responseText) {
try {
this.members = JSON.parse(xhr.responseText); // (5)
} catch (error) {
throw error;
}
}
}
};
xhr.send(null); // (6)
}
ngOnInit() {
this.getMembers();
}
}复制代码
示例说明:
(1) 建立 XMLHttpRequest 对象
(2) 设置请求方式和请求 URL 地址
(3) 监听 readyState
状态变化
(4) 判断请求是否完成且请求成功
(5) 把响应体转换为 JSON 对象,并赋值给 members
属性
(6) 发送 HTTP 请求
虽然使用 XMLHttpRequest API 咱们也实现了一样的功能,但使用 HttpModule
给咱们带来的好处,一目了然。其实 HttpModule
底层实现也是基于 XMLHttpRequest API,只是它对 XMLHttpRequest API 进行了封装,抽象出了 Body、Request、Headers 和 Response 等对象。
HTTP 协议是基于请求与响应,经过 XMLHttpRequest API,咱们能够方便的发送 HTTP 请求。相信不少读者已经用过了如下一款或多款 Fiddler
、Paw (macOS)
、Postman
、Advanced REST client
HTTP 客户端,经过它们咱们也能够方便的发送 HTTP 请求。其实不论是使用上面的那些 HTTP 客户端仍是使用 XMLHttpRequest API,咱们最终都是要构造 HTTP 请求报文,而后向服务器发送 HTTP 请求,接着咱们就须要接收和解析服务器返回的 HTTP 响应报文,最后根据不一样的响应类型解析响应体,进而进行页面渲染。
接下来咱们来分析一下前面 Angular Orgs Members - Http 示例中的代码:
export class AppComponent implements OnInit {
members: Member[];
constructor(private http: Http) { }
ngOnInit() {
this.http.get(`https://api.github.com/orgs/angular/members?page=1&per_page=5`)
.map(res => res.json())
.subscribe(data => {
if (data) this.members = data;
});
}
}复制代码
首先咱们先来分析一下经过构造注入方式,注入的 Http 对象:
constructor(private http: Http) { }复制代码
@NgModule({
providers: [
{provide: Http, useFactory: httpFactory, deps: [XHRBackend, RequestOptions]},
BrowserXhr,
{provide: RequestOptions, useClass: BaseRequestOptions},
{provide: ResponseOptions, useClass: BaseResponseOptions},
XHRBackend,
{provide: XSRFStrategy, useFactory: _createDefaultCookieXSRFStrategy},
],
})
export class HttpModule { }复制代码
export function httpFactory( xhrBackend: XHRBackend, requestOptions: RequestOptions): Http {
return new Http(xhrBackend, requestOptions); // 建立Http对象
}复制代码
// angular2/packages/http/src/http.ts 片断
@Injectable()
export class Http {
constructor(protected _backend: ConnectionBackend,
protected _defaultOptions: RequestOptions) {}
}复制代码
1.建立 XHRBackend 对象
2.建立 RequestOptions 对象
@NgModule({
providers: [
...,
BrowserXhr, // 等价于 {provide: BrowserXhr, useClass: BrowserXhr}
{provide: ResponseOptions, useClass: BaseResponseOptions},
XHRBackend, // 等价于 {provide: XHRBackend, useClass: XHRBackend}
{provide: XSRFStrategy, useFactory: _createDefaultCookieXSRFStrategy},
],
})
export class HttpModule { }复制代码
// angular2/packages/http/src/backends/xhr_backend.ts 片断
@Injectable()
export class XHRBackend implements ConnectionBackend {
constructor(
private _browserXHR: BrowserXhr,
private _baseResponseOptions: ResponseOptions,
private _xsrfStrategy: XSRFStrategy) {}
}复制代码
export abstract class ConnectionBackend {
abstract createConnection(request: any): Connection; // 用于建立链接
}复制代码
(备注:该抽象类中包含了抽象方法,不能直接用于实例化)
export abstract class Connection {
readyState: ReadyState; // 请求状态
request: Request; // 请求对象
response: any; // 响应对象
}复制代码
1.建立 BrowserXhr 对象
2.建立 BaseResponseOptions 对象
3.建立 XSRFStrategy 对象
@NgModule({
providers: [
...,
BrowserXhr, // 等价于 {provide: BrowserXhr, useClass: BrowserXhr}
],
})
export class HttpModule { }复制代码
@Injectable()
export class BrowserXhr {
constructor() {}
build(): any { return <any>(new XMLHttpRequest()); }
}复制代码
@NgModule({
providers: [
...,
{provide: ResponseOptions, useClass: BaseResponseOptions},
...
],
})
export class HttpModule { }复制代码
@Injectable()
export class BaseResponseOptions extends ResponseOptions {
constructor() {
super({status: 200, statusText: 'Ok', type: ResponseType.Default,
headers: new Headers()});
}
}复制代码
export class ResponseOptions {
body: string|Object|ArrayBuffer|Blob; // 响应体的类型
status: number; // 请求的响应状态码
headers: Headers; // 请求头
statusText: string; // 请求的响应状态信息
type: ResponseType; // 响应类型:Basic|Cors|Default|Error|Opaque
url: string; // 响应的URL
constructor({body, status, headers, statusText, type, url}: ResponseOptionsArgs = {}) {
this.body = body != null ? body : null;
this.status = status != null ? status : null;
this.headers = headers != null ? headers : null;
this.statusText = statusText != null ? statusText : null;
this.type = type != null ? type : null;
this.url = url != null ? url : null;
}
// 合并响应参数
merge(options?: ResponseOptionsArgs): ResponseOptions {
return new ResponseOptions({
body: options && options.body != null ? options.body : this.body,
status: options && options.status != null ? options.status : this.status,
headers: options && options.headers != null ? options.headers : this.headers,
statusText: options && options.statusText != null ?
options.statusText : this.statusText,
type: options && options.type != null ? options.type : this.type,
url: options && options.url != null ? options.url : this.url,
});
}
}
// 使用示例
import {ResponseOptions, Response} from '@angular/http';
var options = new ResponseOptions({
body: '{"name":"Jeff"}'
});
var res = new Response(options.merge({
url: 'https://google.com'
}));
console.log('options.url:', options.url); // null
console.log('res.json():', res.json()); // Object {name: "Jeff"}
console.log('res.url:', res.url); // https://google.com复制代码
@NgModule({
providers: [
...,
{provide: XSRFStrategy, useFactory: _createDefaultCookieXSRFStrategy},
],
})
export class HttpModule { }复制代码
// 建立基于Cookie的防止XSRF(Cross Site Request Forgery - 跨域请求伪造)的策略
export function _createDefaultCookieXSRFStrategy() {
return new CookieXSRFStrategy();
}复制代码
// https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
export class CookieXSRFStrategy implements XSRFStrategy {
constructor(
private _cookieName: string = 'XSRF-TOKEN',
private _headerName: string = 'X-XSRF-TOKEN') {}
// 配置请求对象
configureRequest(req: Request): void {
// 从Cookie中获取_cookieName对应的xsrfToken值
const xsrfToken = getDOM().getCookie(this._cookieName);
if (xsrfToken) {
// 请求头添加_headerName请求头,key为_headerName,value为xsrfToken
req.headers.set(this._headerName, xsrfToken);
}
}
}复制代码
export abstract class XSRFStrategy {
abstract configureRequest(req: Request): void;
}复制代码
@NgModule({
providers: [
...,
{provide: RequestOptions, useClass: BaseRequestOptions},
...,
],
})
export class HttpModule { }复制代码
@Injectable()
export class BaseRequestOptions extends RequestOptions {
constructor() { super({method: RequestMethod.Get, headers: new Headers()}); }
}
// 使用示例
import {BaseRequestOptions, Request, RequestMethod} from '@angular/http';
const options = new BaseRequestOptions();
const req = new Request(options.merge({
method: RequestMethod.Post,
url: 'https://google.com'
}));
console.log('req.method:', RequestMethod[req.method]); // Post
console.log('options.url:', options.url); // null
console.log('req.url:', req.url); // https://google.com复制代码
// angular2/packages/http/src/base_request_options.ts 片断
export class RequestOptions {
method: RequestMethod|string; // 请求方法
headers: Headers; // 请求头
body: any; // 请求体
url: string; // 请求URL
params: URLSearchParams; // 参数
// @deprecated from 4.0.0. Use params instead.
get search(): URLSearchParams { return this.params; }
// @deprecated from 4.0.0. Use params instead.
set search(params: URLSearchParams) { this.params = params; }
// 代表在进行跨站 (cross-site) 的访问控制请求时,是否使用认证信息(例如cookie或受权的header)。
withCredentials: boolean;
responseType: ResponseContentType;// 响应类型,就是告诉服务器你指望的响应格式
constructor(
{method, headers, body, url, search, params, withCredentials,
responseType}: RequestOptionsArgs = {}) {
this.method = method != null ? normalizeMethodName(method) : null;
this.headers = headers != null ? headers : null;
this.body = body != null ? body : null;
this.url = url != null ? url : null;
this.params = this._mergeSearchParams(params || search);
this.withCredentials = withCredentials != null ? withCredentials : null;
this.responseType = responseType != null ? responseType : null;
}
// 合并请求参数
merge(options?: RequestOptionsArgs): RequestOptions {
return new RequestOptions({
method: options && options.method != null ? options.method : this.method,
headers: options && options.headers != null ? options.headers
: new Headers(this.headers),
body: options && options.body != null ? options.body : this.body,
url: options && options.url != null ? options.url : this.url,
params: options && this._mergeSearchParams(options.params || options.search),
withCredentials: options && options.withCredentials != null ?
options.withCredentials : this.withCredentials,
responseType: options && options.responseType != null ?
options.responseType : this.responseType
});
}
// 使用示例
import {RequestOptions, Request, RequestMethod} from '@angular/http';
const options = new RequestOptions({
method: RequestMethod.Post
});
const req = new Request(options.merge({
url: 'https://google.com'
}));
console.log('req.method:', RequestMethod[req.method]); // Post
console.log('options.url:', options.url); // null
console.log('req.url:', req.url); // https://google.com复制代码
接下来,咱们来分析一下 AppComponent 中 ngOnInit()
钩子方法中的代码:
ngOnInit() {
this.http.get(`https://api.github.com/orgs/angular/members?page=1&per_page=5`)
.map(res => res.json())
.subscribe(data => {
if (data) this.members = data;
});
}复制代码
this.http.get(`https://api.github.com/orgs/angular/members?page=1&per_page=5`)复制代码
// angular2/packages/http/src/http.ts 片断
@Injectable()
export class Http {
// 构造函数
constructor(protected _backend: ConnectionBackend,
protected _defaultOptions: RequestOptions) {}
// 发送任意类型的请求,返回Observable<Response>对象
request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> {
let responseObservable: any;
if (typeof url === 'string') {
responseObservable = httpRequest(
this._backend,
new Request(mergeOptions(this._defaultOptions, options,
RequestMethod.Get, <string>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<Response> {
return this.request(
new Request(mergeOptions(this._defaultOptions, options,
RequestMethod.Get, url)));
}
// 发送POST请求
post(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
return this.request(new Request(mergeOptions(
this._defaultOptions.merge(new RequestOptions({body: body})), options,
RequestMethod.Post, url)));
}
...
}复制代码
/** * url: 请求地址 * options: 可选的请求参数 */
get(url: string, options?: RequestOptionsArgs): Observable<Response> {
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<Response> {
let responseObservable: any;
if (typeof url === 'string') { // url类型是字符串
responseObservable = httpRequest( // 调用httpRequest() 方法
this._backend, // ConnectionBackend 对象
new Request(mergeOptions(this._defaultOptions, // 建立Request对象
options, RequestMethod.Get, <string>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<Response> {
return backend.createConnection(request).response;
}复制代码
前面咱们已经分析了 ConnectionBackend 对象,接下来咱们来分析一下 Request 对象。
new Request({
method: RequestMethod.Get,
url: 'https://google.com'
});复制代码
// 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;
}
}复制代码
// 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(<string>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)复制代码
function httpRequest(backend: ConnectionBackend, request: Request): Observable<Response> {
return backend.createConnection(request).response; // 建立链接
}复制代码
@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);
}
}复制代码
new XHRConnection(request, this._browserXHR, this._baseResponseOptions);复制代码
// angular2/packages/http/src/backends/xhr_backend.ts 完整代码
export class XHRConnection implements Connection {
request: Request; // 请求对象
response: Observable<Response>; // 响应的Observable对象
readyState: ReadyState; // 请求状态
constructor(req: Request, browserXHR: BrowserXhr, baseResponseOptions?: ResponseOptions) {
this.request = req;
// 建立响应的Observable对象
this.response = new Observable<Response>(
responseObserver: Observer<Response>) => {
// build(): any { return <any>(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 < 300);
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<Response> {
return this.request(
new Request(mergeOptions(this._defaultOptions, options, RequestMethod.Get, url)));
}
request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> {
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<Response>
对象
function httpRequest(backend: ConnectionBackend, request: Request): Observable<Response> {
return backend.createConnection(request).response;
}复制代码
3.调用 RxJS 中的 map()
操做符,对响应 Response 对象进行处理,即转换为 JSON 对象
public map(project: function(value: T, index: number): R, thisArg: any): Observable<R>复制代码
4.订阅返回的 Observable<Response>
对象,即正式发送 HTTP 请求
5.建立 XMLHttpRequest 对象 — _xhr
load
事件,设置 onLoad 处理函数,onLoad 函数内部处理流程:
error
事件,设置 onError 处理函数Angular HttpModule 中核心的内容,咱们已经分析完了,最后在补充一下如何建立 Response 响应对象。
new Response({ body: '{"name":"Jeff"}', url: 'https://google.com' })复制代码
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 <= 299);
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 模块的核心功能,终于分析完了。最后咱们来总结一下:
get()
、post()
、put()
等方法时,会返回一个 Observable<Response>
对象,仅当咱们订阅该 Observable 对象时,才会正式发起 HTTP 请求。Observable<Response>
对象后,返回一个函数对象。调用该函数对象,咱们能够移除 load
、error
事件监听及取消 HTTP 请求。