This repository has been archived by the owner on Dec 18, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 446
/
HttpClient.ts
232 lines (198 loc) · 10.2 KB
/
HttpClient.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { AbortSignal } from "./AbortController";
import { AbortError, HttpError, TimeoutError } from "./Errors";
import { ILogger, LogLevel } from "./ILogger";
/** Represents an HTTP request. */
export interface HttpRequest {
/** The HTTP method to use for the request. */
method?: string;
/** The URL for the request. */
url?: string;
/** The body content for the request. May be a string or an ArrayBuffer (for binary data). */
content?: string | ArrayBuffer;
/** An object describing headers to apply to the request. */
headers?: { [key: string]: string };
/** The XMLHttpRequestResponseType to apply to the request. */
responseType?: XMLHttpRequestResponseType;
/** An AbortSignal that can be monitored for cancellation. */
abortSignal?: AbortSignal;
/** The time to wait for the request to complete before throwing a TimeoutError. Measured in milliseconds. */
timeout?: number;
}
/** Represents an HTTP response. */
export class HttpResponse {
/** Constructs a new instance of {@link @aspnet/signalr.HttpResponse} with the specified status code.
*
* @param {number} statusCode The status code of the response.
*/
constructor(statusCode: number);
/** Constructs a new instance of {@link @aspnet/signalr.HttpResponse} with the specified status code and message.
*
* @param {number} statusCode The status code of the response.
* @param {string} statusText The status message of the response.
*/
constructor(statusCode: number, statusText: string);
/** Constructs a new instance of {@link @aspnet/signalr.HttpResponse} with the specified status code, message and string content.
*
* @param {number} statusCode The status code of the response.
* @param {string} statusText The status message of the response.
* @param {string} content The content of the response.
*/
constructor(statusCode: number, statusText: string, content: string);
/** Constructs a new instance of {@link @aspnet/signalr.HttpResponse} with the specified status code, message and binary content.
*
* @param {number} statusCode The status code of the response.
* @param {string} statusText The status message of the response.
* @param {ArrayBuffer} content The content of the response.
*/
constructor(statusCode: number, statusText: string, content: ArrayBuffer);
constructor(
public readonly statusCode: number,
public readonly statusText?: string,
public readonly content?: string | ArrayBuffer) {
}
}
/** Abstraction over an HTTP client.
*
* This class provides an abstraction over an HTTP client so that a different implementation can be provided on different platforms.
*/
export abstract class HttpClient {
/** Issues an HTTP GET request to the specified URL, returning a Promise that resolves with an {@link @aspnet/signalr.HttpResponse} representing the result.
*
* @param {string} url The URL for the request.
* @returns {Promise<HttpResponse>} A Promise that resolves with an {@link @aspnet/signalr.HttpResponse} describing the response, or rejects with an Error indicating a failure.
*/
public get(url: string): Promise<HttpResponse>;
/** Issues an HTTP GET request to the specified URL, returning a Promise that resolves with an {@link @aspnet/signalr.HttpResponse} representing the result.
*
* @param {string} url The URL for the request.
* @param {HttpRequest} options Additional options to configure the request. The 'url' field in this object will be overridden by the url parameter.
* @returns {Promise<HttpResponse>} A Promise that resolves with an {@link @aspnet/signalr.HttpResponse} describing the response, or rejects with an Error indicating a failure.
*/
public get(url: string, options: HttpRequest): Promise<HttpResponse>;
public get(url: string, options?: HttpRequest): Promise<HttpResponse> {
return this.send({
...options,
method: "GET",
url,
});
}
/** Issues an HTTP POST request to the specified URL, returning a Promise that resolves with an {@link @aspnet/signalr.HttpResponse} representing the result.
*
* @param {string} url The URL for the request.
* @returns {Promise<HttpResponse>} A Promise that resolves with an {@link @aspnet/signalr.HttpResponse} describing the response, or rejects with an Error indicating a failure.
*/
public post(url: string): Promise<HttpResponse>;
/** Issues an HTTP POST request to the specified URL, returning a Promise that resolves with an {@link @aspnet/signalr.HttpResponse} representing the result.
*
* @param {string} url The URL for the request.
* @param {HttpRequest} options Additional options to configure the request. The 'url' field in this object will be overridden by the url parameter.
* @returns {Promise<HttpResponse>} A Promise that resolves with an {@link @aspnet/signalr.HttpResponse} describing the response, or rejects with an Error indicating a failure.
*/
public post(url: string, options: HttpRequest): Promise<HttpResponse>;
public post(url: string, options?: HttpRequest): Promise<HttpResponse> {
return this.send({
...options,
method: "POST",
url,
});
}
/** Issues an HTTP DELETE request to the specified URL, returning a Promise that resolves with an {@link @aspnet/signalr.HttpResponse} representing the result.
*
* @param {string} url The URL for the request.
* @returns {Promise<HttpResponse>} A Promise that resolves with an {@link @aspnet/signalr.HttpResponse} describing the response, or rejects with an Error indicating a failure.
*/
public delete(url: string): Promise<HttpResponse>;
/** Issues an HTTP DELETE request to the specified URL, returning a Promise that resolves with an {@link @aspnet/signalr.HttpResponse} representing the result.
*
* @param {string} url The URL for the request.
* @param {HttpRequest} options Additional options to configure the request. The 'url' field in this object will be overridden by the url parameter.
* @returns {Promise<HttpResponse>} A Promise that resolves with an {@link @aspnet/signalr.HttpResponse} describing the response, or rejects with an Error indicating a failure.
*/
public delete(url: string, options: HttpRequest): Promise<HttpResponse>;
public delete(url: string, options?: HttpRequest): Promise<HttpResponse> {
return this.send({
...options,
method: "DELETE",
url,
});
}
/** Issues an HTTP request to the specified URL, returning a {@link Promise} that resolves with an {@link @aspnet/signalr.HttpResponse} representing the result.
*
* @param {HttpRequest} request An {@link @aspnet/signalr.HttpRequest} describing the request to send.
* @returns {Promise<HttpResponse>} A Promise that resolves with an HttpResponse describing the response, or rejects with an Error indicating a failure.
*/
public abstract send(request: HttpRequest): Promise<HttpResponse>;
}
/** Default implementation of {@link @aspnet/signalr.HttpClient}. */
export class DefaultHttpClient extends HttpClient {
private readonly logger: ILogger;
/** Creates a new instance of the {@link @aspnet/signalr.DefaultHttpClient}, using the provided {@link @aspnet/signalr.ILogger} to log messages. */
public constructor(logger: ILogger) {
super();
this.logger = logger;
}
/** @inheritDoc */
public send(request: HttpRequest): Promise<HttpResponse> {
return new Promise<HttpResponse>((resolve, reject) => {
// Check that abort was not signaled before calling send
if (request.abortSignal && request.abortSignal.aborted) {
reject(new AbortError());
return;
}
const xhr = new XMLHttpRequest();
if (!request.method) {
reject(new Error("No method defined."));
return;
}
if (!request.url) {
reject(new Error("No url defined."));
return;
}
xhr.open(request.method, request.url, true);
xhr.withCredentials = true;
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
// Explicitly setting the Content-Type header for React Native on Android platform.
xhr.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
const headers = request.headers;
if (headers) {
Object.keys(headers)
.forEach((header) => {
xhr.setRequestHeader(header, headers[header]);
});
}
if (request.responseType) {
xhr.responseType = request.responseType;
}
if (request.abortSignal) {
request.abortSignal.onabort = () => {
xhr.abort();
reject(new AbortError());
};
}
if (request.timeout) {
xhr.timeout = request.timeout;
}
xhr.onload = () => {
if (request.abortSignal) {
request.abortSignal.onabort = null;
}
if (xhr.status >= 200 && xhr.status < 300) {
resolve(new HttpResponse(xhr.status, xhr.statusText, xhr.response || xhr.responseText));
} else {
reject(new HttpError(xhr.statusText, xhr.status));
}
};
xhr.onerror = () => {
this.logger.log(LogLevel.Warning, `Error from HTTP request. ${xhr.status}: ${xhr.statusText}`);
reject(new HttpError(xhr.statusText, xhr.status));
};
xhr.ontimeout = () => {
this.logger.log(LogLevel.Warning, `Timeout from HTTP request.`);
reject(new TimeoutError());
};
xhr.send(request.content || "");
});
}
}