1 |
|
2 |
|
3 |
|
4 | import { delay, HttpMethods, HttpOperationResponse, RequestOptionsBase, RestError, stripRequest, WebResource, OperationResponse, OperationSpec } from "@azure/ms-rest-js";
|
5 | import { AzureServiceClient } from "./azureServiceClient";
|
6 | import { LongRunningOperationStates } from "./util/constants";
|
7 |
|
8 | export type LROPollStrategyType = "AzureAsyncOperation" | "Location" | "GetResource";
|
9 |
|
10 | export interface LROPollState {
|
11 | pollStrategyType: LROPollStrategyType;
|
12 | initialResponse: HttpOperationResponse;
|
13 | state: LongRunningOperationStates;
|
14 | mostRecentRequest: WebResource;
|
15 | mostRecentResponse: HttpOperationResponse;
|
16 | resource: any;
|
17 | azureAsyncOperationHeaderValue?: string;
|
18 | locationHeaderValue?: string;
|
19 | options?: RequestOptionsBase;
|
20 | }
|
21 |
|
22 |
|
23 |
|
24 |
|
25 | export abstract class LROPollStrategy {
|
26 | constructor(private readonly _azureServiceClient: AzureServiceClient, protected readonly _pollState: LROPollState) {
|
27 | }
|
28 |
|
29 | public getOperationStatus(): LongRunningOperationStates {
|
30 | return this._pollState.state;
|
31 | }
|
32 |
|
33 | |
34 |
|
35 |
|
36 |
|
37 | public isFinished(): boolean {
|
38 | return isFinished(this._pollState.state);
|
39 | }
|
40 |
|
41 | |
42 |
|
43 |
|
44 |
|
45 | public async pollUntilFinished(): Promise<boolean> {
|
46 | while (!this.isFinished()) {
|
47 | const delayInSeconds: number = getDelayInSeconds(this._azureServiceClient, this._pollState.mostRecentResponse);
|
48 | await delay(delayInSeconds * 1000);
|
49 |
|
50 | await this.sendPollRequest();
|
51 | }
|
52 | return this.isFinalStatusAcceptable();
|
53 | }
|
54 |
|
55 | |
56 |
|
57 |
|
58 |
|
59 | public abstract sendPollRequest(): Promise<void>;
|
60 |
|
61 | public abstract isFinalStatusAcceptable(): boolean;
|
62 |
|
63 | protected shouldDoFinalGetResourceRequest(): boolean {
|
64 | const initialRequestMethod: HttpMethods = this._pollState.initialResponse.request.method;
|
65 | return !this._pollState.resource && (initialRequestMethod === "PUT" || initialRequestMethod === "PATCH" || initialRequestMethod === "POST");
|
66 | }
|
67 |
|
68 | protected abstract doFinalGetResourceRequest(): Promise<void>;
|
69 |
|
70 | public getMostRecentResponse(): HttpOperationResponse {
|
71 | return this._pollState.mostRecentResponse;
|
72 | }
|
73 |
|
74 | public async getOperationResponse(): Promise<HttpOperationResponse> {
|
75 | if (this.shouldDoFinalGetResourceRequest()) {
|
76 | await this.doFinalGetResourceRequest();
|
77 | }
|
78 | const response: HttpOperationResponse = this._pollState.mostRecentResponse;
|
79 | const result: HttpOperationResponse = {
|
80 | ...response,
|
81 | headers: response.headers.clone()
|
82 | };
|
83 | const resource: any = this._pollState.resource;
|
84 | if (!resource) {
|
85 | result.bodyAsText = response.bodyAsText;
|
86 | result.parsedBody = response.parsedBody;
|
87 | } else if (typeof resource.valueOf() === "string") {
|
88 | result.bodyAsText = resource;
|
89 | try {
|
90 | result.parsedBody = JSON.parse(resource);
|
91 | } catch (err) {
|
92 |
|
93 |
|
94 | result.parsedBody = resource;
|
95 | }
|
96 | } else {
|
97 | result.bodyAsText = JSON.stringify(resource);
|
98 | result.parsedBody = resource;
|
99 | }
|
100 | return result;
|
101 | }
|
102 |
|
103 | public getRestError(): RestError {
|
104 | const error = new RestError("");
|
105 | error.request = stripRequest(this._pollState.mostRecentRequest);
|
106 | error.response = this._pollState.mostRecentResponse;
|
107 | error.message = `Long running operation failed with status: "${this._pollState.state}".`;
|
108 | error.body = this._pollState.resource;
|
109 | if (error.body) {
|
110 | const innerError: any = error.body.error;
|
111 | if (innerError) {
|
112 | if (innerError.message) {
|
113 | error.message = `Long running operation failed with error: "${innerError.message}".`;
|
114 | }
|
115 | if (innerError.code) {
|
116 | error.code = innerError.code;
|
117 | }
|
118 | }
|
119 | }
|
120 | return error;
|
121 | }
|
122 |
|
123 | protected updateState(url: string, shouldDeserialize: boolean | ((response: HttpOperationResponse) => boolean)): Promise<void> {
|
124 | return this.updateOperationStatus(url, shouldDeserialize).then(result => {
|
125 | this._pollState.state = getProvisioningState(result.parsedBody) || "Succeeded";
|
126 | this._pollState.mostRecentResponse = result;
|
127 | this._pollState.mostRecentRequest = result.request;
|
128 | this._pollState.resource = getResponseBody(result);
|
129 | }).catch((error) => {
|
130 | let resultStatus: number | undefined;
|
131 | if (error.response && error.response.status) {
|
132 | resultStatus = error.response.status;
|
133 | if (this._pollState.initialResponse.request.method !== "DELETE" || resultStatus! < 400 || 499 < resultStatus!) {
|
134 | throw error;
|
135 | }
|
136 | } else {
|
137 | throw error;
|
138 | }
|
139 | });
|
140 | }
|
141 |
|
142 | /**
|
143 | * Retrieves operation status by querying the operation URL.
|
144 | * @param {string} statusUrl URL used to poll operation result.
|
145 | */
|
146 | protected updateOperationStatus(statusUrl: string, shouldDeserialize: boolean | ((response: HttpOperationResponse) => boolean)): Promise<HttpOperationResponse> {
|
147 | const requestUrl: string = statusUrl.replace(" ", "%20");
|
148 | const httpRequest = new WebResource(requestUrl, "GET");
|
149 | const pollState: LROPollState = this._pollState;
|
150 | httpRequest.operationSpec = pollState.mostRecentRequest.operationSpec;
|
151 | httpRequest.shouldDeserialize = shouldDeserialize;
|
152 | httpRequest.operationResponseGetter = getOperationResponse;
|
153 | const options: RequestOptionsBase | undefined = pollState.options;
|
154 | if (options && options.customHeaders) {
|
155 | const customHeaders = options.customHeaders;
|
156 | for (const headerName of Object.keys(customHeaders)) {
|
157 | httpRequest.headers.set(headerName, customHeaders[headerName]);
|
158 | }
|
159 | }
|
160 | return this._azureServiceClient.sendRequest(httpRequest);
|
161 | }
|
162 |
|
163 | public getPollState(): LROPollState {
|
164 | return this._pollState;
|
165 | }
|
166 | }
|
167 |
|
168 | function getOperationResponse(operationSpec: OperationSpec, response: HttpOperationResponse): OperationResponse | undefined {
|
169 | const statusCode: number = response.status;
|
170 | const operationResponses: { [statusCode: string]: OperationResponse } = operationSpec.responses;
|
171 | let result: OperationResponse | undefined = operationResponses[statusCode];
|
172 | if (!result) {
|
173 | if (statusCode === 200) {
|
174 | result = operationResponses[201] || operationResponses[202];
|
175 | } else if (201 <= statusCode && statusCode <= 299) {
|
176 | result = {};
|
177 | }
|
178 | }
|
179 | return result;
|
180 | }
|
181 |
|
182 | export function getDelayInSeconds(azureServiceClient: AzureServiceClient, previousResponse: HttpOperationResponse): number {
|
183 | let delayInSeconds = 30;
|
184 | if (azureServiceClient.longRunningOperationRetryTimeout != undefined) {
|
185 | delayInSeconds = azureServiceClient.longRunningOperationRetryTimeout;
|
186 | } else {
|
187 | const retryAfterHeaderValue: string | undefined = previousResponse.headers.get("retry-after");
|
188 | if (retryAfterHeaderValue) {
|
189 | const retryAfterDelayInSeconds: number = parseInt(retryAfterHeaderValue);
|
190 | if (!Number.isNaN(retryAfterDelayInSeconds)) {
|
191 | delayInSeconds = retryAfterDelayInSeconds;
|
192 | }
|
193 | }
|
194 | }
|
195 | return delayInSeconds;
|
196 | }
|
197 |
|
198 | function getProvisioningState(responseBody: any): LongRunningOperationStates | undefined {
|
199 | let result: LongRunningOperationStates | undefined;
|
200 | if (responseBody) {
|
201 | if (responseBody.provisioningState) {
|
202 | result = responseBody.provisioningState;
|
203 | } else if (responseBody.properties) {
|
204 | result = responseBody.properties.provisioningState;
|
205 | }
|
206 | }
|
207 | return result;
|
208 | }
|
209 |
|
210 | function getResponseBody(response: HttpOperationResponse): any {
|
211 | let result: any;
|
212 | try {
|
213 | if (response.parsedBody) {
|
214 | result = response.parsedBody;
|
215 | } else if (response.bodyAsText && response.bodyAsText.length > 0) {
|
216 | result = JSON.parse(response.bodyAsText);
|
217 | }
|
218 | } catch (error) {
|
219 | const deserializationError = new RestError(`Error "${error}" occurred in parsing the responseBody " +
|
220 | "while creating the PollingState for Long Running Operation- "${response.bodyAsText}"`);
|
221 | deserializationError.request = response.request;
|
222 | deserializationError.response = response;
|
223 | throw deserializationError;
|
224 | }
|
225 | return result;
|
226 | }
|
227 |
|
228 | function getStatusFromResponse(response: HttpOperationResponse, responseBody?: any): LongRunningOperationStates {
|
229 | if (responseBody == undefined) {
|
230 | responseBody = getResponseBody(response);
|
231 | }
|
232 |
|
233 | let result: LongRunningOperationStates;
|
234 | switch (response.status) {
|
235 | case 202:
|
236 | result = "InProgress";
|
237 | break;
|
238 |
|
239 | case 204:
|
240 | result = "Succeeded";
|
241 | break;
|
242 |
|
243 | case 201:
|
244 | result = getProvisioningState(responseBody) || "InProgress";
|
245 | break;
|
246 |
|
247 | case 200:
|
248 | const provisioningState: LongRunningOperationStates | undefined = getProvisioningState(responseBody);
|
249 | if (provisioningState) {
|
250 | result = provisioningState;
|
251 | } else if (getAzureAsyncOperationHeaderValue(response) || getLocationHeaderValue(response)) {
|
252 | result = "InProgress";
|
253 | } else {
|
254 | result = "Succeeded";
|
255 | }
|
256 | break;
|
257 |
|
258 | default:
|
259 | result = "Failed";
|
260 | break;
|
261 | }
|
262 | return result;
|
263 | }
|
264 |
|
265 | const terminalStates: LongRunningOperationStates[] = ["Succeeded", "Failed", "Canceled", "Cancelled"];
|
266 |
|
267 | /**
|
268 | * Get whether or not a long-running operation with the provided status is finished.
|
269 | * @param status The current status of a long-running operation.
|
270 | * @returns Whether or not a long-running operation with the provided status is finished.
|
271 | */
|
272 | export function isFinished(status: LongRunningOperationStates): boolean {
|
273 | let result = false;
|
274 | for (const terminalState of terminalStates) {
|
275 | if (longRunningOperationStatesEqual(status, terminalState)) {
|
276 | result = true;
|
277 | break;
|
278 | }
|
279 | }
|
280 | return result;
|
281 | }
|
282 |
|
283 | export function longRunningOperationStatesEqual(lhs: LongRunningOperationStates, rhs: LongRunningOperationStates): boolean {
|
284 | const lhsLowerCased: string = lhs && lhs.toLowerCase();
|
285 | const rhsLowerCased: string = rhs && rhs.toLowerCase();
|
286 | return lhsLowerCased === rhsLowerCased;
|
287 | }
|
288 |
|
289 | /**
|
290 | * Create a new long-running operation polling strategy based on the provided initial response.
|
291 | * @param initialResponse The initial response to the long-running operation's initial request.
|
292 | * @param azureServiceClient The AzureServiceClient that was used to send the initial request.
|
293 | * @param options Any options that were provided to the initial request.
|
294 | */
|
295 | export function createLROPollStrategyFromInitialResponse(initialResponse: HttpOperationResponse, azureServiceClient: AzureServiceClient, options?: RequestOptionsBase): LROPollStrategy | undefined {
|
296 | const initialRequestMethod: HttpMethods = initialResponse.request.method;
|
297 | const initialResponseStatus: number = initialResponse.status;
|
298 |
|
299 | let lroPollStrategyType: LROPollStrategyType | undefined;
|
300 | if (getAzureAsyncOperationHeaderValue(initialResponse)) {
|
301 | lroPollStrategyType = "AzureAsyncOperation";
|
302 | } else if (getLocationHeaderValue(initialResponse)) {
|
303 | lroPollStrategyType = "Location";
|
304 | } else if (initialRequestMethod === "PUT" || initialRequestMethod === "PATCH") {
|
305 | lroPollStrategyType = "GetResource";
|
306 | } else if (initialResponseStatus !== 201 && initialResponseStatus !== 202 && !isFinished(getStatusFromResponse(initialResponse))) {
|
307 | throw new Error("Can't determine long running operation polling strategy.");
|
308 | }
|
309 |
|
310 | let result: LROPollStrategy | undefined;
|
311 | if (lroPollStrategyType) {
|
312 | const resource: any = getResponseBody(initialResponse);
|
313 | const lroPollState: LROPollState = {
|
314 | pollStrategyType: lroPollStrategyType,
|
315 | options: options,
|
316 | initialResponse: initialResponse,
|
317 | mostRecentResponse: initialResponse,
|
318 | mostRecentRequest: initialResponse.request,
|
319 | azureAsyncOperationHeaderValue: getAzureAsyncOperationHeaderValue(initialResponse),
|
320 | locationHeaderValue: getLocationHeaderValue(initialResponse),
|
321 | resource: resource,
|
322 | state: getStatusFromResponse(initialResponse, resource)
|
323 | };
|
324 | result = createLROPollStrategyFromPollState(azureServiceClient, lroPollState);
|
325 | } else {
|
326 | result = undefined;
|
327 | }
|
328 | return result;
|
329 | }
|
330 |
|
331 | export function createLROPollStrategyFromPollState(azureServiceClient: AzureServiceClient, lroPollState: LROPollState): LROPollStrategy | undefined {
|
332 | let result: LROPollStrategy;
|
333 | switch (lroPollState.pollStrategyType) {
|
334 | case "AzureAsyncOperation":
|
335 | result = new AzureAsyncOperationLROPollStrategy(azureServiceClient, lroPollState);
|
336 | break;
|
337 |
|
338 | case "Location":
|
339 | result = new LocationLROPollStrategy(azureServiceClient, lroPollState);
|
340 | break;
|
341 |
|
342 | case "GetResource":
|
343 | result = new GetResourceLROPollStrategy(azureServiceClient, lroPollState);
|
344 | break;
|
345 |
|
346 | default:
|
347 | throw new Error(`Unrecognized LRO poll strategy type: "${lroPollState.pollStrategyType}"`);
|
348 | break;
|
349 | }
|
350 | return result;
|
351 | }
|
352 |
|
353 | function getLocationHeaderValue(response: HttpOperationResponse): string | undefined {
|
354 | return response.headers.get("location");
|
355 | }
|
356 |
|
357 | /**
|
358 | * A long-running operation polling strategy that is based on the location header.
|
359 | */
|
360 | class LocationLROPollStrategy extends LROPollStrategy {
|
361 | private locationStrategyShouldDeserialize(parsedResponse: HttpOperationResponse): boolean {
|
362 | let shouldDeserialize = false;
|
363 |
|
364 | const initialResponse: HttpOperationResponse = this._pollState.initialResponse;
|
365 | const initialRequestMethod: HttpMethods = initialResponse.request.method;
|
366 | const statusCode: number = parsedResponse.status;
|
367 | if (statusCode === 200 ||
|
368 | (statusCode === 201 && (initialRequestMethod === "PUT" || initialRequestMethod === "PATCH")) ||
|
369 | (statusCode === 204 && (initialRequestMethod === "DELETE" || initialRequestMethod === "POST"))) {
|
370 | shouldDeserialize = true;
|
371 | }
|
372 |
|
373 | return shouldDeserialize;
|
374 | }
|
375 | /**
|
376 | * Retrieve PUT operation status by polling from "location" header.
|
377 | * @param {string} method - The HTTP method.
|
378 | * @param {PollingState} pollingState - The object to persist current operation state.
|
379 | */
|
380 | public sendPollRequest(): Promise<void> {
|
381 | const lroPollState: LROPollState = this._pollState;
|
382 | return this.updateOperationStatus(lroPollState.locationHeaderValue!, this.locationStrategyShouldDeserialize.bind(this)).then((result: HttpOperationResponse) => {
|
383 | const locationHeaderValue: string | undefined = getLocationHeaderValue(result);
|
384 | if (locationHeaderValue) {
|
385 | lroPollState.locationHeaderValue = locationHeaderValue;
|
386 | }
|
387 |
|
388 | lroPollState.mostRecentResponse = result;
|
389 | lroPollState.mostRecentRequest = result.request;
|
390 |
|
391 | const initialResponse: HttpOperationResponse = lroPollState.initialResponse;
|
392 | const initialRequestMethod: HttpMethods = initialResponse.request.method;
|
393 | const initialResponseStatusCode: number = initialResponse.status;
|
394 | const statusCode: number = result.status;
|
395 | if (statusCode === 202) {
|
396 | lroPollState.state = "InProgress";
|
397 | } else if (statusCode === 200 ||
|
398 | (statusCode === 201 && (initialRequestMethod === "PUT" || initialRequestMethod === "PATCH")) ||
|
399 | (statusCode === 204 && (initialRequestMethod === "DELETE" || initialRequestMethod === "POST"))) {
|
400 | lroPollState.state = "Succeeded";
|
401 | lroPollState.resource = getResponseBody(result);
|
402 | } else if (statusCode === 404 && initialRequestMethod === "POST" &&
|
403 | (initialResponseStatusCode === 200 || initialResponseStatusCode === 201 || initialResponseStatusCode === 202)) {
|
404 | lroPollState.state = "Failed";
|
405 | lroPollState.resource = getResponseBody(result);
|
406 | } else if (400 <= statusCode && statusCode <= 499) {
|
407 | const resultBody: string = result.bodyAsText!;
|
408 | let errorMessage: string = resultBody;
|
409 | try {
|
410 | const resultObject = JSON.parse(resultBody);
|
411 | errorMessage = resultObject.message;
|
412 | } catch (parseError) {
|
413 |
|
414 | }
|
415 |
|
416 | throw new RestError(errorMessage, undefined, statusCode, stripRequest(result.request), result, resultBody);
|
417 | } else {
|
418 | throw new Error(`The response with status code ${statusCode} from polling for long running operation url "${lroPollState.locationHeaderValue}" is not valid.`);
|
419 | }
|
420 | });
|
421 | }
|
422 |
|
423 | public isFinalStatusAcceptable(): boolean {
|
424 | const lroPollState: LROPollState = this._pollState;
|
425 | const initialResponse: HttpOperationResponse = lroPollState.initialResponse;
|
426 | const initialResponseStatusCode: number = initialResponse.status;
|
427 | return longRunningOperationStatesEqual(lroPollState.state, "Succeeded") ||
|
428 | (initialResponse.request.method === "POST" && lroPollState.mostRecentResponse.status === 404 &&
|
429 | (initialResponseStatusCode === 200 ||
|
430 | initialResponseStatusCode === 201 ||
|
431 | initialResponseStatusCode === 202));
|
432 | }
|
433 |
|
434 | protected shouldDoFinalGetResourceRequest(): boolean {
|
435 | const lroPollState: LROPollState = this._pollState;
|
436 | const initialResponse: HttpOperationResponse = lroPollState.initialResponse;
|
437 | let result: boolean;
|
438 | const initialRequestMethod: HttpMethods = initialResponse.request.method;
|
439 | const initialResponseStatusCode: number = initialResponse.status;
|
440 | if (initialRequestMethod === "POST" && lroPollState.mostRecentResponse.status === 404 &&
|
441 | (initialResponseStatusCode === 200 ||
|
442 | initialResponseStatusCode === 201 ||
|
443 | initialResponseStatusCode === 202)) {
|
444 | result = false;
|
445 | } else {
|
446 | result = super.shouldDoFinalGetResourceRequest() ||
|
447 | (initialRequestMethod === "POST" && initialResponseStatusCode === 201);
|
448 | }
|
449 | return result;
|
450 | }
|
451 |
|
452 | protected doFinalGetResourceRequest(): Promise<void> {
|
453 | const lroPollState: LROPollState = this._pollState;
|
454 | const initialResponse: HttpOperationResponse = lroPollState.initialResponse;
|
455 | let getResourceRequestUrl: string;
|
456 | const initialResponseStatusCode: number = initialResponse.status;
|
457 | const initialRequest: WebResource = initialResponse.request;
|
458 | if (initialRequest.method === "POST" &&
|
459 | (initialResponseStatusCode === 200 ||
|
460 | initialResponseStatusCode === 201 ||
|
461 | initialResponseStatusCode === 202)) {
|
462 | getResourceRequestUrl = lroPollState.locationHeaderValue!;
|
463 | } else {
|
464 | getResourceRequestUrl = initialRequest.url;
|
465 | }
|
466 | return this.updateState(getResourceRequestUrl, true);
|
467 | }
|
468 | }
|
469 |
|
470 | function getAzureAsyncOperationHeaderValue(response: HttpOperationResponse): string | undefined {
|
471 | return response.headers.get("azure-asyncoperation");
|
472 | }
|
473 |
|
474 | /**
|
475 | * A long-running operation polling strategy that is based on the azure-asyncoperation header.
|
476 | */
|
477 | class AzureAsyncOperationLROPollStrategy extends LROPollStrategy {
|
478 | /**
|
479 | * Retrieve operation status by polling from "azure-asyncoperation" header.
|
480 | * @param {PollingState} pollingState - The object to persist current operation state.
|
481 | * @param {boolean} inPostOrDelete - Invoked by Post Or Delete operation.
|
482 | */
|
483 | public sendPollRequest(): Promise<void> {
|
484 | const lroPollState: LROPollState = this._pollState;
|
485 | return this.updateOperationStatus(lroPollState.azureAsyncOperationHeaderValue!, false).then((response: HttpOperationResponse) => {
|
486 | const statusCode: number = response.status;
|
487 | const parsedResponse: any = response.parsedBody;
|
488 | if (statusCode !== 200 && statusCode !== 201 && statusCode !== 202 && statusCode !== 204) {
|
489 | const error = new RestError(`Invalid status code (${statusCode}) with response body "${response.bodyAsText}" occurred when polling for operation status.`);
|
490 | error.statusCode = statusCode;
|
491 | error.request = stripRequest(response.request);
|
492 | error.response = response;
|
493 | error.body = parsedResponse;
|
494 | throw error;
|
495 | }
|
496 |
|
497 | if (!parsedResponse) {
|
498 | throw new Error("The response from long running operation does not contain a body.");
|
499 | } else if (!parsedResponse.status) {
|
500 | throw new Error(`The response "${response.bodyAsText}" from long running operation does not contain the status property.`);
|
501 | }
|
502 |
|
503 | const azureAsyncOperationHeaderValue: string | undefined = getAzureAsyncOperationHeaderValue(response);
|
504 | if (azureAsyncOperationHeaderValue) {
|
505 | lroPollState.azureAsyncOperationHeaderValue = azureAsyncOperationHeaderValue;
|
506 | }
|
507 |
|
508 | lroPollState.state = parsedResponse.status;
|
509 | lroPollState.mostRecentResponse = response;
|
510 | lroPollState.mostRecentRequest = response.request;
|
511 | lroPollState.resource = getResponseBody(response);
|
512 | });
|
513 | }
|
514 |
|
515 | protected shouldDoFinalGetResourceRequest(): boolean {
|
516 | const lroPollState: LROPollState = this._pollState;
|
517 | const initialResponse: HttpOperationResponse = lroPollState.initialResponse;
|
518 | const initialRequestMethod: HttpMethods = initialResponse.request.method;
|
519 | let result = false;
|
520 | if (initialRequestMethod === "PUT" || initialRequestMethod === "PATCH") {
|
521 | result = true;
|
522 | } else {
|
523 | if (lroPollState.locationHeaderValue) {
|
524 | const initialResponseStatusCode: number = initialResponse.status;
|
525 | if (initialRequestMethod === "POST") {
|
526 | result = initialResponseStatusCode === 200 || initialResponseStatusCode === 201;
|
527 | } else if (initialRequestMethod === "DELETE") {
|
528 | result = initialResponseStatusCode === 200 || initialResponseStatusCode === 202;
|
529 | }
|
530 | }
|
531 | }
|
532 | return result;
|
533 | }
|
534 |
|
535 | protected doFinalGetResourceRequest(): Promise<void> {
|
536 | const lroPollState: LROPollState = this._pollState;
|
537 | const locationHeaderValue: string | undefined = lroPollState.locationHeaderValue;
|
538 | const initialResponse: HttpOperationResponse = lroPollState.initialResponse;
|
539 | const initialRequest: WebResource = initialResponse.request;
|
540 | let getResourceRequestUrl: string = initialRequest.url;
|
541 | if (locationHeaderValue) {
|
542 | const initialRequestMethod: HttpMethods = initialRequest.method;
|
543 | const initialResponseStatusCode: number = initialResponse.status;
|
544 | if (initialRequestMethod === "POST" && (initialResponseStatusCode === 200 || initialResponseStatusCode === 201 || initialResponseStatusCode === 202)) {
|
545 | getResourceRequestUrl = locationHeaderValue;
|
546 | } else if (initialRequestMethod === "DELETE" && (initialResponseStatusCode === 200 || initialResponseStatusCode === 202)) {
|
547 | getResourceRequestUrl = locationHeaderValue;
|
548 | }
|
549 | }
|
550 | return this.updateState(getResourceRequestUrl, true);
|
551 | }
|
552 |
|
553 | public isFinalStatusAcceptable(): boolean {
|
554 | const lroPollState: LROPollState = this._pollState;
|
555 | const initialResponse: HttpOperationResponse = lroPollState.initialResponse;
|
556 | const initialResponseStatusCode: number = initialResponse.status;
|
557 | return longRunningOperationStatesEqual(lroPollState.state, "Succeeded") ||
|
558 | (initialResponse.request.method === "POST" && (initialResponseStatusCode === 200 || initialResponseStatusCode === 201));
|
559 | }
|
560 | }
|
561 |
|
562 | /**
|
563 | * A long-running operation polling strategy that is based on the resource's provisioning state.
|
564 | */
|
565 | class GetResourceLROPollStrategy extends LROPollStrategy {
|
566 | public sendPollRequest(): Promise<void> {
|
567 | const lroPollState: LROPollState = this._pollState;
|
568 | return this.updateOperationStatus(lroPollState.initialResponse.request.url, false).then(result => {
|
569 | const statusCode: number = result.status;
|
570 | const responseBody: any = result.parsedBody;
|
571 | if (statusCode !== 200 && statusCode !== 201 && statusCode !== 202 && statusCode !== 204) {
|
572 | const error = new RestError(`Invalid status code with response body "${result.bodyAsText}" occurred when polling for operation status.`);
|
573 | error.statusCode = statusCode;
|
574 | error.request = stripRequest(result.request);
|
575 | error.response = result;
|
576 | error.body = responseBody;
|
577 | throw error;
|
578 | }
|
579 |
|
580 | if (!result.parsedBody) {
|
581 | throw new Error("The response from long running operation does not contain a body.");
|
582 | }
|
583 |
|
584 | lroPollState.state = getProvisioningState(result.parsedBody) || "Succeeded";
|
585 | lroPollState.mostRecentResponse = result;
|
586 | lroPollState.mostRecentRequest = result.request;
|
587 | lroPollState.resource = getResponseBody(result);
|
588 | });
|
589 | }
|
590 |
|
591 | public isFinalStatusAcceptable(): boolean {
|
592 | return longRunningOperationStatesEqual(this._pollState.state, "Succeeded");
|
593 | }
|
594 |
|
595 | protected doFinalGetResourceRequest(): Promise<void> {
|
596 | return this.sendPollRequest();
|
597 | }
|
598 | }
|
599 |
|
\ | No newline at end of file |