axiosHttpClient.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. // Copyright (c) Microsoft Corporation. All rights reserved.
  2. // Licensed under the MIT License. See License.txt in the project root for license information.
  3. import * as tslib_1 from "tslib";
  4. import axios from "axios";
  5. import { Transform } from "stream";
  6. import FormData from "form-data";
  7. import * as tough from "tough-cookie";
  8. import { HttpHeaders } from "./httpHeaders";
  9. import { RestError } from "./restError";
  10. import * as tunnel from "tunnel";
  11. import { URLBuilder } from "./url";
  12. /**
  13. * A HttpClient implementation that uses axios to send HTTP requests.
  14. */
  15. var AxiosHttpClient = /** @class */ (function () {
  16. function AxiosHttpClient() {
  17. this.cookieJar = new tough.CookieJar();
  18. }
  19. AxiosHttpClient.prototype.sendRequest = function (httpRequest) {
  20. return tslib_1.__awaiter(this, void 0, void 0, function () {
  21. var formData, requestForm_1, appendFormValue, _i, _a, formKey, formValue, j, contentType, cookieString, abortSignal, abortListener, cancelToken, rawHeaders, httpRequestBody, axiosBody, onUploadProgress, loadedBytes_1, uploadReportStream, res, config, agent, err_1, axiosErr, headers, onDownloadProgress, responseBody, loadedBytes_2, downloadReportStream, length_1, operationResponse, setCookieHeader_1;
  22. var _this = this;
  23. return tslib_1.__generator(this, function (_b) {
  24. switch (_b.label) {
  25. case 0:
  26. if (typeof httpRequest !== "object") {
  27. throw new Error("httpRequest (WebResource) cannot be null or undefined and must be of type object.");
  28. }
  29. if (httpRequest.formData) {
  30. formData = httpRequest.formData;
  31. requestForm_1 = new FormData();
  32. appendFormValue = function (key, value) {
  33. // value function probably returns a stream so we can provide a fresh stream on each retry
  34. if (typeof value === "function") {
  35. value = value();
  36. }
  37. if (value && value.hasOwnProperty("value") && value.hasOwnProperty("options")) {
  38. requestForm_1.append(key, value.value, value.options);
  39. }
  40. else {
  41. requestForm_1.append(key, value);
  42. }
  43. };
  44. for (_i = 0, _a = Object.keys(formData); _i < _a.length; _i++) {
  45. formKey = _a[_i];
  46. formValue = formData[formKey];
  47. if (Array.isArray(formValue)) {
  48. for (j = 0; j < formValue.length; j++) {
  49. appendFormValue(formKey, formValue[j]);
  50. }
  51. }
  52. else {
  53. appendFormValue(formKey, formValue);
  54. }
  55. }
  56. httpRequest.body = requestForm_1;
  57. httpRequest.formData = undefined;
  58. contentType = httpRequest.headers.get("Content-Type");
  59. if (contentType && contentType.indexOf("multipart/form-data") !== -1) {
  60. if (typeof requestForm_1.getBoundary === "function") {
  61. httpRequest.headers.set("Content-Type", "multipart/form-data; boundary=" + requestForm_1.getBoundary());
  62. }
  63. else {
  64. // browser will automatically apply a suitable content-type header
  65. httpRequest.headers.remove("Content-Type");
  66. }
  67. }
  68. }
  69. if (!(this.cookieJar && !httpRequest.headers.get("Cookie"))) return [3 /*break*/, 2];
  70. return [4 /*yield*/, new Promise(function (resolve, reject) {
  71. _this.cookieJar.getCookieString(httpRequest.url, function (err, cookie) {
  72. if (err) {
  73. reject(err);
  74. }
  75. else {
  76. resolve(cookie);
  77. }
  78. });
  79. })];
  80. case 1:
  81. cookieString = _b.sent();
  82. httpRequest.headers.set("Cookie", cookieString);
  83. _b.label = 2;
  84. case 2:
  85. abortSignal = httpRequest.abortSignal;
  86. if (abortSignal && abortSignal.aborted) {
  87. throw new RestError("The request was aborted", RestError.REQUEST_ABORTED_ERROR, undefined, httpRequest);
  88. }
  89. cancelToken = abortSignal && new axios.CancelToken(function (canceler) {
  90. abortListener = function () { return canceler(); };
  91. abortSignal.addEventListener("abort", abortListener);
  92. });
  93. rawHeaders = httpRequest.headers.rawHeaders();
  94. httpRequestBody = httpRequest.body;
  95. axiosBody =
  96. // Workaround for https://github.com/axios/axios/issues/755
  97. // tslint:disable-next-line:no-null-keyword
  98. typeof httpRequestBody === "undefined" ? null :
  99. typeof httpRequestBody === "function" ? httpRequestBody() :
  100. httpRequestBody;
  101. onUploadProgress = httpRequest.onUploadProgress;
  102. if (onUploadProgress && axiosBody) {
  103. loadedBytes_1 = 0;
  104. uploadReportStream = new Transform({
  105. transform: function (chunk, _encoding, callback) {
  106. loadedBytes_1 += chunk.length;
  107. onUploadProgress({ loadedBytes: loadedBytes_1 });
  108. callback(undefined, chunk);
  109. }
  110. });
  111. if (isReadableStream(axiosBody)) {
  112. axiosBody.pipe(uploadReportStream);
  113. }
  114. else {
  115. uploadReportStream.end(axiosBody);
  116. }
  117. axiosBody = uploadReportStream;
  118. }
  119. _b.label = 3;
  120. case 3:
  121. _b.trys.push([3, 5, 6, 7]);
  122. config = {
  123. method: httpRequest.method,
  124. url: httpRequest.url,
  125. headers: rawHeaders,
  126. data: axiosBody,
  127. transformResponse: function (data) { return data; },
  128. validateStatus: function () { return true; },
  129. // Workaround for https://github.com/axios/axios/issues/1362
  130. maxContentLength: Infinity,
  131. responseType: httpRequest.streamResponseBody ? "stream" : "text",
  132. cancelToken: cancelToken,
  133. timeout: httpRequest.timeout,
  134. proxy: false
  135. };
  136. if (httpRequest.proxySettings) {
  137. agent = createProxyAgent(httpRequest.url, httpRequest.proxySettings, httpRequest.headers);
  138. if (agent.isHttps) {
  139. config.httpsAgent = agent.agent;
  140. }
  141. else {
  142. config.httpAgent = agent.agent;
  143. }
  144. }
  145. // This hack is still required with 0.19.0 version of axios since axios tries to merge the
  146. // Content-Type header from it's config["<method name>"] where the method name is lower-case,
  147. // into the request header. It could be possible that the Content-Type header is not present
  148. // in the original request and this would create problems while creating the signature for
  149. // storage data plane sdks.
  150. axios.interceptors.request.use(function (config) { return (tslib_1.__assign({}, config, { method: config.method && config.method.toUpperCase() })); });
  151. return [4 /*yield*/, axios.request(config)];
  152. case 4:
  153. res = _b.sent();
  154. return [3 /*break*/, 7];
  155. case 5:
  156. err_1 = _b.sent();
  157. if (err_1 instanceof axios.Cancel) {
  158. throw new RestError(err_1.message, RestError.REQUEST_SEND_ERROR, undefined, httpRequest);
  159. }
  160. else {
  161. axiosErr = err_1;
  162. throw new RestError(axiosErr.message, RestError.REQUEST_SEND_ERROR, undefined, httpRequest);
  163. }
  164. return [3 /*break*/, 7];
  165. case 6:
  166. if (abortSignal && abortListener) {
  167. abortSignal.removeEventListener("abort", abortListener);
  168. }
  169. return [7 /*endfinally*/];
  170. case 7:
  171. headers = new HttpHeaders(res.headers);
  172. onDownloadProgress = httpRequest.onDownloadProgress;
  173. responseBody = res.data;
  174. if (onDownloadProgress) {
  175. if (isReadableStream(responseBody)) {
  176. loadedBytes_2 = 0;
  177. downloadReportStream = new Transform({
  178. transform: function (chunk, _encoding, callback) {
  179. loadedBytes_2 += chunk.length;
  180. onDownloadProgress({ loadedBytes: loadedBytes_2 });
  181. callback(undefined, chunk);
  182. }
  183. });
  184. responseBody.pipe(downloadReportStream);
  185. responseBody = downloadReportStream;
  186. }
  187. else {
  188. length_1 = parseInt(headers.get("Content-Length")) || responseBody.length || undefined;
  189. if (length_1) {
  190. // Calling callback for non-stream response for consistency with browser
  191. onDownloadProgress({ loadedBytes: length_1 });
  192. }
  193. }
  194. }
  195. operationResponse = {
  196. request: httpRequest,
  197. status: res.status,
  198. headers: headers,
  199. readableStreamBody: httpRequest.streamResponseBody ? responseBody : undefined,
  200. bodyAsText: httpRequest.streamResponseBody ? undefined : responseBody
  201. };
  202. if (!this.cookieJar) return [3 /*break*/, 9];
  203. setCookieHeader_1 = operationResponse.headers.get("Set-Cookie");
  204. if (!(setCookieHeader_1 != undefined)) return [3 /*break*/, 9];
  205. return [4 /*yield*/, new Promise(function (resolve, reject) {
  206. _this.cookieJar.setCookie(setCookieHeader_1, httpRequest.url, function (err) {
  207. if (err) {
  208. reject(err);
  209. }
  210. else {
  211. resolve();
  212. }
  213. });
  214. })];
  215. case 8:
  216. _b.sent();
  217. _b.label = 9;
  218. case 9: return [2 /*return*/, operationResponse];
  219. }
  220. });
  221. });
  222. };
  223. return AxiosHttpClient;
  224. }());
  225. export { AxiosHttpClient };
  226. function isReadableStream(body) {
  227. return typeof body.pipe === "function";
  228. }
  229. export function createProxyAgent(requestUrl, proxySettings, headers) {
  230. var tunnelOptions = {
  231. proxy: {
  232. host: URLBuilder.parse(proxySettings.host).getHost(),
  233. port: proxySettings.port,
  234. headers: (headers && headers.rawHeaders()) || {}
  235. }
  236. };
  237. if ((proxySettings.username && proxySettings.password)) {
  238. tunnelOptions.proxy.proxyAuth = proxySettings.username + ":" + proxySettings.password;
  239. }
  240. var requestScheme = URLBuilder.parse(requestUrl).getScheme() || "";
  241. var isRequestHttps = requestScheme.toLowerCase() === "https";
  242. var proxyScheme = URLBuilder.parse(proxySettings.host).getScheme() || "";
  243. var isProxyHttps = proxyScheme.toLowerCase() === "https";
  244. var proxyAgent = {
  245. isHttps: isRequestHttps,
  246. agent: createTunnel(isRequestHttps, isProxyHttps, tunnelOptions)
  247. };
  248. return proxyAgent;
  249. }
  250. export function createTunnel(isRequestHttps, isProxyHttps, tunnelOptions) {
  251. if (isRequestHttps && isProxyHttps) {
  252. return tunnel.httpsOverHttps(tunnelOptions);
  253. }
  254. else if (isRequestHttps && !isProxyHttps) {
  255. return tunnel.httpsOverHttp(tunnelOptions);
  256. }
  257. else if (!isRequestHttps && isProxyHttps) {
  258. return tunnel.httpOverHttps(tunnelOptions);
  259. }
  260. else {
  261. return tunnel.httpOverHttp(tunnelOptions);
  262. }
  263. }
  264. //# sourceMappingURL=axiosHttpClient.js.map