Skip to content

Commit 39f5c01

Browse files
Update undici to 8.5.0 (bluesky-social#5116)
* Update undici to 8.5.0 * tidy * tidy * fix lock file
1 parent abab1ca commit 39f5c01

11 files changed

Lines changed: 84 additions & 62 deletions

File tree

.changeset/cyan-loops-swim.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@atproto-labs/fetch-node': patch
3+
'@atproto/dev-env': patch
4+
'@atproto/ozone': patch
5+
'@atproto/bsky': patch
6+
'@atproto/pds': patch
7+
---
8+
9+
Update undici dependency to v8.5.0

packages/bsky/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
"structured-headers": "^1.0.1",
7171
"typed-emitter": "^2.1.0",
7272
"uint8arrays": "^5.0.0",
73-
"undici": "^6.19.8",
73+
"undici": "^8.5.0",
7474
"zod": "3.23.8"
7575
},
7676
"devDependencies": {

packages/bsky/src/api/blob-dispatcher.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Agent, Dispatcher, Pool, RetryAgent } from 'undici'
1+
import { Agent, Dispatcher, Pool, interceptors } from 'undici'
22
import { isUnicastIp, unicastLookup } from '@atproto-labs/fetch-node'
33
import { ServerConfig } from '../config.js'
44
import { RETRYABLE_HTTP_STATUS_CODES } from '../util/retry.js'
@@ -27,11 +27,15 @@ export function createBlobDispatcher(cfg: ServerConfig): Dispatcher {
2727
},
2828
})
2929

30-
return cfg.proxyMaxRetries > 0
31-
? new RetryAgent(baseDispatcher, {
32-
statusCodes: [...RETRYABLE_HTTP_STATUS_CODES],
33-
methods: ['GET', 'HEAD'],
34-
maxRetries: cfg.proxyMaxRetries,
35-
})
36-
: baseDispatcher
30+
return baseDispatcher.compose(
31+
interceptors.redirect({
32+
maxRedirections: 10,
33+
throwOnMaxRedirect: true,
34+
}),
35+
interceptors.retry({
36+
statusCodes: [...RETRYABLE_HTTP_STATUS_CODES],
37+
methods: ['GET', 'HEAD'],
38+
maxRetries: cfg.proxyMaxRetries,
39+
}),
40+
)
3741
}

packages/bsky/src/api/blob-resolver.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,6 @@ export async function streamBlob(
197197
path: url.pathname + url.search,
198198
headers,
199199
signal: options.signal,
200-
maxRedirections: 10,
201200
},
202201
(upstream) => {
203202
headersReceived = true

packages/dev-env/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"get-port": "^5.1.1",
4343
"multiformats": "^13.0.0",
4444
"uint8arrays": "^5.0.0",
45-
"undici": "^6.14.1"
45+
"undici": "^8.5.0"
4646
},
4747
"devDependencies": {
4848
"@types/express": "^4.17.13"

packages/internal/fetch-node/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"@atproto-labs/fetch": "workspace:^",
2929
"@atproto-labs/pipe": "workspace:^",
3030
"ipaddr.js": "^2.1.0",
31-
"undici": "^6.14.1"
31+
"undici": "^8.5.0"
3232
},
3333
"devDependencies": {},
3434
"scripts": {

packages/ozone/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
"structured-headers": "^1.0.1",
5151
"typed-emitter": "^2.1.0",
5252
"uint8arrays": "^5.0.0",
53-
"undici": "^6.14.1",
53+
"undici": "^8.5.0",
5454
"ws": "^8.12.0"
5555
},
5656
"devDependencies": {

packages/pds/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
"pino-http": "^8.2.1",
7575
"typed-emitter": "^2.1.0",
7676
"uint8arrays": "^5.0.0",
77-
"undici": "^6.19.8",
77+
"undici": "^8.5.0",
7878
"zod": "^3.23.8"
7979
},
8080
"devDependencies": {

packages/pds/src/context.ts

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,7 @@ import {
2121
createServiceAuthHeaders,
2222
createServiceJwt,
2323
} from '@atproto/xrpc-server'
24-
import {
25-
Fetch,
26-
isUnicastIp,
27-
safeFetchWrap,
28-
unicastLookup,
29-
} from '@atproto-labs/fetch-node'
24+
import { Fetch, safeFetchWrap } from '@atproto-labs/fetch-node'
3025
import { AccountManager } from './account-manager/account-manager.js'
3126
import { OAuthStore } from './account-manager/oauth-store.js'
3227
import { ScopeReferenceGetter } from './account-manager/scope-reference-getter.js'
@@ -47,6 +42,7 @@ import { ImageUrlBuilder } from './image/image-url-builder.js'
4742
import { fetchLogger, lexiconResolverLogger, oauthLogger } from './logger.js'
4843
import { ServerMailer } from './mailer/index.js'
4944
import { ModerationMailer } from './mailer/moderation.js'
45+
import { buildProxyAgent } from './pipethrough.js'
5046
import { LocalViewer, LocalViewerCreator } from './read-after-write/viewer.js'
5147
import { getRedisClient } from './redis.js'
5248
import { Sequencer } from './sequencer/index.js'
@@ -295,36 +291,7 @@ export class AppContext {
295291
)
296292

297293
// An agent for performing HTTP requests based on user provided URLs.
298-
const proxyAgentBase = new undici.Agent({
299-
allowH2: cfg.proxy.allowHTTP2, // This is experimental
300-
headersTimeout: cfg.proxy.headersTimeout,
301-
maxResponseSize: cfg.proxy.maxResponseSize,
302-
bodyTimeout: cfg.proxy.bodyTimeout,
303-
factory: cfg.proxy.disableSsrfProtection
304-
? undefined
305-
: (origin, opts) => {
306-
const { protocol, hostname } =
307-
origin instanceof URL ? origin : new URL(origin)
308-
if (protocol !== 'https:') {
309-
throw new Error(`Forbidden protocol "${protocol}"`)
310-
}
311-
if (isUnicastIp(hostname) === false) {
312-
throw new Error('Hostname resolved to non-unicast address')
313-
}
314-
return new undici.Pool(origin, opts)
315-
},
316-
connect: {
317-
lookup: cfg.proxy.disableSsrfProtection ? undefined : unicastLookup,
318-
},
319-
})
320-
const proxyAgent =
321-
cfg.proxy.maxRetries > 0
322-
? new undici.RetryAgent(proxyAgentBase, {
323-
statusCodes: [], // Only retry on socket errors
324-
methods: ['GET', 'HEAD'],
325-
maxRetries: cfg.proxy.maxRetries,
326-
})
327-
: proxyAgentBase
294+
const proxyAgent = buildProxyAgent(cfg.proxy)
328295

329296
/**
330297
* A fetch() function that protects against SSRF attacks, large responses &

packages/pds/src/pipethrough.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { IncomingHttpHeaders, ServerResponse } from 'node:http'
22
import { PassThrough, Readable, finished } from 'node:stream'
33
import { Request } from 'express'
4-
import { Dispatcher } from 'undici'
4+
import { Agent, Dispatcher, Pool, interceptors } from 'undici'
55
import {
66
decodeStream,
77
getServiceEndpoint,
@@ -20,12 +20,49 @@ import {
2020
excludeErrorResult,
2121
parseReqNsid,
2222
} from '@atproto/xrpc-server'
23+
import { isUnicastIp, unicastLookup } from '@atproto-labs/fetch-node'
2324
import { buildProxiedContentEncoding } from '@atproto-labs/xrpc-utils'
2425
import { isAccessPrivileged } from './auth-scope.js'
26+
import { ProxyConfig } from './config/config.js'
2527
import { AppContext } from './context.js'
2628
import { chat, com, tools } from './lexicons/index.js'
2729
import { httpLogger } from './logger.js'
2830

31+
export function buildProxyAgent(cfg: ProxyConfig): Dispatcher {
32+
const agent = new Agent({
33+
allowH2: cfg.allowHTTP2,
34+
headersTimeout: cfg.headersTimeout,
35+
maxResponseSize: cfg.maxResponseSize,
36+
bodyTimeout: cfg.bodyTimeout,
37+
factory: cfg.disableSsrfProtection
38+
? undefined
39+
: (origin, opts) => {
40+
const { protocol, hostname } =
41+
origin instanceof URL ? origin : new URL(origin)
42+
if (protocol !== 'https:') {
43+
throw new Error(`Forbidden protocol "${protocol}"`)
44+
}
45+
if (isUnicastIp(hostname) === false) {
46+
throw new Error('Hostname resolved to non-unicast address')
47+
}
48+
return new Pool(origin, opts)
49+
},
50+
connect: {
51+
lookup: cfg.disableSsrfProtection ? undefined : unicastLookup,
52+
},
53+
})
54+
55+
return agent.compose(
56+
cfg.maxRetries > 0
57+
? interceptors.retry({
58+
statusCodes: [], // Only retry on socket errors
59+
methods: ['GET', 'HEAD'],
60+
maxRetries: cfg.maxRetries,
61+
})
62+
: (dispatch) => dispatch,
63+
)
64+
}
65+
2966
export const proxyHandler = (ctx: AppContext): CatchallHandler => {
3067
const performAuth = ctx.authVerifier.authorization<RpcPermissionMatch>({
3168
authorize: (permissions, { params }) => permissions.assertRpc(params),

0 commit comments

Comments
 (0)