Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: dotstorage hosts resolution layer #105

Merged
merged 1 commit into from
Oct 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/edge-gateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
wrangler secret put SENTRY_DSN --env $(whoami) # Get from Sentry
wrangler secret put LOKI_URL --env $(whoami) # Get from Loki
wrangler secret put LOKI_TOKEN --env $(whoami) # Get from Loki
wrangler secret put CDN_GATEWAYS_RACE --env $(whoami) # JSON String with array of CDN Gateways URLs (eg. echo -e '["https://freeway.dag.haus"]' | wrangler secret ...)
wrangler secret put IPFS_GATEWAYS_RACE_L1 --env $(whoami) # JSON String with array of IPFS Gateways URLs (eg. echo -e '["https://ipfs.io","https://dagula.dag.haus"]' | wrangler secret ...)
wrangler secret put IPFS_GATEWAYS_RACE_L2 --env $(whoami) # JSON String with array of IPFS Gateways URLs (eg. echo -e '["https://cf.dag.haus","https://w3link.mypinata.cloud"]' | wrangler secret ...)
```
Expand Down
2 changes: 2 additions & 0 deletions packages/edge-gateway/src/bindings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface EnvInput {
CID_VERIFIER_ENABLED: string
CID_VERIFIER_URL: string
CID_VERIFIER: Fetcher
CDN_GATEWAYS_RACE: string
IPFS_GATEWAYS_RACE_L1: string
IPFS_GATEWAYS_RACE_L2: string
GATEWAY_HOSTNAME: string
Expand All @@ -43,6 +44,7 @@ export interface EnvTransformed {
REQUEST_TIMEOUT: number
IPFS_GATEWAY_HOSTNAME: string
IPNS_GATEWAY_HOSTNAME: string
cdnGateways: Array<string>
ipfsGatewaysL1: Array<string>
ipfsGatewaysL2: Array<string>
sentry?: Toucan
Expand Down
3 changes: 3 additions & 0 deletions packages/edge-gateway/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export const RESOLUTION_IDENTIFIERS = {
PERMA_CACHE: 'perma-cache'
}

/** @type {string[]} */
export const DEFAULT_CDN_GATEWAYS = []

export const DEFAULT_RACE_L1_GATEWAYS = [
'https://ipfs.io'
]
Expand Down
6 changes: 5 additions & 1 deletion packages/edge-gateway/src/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { createGatewayRacer } from 'ipfs-gateway-race'
import pkg from '../package.json'
import {
DEFAULT_RACE_L1_GATEWAYS,
DEFAULT_RACE_L2_GATEWAYS
DEFAULT_RACE_L2_GATEWAYS,
DEFAULT_CDN_GATEWAYS
} from './constants.js'

/**
Expand All @@ -33,6 +34,9 @@ export function envAll (request, env, ctx) {

env.sentry = getSentry(request, env, ctx)

// Set CDN Gateways
env.cdnGateways = parseGatewayUrls(env.CDN_GATEWAYS_RACE, DEFAULT_CDN_GATEWAYS, env)

// Set Layer 1 racer
env.ipfsGatewaysL1 = parseGatewayUrls(env.IPFS_GATEWAYS_RACE_L1, DEFAULT_RACE_L1_GATEWAYS, env)
env.gwRacerL1 = createGatewayRacer(env.ipfsGatewaysL1, {
Expand Down
38 changes: 28 additions & 10 deletions packages/edge-gateway/src/gateway.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import pAny, { AggregateError } from 'p-any'
import pDefer from 'p-defer'
import pSettle from 'p-settle'
import { FilterError } from 'p-some'
import { gatewayFetch } from 'ipfs-gateway-race'

import { getCidFromSubdomainUrl } from './utils/cid.js'
import { getHeaders } from './utils/headers.js'
Expand Down Expand Up @@ -75,7 +76,7 @@ export async function gatewayGet (request, env, ctx) {
}

// 2nd layer
const dotstorageRes = await getFromDotstorage(request, cid, { pathname })
const dotstorageRes = await getFromDotstorage(request, env, cid, { pathname })
if (dotstorageRes) {
return getResponseWithCustomHeaders(
dotstorageRes.response,
Expand Down Expand Up @@ -163,16 +164,17 @@ async function getFromCdn (request, env, cache) {

/**
* @param {Request} request
* @param {Env} env
* @param {string} cid
* @param {{ pathname?: string}} [options]
* @return {Promise<ProxiedCDNResponse | undefined>}
*/
async function getFromDotstorage (request, cid, options = {}) {
async function getFromDotstorage (request, env, cid, options = {}) {
const pathname = options.pathname || ''
try {
// Get onlyIfCached hosts provided
/** @type {string[]} */
const hosts = []
const onlyIfCachedHosts = []
// @ts-ignore custom entry in cf object
if (request.cf?.onlyIfCachedGateways) {
/** @type {URL[]} */
Expand All @@ -181,15 +183,15 @@ async function getFromDotstorage (request, cid, options = {}) {
request.cf?.onlyIfCachedGateways
).map((/** @type {string} */ gw) => new URL(gw))

onlyIfCachedGateways.forEach((gw) => hosts.push(gw.host))
onlyIfCachedGateways.forEach((gw) => onlyIfCachedHosts.push(gw.host))
}

// Add only if cached header
const headers = getHeaders(request)
headers.set('Cache-Control', 'only-if-cached')
const proxiedCDNResponse = await pAny([
...onlyIfCachedHosts.map(async (host) => {
// Add only if cached header
const headers = getHeaders(request)
headers.set('Cache-Control', 'only-if-cached')

const proxiedCDNResponse = await pAny(
hosts.map(async (host) => {
const response = await fetch(`https://${cid}.ipfs.${host}${pathname}`, {
headers
})
Expand All @@ -202,8 +204,24 @@ async function getFromDotstorage (request, cid, options = {}) {
response,
resolutionIdentifier: host
}
}),
...env.cdnGateways.map(async (host) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was not added as a GatewayRacer instance yet, but should be added in follow up.

At the moment, we have as part of this step (dotstorage cdn layer) performing requests to only if cached gateways (nftstorage.link asks w3s.link cache, and other way around). This means part of this race requires different headers, as well as non path resolution.

To use gateway racer now, we would need to add support there to specify headers per gateway target and to support path+subdomain resolution. This might not be needed, because we might go with CF Cache Reserve + custom hostname for nftstorage.link which would mean the onlyIfCachedHosts would not be needed anymore.

const gwResponse = await gatewayFetch(host, cid, pathname, {
timeout: env.REQUEST_TIMEOUT
})

// @ts-ignore 'response' does not exist on type 'GatewayResponseFailure'
if (!gwResponse?.response.ok) {
throw new Error()
}

return {
// @ts-ignore 'response' does not exist on type 'GatewayResponseFailure'
response: gwResponse?.response,
resolutionIdentifier: host
}
})
)
])

return proxiedCDNResponse
} catch (_) {}
Expand Down
24 changes: 24 additions & 0 deletions packages/edge-gateway/test/cdn.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,27 @@ test('Should not get from cache if no-cache cache control header is provided', a
clearTimeout(timer)
}
})

test('should get from cdn gateways race if they can resolve', async (t) => {
const url =
'https://bafkreihl44bu5rqxctfvl3ahcln7gnjgmjqi7v5wfwojqwriqnq7wo4n7u.ipfs.localhost:8787'

const mf = getMiniflare({
CDN_GATEWAYS_RACE: '["http://localhost:9082"]',
IPFS_GATEWAYS_RACE_L1: '["http://localhost:9083"]',
IPFS_GATEWAYS_RACE_L2: '["http://localhost:9081"]'
})

const response = await mf.dispatchFetch(
url
)
await response.waitUntil()
t.is(await response.text(), 'Hello dot.storage! 😎')

// Validate content headers
t.is(response.headers.get('content-length'), '23')

// Validate x-dotstorage headers
t.is(response.headers.get('x-dotstorage-resolution-layer'), 'dotstorage-race')
t.is(response.headers.get('x-dotstorage-resolution-id'), 'http://localhost:9082')
})
2 changes: 1 addition & 1 deletion packages/ipfs-gateway-race/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export function createGatewayRacer (ipfsGateways, options = {}) {
* @param {number} [options.timeout]
* @param {AbortSignal} [options.signal]
*/
async function gatewayFetch (
export async function gatewayFetch (
gwUrl,
cid,
pathname,
Expand Down