Skip to content

Commit

Permalink
feat(server): ⚡️ Subdomain Gateway Using Fastify (#31)
Browse files Browse the repository at this point in the history
Co-authored-by: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com>
  • Loading branch information
whizzzkid and SgtPooki authored Oct 25, 2023
1 parent 37ee45b commit 26cb74a
Show file tree
Hide file tree
Showing 8 changed files with 643 additions and 835 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ $ docker run -it -p 8080:8080 -e DEBUG="helia-http-gateway" helia
| `DEBUG` | Debug level | `''`|
| `PORT` | Port to listen on | `8080` |
| `HOST` | Host to listen on | `0.0.0.0` |
| `METRICS` | Whether to enable prometheus metrics. Any value other than 'true' will disable metrics. | `true` |
| `USE_BITSWAP` | Use bitswap to fetch content from IPFS | `true` |
| `USE_TRUSTLESS_GATEWAYS` | Whether to fetch content from trustless-gateways or not | `true` |
| `TRUSTLESS_GATEWAYS` | Comma separated list of trusted gateways to fetch content from | [Defined in Helia](https://github.com/ipfs/helia/blob/main/packages/helia/src/block-brokers/trustless-gateway/index.ts) |
Expand Down
1,145 changes: 487 additions & 658 deletions package-lock.json

Large diffs are not rendered by default.

14 changes: 6 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"test": "echo \"Error: no test specified\" && exit 1",
"test:e2e": "playwright test",
"test:http-e2e": "cross-env USE_BITSWAP=false USE_LIBP2P=false playwright test",
"test:e2e-flame": "concurrently -k --ks SIGINT -s all -n \"gateway,playwright\" -c \"magenta,blue\" \"npm run start:dev-flame\" \"wait-on 'http://127.0.0.1:$PORT' && npm run test:e2e\"",
"test:e2e-doctor": "concurrently -k --ks SIGINT -s all -n \"gateway,playwright\" -c \"magenta,blue\" \"npm run start:dev-doctor\" \"wait-on 'http://127.0.0.1:$PORT' && npm run test:e2e\"",
"test:e2e-flame": "concurrently -k --ks SIGINT -s all -n \"gateway,playwright\" -c \"magenta,blue\" \"npm run start:dev-flame\" \"wait-on 'tcp:$PORT' && npm run test:e2e\"",
"test:e2e-doctor": "concurrently -k --ks SIGINT -s all -n \"gateway,playwright\" -c \"magenta,blue\" \"npm run start:dev-doctor\" \"wait-on 'tcp:$PORT' && npm run test:e2e\"",
"healthcheck": "node dist/src/healthcheck.js"
},
"type": "module",
Expand Down Expand Up @@ -46,8 +46,6 @@
"homepage": "https://github.com/ipfs/helia-http-gateway#readme",
"devDependencies": {
"@playwright/test": "^1.39.0",
"@types/express": "4.x",
"@types/express-session": "1.17.9",
"@types/mime-types": "2.x",
"@types/node": "20.x",
"aegir": "40.x",
Expand All @@ -62,13 +60,13 @@
"@helia/unixfs": "1.x",
"blockstore-level": "^1.1.4",
"datastore-level": "^10.1.4",
"express": "4.x",
"express-prom-bundle": "6.x",
"express-session": "1.17.3",
"fastify": "4.24.3",
"fastify-metrics": "10.3.2",
"file-type": "18.x",
"helia": "next",
"lru-cache": "10.x",
"mime-types": "2.x",
"p-try-each": "1.x"
"p-try-each": "1.x",
"pino-pretty": "10.2.3"
}
}
4 changes: 2 additions & 2 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { defineConfig, devices } from '@playwright/test'
import { HOST, PORT } from './src/constants.js'
import { PORT } from './src/constants.js'

/**
* See https://playwright.dev/docs/test-configuration.
Expand Down Expand Up @@ -51,7 +51,7 @@ export default defineConfig({
/* Run your local dev server before starting the tests */
webServer: {
command: (process.env.DOCTOR != null) ? 'npm run start:dev-doctor' : 'npm run start:dev',
url: `http://${HOST}:${PORT}`,
port: PORT,
// Tiros does not re-use the existing server.
reuseExistingServer: process.env.CI == null
}
Expand Down
9 changes: 9 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ export const PORT = Number(process.env.PORT ?? 8080)

export const HOST = process.env.HOST ?? '0.0.0.0'

export const DEBUG = process.env.DEBUG ?? ''

/**
* If set to any value other than 'true', we will disable prometheus metrics.
*
* @default 'true'
*/
export const METRICS = process.env.METRICS ?? 'true'

/**
* If not set, we will enable bitswap by default.
*/
Expand Down
37 changes: 28 additions & 9 deletions src/heliaFetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,20 @@ const ROOT_FILE_PATTERNS = [

const DELEGATED_ROUTING_API = 'https://node3.delegate.ipfs.io/api/v0/name/resolve/'

interface HeliaPathParts {
namespace: string
address: string
relativePath: string
}

/**
* Fetches files from IPFS or IPNS
*/
export class HeliaFetch {
private fs!: UnixFS
private readonly delegatedRoutingApi: string
private readonly log: debug.Debugger
private readonly PARSE_PATH_REGEX = /^\/(?<namespace>ip[fn]s)\/(?<address>[^/$]+)(?<relativePath>[^$]*)/
private readonly rootFilePatterns: string[]
public node!: Helia
public ready: Promise<void>
Expand Down Expand Up @@ -73,19 +80,18 @@ export class HeliaFetch {
/**
* Parse a path into its namespace, address, and relative path
*/
public parsePath (path: string): { namespace: string, address: string, relativePath: string } {
public parsePath (path: string): HeliaPathParts {
if (path === undefined) {
throw new Error('Path is empty')
}
this.log(`Parsing path: ${path}`)
const regex = /^\/(?<namespace>ip[fn]s)\/(?<address>[^/$]+)(?<relativePath>[^$]*)/
const result = path.match(regex)
const result = path.match(this.PARSE_PATH_REGEX)
if (result == null || result?.groups == null) {
this.log(`Error parsing path: ${path}:`, result)
throw new Error(`Path: ${path} is not valid, provide path as /ipfs/<cid> or /ipns/<path>`)
}
this.log('Parsed path:', result?.groups)
return result.groups as { namespace: string, address: string, relativePath: string }
return result.groups as unknown as HeliaPathParts
}

/**
Expand All @@ -96,13 +102,11 @@ export class HeliaFetch {
}

/**
* fetch a path from IPFS or IPNS
* fetch a path from a given namespace and address.
*/
public async fetch (path: string): Promise<AsyncIterable<Uint8Array>> {
public async fetch ({ namespace, address, relativePath }: HeliaPathParts): Promise<AsyncIterable<Uint8Array>> {
try {
await this.ready
this.log('Fetching:', path)
const { namespace, address, relativePath } = this.parsePath(path)
this.log('Processing Fetch:', { namespace, address, relativePath })
switch (namespace) {
case 'ipfs':
Expand All @@ -112,6 +116,21 @@ export class HeliaFetch {
default:
throw new Error('Namespace is not valid, provide path as /ipfs/<cid> or /ipns/<path>')
}
} catch (error) {
// eslint-disable-next-line no-console
this.log(`Error fetching: ${namespace}/${address}${relativePath}`, error)
throw error
}
}

/**
* fetch a path as string from IPFS or IPNS
*/
public async fetchPath (path: string): Promise<AsyncIterable<Uint8Array>> {
try {
this.log('Fetching:', path)
const { namespace, address, relativePath } = this.parsePath(path)
return await this.fetch({ namespace, address, relativePath })
} catch (error) {
// eslint-disable-next-line no-console
this.log(`Error fetching: ${path}`, error)
Expand Down Expand Up @@ -151,7 +170,7 @@ export class HeliaFetch {
}
const finalPath = `${this.ipnsResolutionCache.get(address)}${options?.path ?? ''}`
this.log('Final IPFS path:', finalPath)
return this.fetch(finalPath)
return this.fetchPath(finalPath)
}

/**
Expand Down
Loading

0 comments on commit 26cb74a

Please sign in to comment.