2018-05-18 20:31:28 +03:00
|
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
// Licensed under the MIT License. See License.txt in the project root for license information.
|
|
|
|
import * as assert from "assert";
|
|
|
|
import * as should from "should";
|
2018-06-23 00:04:19 +03:00
|
|
|
import { DefaultHttpClient } from "../../lib/defaultHttpClient";
|
2018-09-13 00:58:42 +03:00
|
|
|
import { RestError } from "../../lib/restError";
|
2018-05-31 00:27:16 +03:00
|
|
|
import { isNode } from "../../lib/util/utils";
|
2018-07-26 19:23:42 +03:00
|
|
|
import { WebResource, HttpRequestBody } from "../../lib/webResource";
|
2018-05-31 00:27:16 +03:00
|
|
|
import { baseURL } from "../testUtils";
|
2018-07-26 19:23:42 +03:00
|
|
|
import { createReadStream } from 'fs';
|
|
|
|
import { join } from 'path';
|
2018-05-24 20:56:37 +03:00
|
|
|
|
|
|
|
function getAbortController(): AbortController {
|
|
|
|
let controller: AbortController;
|
|
|
|
if (typeof AbortController === "function") {
|
|
|
|
controller = new AbortController();
|
|
|
|
} else {
|
|
|
|
const AbortControllerPonyfill = require("abortcontroller-polyfill/dist/cjs-ponyfill").AbortController;
|
|
|
|
controller = new AbortControllerPonyfill();
|
|
|
|
}
|
|
|
|
return controller;
|
|
|
|
}
|
2018-05-18 20:31:28 +03:00
|
|
|
|
2018-06-23 00:04:19 +03:00
|
|
|
describe("defaultHttpClient", () => {
|
2018-05-18 20:31:28 +03:00
|
|
|
it("should send HTTP requests", async () => {
|
|
|
|
const request = new WebResource(`${baseURL}/example-index.html`, "GET");
|
2018-06-23 00:04:19 +03:00
|
|
|
const httpClient = new DefaultHttpClient();
|
2018-05-18 20:31:28 +03:00
|
|
|
|
|
|
|
const response = await httpClient.sendRequest(request);
|
|
|
|
assert.deepStrictEqual(response.request, request);
|
|
|
|
assert.strictEqual(response.status, 200);
|
|
|
|
assert(response.headers);
|
2018-05-22 02:07:33 +03:00
|
|
|
// content-length varies based on OS line endings
|
|
|
|
assert.strictEqual(response.headers.get("content-length"), response.bodyAsText!.length.toString());
|
2018-05-18 20:31:28 +03:00
|
|
|
assert.strictEqual(response.headers.get("content-type")!.split(";")[0], "text/html");
|
|
|
|
const responseBody: string | null | undefined = response.bodyAsText;
|
|
|
|
const expectedResponseBody =
|
2018-06-01 01:31:45 +03:00
|
|
|
`<!doctype html>
|
2018-05-18 20:31:28 +03:00
|
|
|
<html>
|
|
|
|
<head>
|
|
|
|
<title>Example Domain</title>
|
|
|
|
|
|
|
|
<meta charset="utf-8" />
|
|
|
|
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
|
|
<style type="text/css">
|
|
|
|
body {
|
|
|
|
background-color: #f0f0f2;
|
|
|
|
margin: 0;
|
|
|
|
padding: 0;
|
|
|
|
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
|
|
|
|
|
|
}
|
|
|
|
div {
|
|
|
|
width: 600px;
|
|
|
|
margin: 5em auto;
|
|
|
|
padding: 50px;
|
|
|
|
background-color: #fff;
|
|
|
|
border-radius: 1em;
|
|
|
|
}
|
|
|
|
a:link, a:visited {
|
|
|
|
color: #38488f;
|
|
|
|
text-decoration: none;
|
|
|
|
}
|
|
|
|
@media (max-width: 700px) {
|
|
|
|
body {
|
|
|
|
background-color: #fff;
|
|
|
|
}
|
|
|
|
div {
|
|
|
|
width: auto;
|
|
|
|
margin: 0 auto;
|
|
|
|
border-radius: 0;
|
|
|
|
padding: 1em;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
</head>
|
|
|
|
|
|
|
|
<body>
|
|
|
|
<div>
|
|
|
|
<h1>Example Domain</h1>
|
|
|
|
<p>This domain is established to be used for illustrative examples in documents. You may use this
|
|
|
|
domain in examples without prior coordination or asking for permission.</p>
|
|
|
|
<p><a href="http://www.iana.org/domains/example">More information...</a></p>
|
|
|
|
</div>
|
|
|
|
</body>
|
|
|
|
</html>
|
2018-05-22 02:07:33 +03:00
|
|
|
`;
|
|
|
|
assert.strictEqual(
|
|
|
|
responseBody && responseBody.replace(/\r\n/g, "\n"),
|
|
|
|
expectedResponseBody.replace(/\r\n/g, "\n"));
|
2018-05-18 20:31:28 +03:00
|
|
|
});
|
|
|
|
|
2018-05-22 01:48:23 +03:00
|
|
|
it("should return a response instead of throwing for awaited 404", async () => {
|
2018-05-18 20:31:28 +03:00
|
|
|
const request = new WebResource(`${baseURL}/nonexistent`, "GET");
|
2018-06-23 00:04:19 +03:00
|
|
|
const httpClient = new DefaultHttpClient();
|
2018-05-18 20:31:28 +03:00
|
|
|
|
2018-05-22 01:48:23 +03:00
|
|
|
const response = await httpClient.sendRequest(request);
|
|
|
|
assert(response);
|
2018-05-18 20:31:28 +03:00
|
|
|
});
|
|
|
|
|
2018-06-01 01:31:45 +03:00
|
|
|
it("should allow canceling requests", async function () {
|
2018-05-24 20:56:37 +03:00
|
|
|
const controller = getAbortController();
|
2018-07-06 14:16:28 +03:00
|
|
|
const request = new WebResource(`${baseURL}/fileupload`, "POST", new Uint8Array(1024 * 1024 * 10), undefined, undefined, true, undefined, controller.signal);
|
2018-06-23 00:04:19 +03:00
|
|
|
const client = new DefaultHttpClient();
|
2018-05-18 20:31:28 +03:00
|
|
|
const promise = client.sendRequest(request);
|
2018-05-31 00:01:03 +03:00
|
|
|
controller.abort();
|
2018-05-18 20:31:28 +03:00
|
|
|
try {
|
|
|
|
await promise;
|
2018-06-01 01:31:45 +03:00
|
|
|
assert.fail("");
|
2018-05-18 20:31:28 +03:00
|
|
|
} catch (err) {
|
|
|
|
should(err).not.be.instanceof(assert.AssertionError);
|
|
|
|
}
|
|
|
|
});
|
2018-05-22 01:29:49 +03:00
|
|
|
|
2018-06-01 01:31:45 +03:00
|
|
|
it("should not overwrite a user-provided cookie (nodejs only)", async function () {
|
2018-05-26 01:22:21 +03:00
|
|
|
// Cookie is only allowed to be set by the browser based on an actual response Set-Cookie header
|
|
|
|
if (!isNode) {
|
|
|
|
this.skip();
|
|
|
|
}
|
|
|
|
|
2018-06-23 00:04:19 +03:00
|
|
|
const client = new DefaultHttpClient();
|
2018-05-26 01:22:21 +03:00
|
|
|
|
2018-05-26 01:12:59 +03:00
|
|
|
const request1 = new WebResource(`${baseURL}/set-cookie`);
|
|
|
|
await client.sendRequest(request1);
|
|
|
|
|
2018-05-26 01:22:21 +03:00
|
|
|
const request2 = new WebResource(`${baseURL}/cookie`);
|
|
|
|
const response2 = await client.sendRequest(request2);
|
|
|
|
should(response2.headers.get("Cookie")).equal("data=123456");
|
|
|
|
|
|
|
|
const request3 = new WebResource(`${baseURL}/cookie`, "GET", undefined, undefined, { Cookie: "data=abcdefg" });
|
|
|
|
const response3 = await client.sendRequest(request3);
|
|
|
|
should(response3.headers.get("Cookie")).equal("data=abcdefg");
|
2018-05-26 01:12:59 +03:00
|
|
|
});
|
|
|
|
|
2018-05-22 01:29:49 +03:00
|
|
|
it("should allow canceling multiple requests with one token", async function () {
|
2018-05-24 20:56:37 +03:00
|
|
|
const controller = getAbortController();
|
2018-06-01 01:31:45 +03:00
|
|
|
const buf = new Uint8Array(1024 * 1024 * 1);
|
2018-05-22 01:29:49 +03:00
|
|
|
const requests = [
|
2018-07-06 14:16:28 +03:00
|
|
|
new WebResource(`${baseURL}/fileupload`, "POST", buf, undefined, undefined, true, undefined, controller.signal),
|
|
|
|
new WebResource(`${baseURL}/fileupload`, "POST", buf, undefined, undefined, true, undefined, controller.signal)
|
2018-05-22 01:29:49 +03:00
|
|
|
];
|
2018-06-23 00:04:19 +03:00
|
|
|
const client = new DefaultHttpClient();
|
2018-05-22 01:29:49 +03:00
|
|
|
const promises = requests.map(r => client.sendRequest(r));
|
2018-05-31 00:01:03 +03:00
|
|
|
controller.abort();
|
2018-05-22 01:29:49 +03:00
|
|
|
// Ensure each promise is individually rejected
|
|
|
|
for (const promise of promises) {
|
|
|
|
try {
|
|
|
|
await promise;
|
2018-06-01 01:31:45 +03:00
|
|
|
assert.fail("");
|
2018-05-22 01:29:49 +03:00
|
|
|
} catch (err) {
|
|
|
|
should(err).not.be.instanceof(assert.AssertionError);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2018-05-30 20:37:36 +03:00
|
|
|
|
2018-07-26 19:23:42 +03:00
|
|
|
it("should report upload and download progress for simple bodies", async function () {
|
|
|
|
let uploadNotified = false;
|
|
|
|
let downloadNotified = false;
|
2018-05-30 20:37:36 +03:00
|
|
|
|
2018-07-26 19:23:42 +03:00
|
|
|
const body = isNode ? new Buffer(1024 * 1024) : new Uint8Array(1024 * 1024);
|
|
|
|
const request = new WebResource(`${baseURL}/fileupload`, "POST", body, undefined, undefined, false, undefined, undefined, 0,
|
|
|
|
ev => {
|
|
|
|
uploadNotified = true;
|
|
|
|
if (typeof ProgressEvent !== "undefined") {
|
|
|
|
ev.should.not.be.instanceof(ProgressEvent);
|
|
|
|
}
|
|
|
|
ev.loadedBytes.should.be.a.Number;
|
|
|
|
},
|
|
|
|
ev => {
|
|
|
|
downloadNotified = true;
|
|
|
|
if (typeof ProgressEvent !== "undefined") {
|
|
|
|
ev.should.not.be.instanceof(ProgressEvent);
|
|
|
|
}
|
|
|
|
ev.loadedBytes.should.be.a.Number;
|
|
|
|
});
|
|
|
|
|
|
|
|
const client = new DefaultHttpClient();
|
|
|
|
await client.sendRequest(request);
|
|
|
|
assert(uploadNotified);
|
|
|
|
assert(downloadNotified);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("should report upload and download progress for blob or stream bodies", async function() {
|
2018-05-30 20:37:36 +03:00
|
|
|
let uploadNotified = false;
|
|
|
|
let downloadNotified = false;
|
|
|
|
|
2018-07-26 19:23:42 +03:00
|
|
|
let body: HttpRequestBody;
|
|
|
|
if (isNode) {
|
|
|
|
body = () => createReadStream(join(__dirname, "..", "resources", "example-index.html"));
|
|
|
|
} else {
|
|
|
|
body = new Blob([new Uint8Array(1024 * 1024)]);
|
|
|
|
}
|
|
|
|
const request = new WebResource(`${baseURL}/fileupload`, "POST", body, undefined, undefined, true, undefined, undefined, 0,
|
2018-05-30 20:37:36 +03:00
|
|
|
ev => {
|
|
|
|
uploadNotified = true;
|
2018-07-26 19:23:42 +03:00
|
|
|
if (typeof ProgressEvent !== "undefined") {
|
|
|
|
ev.should.not.be.instanceof(ProgressEvent);
|
|
|
|
}
|
2018-05-30 20:53:50 +03:00
|
|
|
ev.loadedBytes.should.be.a.Number;
|
2018-05-30 20:37:36 +03:00
|
|
|
},
|
|
|
|
ev => {
|
|
|
|
downloadNotified = true;
|
2018-07-26 19:23:42 +03:00
|
|
|
if (typeof ProgressEvent !== "undefined") {
|
|
|
|
ev.should.not.be.instanceof(ProgressEvent);
|
|
|
|
}
|
2018-05-30 20:53:50 +03:00
|
|
|
ev.loadedBytes.should.be.a.Number;
|
2018-05-30 20:37:36 +03:00
|
|
|
});
|
|
|
|
|
2018-06-23 00:04:19 +03:00
|
|
|
const client = new DefaultHttpClient();
|
2018-06-23 00:38:37 +03:00
|
|
|
const response = await client.sendRequest(request);
|
2018-07-26 19:23:42 +03:00
|
|
|
const streamBody = response.readableStreamBody;
|
2018-06-23 00:38:37 +03:00
|
|
|
if (response.blobBody) {
|
2018-08-29 20:16:55 +03:00
|
|
|
await response.blobBody;
|
2018-07-26 19:23:42 +03:00
|
|
|
} else if (streamBody) {
|
|
|
|
streamBody.on('data', () => {});
|
|
|
|
await new Promise((resolve, reject) => {
|
|
|
|
streamBody.on("end", resolve);
|
|
|
|
streamBody.on("error", reject);
|
|
|
|
});
|
2018-06-23 00:38:37 +03:00
|
|
|
}
|
2018-05-30 20:37:36 +03:00
|
|
|
assert(uploadNotified);
|
|
|
|
assert(downloadNotified);
|
|
|
|
});
|
2018-07-17 23:35:55 +03:00
|
|
|
|
|
|
|
it("should honor request timeouts", async function () {
|
|
|
|
const request = new WebResource(`${baseURL}/slow`, "GET", undefined, undefined, undefined, false, false, undefined, 100);
|
|
|
|
const client = new DefaultHttpClient();
|
|
|
|
try {
|
|
|
|
await client.sendRequest(request);
|
|
|
|
throw new Error("request did not fail as expected");
|
|
|
|
} catch (err) {
|
|
|
|
err.message.should.match(/timeout/);
|
|
|
|
}
|
|
|
|
});
|
2018-08-23 02:16:40 +03:00
|
|
|
|
|
|
|
it("should give a graceful error for nonexistent hosts", async function() {
|
|
|
|
const request = new WebResource(`http://foo.notawebsite/`);
|
|
|
|
const client = new DefaultHttpClient();
|
|
|
|
try {
|
|
|
|
await client.sendRequest(request);
|
|
|
|
throw new Error("request did not fail as expected");
|
|
|
|
} catch (err) {
|
2018-09-13 00:58:42 +03:00
|
|
|
err.should.be.instanceof(RestError);
|
2018-08-23 02:16:40 +03:00
|
|
|
err.code.should.equal("REQUEST_SEND_ERROR");
|
|
|
|
}
|
|
|
|
});
|
2018-09-04 19:36:49 +03:00
|
|
|
|
|
|
|
it("should interpret undefined as an empty body", async function () {
|
|
|
|
const request = new WebResource(`${baseURL}/expect-empty`, "PUT");
|
|
|
|
const client = new DefaultHttpClient();
|
|
|
|
const response = await client.sendRequest(request);
|
|
|
|
response.status.should.equal(200, response.bodyAsText!);
|
|
|
|
});
|
2018-05-18 20:31:28 +03:00
|
|
|
});
|