mirror of
https://github.com/diced/zipline.git
synced 2025-04-11 23:31:17 -05:00
fix: ranged requests handled properly
This commit is contained in:
parent
8171a9ac61
commit
f408811f60
6 changed files with 140 additions and 43 deletions
|
@ -9,4 +9,5 @@ export abstract class Datasource {
|
|||
public abstract size(file: string): Promise<number | null>;
|
||||
public abstract get(file: string): Readable | Promise<Readable>;
|
||||
public abstract fullSize(): Promise<number>;
|
||||
public abstract range(file: string, start: number, end: number): Promise<Readable>;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ export class Local extends Datasource {
|
|||
}
|
||||
|
||||
public async save(file: string, data: Buffer): Promise<void> {
|
||||
await writeFile(join(this.path, file), data);
|
||||
await writeFile(join(this.path, file), Uint8Array.from(data));
|
||||
}
|
||||
|
||||
public async delete(file: string): Promise<void> {
|
||||
|
@ -56,4 +56,11 @@ export class Local extends Datasource {
|
|||
|
||||
return size;
|
||||
}
|
||||
|
||||
public async range(file: string, start: number, end: number): Promise<ReadStream> {
|
||||
const path = join(this.path, file);
|
||||
const readStream = createReadStream(path, { start, end });
|
||||
|
||||
return readStream;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,4 +81,15 @@ export class S3 extends Datasource {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
public async range(file: string, start: number, end: number): Promise<Readable> {
|
||||
return new Promise((res) => {
|
||||
this.s3.getPartialObject(this.config.bucket, file, start, end, (err, stream) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
res(null);
|
||||
} else res(stream);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,20 @@
|
|||
export function parseRangeHeader(header?: string): [number, number] {
|
||||
if (!header || !header.startsWith('bytes=')) return [0, Infinity];
|
||||
export function parseRange(header: string, length: number): [number, number] {
|
||||
const range = header.trim().substring(6);
|
||||
|
||||
const range = header.replace('bytes=', '').split('-');
|
||||
const start = Number(range[0]) || 0;
|
||||
const end = Number(range[1]) || Infinity;
|
||||
let start, end;
|
||||
|
||||
if (range.startsWith('-')) {
|
||||
end = length - 1;
|
||||
start = length - 1 - Number(range.substring(1));
|
||||
} else {
|
||||
const [s, e] = range.split('-').map(Number);
|
||||
start = s;
|
||||
end = e || length - 1;
|
||||
}
|
||||
|
||||
if (end > length - 1) {
|
||||
end = length - 1;
|
||||
}
|
||||
|
||||
return [start, end];
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { File } from '@prisma/client';
|
|||
import { FastifyInstance, FastifyReply } from 'fastify';
|
||||
import fastifyPlugin from 'fastify-plugin';
|
||||
import exts from 'lib/exts';
|
||||
import { parseRangeHeader } from 'lib/utils/range';
|
||||
import { parseRange } from 'lib/utils/range';
|
||||
|
||||
function dbFileDecorator(fastify: FastifyInstance, _, done) {
|
||||
fastify.decorateReply('dbFile', dbFile);
|
||||
|
@ -17,28 +17,70 @@ function dbFileDecorator(fastify: FastifyInstance, _, done) {
|
|||
const size = await this.server.datasource.size(file.name);
|
||||
if (size === null) return this.notFound();
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
let [rangeStart, rangeEnd] = parseRangeHeader(this.request.headers.range);
|
||||
if (rangeStart >= rangeEnd)
|
||||
return this.code(416)
|
||||
.header('Content-Range', `bytes 0/${size - 1}`)
|
||||
.send();
|
||||
if (rangeEnd === Infinity) rangeEnd = size - 1;
|
||||
|
||||
const data = await this.server.datasource.get(file.name);
|
||||
|
||||
// only send content-range if the client asked for it
|
||||
if (this.request.headers.range) {
|
||||
this.code(206);
|
||||
this.header('Content-Range', `bytes ${rangeStart}-${rangeEnd}/${size}`);
|
||||
const [start, end] = parseRange(this.request.headers.range, size);
|
||||
if (start >= size || end >= size) {
|
||||
const buf = await datasource.get(file.name);
|
||||
if (!buf) return this.server.nextServer.render404(this.request.raw, this.raw);
|
||||
|
||||
return this.type(file.mimetype || 'application/octet-stream')
|
||||
.headers({
|
||||
'Content-Length': size,
|
||||
...(file.originalName
|
||||
? {
|
||||
'Content-Disposition': `${download ? 'attachment; ' : ''}filename="${encodeURIComponent(
|
||||
file.originalName,
|
||||
)}"`,
|
||||
}
|
||||
: download && {
|
||||
'Content-Disposition': 'attachment;',
|
||||
}),
|
||||
})
|
||||
.status(416)
|
||||
.send(buf);
|
||||
}
|
||||
|
||||
const buf = await datasource.range(file.name, start || 0, end);
|
||||
if (!buf) return this.server.nextServer.render404(this.request.raw, this.raw);
|
||||
|
||||
return this.type(file.mimetype || 'application/octet-stream')
|
||||
.headers({
|
||||
'Content-Range': `bytes ${start}-${end}/${size}`,
|
||||
'Accept-Ranges': 'bytes',
|
||||
'Content-Length': end - start + 1,
|
||||
...(file.originalName
|
||||
? {
|
||||
'Content-Disposition': `${download ? 'attachment; ' : ''}filename="${encodeURIComponent(
|
||||
file.originalName,
|
||||
)}"`,
|
||||
}
|
||||
: download && {
|
||||
'Content-Disposition': 'attachment;',
|
||||
}),
|
||||
})
|
||||
.status(206)
|
||||
.send(buf);
|
||||
}
|
||||
|
||||
this.header('Content-Length', rangeEnd - rangeStart + 1);
|
||||
this.header('Content-Type', download ? 'application/octet-stream' : file.mimetype);
|
||||
this.header('Content-Disposition', `inline; filename="${encodeURI(file.originalName || file.name)}"`);
|
||||
this.header('Accept-Ranges', 'bytes');
|
||||
const data = await datasource.get(file.name);
|
||||
if (!data) return this.server.nextServer.render404(this.request.raw, this.raw);
|
||||
|
||||
return this.send(data);
|
||||
return this.type(file.mimetype || 'application/octet-stream')
|
||||
.headers({
|
||||
'Content-Length': size,
|
||||
'Accept-Ranges': 'bytes',
|
||||
...(file.originalName
|
||||
? {
|
||||
'Content-Disposition': `${download ? 'attachment; ' : ''}filename="${encodeURIComponent(
|
||||
file.originalName,
|
||||
)}"`,
|
||||
}
|
||||
: download && {
|
||||
'Content-Disposition': 'attachment;',
|
||||
}),
|
||||
})
|
||||
.status(200)
|
||||
.send(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import fastifyPlugin from 'fastify-plugin';
|
|||
import { createBrotliCompress, createDeflate, createGzip } from 'zlib';
|
||||
import pump from 'pump';
|
||||
import { Transform } from 'stream';
|
||||
import { parseRangeHeader } from 'lib/utils/range';
|
||||
import { parseRange } from 'lib/utils/range';
|
||||
|
||||
function rawFileDecorator(fastify: FastifyInstance, _, done) {
|
||||
fastify.decorateReply('rawFile', rawFile);
|
||||
|
@ -18,25 +18,41 @@ function rawFileDecorator(fastify: FastifyInstance, _, done) {
|
|||
|
||||
const mimetype = await guess(extname(id).slice(1));
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
let [rangeStart, rangeEnd] = parseRangeHeader(this.request.headers.range);
|
||||
if (rangeStart >= rangeEnd)
|
||||
return this.code(416)
|
||||
.header('Content-Range', `bytes 0/${size - 1}`)
|
||||
.send();
|
||||
if (rangeEnd === Infinity) rangeEnd = size - 1;
|
||||
|
||||
const data = await this.server.datasource.get(id);
|
||||
|
||||
// only send content-range if the client asked for it
|
||||
if (this.request.headers.range) {
|
||||
this.code(206);
|
||||
this.header('Content-Range', `bytes ${rangeStart}-${rangeEnd}/${size}`);
|
||||
const [start, end] = parseRange(this.request.headers.range, size);
|
||||
if (start >= size || end >= size) {
|
||||
const buf = await datasource.get(id);
|
||||
if (!buf) return this.server.nextServer.render404(this.request.raw, this.raw);
|
||||
|
||||
return this.type(mimetype || 'application/octet-stream')
|
||||
.headers({
|
||||
'Content-Length': size,
|
||||
...(download && {
|
||||
'Content-Disposition': 'attachment;',
|
||||
}),
|
||||
})
|
||||
.status(416)
|
||||
.send(buf);
|
||||
}
|
||||
|
||||
const buf = await datasource.range(id, start || 0, end);
|
||||
if (!buf) return this.server.nextServer.render404(this.request.raw, this.raw);
|
||||
|
||||
return this.type(mimetype || 'application/octet-stream')
|
||||
.headers({
|
||||
'Content-Range': `bytes ${start}-${end}/${size}`,
|
||||
'Accept-Ranges': 'bytes',
|
||||
'Content-Length': end - start + 1,
|
||||
...(download && {
|
||||
'Content-Disposition': 'attachment;',
|
||||
}),
|
||||
})
|
||||
.status(206)
|
||||
.send(buf);
|
||||
}
|
||||
|
||||
this.header('Content-Length', rangeEnd - rangeStart + 1);
|
||||
this.header('Content-Type', download ? 'application/octet-stream' : mimetype);
|
||||
this.header('Accept-Ranges', 'bytes');
|
||||
const data = await datasource.get(id);
|
||||
if (!data) return this.server.nextServer.render404(this.request.raw, this.raw);
|
||||
|
||||
if (
|
||||
this.server.config.core.compression.enabled &&
|
||||
|
@ -49,7 +65,16 @@ function rawFileDecorator(fastify: FastifyInstance, _, done) {
|
|||
)
|
||||
return this.send(useCompress.call(this, data));
|
||||
|
||||
return this.send(data);
|
||||
return this.type(mimetype || 'application/octet-stream')
|
||||
.headers({
|
||||
'Content-Length': size,
|
||||
'Accept-Ranges': 'bytes',
|
||||
...(download && {
|
||||
'Content-Disposition': 'attachment;',
|
||||
}),
|
||||
})
|
||||
.status(200)
|
||||
.send(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue