/
RequestHandler.ts
152 lines (132 loc) · 4.39 KB
/
RequestHandler.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
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import AbortController from "node-abort-controller";
import fetch, { RequestInit, Response } from "node-fetch";
import { trimSlashes } from "../common";
import { Constants } from "../common/constants";
import { logger } from "../common/logger";
import { executePlugins, PluginOn } from "../plugins/Plugin";
import * as RetryUtility from "../retry/retryUtility";
import { defaultHttpAgent, defaultHttpsAgent } from "./defaultAgent";
import { ErrorResponse } from "./ErrorResponse";
import { bodyFromData } from "./request";
import { RequestContext } from "./RequestContext";
import { Response as CosmosResponse } from "./Response";
import { TimeoutError } from "./TimeoutError";
/** @hidden */
const log = logger("RequestHandler");
async function executeRequest(requestContext: RequestContext) {
return executePlugins(requestContext, httpRequest, PluginOn.request);
}
/**
* @ignore
* @param requestContext
*/
async function httpRequest(requestContext: RequestContext) {
const controller = new AbortController();
const signal = controller.signal;
// Wrap users passed abort events and call our own internal abort()
const userSignal = requestContext.options && requestContext.options.abortSignal;
if (userSignal) {
if (userSignal.aborted) {
controller.abort();
} else {
userSignal.addEventListener("abort", () => {
controller.abort();
});
}
}
const timeout = setTimeout(() => {
controller.abort();
}, requestContext.connectionPolicy.requestTimeout);
let response: Response;
if (requestContext.body) {
requestContext.body = bodyFromData(requestContext.body);
}
try {
response = await fetch(trimSlashes(requestContext.endpoint) + requestContext.path, {
method: requestContext.method,
headers: requestContext.headers as any,
agent: (parsedUrl: URL) => {
if (requestContext.requestAgent) {
return requestContext.requestAgent;
}
return parsedUrl.protocol === "http" ? defaultHttpAgent : defaultHttpsAgent;
},
signal,
body: requestContext.body
} as RequestInit);
} catch (error) {
if (error.name === "AbortError") {
// If the user passed signal caused the abort, cancel the timeout and rethrow the error
if (userSignal && userSignal.aborted === true) {
clearTimeout(timeout);
throw error;
}
// If the user didn't cancel, it must be an abort we called due to timeout
throw new TimeoutError();
}
throw error;
}
clearTimeout(timeout);
const result = response.status === 204 || response.status === 304 ? null : await response.json();
const headers = {} as any;
response.headers.forEach((value: string, key: string) => {
headers[key] = value;
});
const substatus = headers[Constants.HttpHeaders.SubStatus]
? parseInt(headers[Constants.HttpHeaders.SubStatus], 10)
: undefined;
if (response.status >= 400) {
const errorResponse: ErrorResponse = new Error(result.message);
log.warn(
response.status +
" " +
requestContext.endpoint +
" " +
requestContext.path +
" " +
result.message
);
errorResponse.code = response.status;
errorResponse.body = result;
errorResponse.headers = headers;
if (Constants.HttpHeaders.ActivityId in headers) {
errorResponse.activityId = headers[Constants.HttpHeaders.ActivityId];
}
if (Constants.HttpHeaders.SubStatus in headers) {
errorResponse.substatus = substatus;
}
if (Constants.HttpHeaders.RetryAfterInMs in headers) {
errorResponse.retryAfterInMs = parseInt(headers[Constants.HttpHeaders.RetryAfterInMs], 10);
Object.defineProperty(errorResponse, "retryAfterInMilliseconds", {
get: () => {
return errorResponse.retryAfterInMs;
}
});
}
throw errorResponse;
}
return {
headers,
result,
code: response.status,
substatus
};
}
/**
* @ignore
* @param requestContext
*/
export async function request<T>(requestContext: RequestContext): Promise<CosmosResponse<T>> {
if (requestContext.body) {
requestContext.body = bodyFromData(requestContext.body);
if (!requestContext.body) {
throw new Error("parameter data must be a javascript object, string, or Buffer");
}
}
return RetryUtility.execute({
requestContext,
executeRequest
});
}