Skip to content

Commit 4796ed3

Browse files
authored
refactor: Break down middleware to prepare for UCAN Egress (#121)
Should be a pure refactoring, but moves around a lot of code, and adds some tests.
1 parent d097a91 commit 4796ed3

22 files changed

+654
-274
lines changed

src/bindings.d.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { CID } from '@web3-storage/gateway-lib/handlers'
2-
import { Environment as RateLimiterEnvironment } from './handlers/rate-limiter.types.ts'
3-
import { Environment as CarBlockEnvironment } from './handlers/car-block.types.ts'
2+
import { Environment as RateLimiterEnvironment } from './middleware/withRateLimit.types.ts'
3+
import { Environment as CarBlockEnvironment } from './middleware/withCarBlockHandler.types.ts'
4+
import { Environment as ContentClaimsDagulaEnvironment } from './middleware/withCarBlockHandler.types.ts'
45

5-
export interface Environment extends CarBlockEnvironment, RateLimiterEnvironment {
6+
export interface Environment
7+
extends CarBlockEnvironment,
8+
RateLimiterEnvironment,
9+
ContentClaimsDagulaEnvironment {
610
VERSION: string
7-
CONTENT_CLAIMS_SERVICE_URL?: string
811
}
912

1013
export interface AccountingService {
@@ -15,4 +18,3 @@ export interface AccountingService {
1518
export interface Accounting {
1619
create: ({ serviceURL }: { serviceURL?: string }) => AccountingService
1720
}
18-

src/index.js

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,35 +18,53 @@ import {
1818
import {
1919
withContentClaimsDagula,
2020
withVersionHeader,
21-
withCarBlockHandler
22-
} from './middleware.js'
23-
import { withRateLimit } from './handlers/rate-limiter.js'
21+
withAuthToken,
22+
withCarBlockHandler,
23+
withRateLimit,
24+
withNotFound,
25+
withLocator
26+
} from './middleware/index.js'
2427

2528
/**
26-
* @typedef {import('./bindings.js').Environment} Environment
27-
* @typedef {import('@web3-storage/gateway-lib').IpfsUrlContext} IpfsUrlContext
28-
* @typedef {import('@web3-storage/gateway-lib').BlockContext} BlockContext
29-
* @typedef {import('@web3-storage/gateway-lib').DagContext} DagContext
30-
* @typedef {import('@web3-storage/gateway-lib').UnixfsContext} UnixfsContext
29+
* @import {
30+
* Handler,
31+
* Middleware,
32+
* Context,
33+
* IpfsUrlContext,
34+
* BlockContext,
35+
* DagContext,
36+
* UnixfsContext
37+
* } from '@web3-storage/gateway-lib'
38+
* @import { Environment } from './bindings.js'
3139
*/
3240

3341
export default {
34-
/** @type {import('@web3-storage/gateway-lib').Handler<import('@web3-storage/gateway-lib').Context, import('./bindings.js').Environment>} */
42+
/** @type {Handler<Context, Environment>} */
3543
fetch (request, env, ctx) {
3644
console.log(request.method, request.url)
3745
const middleware = composeMiddleware(
46+
// Prepare the Context
3847
withCdnCache,
3948
withContext,
4049
withCorsHeaders,
4150
withVersionHeader,
4251
withErrorHandler,
4352
withParsedIpfsUrl,
44-
withRateLimit,
4553
createWithHttpMethod('GET', 'HEAD'),
54+
withAuthToken,
55+
withLocator,
56+
57+
// Rate-limit requests
58+
withRateLimit,
59+
60+
// Fetch data
4661
withCarBlockHandler,
62+
withNotFound,
4763
withContentClaimsDagula,
4864
withFormatRawHandler,
4965
withFormatCarHandler,
66+
67+
// Prepare the Response
5068
withContentDispositionHeader,
5169
withFixedLengthStream
5270
)
@@ -55,29 +73,35 @@ export default {
5573
}
5674

5775
/**
58-
* @type {import('@web3-storage/gateway-lib').Middleware<BlockContext & UnixfsContext & IpfsUrlContext, BlockContext & UnixfsContext & IpfsUrlContext, Environment>}
76+
* @type {Middleware<BlockContext & UnixfsContext & IpfsUrlContext, BlockContext & UnixfsContext & IpfsUrlContext, Environment>}
5977
*/
6078
export function withFormatRawHandler (handler) {
6179
return async (request, env, ctx) => {
6280
const { headers } = request
6381
const { searchParams } = ctx
6482
if (!searchParams) throw new Error('missing URL search params')
65-
if (searchParams.get('format') === 'raw' || headers.get('Accept')?.includes('application/vnd.ipld.raw')) {
83+
if (
84+
searchParams.get('format') === 'raw' ||
85+
headers.get('Accept')?.includes('application/vnd.ipld.raw')
86+
) {
6687
return await handleBlock(request, env, ctx)
6788
}
6889
return handler(request, env, ctx) // pass to other handlers
6990
}
7091
}
7192

7293
/**
73-
* @type {import('@web3-storage/gateway-lib').Middleware<DagContext & IpfsUrlContext, DagContext & IpfsUrlContext, Environment>}
94+
* @type {Middleware<DagContext & IpfsUrlContext, DagContext & IpfsUrlContext, Environment>}
7495
*/
7596
export function withFormatCarHandler (handler) {
7697
return async (request, env, ctx) => {
7798
const { headers } = request
7899
const { searchParams } = ctx
79100
if (!searchParams) throw new Error('missing URL search params')
80-
if (searchParams.get('format') === 'car' || headers.get('Accept')?.includes('application/vnd.ipld.car')) {
101+
if (
102+
searchParams.get('format') === 'car' ||
103+
headers.get('Accept')?.includes('application/vnd.ipld.car')
104+
) {
81105
return await handleCar(request, env, ctx)
82106
}
83107
return handler(request, env, ctx) // pass to other handlers

src/middleware.js

Lines changed: 0 additions & 100 deletions
This file was deleted.

src/middleware/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export { withAuthToken } from './withAuthToken.js'
2+
export { withCarBlockHandler } from './withCarBlockHandler.js'
3+
export { withContentClaimsDagula } from './withContentClaimsDagula.js'
4+
export { withRateLimit } from './withRateLimit.js'
5+
export { withVersionHeader } from './withVersionHeader.js'
6+
export { withNotFound } from './withNotFound.js'
7+
export { withLocator } from './withLocator.js'

src/middleware/withAuthToken.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* @import {
3+
* Middleware,
4+
* Context as MiddlewareContext,
5+
* } from '@web3-storage/gateway-lib'
6+
* @import {
7+
* Environment,
8+
* InContext,
9+
* OutContext,
10+
* } from './withAuthToken.types.js'
11+
*/
12+
13+
/**
14+
* Finds an authentication token in the URL query parameters or the
15+
* `Authorization` header and adds it to the context as `authToken`.
16+
*
17+
* @type {Middleware<OutContext<MiddlewareContext>, InContext, Environment>}
18+
*/
19+
export function withAuthToken (handler) {
20+
return async (req, env, ctx) => {
21+
return handler(req, env, {
22+
...ctx,
23+
authToken:
24+
getAuthTokenFromQueryParams(req) || getAuthTokenFromHeaders(req)
25+
})
26+
}
27+
}
28+
29+
/**
30+
* @param {Request} request
31+
* @returns {string | null}
32+
*/
33+
function getAuthTokenFromQueryParams (request) {
34+
return new URL(request.url).searchParams.get('authToken')
35+
}
36+
37+
const BEARER_PREFIX = 'Bearer '
38+
39+
/**
40+
* @param {Request} request
41+
* @returns {string | null}
42+
*/
43+
function getAuthTokenFromHeaders (request) {
44+
const authHeader = request.headers.get('Authorization')
45+
if (authHeader && authHeader.startsWith(BEARER_PREFIX)) {
46+
return authHeader.substring(BEARER_PREFIX.length)
47+
}
48+
return null
49+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import {
2+
Environment as MiddlewareEnvironment,
3+
Context as MiddlewareContext,
4+
} from '@web3-storage/gateway-lib'
5+
6+
export interface Environment extends MiddlewareEnvironment {}
7+
8+
export interface InContext extends MiddlewareContext {}
9+
10+
export type OutContext<IncomingContext> = IncomingContext & {
11+
authToken: string | null
12+
}
Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,60 @@
11
/* eslint-env browser */
22
/* global FixedLengthStream */
3+
4+
import { CAR_CODE } from '../constants.js'
35
import { HttpError } from '@web3-storage/gateway-lib/util'
46
// @ts-expect-error no types
57
import httpRangeParse from 'http-range-parse'
68
import { base58btc } from 'multiformats/bases/base58'
7-
import { CAR_CODE } from '../constants.js'
89

910
/**
10-
* @import { Context, IpfsUrlContext as CarBlockHandlerContext, Handler } from '@web3-storage/gateway-lib'
1111
* @import { R2Bucket, KVNamespace, RateLimit } from '@cloudflare/workers-types'
12-
* @import { Environment } from './car-block.types.js'
12+
* @import {
13+
* IpfsUrlContext,
14+
* Middleware,
15+
* Context,
16+
* IpfsUrlContext as CarBlockHandlerContext,
17+
* Handler
18+
* } from '@web3-storage/gateway-lib'
19+
* @import { Environment } from './withCarBlockHandler.types.js'
1320
*/
1421

1522
/** @typedef {{ offset: number, length?: number } | { offset?: number, length: number } | { suffix: number }} Range */
1623

24+
/**
25+
* Middleware that will serve CAR files if a CAR codec is found in the path
26+
* CID. If the CID is not a CAR CID it delegates to the next middleware.
27+
*
28+
* @type {Middleware<IpfsUrlContext, IpfsUrlContext, Environment>}
29+
*/
30+
31+
export function withCarBlockHandler (handler) {
32+
return async (request, env, ctx) => {
33+
const { dataCid, searchParams } = ctx
34+
if (!dataCid) throw new Error('missing data CID')
35+
36+
// if not CAR codec, or if trusted gateway format has been requested...
37+
const formatParam = searchParams.get('format')
38+
const acceptHeader = request.headers.get('Accept')
39+
if (dataCid.code !== CAR_CODE ||
40+
formatParam === 'car' ||
41+
acceptHeader === 'application/vnd.ipld.car' ||
42+
formatParam === 'raw' ||
43+
acceptHeader === 'application/vnd.ipld.raw') {
44+
return handler(request, env, ctx) // pass to other handlers
45+
}
46+
47+
try {
48+
return await handleCarBlock(request, env, ctx)
49+
} catch (err) {
50+
if (err instanceof HttpError && err.status === 404) {
51+
return handler(request, env, ctx) // use content claims to resolve
52+
}
53+
throw err
54+
}
55+
}
56+
}
57+
1758
/**
1859
* Handler that serves CAR files directly from R2.
1960
*
File renamed without changes.

0 commit comments

Comments
 (0)