mirror of
https://github.com/hzrd149/nsite-gateway.git
synced 2025-07-25 14:45:32 +00:00
Support range requests
This commit is contained in:
parent
6f8b0038c3
commit
d45cf57ec9
5
.changeset/proud-bobcats-trade.md
Normal file
5
.changeset/proud-bobcats-trade.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nsite-gateway": minor
|
||||
---
|
||||
|
||||
Support range requests
|
@ -28,6 +28,7 @@
|
||||
"keyv": "^5.4.0",
|
||||
"koa": "^2.16.1",
|
||||
"koa-morgan": "^1.0.1",
|
||||
"koa-range": "^0.3.0",
|
||||
"koa-send": "^5.0.1",
|
||||
"koa-static": "^5.0.0",
|
||||
"mime": "^4.0.7",
|
||||
@ -46,6 +47,7 @@
|
||||
"@types/follow-redirects": "^1.14.4",
|
||||
"@types/koa": "^2.15.0",
|
||||
"@types/koa-morgan": "^1.0.8",
|
||||
"@types/koa-range": "^0.3.5",
|
||||
"@types/koa-send": "^4.1.6",
|
||||
"@types/koa-static": "^4.0.4",
|
||||
"@types/koa__cors": "^5.0.0",
|
||||
|
26
pnpm-lock.yaml
generated
26
pnpm-lock.yaml
generated
@ -38,6 +38,9 @@ importers:
|
||||
koa-morgan:
|
||||
specifier: ^1.0.1
|
||||
version: 1.0.1
|
||||
koa-range:
|
||||
specifier: ^0.3.0
|
||||
version: 0.3.0
|
||||
koa-send:
|
||||
specifier: ^5.0.1
|
||||
version: 5.0.1
|
||||
@ -87,6 +90,9 @@ importers:
|
||||
'@types/koa-morgan':
|
||||
specifier: ^1.0.8
|
||||
version: 1.0.8
|
||||
'@types/koa-range':
|
||||
specifier: ^0.3.5
|
||||
version: 0.3.5
|
||||
'@types/koa-send':
|
||||
specifier: ^4.1.6
|
||||
version: 4.1.6
|
||||
@ -639,6 +645,9 @@ packages:
|
||||
'@types/koa-morgan@1.0.8':
|
||||
resolution: {integrity: sha512-2GredUi+iA3V0XrbzdsOAYgwj4F6+FnN+f5YjoKjessIE2lrMkqnc06YQQnzbMG75hRsXjyD+p6d5vlI70s1vg==}
|
||||
|
||||
'@types/koa-range@0.3.5':
|
||||
resolution: {integrity: sha512-DvbLgWVctu3k0dnuQsb2If7ROdYvUK+oGOJTxFviQjfrMjaewZYM1IPYoH1yZ2zhcpD+hKzBiGi5DMOj0kLAQg==}
|
||||
|
||||
'@types/koa-send@4.1.6':
|
||||
resolution: {integrity: sha512-vgnNGoOJkx7FrF0Jl6rbK1f8bBecqAchKpXtKuXzqIEdXTDO6dsSTjr+eZ5m7ltSjH4K/E7auNJEQCAd0McUPA==}
|
||||
|
||||
@ -1287,6 +1296,10 @@ packages:
|
||||
koa-morgan@1.0.1:
|
||||
resolution: {integrity: sha512-JOUdCNlc21G50afBXfErUrr1RKymbgzlrO5KURY+wmDG1Uvd2jmxUJcHgylb/mYXy2SjiNZyYim/ptUBGsIi3A==}
|
||||
|
||||
koa-range@0.3.0:
|
||||
resolution: {integrity: sha512-Ich3pCz6RhtbajYXRWjIl6O5wtrLs6kE3nkXc9XmaWe+MysJyZO7K4L3oce1Jpg/iMgCbj+5UCiMm/rqVtcDIg==}
|
||||
engines: {node: '>=7'}
|
||||
|
||||
koa-send@5.0.1:
|
||||
resolution: {integrity: sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==}
|
||||
engines: {node: '>= 8'}
|
||||
@ -1751,6 +1764,9 @@ packages:
|
||||
resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
stream-slice@0.1.2:
|
||||
resolution: {integrity: sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA==}
|
||||
|
||||
string-width@4.2.3:
|
||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||
engines: {node: '>=8'}
|
||||
@ -2415,6 +2431,10 @@ snapshots:
|
||||
'@types/koa': 2.15.0
|
||||
'@types/morgan': 1.9.10
|
||||
|
||||
'@types/koa-range@0.3.5':
|
||||
dependencies:
|
||||
'@types/koa': 2.15.0
|
||||
|
||||
'@types/koa-send@4.1.6':
|
||||
dependencies:
|
||||
'@types/koa': 2.15.0
|
||||
@ -3130,6 +3150,10 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
koa-range@0.3.0:
|
||||
dependencies:
|
||||
stream-slice: 0.1.2
|
||||
|
||||
koa-send@5.0.1:
|
||||
dependencies:
|
||||
debug: 4.4.1(supports-color@5.5.0)
|
||||
@ -3671,6 +3695,8 @@ snapshots:
|
||||
|
||||
statuses@1.5.0: {}
|
||||
|
||||
stream-slice@0.1.2: {}
|
||||
|
||||
string-width@4.2.3:
|
||||
dependencies:
|
||||
emoji-regex: 8.0.0
|
||||
|
@ -29,8 +29,12 @@ export async function findBlobURLs(sha256: string, servers: string[]): Promise<s
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/** Downloads a file from multiple servers */
|
||||
export async function streamBlob(sha256: string, servers: string[]): Promise<IncomingMessage | undefined> {
|
||||
/** Downloads a file from multiple servers with optional range support */
|
||||
export async function streamBlob(
|
||||
sha256: string,
|
||||
servers: string[],
|
||||
headers?: Record<string, string>,
|
||||
): Promise<IncomingMessage | undefined> {
|
||||
if (servers.length === 0) return undefined;
|
||||
|
||||
// First find all available URLs
|
||||
@ -49,7 +53,7 @@ export async function streamBlob(sha256: string, servers: string[]): Promise<Inc
|
||||
}, 10_000);
|
||||
|
||||
const url = new URL(urlString);
|
||||
const response = await makeRequestWithAbort(url, controller);
|
||||
const response = await makeRequestWithAbort(url, controller, headers);
|
||||
res = response;
|
||||
clearTimeout(timeout);
|
||||
|
||||
@ -58,6 +62,7 @@ export async function streamBlob(sha256: string, servers: string[]): Promise<Inc
|
||||
const size = response.headers["content-length"];
|
||||
if (size && parseInt(size) > MAX_FILE_SIZE) throw new Error("File too large");
|
||||
|
||||
// Accept both 200 (full content) and 206 (partial content) status codes
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) return response;
|
||||
} catch (error) {
|
||||
if (res) res.resume();
|
||||
|
@ -4,7 +4,7 @@ const { http, https } = followRedirects;
|
||||
|
||||
import agent from "../proxy.js";
|
||||
|
||||
export function makeRequestWithAbort(url: URL, controller: AbortController) {
|
||||
export function makeRequestWithAbort(url: URL, controller: AbortController, headers?: Record<string, string>) {
|
||||
return new Promise<IncomingMessage>((res, rej) => {
|
||||
controller.signal.addEventListener("abort", () => rej(new Error("Aborted")));
|
||||
|
||||
@ -13,6 +13,7 @@ export function makeRequestWithAbort(url: URL, controller: AbortController) {
|
||||
{
|
||||
signal: controller.signal,
|
||||
agent,
|
||||
headers,
|
||||
},
|
||||
(response) => {
|
||||
res(response);
|
||||
|
19
src/index.ts
19
src/index.ts
@ -8,6 +8,7 @@ import fs from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import mime from "mime";
|
||||
import morgan from "koa-morgan";
|
||||
import range from "koa-range";
|
||||
import { npubEncode } from "nostr-tools/nip19";
|
||||
import { nip19 } from "nostr-tools";
|
||||
|
||||
@ -38,6 +39,9 @@ morgan.token("host", (req) => req.headers.host ?? "");
|
||||
|
||||
app.use(morgan(":method :host:url :status :response-time ms - :res[content-length]"));
|
||||
|
||||
// add range request support
|
||||
app.use(range);
|
||||
|
||||
// set CORS headers
|
||||
app.use(
|
||||
cors({
|
||||
@ -114,7 +118,13 @@ app.use(async (ctx, next) => {
|
||||
if (servers.length === 0) throw new Error("Failed to find blossom servers");
|
||||
|
||||
try {
|
||||
const res = await streamBlob(event.sha256, servers);
|
||||
// Prepare headers for range requests
|
||||
const requestHeaders: Record<string, string> = {};
|
||||
if (ctx.headers.range) {
|
||||
requestHeaders.range = ctx.headers.range;
|
||||
}
|
||||
|
||||
const res = await streamBlob(event.sha256, servers, requestHeaders);
|
||||
if (!res) {
|
||||
ctx.status = 502;
|
||||
ctx.body = `Failed to find blob\npath: ${event.path}\nsha256: ${event.sha256}\nservers: ${servers.join(", ")}`;
|
||||
@ -128,6 +138,10 @@ app.use(async (ctx, next) => {
|
||||
// pass headers along
|
||||
if (res.headers["content-length"]) ctx.set("content-length", res.headers["content-length"]);
|
||||
|
||||
// handle range response headers
|
||||
if (res.headers["accept-ranges"]) ctx.set("accept-ranges", res.headers["accept-ranges"]);
|
||||
if (res.headers["content-range"]) ctx.set("content-range", res.headers["content-range"]);
|
||||
|
||||
// set Onion-Location header
|
||||
if (ONION_HOST) {
|
||||
const url = new URL(ONION_HOST);
|
||||
@ -140,7 +154,8 @@ app.use(async (ctx, next) => {
|
||||
ctx.set("Cache-Control", "public, max-age=3600");
|
||||
ctx.set("Last-Modified", res.headers["last-modified"] || new Date(event.created_at * 1000).toUTCString());
|
||||
|
||||
ctx.status = 200;
|
||||
// set appropriate status code (206 for partial content, 200 for full content)
|
||||
ctx.status = res.statusCode === 206 ? 206 : 200;
|
||||
ctx.body = res;
|
||||
return;
|
||||
} catch (error) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user