Skip to content

Commit

Permalink
send-drive-file のリファクタなど
Browse files Browse the repository at this point in the history
  • Loading branch information
mei23 committed Aug 12, 2020
1 parent c213b98 commit 71de0af
Showing 1 changed file with 58 additions and 87 deletions.
145 changes: 58 additions & 87 deletions src/server/file/send-drive-file.ts
@@ -1,10 +1,10 @@
import * as Router from '@koa/router';
import * as send from 'koa-send';
import * as mongodb from 'mongodb';
import * as tmp from 'tmp';
import * as fs from 'fs';
import * as stream from 'stream';
import * as rename from 'rename';
import DriveFile, { getDriveFileBucket } from '../../models/drive-file';
import DriveFile, { getDriveFileBucket, IDriveFile } from '../../models/drive-file';
import DriveFileThumbnail, { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail';
import DriveFileWebpublic, { getDriveFileWebpublicBucket } from '../../models/drive-file-webpublic';
import { serverLogger } from '..';
Expand All @@ -16,38 +16,36 @@ import { detectType } from '../../misc/get-file-info';
import { downloadUrl } from '../../misc/download-url';
import { InternalStorage } from '../../services/drive/internal-storage';

const assets = `${__dirname}/../../server/file/assets/`;

const commonReadableHandlerGenerator = (ctx: Router.RouterContext) => (e: Error): void => {
serverLogger.error(e);
ctx.status = 500;
ctx.set('Cache-Control', 'max-age=300');
};

export default async function(ctx: Router.RouterContext) {
// Validate id
//#region Validate id
if (!mongodb.ObjectID.isValid(ctx.params.id)) {
ctx.throw(400, 'incorrect id');
return;
return await sendError(ctx, 404);
}
//#endregion

//#region Fetch driveFile
const fileId = new mongodb.ObjectID(ctx.params.id);

// Fetch drive file
const file = await DriveFile.findOne({ _id: fileId });

if (file == null) {
ctx.status = 404;
ctx.set('Cache-Control', 'max-age=86400');
await send(ctx, '/dummy.png', { root: assets });
return;
return await sendError(ctx, 404);
}
//#endregion

// 未保存/期限切れリモートファイル
if (file.metadata.withoutChunks && (file.metadata.isRemote || file.metadata._user && file.metadata._user.host != null)) {
//#region 未保存/期限切れリモートファイル
if (file.metadata?.withoutChunks && (file.metadata.isRemote || file.metadata._user.host != null)) {
// urlは過去のバグで張り替え忘れている可能性があるためuriを優先する
const url = file.metadata.uri || file.metadata.url;

if (url == null) {
return await sendError(ctx, 404);
}

// Create temp file
const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
tmp.file((e, path, fd, cleanup) => {
Expand Down Expand Up @@ -80,46 +78,32 @@ export default async function(ctx: Router.RouterContext) {
};

const file = await convertFile();
ctx.body = file.data;
ctx.set('Content-Type', file.type);
ctx.set('Cache-Control', 'max-age=31536000, immutable');
return await sendNormal(ctx, file.data, file.type);
} catch (e) {
serverLogger.error(e);

if (typeof e == 'number' && e >= 400 && e < 500) {
ctx.status = e;
ctx.set('Cache-Control', 'max-age=86400');
} else {
ctx.status = 500;
ctx.set('Cache-Control', 'max-age=300');
}
return await sendError(ctx, typeof e === 'number' && e >= 400 && e < 500 ? e : 500);
} finally {
cleanup();
}
return;
}
//#endregion 未保存/期限切れリモートファイル

// 削除済み
if (file.metadata.deletedAt) {
ctx.status = 410;
ctx.set('Cache-Control', 'max-age=86400');
await send(ctx, '/tombstone.png', { root: assets });
return;
if (file.metadata?.deletedAt) {
return await sendError(ctx, 410);
}

// ローカル保存じゃないのにここに来た
if (file.metadata.withoutChunks) {
ctx.status = 204;
ctx.set('Cache-Control', 'max-age=86400');
return;
if (file.metadata?.withoutChunks) {
return await sendError(ctx, 404);
}

// ファイルシステム格納
//#region ファイルシステム格納
if (file.metadata?.fileSystem) {
const isThumbnail = 'thumbnail' in ctx.query;
const isWebpublic = 'web' in ctx.query;

if (isThumbnail || isWebpublic) {
if (isThumbnail || isWebpublic) { // オリジナル以外
const key = isThumbnail ? file.metadata.storageProps?.thumbnailKey : (file.metadata.storageProps?.webpublicKey || file.metadata.storageProps?.key);
if (!key) throw 'fs but key not found';

Expand All @@ -129,87 +113,74 @@ export default async function(ctx: Router.RouterContext) {
extname: ext ? `.${ext}` : undefined
}).toString();

ctx.body = InternalStorage.read(key);
ctx.set('Content-Type', mime);
ctx.set('Cache-Control', 'max-age=31536000, immutable');
ctx.set('Content-Disposition', contentDisposition('inline', filename));
return;
return await sendNormal(ctx, InternalStorage.read(key), mime, filename);
} else { // オリジナル
// オリジナルはキーチェック
if (file.metadata && file.metadata.accessKey && file.metadata.accessKey != ctx.query['original']) {
ctx.status = 403;
ctx.set('Cache-Control', 'max-age=86400');
return;
return await sendError(ctx, 403);
}

const key = file.metadata.storageProps?.key;
if (!key) throw 'fs but key not found';

const readable = InternalStorage.read(key);
readable.on('error', commonReadableHandlerGenerator(ctx));
ctx.body = readable;
ctx.set('Content-Type', file.contentType);
ctx.set('Cache-Control', 'max-age=31536000, immutable');
ctx.set('Content-Disposition', contentDisposition('inline', file.filename));
return;
return await sendNormal(ctx, readable, file.contentType, file.filename);
}
}
//#endregion ファイルシステム格納

const sendRaw = async () => {
if (file.metadata && file.metadata.accessKey && file.metadata.accessKey != ctx.query['original']) {
ctx.status = 403;
ctx.set('Cache-Control', 'max-age=86400');
return;
}

const bucket = await getDriveFileBucket();
const readable = bucket.openDownloadStream(fileId);
readable.on('error', commonReadableHandlerGenerator(ctx));
ctx.body = readable;
ctx.set('Content-Type', file.contentType);
ctx.set('Cache-Control', 'max-age=31536000, immutable');
};

//#region DB格納
if ('thumbnail' in ctx.query) {
const thumb = await DriveFileThumbnail.findOne({
'metadata.originalId': fileId
'metadata.originalId': file._id
});

if (thumb != null) {
ctx.set('Content-Type', thumb.contentType || 'image/jpeg');
ctx.set('Content-Disposition', contentDisposition('inline', `${rename(file.filename, { suffix: '-thumb', extname: '.jpeg' })}`));
const bucket = await getDriveFileThumbnailBucket();
ctx.body = bucket.openDownloadStream(thumb._id);
ctx.set('Cache-Control', 'max-age=31536000, immutable');
return await sendNormal(ctx, bucket.openDownloadStream(thumb._id), thumb.contentType || 'image/jpeg', `${rename(file.filename, { suffix: '-thumb', extname: '.jpg' })}`);
} else {
if (file.contentType.startsWith('image/')) {
ctx.set('Content-Disposition', contentDisposition('inline', `${file.filename}`));
await sendRaw();
return await sendRaw(ctx, file);
} else {
ctx.status = 404;
ctx.set('Cache-Control', 'max-age=86400');
await send(ctx, '/thumbnail-not-available.png', { root: assets });
return await sendError(ctx, 404);
}
}
} else if ('web' in ctx.query) {
const web = await DriveFileWebpublic.findOne({
'metadata.originalId': fileId
'metadata.originalId': file._id
});

if (web != null) {
ctx.set('Content-Type', web.contentType || file.contentType);
ctx.set('Content-Disposition', contentDisposition('inline', `${rename(file.filename, { suffix: '-web' })}`));

const bucket = await getDriveFileWebpublicBucket();
ctx.body = bucket.openDownloadStream(web._id);
ctx.set('Cache-Control', 'max-age=31536000, immutable');
return await sendNormal(ctx, bucket.openDownloadStream(web._id), web.contentType || file.contentType, `${rename(file.filename, { suffix: '-web' })}`);
} else {
ctx.set('Content-Disposition', contentDisposition('inline', `${file.filename}`));
await sendRaw();
return await sendRaw(ctx, file);
}
} else {
ctx.set('Content-Disposition', contentDisposition('inline', `${file.filename}`));
return await sendRaw(ctx, file);
}
}

await sendRaw();
async function sendRaw(ctx: Router.RouterContext, file: IDriveFile): Promise<void> {
if (file.metadata && file.metadata.accessKey && file.metadata.accessKey != ctx.query['original']) {
return await sendError(ctx, 403);
}

const bucket = await getDriveFileBucket();
const readable = bucket.openDownloadStream(file._id);
readable.on('error', commonReadableHandlerGenerator(ctx));
return await sendNormal(ctx, readable, file.contentType, file.filename);
}

async function sendNormal(ctx: Router.RouterContext, body: Buffer | stream.Stream, contentType: string, filename?: string): Promise<void> {
ctx.body = body;
ctx.set('Content-Type', contentType);
ctx.set('Cache-Control', 'public, max-age=31536000, immutable');
if (filename) ctx.set('Content-Disposition', contentDisposition('inline', filename));
}

async function sendError(ctx: Router.RouterContext, status: number): Promise<void> {
ctx.status = status;
ctx.set('Cache-Control', 'public, max-age=3600');
}

0 comments on commit 71de0af

Please sign in to comment.