UNPKG

3.36 kBPlain TextView Raw
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
4import { HttpOperationResponse } from "../httpOperationResponse";
5import { URLBuilder } from "../url";
6import { WebResourceLike } from "../webResource";
7import {
8 BaseRequestPolicy,
9 RequestPolicy,
10 RequestPolicyFactory,
11 RequestPolicyOptionsLike,
12} from "./requestPolicy";
13
14/**
15 * Options for how redirect responses are handled.
16 */
17export interface RedirectOptions {
18 /*
19 * When true, redirect responses are followed. Defaults to true.
20 */
21 handleRedirects: boolean;
22
23 /*
24 * The maximum number of times the redirect URL will be tried before
25 * failing. Defaults to 20.
26 */
27 maxRetries?: number;
28}
29
30export const DefaultRedirectOptions: RedirectOptions = {
31 handleRedirects: true,
32 maxRetries: 20,
33};
34
35export function redirectPolicy(maximumRetries = 20): RequestPolicyFactory {
36 return {
37 create: (nextPolicy: RequestPolicy, options: RequestPolicyOptionsLike) => {
38 return new RedirectPolicy(nextPolicy, options, maximumRetries);
39 },
40 };
41}
42
43export class RedirectPolicy extends BaseRequestPolicy {
44 constructor(
45 nextPolicy: RequestPolicy,
46 options: RequestPolicyOptionsLike,
47 readonly maxRetries = 20
48 ) {
49 super(nextPolicy, options);
50 }
51
52 public sendRequest(request: WebResourceLike): Promise<HttpOperationResponse> {
53 return this._nextPolicy
54 .sendRequest(request)
55 .then((response) => handleRedirect(this, response, 0));
56 }
57}
58
59function handleRedirect(
60 policy: RedirectPolicy,
61 response: HttpOperationResponse,
62 currentRetries: number
63): Promise<HttpOperationResponse> {
64 const { request, status } = response;
65 const locationHeader = response.headers.get("location");
66 if (
67 locationHeader &&
68 (status === 300 ||
69 (status === 301 && ["GET", "HEAD"].includes(request.method)) ||
70 (status === 302 && ["GET", "POST", "HEAD"].includes(request.method)) ||
71 (status === 303 && "POST" === request.method) ||
72 status === 307) &&
73 ((request.redirectLimit !== undefined && currentRetries < request.redirectLimit) ||
74 (request.redirectLimit === undefined && currentRetries < policy.maxRetries))
75 ) {
76 const builder = URLBuilder.parse(request.url);
77 builder.setPath(locationHeader);
78 request.url = builder.toString();
79
80 // POST request with Status code 302 and 303 should be converted into a
81 // redirected GET request if the redirect url is present in the location header
82 // reference: https://tools.ietf.org/html/rfc7231#page-57 && https://fetch.spec.whatwg.org/#http-redirect-fetch
83 if ((status === 302 || status === 303) && request.method === "POST") {
84 request.method = "GET";
85 delete request.body;
86 }
87
88 return policy._nextPolicy
89 .sendRequest(request)
90 .then((res) => handleRedirect(policy, res, currentRetries + 1))
91 .then((res) => recordRedirect(res, request.url));
92 }
93
94 return Promise.resolve(response);
95}
96
97function recordRedirect(response: HttpOperationResponse, redirect: string): HttpOperationResponse {
98 // This is called as the recursive calls to handleRedirect() unwind,
99 // only record the deepest/last redirect
100 if (!response.redirected) {
101 response.redirected = true;
102 response.url = redirect;
103 }
104 return response;
105}