Skip to content

Commit 8d1ffd4

Browse files
committed
✨ Add redirect cache
Caches redirects and proactively performs redirection to avoid 301 Redirect calls, which consume REST API calls excessively and unnecessarily
1 parent 36ab4d8 commit 8d1ffd4

File tree

3 files changed

+84
-18
lines changed

3 files changed

+84
-18
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ This extension is a fork of the official [GitHub Actions Extension](https://gith
99
- Icons updated to use codicons and the testing icon set, making them themable
1010
- Revamped logging, much more detailed as to what is going on
1111

12-
1312
## Why a fork?
1413
I initially maintained a parallel fork and went to a lot of work to make upstream changes independent and very mergeable. After a year of the GitHub team completely ignoring me and other PRs, it is clear the extension is in security maintenance mode, with only package dependency bumps being added.
1514

src/api/api.ts

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1-
import { retry } from "@octokit/plugin-retry"
2-
import { throttling } from "@octokit/plugin-throttling"
1+
// import { retry } from "@octokit/plugin-retry"
2+
// import { throttling } from "@octokit/plugin-throttling"
33
import { Octokit } from "@octokit/rest"
44
import { version } from "package.json"
55

66
import { conditionalRequest } from "~/api/conditionalRequests"
77
import { getGitHubApiUri } from "~/configuration/configReader"
88
import { createOctokitLogger } from "~/log"
99

10+
import { cacheRedirect } from "./handlePermanentRedirect"
11+
// import { rateLimitTelemetryPlugin } from "./rateLimitTelemetry";
12+
1013
export const userAgent = `VS Code GitHub Actions (${version})`
1114

12-
const GhaOctokit = Octokit.plugin(conditionalRequest, throttling, retry)
15+
const GhaOctokit = Octokit.plugin(cacheRedirect, conditionalRequest)
1316
export type GhaOctokit = InstanceType<typeof GhaOctokit>
1417

1518
export function getClient(token: string) {
@@ -18,20 +21,23 @@ export function getClient(token: string) {
1821
log: createOctokitLogger(),
1922
userAgent: userAgent,
2023
baseUrl: getGitHubApiUri(),
21-
throttle: {
22-
onRateLimit: (retryAfter, options, octokit) => {
23-
octokit.log.warn(`Request quota exhausted for request ${options.method} ${options.url}.`)
24+
request: {
25+
redirect: "manual"
26+
}
27+
// throttle: {
28+
// onRateLimit: (retryAfter, options, octokit) => {
29+
// octokit.log.warn(`Request quota exhausted for request ${options.method} ${options.url}.`)
2430

25-
if (options.request.retryCount === 0) {
26-
octokit.log.info(`Retrying after ${retryAfter} seconds.`)
27-
return true
28-
}
29-
},
30-
onSecondaryRateLimit: (retryAfter, options, octokit) => {
31-
octokit.log.warn(
32-
`Abuse detected for request ${options.method} ${options.url}. Retrying after ${retryAfter} seconds.`,
33-
)
34-
},
35-
},
31+
// if (options.request.retryCount === 0) {
32+
// octokit.log.info(`Retrying after ${retryAfter} seconds.`)
33+
// return true
34+
// }
35+
// },
36+
// onSecondaryRateLimit: (retryAfter, options, octokit) => {
37+
// octokit.log.warn(
38+
// `Abuse detected for request ${options.method} ${options.url}. Retrying after ${retryAfter} seconds.`,
39+
// )
40+
// },
41+
// },
3642
})
3743
}

src/api/handlePermanentRedirect.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { OctokitPlugin } from "@octokit/core/types"
2+
3+
import { logTrace } from "~/log"
4+
5+
// HTTP status code for permanent redirect
6+
const HTTP_PERMANENT_REDIRECT = 301
7+
8+
// Map to store permanent redirects. Key is original URL, value is new URL
9+
const redirectCache = new Map<string, string>()
10+
11+
/**
12+
* Octokit plugin that caches 301 Moved Permanently redirects and automatically
13+
* applies them to subsequent requests to the same URL. 301 redirects count against the REST API limit so we want to minimize them by caching the new location and transparently applying it to future requests.
14+
*
15+
* This plugin uses octokit.hook.wrap to intercept all requests and:
16+
* 1. Checks if the current URL has a cached redirect before requesting
17+
* 2. Applies cached redirects transparently
18+
* 3. On 301 responses, caches the new location and retries the request
19+
*
20+
* @param octokit - The Octokit instance to wrap with redirect handling
21+
*
22+
* @link https://octokit.github.io/rest.js/v22/#plugins
23+
*/
24+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
25+
export const cacheRedirect: OctokitPlugin = (octokit) => {
26+
// hook into the request lifecycle
27+
octokit.hook.wrap("request", async (request, options) => {
28+
const repoCacheKey = [options.owner || undefined, options.repo || undefined, options.url].join("-")
29+
30+
const redirectCacheKey = [repoCacheKey, options.url].join("-")
31+
const redirectCacheHit = redirectCache.get(redirectCacheKey)
32+
if (redirectCacheHit) {
33+
octokit.log.debug(`Redirect cache hit for ${options.url}${redirectCacheHit} [${redirectCacheKey}]`)
34+
options.baseUrl = ""
35+
options.url = redirectCacheHit
36+
}
37+
38+
let response: any = await request(options)
39+
if (response.status === HTTP_PERMANENT_REDIRECT) {
40+
const locationHeader = response.headers["location"]
41+
42+
if (locationHeader) {
43+
redirectCache.set(redirectCacheKey, locationHeader)
44+
octokit.log.info(
45+
`↩️ Permanent Redirect Detected and Cached: ${options.url}${locationHeader} [${redirectCacheKey}]`,
46+
)
47+
options.url = locationHeader
48+
response = await request(options)
49+
}
50+
}
51+
return response
52+
})
53+
}
54+
55+
/**
56+
* Clears all cached redirects. Useful for testing or resetting state.
57+
*/
58+
export function clearRedirectCache(): void {
59+
redirectCache.clear()
60+
logTrace("🗑️ Redirect cache cleared")
61+
}

0 commit comments

Comments
 (0)