Skip to content
Permalink
9bfb9ba527
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
178 lines (164 sloc) 5.39 KB
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import { HttpClient } from "./httpClient";
import { HttpHeaders } from "./httpHeaders";
import { WebResourceLike, TransferProgressEvent } from "./webResource";
import { HttpOperationResponse } from "./httpOperationResponse";
import { RestError } from "./restError";
/**
* A HttpClient implementation that uses XMLHttpRequest to send HTTP requests.
*/
export class XhrHttpClient implements HttpClient {
public sendRequest(request: WebResourceLike): Promise<HttpOperationResponse> {
const xhr = new XMLHttpRequest();
if (request.agentSettings) {
throw new Error("HTTP agent settings not supported in browser environment");
}
if (request.proxySettings) {
throw new Error("HTTP proxy is not supported in browser environment");
}
const abortSignal = request.abortSignal;
if (abortSignal) {
const listener = () => {
xhr.abort();
};
abortSignal.addEventListener("abort", listener);
xhr.addEventListener("readystatechange", () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
abortSignal.removeEventListener("abort", listener);
}
});
}
addProgressListener(xhr.upload, request.onUploadProgress);
addProgressListener(xhr, request.onDownloadProgress);
if (request.formData) {
const formData = request.formData;
const requestForm = new FormData();
const appendFormValue = (key: string, value: any) => {
if (value && value.hasOwnProperty("value") && value.hasOwnProperty("options")) {
requestForm.append(key, value.value, value.options);
} else {
requestForm.append(key, value);
}
};
for (const formKey of Object.keys(formData)) {
const formValue = formData[formKey];
if (Array.isArray(formValue)) {
for (let j = 0; j < formValue.length; j++) {
appendFormValue(formKey, formValue[j]);
}
} else {
appendFormValue(formKey, formValue);
}
}
request.body = requestForm;
request.formData = undefined;
const contentType = request.headers.get("Content-Type");
if (contentType && contentType.indexOf("multipart/form-data") !== -1) {
// browser will automatically apply a suitable content-type header
request.headers.remove("Content-Type");
}
}
xhr.open(request.method, request.url);
xhr.timeout = request.timeout;
xhr.withCredentials = request.withCredentials;
for (const header of request.headers.headersArray()) {
xhr.setRequestHeader(header.name, header.value);
}
xhr.responseType = request.streamResponseBody ? "blob" : "text";
// tslint:disable-next-line:no-null-keyword
xhr.send(request.body === undefined ? null : request.body);
if (request.streamResponseBody) {
return new Promise((resolve, reject) => {
xhr.addEventListener("readystatechange", () => {
// Resolve as soon as headers are loaded
if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
const blobBody = new Promise<Blob>((resolve, reject) => {
xhr.addEventListener("load", () => {
resolve(xhr.response);
});
rejectOnTerminalEvent(request, xhr, reject);
});
resolve({
request,
status: xhr.status,
headers: parseHeaders(xhr),
blobBody,
});
}
});
rejectOnTerminalEvent(request, xhr, reject);
});
} else {
return new Promise(function (resolve, reject) {
xhr.addEventListener("load", () =>
resolve({
request,
status: xhr.status,
headers: parseHeaders(xhr),
bodyAsText: xhr.responseText,
})
);
rejectOnTerminalEvent(request, xhr, reject);
});
}
}
}
function addProgressListener(
xhr: XMLHttpRequestEventTarget,
listener?: (progress: TransferProgressEvent) => void
) {
if (listener) {
xhr.addEventListener("progress", (rawEvent) =>
listener({
loadedBytes: rawEvent.loaded,
})
);
}
}
// exported locally for testing
export function parseHeaders(xhr: XMLHttpRequest) {
const responseHeaders = new HttpHeaders();
const headerLines = xhr
.getAllResponseHeaders()
.trim()
.split(/[\r\n]+/);
for (const line of headerLines) {
const index = line.indexOf(":");
const headerName = line.slice(0, index);
const headerValue = line.slice(index + 2);
responseHeaders.set(headerName, headerValue);
}
return responseHeaders;
}
function rejectOnTerminalEvent(
request: WebResourceLike,
xhr: XMLHttpRequest,
reject: (err: any) => void
) {
xhr.addEventListener("error", () =>
reject(
new RestError(
`Failed to send request to ${request.url}`,
RestError.REQUEST_SEND_ERROR,
undefined,
request
)
)
);
xhr.addEventListener("abort", () =>
reject(
new RestError("The request was aborted", RestError.REQUEST_ABORTED_ERROR, undefined, request)
)
);
xhr.addEventListener("timeout", () =>
reject(
new RestError(
`timeout of ${xhr.timeout}ms exceeded`,
RestError.REQUEST_SEND_ERROR,
undefined,
request
)
)
);
}