Add range header support to files.get (#6950)

This commit is contained in:
Tom Moor
2024-05-28 08:12:25 -04:00
committed by GitHub
parent f58f309321
commit 50bbe05334
4 changed files with 52 additions and 6 deletions

View File

@@ -15,6 +15,7 @@ import { Attachment } from "@server/models";
import AttachmentHelper from "@server/models/helpers/AttachmentHelper";
import { authorize } from "@server/policies";
import FileStorage from "@server/storage/files";
import LocalStorage from "@server/storage/files/LocalStorage";
import { APIContext } from "@server/types";
import { RateLimiterStrategy } from "@server/utils/RateLimiter";
import { getJWTPayload } from "@server/utils/jwt";
@@ -84,12 +85,12 @@ router.get(
"application/octet-stream"
);
ctx.attachment(fileName);
ctx.body = await FileStorage.getFileStream(key);
} else {
const attachment = await Attachment.findOne({
where: { key },
rejectOnEmpty: true,
});
authorize(actor, "read", attachment);
ctx.set("Cache-Control", cacheHeader);
@@ -97,11 +98,47 @@ router.get(
ctx.attachment(attachment.name, {
type: FileStorage.getContentDisposition(attachment.contentType),
});
ctx.body = attachment.stream;
}
// Handle byte range requests
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests
const stats = await (FileStorage as LocalStorage).stat(key);
const range = getByteRange(ctx, stats.size);
if (range) {
ctx.set("Content-Length", String(range.end - range.start + 1));
ctx.set(
"Content-Range",
`bytes ${range.start}-${range.end}/${stats.size}`
);
} else {
ctx.set("Content-Length", String(stats.size));
}
ctx.body = await FileStorage.getFileStream(key, range);
}
);
function getByteRange(
ctx: APIContext<T.FilesGetReq>,
size: number
): { start: number; end: number } | undefined {
const { range } = ctx.headers;
if (!range) {
return;
}
const match = range.match(/bytes=(\d+)-(\d+)?/);
if (!match) {
return;
}
const start = parseInt(match[1], 10);
const end = parseInt(match[2], 10) || size - 1;
return { start, end };
}
function getKeyFromContext(ctx: APIContext<T.FilesGetReq>): string {
const { key, sig } = ctx.input.query;
if (sig) {

View File

@@ -32,7 +32,8 @@ export default abstract class BaseStorage {
* @param key The path to the file
*/
public abstract getFileStream(
key: string
key: string,
range?: { start?: number; end?: number }
): Promise<NodeJS.ReadableStream | null>;
/**

View File

@@ -131,8 +131,12 @@ export default class LocalStorage extends BaseStorage {
};
}
public getFileStream(key: string) {
return Promise.resolve(fs.createReadStream(this.getFilePath(key)));
public getFileStream(key: string, range?: { start: number; end: number }) {
return Promise.resolve(fs.createReadStream(this.getFilePath(key), range));
}
public stat(key: string) {
return fs.stat(this.getFilePath(key));
}
private getFilePath(key: string) {

View File

@@ -201,12 +201,16 @@ export default class S3Storage extends BaseStorage {
});
}
public getFileStream(key: string): Promise<NodeJS.ReadableStream | null> {
public getFileStream(
key: string,
range?: { start: number; end: number }
): Promise<NodeJS.ReadableStream | null> {
return this.client
.send(
new GetObjectCommand({
Bucket: this.getBucket(),
Key: key,
Range: range ? `bytes=${range.start}-${range.end}` : undefined,
})
)
.then((item) => item.Body as NodeJS.ReadableStream)