diff --git a/drc-portals/app/ga4gh/drs/v1/objects/[object_id]/access/[access_id]/ftps/ftps.py b/drc-portals/app/ga4gh/drs/v1/objects/[object_id]/access/[access_id]/ftps/ftps.py new file mode 100644 index 000000000..2ea85c72d --- /dev/null +++ b/drc-portals/app/ga4gh/drs/v1/objects/[object_id]/access/[access_id]/ftps/ftps.py @@ -0,0 +1,7 @@ +import sys +import shutil +from ufs.access.url import open_from_url + +def stream(*, access_url: str): + with open_from_url(access_url) as fr: + shutil.copyfileobj(fr, sys.stdout) diff --git a/drc-portals/app/ga4gh/drs/v1/objects/[object_id]/access/[access_id]/ftps/route.ts b/drc-portals/app/ga4gh/drs/v1/objects/[object_id]/access/[access_id]/ftps/route.ts new file mode 100644 index 000000000..05f4e4d87 --- /dev/null +++ b/drc-portals/app/ga4gh/drs/v1/objects/[object_id]/access/[access_id]/ftps/route.ts @@ -0,0 +1,35 @@ +import prisma from "@/lib/prisma" +import { safeAsync } from "@/utils/safe" +import path from 'path' +import { pythonStream } from "@/utils/python"; + +const __rootdir = path.resolve(__dirname, '..', '..') + +export async function GET(request: Request, { params }: { params: { object_id: string, access_id: string } }) { + if (params.access_id !== 'c2m2_file') return null + const object = await safeAsync(() => prisma.c2M2FileNode.findUnique({ + where: { + id: params.object_id, + }, + select: { + access_url: true, + }, + })) + if (!object?.data?.access_url) return null + if (!object.data.access_url.startsWith('ftps://')) return null + process.env.PYTHONPATH = path.resolve(__rootdir, 'drc-portals', 'app', 'ga4gh', 'drs', 'v1', 'objects', '[object_id]', 'access', '[access_id]', 'ftps') + const stdout = pythonStream('ftps.stream', { + kargs: [ + { + access_url: object?.data?.access_url, + }, + ], + }) + const stream = new ReadableStream({ + async start(controller) { + stdout.on('data', chunk => controller.enqueue(chunk)) + stdout.on('end', () => controller.close()) + } + }) + return new Response(stream, { status: 200 }) +} diff --git a/drc-portals/app/ga4gh/drs/v1/objects/[object_id]/access/[access_id]/http/route.ts b/drc-portals/app/ga4gh/drs/v1/objects/[object_id]/access/[access_id]/http/route.ts new file mode 100644 index 000000000..2d968291a --- /dev/null +++ b/drc-portals/app/ga4gh/drs/v1/objects/[object_id]/access/[access_id]/http/route.ts @@ -0,0 +1,28 @@ +import prisma from "@/lib/prisma" +import { safeAsync } from "@/utils/safe" +import http from 'http' + +export async function GET(request: Request, { params }: { params: { object_id: string, access_id: string } }) { + if (params.access_id !== 'c2m2_file') return null + const object = await safeAsync(() => prisma.c2M2FileNode.findUnique({ + where: { + id: params.object_id, + }, + select: { + access_url: true, + }, + })) + if (!object?.data?.access_url) return null + if (!object.data.access_url.startsWith('http://')) return null + const res = await new Promise((resolve, reject) => { + if (!object?.data?.access_url) reject() + else http.get(object.data.access_url, res => resolve(res)) + }) + const stream = new ReadableStream({ + async start(controller) { + res.on('data', chunk => controller.enqueue(chunk)) + res.on('end', () => controller.close()) + } + }) + return new Response(stream, { status: res.statusCode }) +} diff --git a/drc-portals/app/ga4gh/drs/v1/objects/[object_id]/access/[access_id]/route.ts b/drc-portals/app/ga4gh/drs/v1/objects/[object_id]/access/[access_id]/route.ts index 7c94df426..a4fc7b208 100644 --- a/drc-portals/app/ga4gh/drs/v1/objects/[object_id]/access/[access_id]/route.ts +++ b/drc-portals/app/ga4gh/drs/v1/objects/[object_id]/access/[access_id]/route.ts @@ -54,6 +54,7 @@ async function getC2M2FileUrl(object_id: string, access_id: string) { }, })) if (!object?.data?.access_url) return null + // DRS passthrough if (object.data.access_url.startsWith('drs://')) { // We'll just proxy to the upstream DRS server, hopefully the client doesn't mind this. Redirects don't seem to work const upstreamDRS = object.data.access_url.replace(/^drs:\/\/([^/]+)\/(.+)$/g, `https://$1/ga4gh/drs/v1/objects/$2/access/${access_id}`) @@ -62,7 +63,17 @@ async function getC2M2FileUrl(object_id: string, access_id: string) { else if (req.status === 404) return null else return req } - return { url: object.data.access_url } + // supported by DRS + if (/^(s3|gs|ftp|gsiftp|globus|htsget|https|file):\/\//.exec(object.data.access_url) !== null) { + return { url: object.data.access_url } + } + // supported by us + const compat = /^(http|ftps):\/\//.exec(object.data.access_url) + if (compat !== null) { + return { url: `${process.env.PUBLIC_URL}/ga4gh/drs/v1/objects/${object_id}/access/${access_id}/${compat[1]}` } + } + // unsupported + return null } export async function GET(request: Request, { params }: { params: { object_id: string, access_id: string } }) {