feat(common): new HttpClient API
HttpClient is an evolution of the existing Angular HTTP API, which exists alongside of it in a separate package, @angular/common/http. This structure ensures that existing codebases can slowly migrate to the new API. The new API improves significantly on the ergonomics and features of the legacy API. A partial list of new features includes: * Typed, synchronous response body access, including support for JSON body types * JSON is an assumed default and no longer needs to be explicitly parsed * Interceptors allow middleware logic to be inserted into the pipeline * Immutable request/response objects * Progress events for both request upload and response download * Post-request verification & flush based testing framework
This commit is contained in:

committed by
Jason Aden

parent
2a7ebbe982
commit
37797e2b4e
329
packages/common/http/src/response.ts
Normal file
329
packages/common/http/src/response.ts
Normal file
@ -0,0 +1,329 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {empty} from 'rxjs/observable/empty';
|
||||
|
||||
import {HttpHeaders} from './headers';
|
||||
|
||||
/**
|
||||
* Type enumeration for the different kinds of `HttpEvent`.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export enum HttpEventType {
|
||||
/**
|
||||
* The request was sent out over the wire.
|
||||
*/
|
||||
Sent,
|
||||
|
||||
/**
|
||||
* An upload progress event was received.
|
||||
*/
|
||||
UploadProgress,
|
||||
|
||||
/**
|
||||
* The response status code and headers were received.
|
||||
*/
|
||||
ResponseHeader,
|
||||
|
||||
/**
|
||||
* A download progress event was received.
|
||||
*/
|
||||
DownloadProgress,
|
||||
|
||||
/**
|
||||
* The full response including the body was received.
|
||||
*/
|
||||
Response,
|
||||
|
||||
/**
|
||||
* A custom event from an interceptor or a backend.
|
||||
*/
|
||||
User,
|
||||
}
|
||||
|
||||
/**
|
||||
* Base interface for progress events.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface HttpProgressEvent {
|
||||
/**
|
||||
* Progress event type is either upload or download.
|
||||
*/
|
||||
type: HttpEventType.DownloadProgress|HttpEventType.UploadProgress;
|
||||
|
||||
/**
|
||||
* Number of bytes uploaded or downloaded.
|
||||
*/
|
||||
loaded: number;
|
||||
|
||||
/**
|
||||
* Total number of bytes to upload or download. Depending on the request or
|
||||
* response, this may not be computable and thus may not be present.
|
||||
*/
|
||||
total?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* A download progress event.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface HttpDownloadProgressEvent extends HttpProgressEvent {
|
||||
type: HttpEventType.DownloadProgress;
|
||||
|
||||
/**
|
||||
* The partial response body as downloaded so far.
|
||||
*
|
||||
* Only present if the responseType was `text`.
|
||||
*/
|
||||
partialText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* An upload progress event.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface HttpUploadProgressEvent extends HttpProgressEvent {
|
||||
type: HttpEventType.UploadProgress;
|
||||
}
|
||||
|
||||
/**
|
||||
* An event indicating that the request was sent to the server. Useful
|
||||
* when a request may be retried multiple times, to distinguish between
|
||||
* retries on the final event stream.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface HttpSentEvent { type: HttpEventType.Sent; }
|
||||
|
||||
/**
|
||||
* A user-defined event.
|
||||
*
|
||||
* Grouping all custom events under this type ensures they will be handled
|
||||
* and forwarded by all implementations of interceptors.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface HttpUserEvent<T> { type: HttpEventType.User; }
|
||||
|
||||
/**
|
||||
* An error that represents a failed attempt to JSON.parse text coming back
|
||||
* from the server.
|
||||
*
|
||||
* It bundles the Error object with the actual response body that failed to parse.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface HttpJsonParseError { error: Error, text: string, }
|
||||
|
||||
/**
|
||||
* Union type for all possible events on the response stream.
|
||||
*
|
||||
* Typed according to the expected type of the response.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export type HttpEvent<T> =
|
||||
HttpSentEvent | HttpHeaderResponse | HttpResponse<T>| HttpProgressEvent | HttpUserEvent<T>;
|
||||
|
||||
/**
|
||||
* Base class for both `HttpResponse` and `HttpHeaderResponse`.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export abstract class HttpResponseBase {
|
||||
/**
|
||||
* All response headers.
|
||||
*/
|
||||
readonly headers: HttpHeaders;
|
||||
|
||||
/**
|
||||
* Response status code.
|
||||
*/
|
||||
readonly status: number;
|
||||
|
||||
/**
|
||||
* Textual description of response status code.
|
||||
*
|
||||
* Do not depend on this.
|
||||
*/
|
||||
readonly statusText: string;
|
||||
|
||||
/**
|
||||
* URL of the resource retrieved, or null if not available.
|
||||
*/
|
||||
readonly url: string|null;
|
||||
|
||||
/**
|
||||
* Whether the status code falls in the 2xx range.
|
||||
*/
|
||||
readonly ok: boolean;
|
||||
|
||||
/**
|
||||
* Type of the response, narrowed to either the full response or the header.
|
||||
*/
|
||||
readonly type: HttpEventType.Response|HttpEventType.ResponseHeader;
|
||||
|
||||
/**
|
||||
* Super-constructor for all responses.
|
||||
*
|
||||
* The single parameter accepted is an initialization hash. Any properties
|
||||
* of the response passed there will override the default values.
|
||||
*/
|
||||
constructor(
|
||||
init: {
|
||||
headers?: HttpHeaders,
|
||||
status?: number,
|
||||
statusText?: string,
|
||||
url?: string,
|
||||
},
|
||||
defaultStatus: number = 200, defaultStatusText: string = 'OK') {
|
||||
// If the hash has values passed, use them to initialize the response.
|
||||
// Otherwise use the default values.
|
||||
this.headers = init.headers || new HttpHeaders();
|
||||
this.status = init.status !== undefined ? init.status : defaultStatus;
|
||||
this.statusText = init.statusText || defaultStatusText;
|
||||
this.url = init.url || null;
|
||||
|
||||
// Cache the ok value to avoid defining a getter.
|
||||
this.ok = this.status >= 200 && this.status < 300;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A partial HTTP response which only includes the status and header data,
|
||||
* but no response body.
|
||||
*
|
||||
* `HttpHeaderResponse` is a `HttpEvent` available on the response
|
||||
* event stream, only when progress events are requested.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class HttpHeaderResponse extends HttpResponseBase {
|
||||
/**
|
||||
* Create a new `HttpHeaderResponse` with the given parameters.
|
||||
*/
|
||||
constructor(init: {
|
||||
headers?: HttpHeaders,
|
||||
status?: number,
|
||||
statusText?: string,
|
||||
url?: string,
|
||||
} = {}) {
|
||||
super(init);
|
||||
}
|
||||
|
||||
readonly type: HttpEventType.ResponseHeader = HttpEventType.ResponseHeader;
|
||||
|
||||
/**
|
||||
* Copy this `HttpHeaderResponse`, overriding its contents with the
|
||||
* given parameter hash.
|
||||
*/
|
||||
clone(update: {headers?: HttpHeaders; status?: number; statusText?: string; url?: string;} = {}):
|
||||
HttpHeaderResponse {
|
||||
// Perform a straightforward initialization of the new HttpHeaderResponse,
|
||||
// overriding the current parameters with new ones if given.
|
||||
return new HttpHeaderResponse({
|
||||
headers: update.headers || this.headers,
|
||||
status: update.status !== undefined ? update.status : this.status,
|
||||
statusText: update.statusText || this.statusText,
|
||||
url: update.url || this.url || undefined,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A full HTTP response, including a typed response body (which may be `null`
|
||||
* if one was not returned).
|
||||
*
|
||||
* `HttpResponse` is a `HttpEvent` available on the response event
|
||||
* stream.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class HttpResponse<T> extends HttpResponseBase {
|
||||
/**
|
||||
* The response body, or `null` if one was not returned.
|
||||
*/
|
||||
readonly body: T|null;
|
||||
|
||||
/**
|
||||
* Construct a new `HttpResponse`.
|
||||
*/
|
||||
constructor(init: {
|
||||
body?: T | null, headers?: HttpHeaders; status?: number; statusText?: string; url?: string;
|
||||
} = {}) {
|
||||
super(init);
|
||||
this.body = init.body || null;
|
||||
}
|
||||
|
||||
readonly type: HttpEventType.Response = HttpEventType.Response;
|
||||
|
||||
clone(): HttpResponse<T>;
|
||||
clone(update: {headers?: HttpHeaders; status?: number; statusText?: string; url?: string;}):
|
||||
HttpResponse<T>;
|
||||
clone<V>(update: {
|
||||
body?: V | null, headers?: HttpHeaders; status?: number; statusText?: string; url?: string;
|
||||
}): HttpResponse<V>;
|
||||
clone(update: {
|
||||
body?: any | null; headers?: HttpHeaders; status?: number; statusText?: string; url?: string;
|
||||
} = {}): HttpResponse<any> {
|
||||
return new HttpResponse<any>({
|
||||
body: (update.body !== undefined) ? update.body : this.body,
|
||||
headers: update.headers || this.headers,
|
||||
status: (update.status !== undefined) ? update.status : this.status,
|
||||
statusText: update.statusText || this.statusText,
|
||||
url: update.url || this.url || undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A response that represents an error or failure, either from a
|
||||
* non-successful HTTP status, an error while executing the request,
|
||||
* or some other failure which occurred during the parsing of the response.
|
||||
*
|
||||
* Any error returned on the `Observable` response stream will be
|
||||
* wrapped in an `HttpErrorResponse` to provide additional context about
|
||||
* the state of the HTTP layer when the error occurred. The error property
|
||||
* will contain either a wrapped Error object or the error response returned
|
||||
* from the server.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export class HttpErrorResponse extends HttpResponseBase implements Error {
|
||||
readonly name = 'HttpErrorResponse';
|
||||
readonly message: string;
|
||||
readonly error: any|null;
|
||||
|
||||
/**
|
||||
* Errors are never okay, even when the status code is in the 2xx success range.
|
||||
*/
|
||||
readonly ok = false;
|
||||
|
||||
constructor(init: {
|
||||
error?: any; headers?: HttpHeaders; status?: number; statusText?: string; url?: string;
|
||||
}) {
|
||||
// Initialize with a default status of 0 / Unknown Error.
|
||||
super(init, 0, 'Unknown Error');
|
||||
|
||||
// If the response was successful, then this was a parse error. Otherwise, it was
|
||||
// a protocol-level failure of some sort. Either the request failed in transit
|
||||
// or the server returned an unsuccessful status code.
|
||||
if (this.status >= 200 && this.status < 300) {
|
||||
this.message = `Http failure during parsing for ${init.url || '(unknown url)'}`;
|
||||
} else {
|
||||
this.message =
|
||||
`Http failure response for ${init.url || '(unknown url)'}: ${init.status} ${init.statusText}`;
|
||||
}
|
||||
this.error = init.error || null;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user