パッケージ公開に最適化

This commit is contained in:
tamaina
2023-02-05 12:34:04 +00:00
parent c6b14befe9
commit 85a7f47539
24 changed files with 737 additions and 60 deletions

View File

@ -1,16 +1,35 @@
import * as fs from 'node:fs';
import * as stream from 'node:stream';
import * as util from 'node:util';
import * as http from 'node:http';
import * as https from 'node:https';
import got, * as Got from 'got';
import IPCIDR from 'ip-cidr';
import PrivateIp from 'private-ip';
import { StatusError } from './status-error.js';
import config from '../config.js';
import { httpAgent, httpsAgent } from './http.js';
import { getAgents } from './http.js';
const pipeline = util.promisify(stream.pipeline);
export async function downloadUrl(url: string, path: string): Promise<void> {
export type DownloadConfig = {
[x: string]: any;
userAgent: string;
allowedPrivateNetworks: string[];
maxSize: number;
httpAgent: http.Agent,
httpsAgent: https.Agent,
proxy?: boolean;
}
export const defaultDownloadConfig = {
userAgent: `MisskeyMediaProxy/0.0.0`,
allowedPrivateNetworks: [],
maxSize: 262144000,
proxy: false,
...getAgents()
}
export async function downloadUrl(url: string, path: string, settings:DownloadConfig = defaultDownloadConfig): Promise<void> {
if (process.env.NODE_ENV !== 'production') console.log(`Downloading ${url} to ${path} ...`);
const timeout = 30 * 1000;
@ -18,7 +37,7 @@ export async function downloadUrl(url: string, path: string): Promise<void> {
const req = got.stream(url, {
headers: {
'User-Agent': config.userAgent,
'User-Agent': settings.userAgent,
},
timeout: {
lookup: timeout,
@ -30,8 +49,8 @@ export async function downloadUrl(url: string, path: string): Promise<void> {
request: operationTimeout, // whole operation timeout
},
agent: {
http: httpAgent,
https: httpsAgent,
http: settings.httpAgent,
https: settings.httpsAgent,
},
http2: true,
retry: {
@ -39,8 +58,8 @@ export async function downloadUrl(url: string, path: string): Promise<void> {
},
enableUnixSockets: false,
}).on('response', (res: Got.Response) => {
if ((process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') && !config.proxy && res.ip) {
if (isPrivateIp(res.ip)) {
if ((process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') && !settings.proxy && res.ip) {
if (isPrivateIp(res.ip, settings.allowedPrivateNetworks)) {
console.log(`Blocked address: ${res.ip}`);
req.destroy();
}
@ -49,14 +68,14 @@ export async function downloadUrl(url: string, path: string): Promise<void> {
const contentLength = res.headers['content-length'];
if (contentLength != null) {
const size = Number(contentLength);
if (size > config.maxSize) {
console.log(`maxSize exceeded (${size} > ${config.maxSize}) on response`);
if (size > settings.maxSize) {
console.log(`maxSize exceeded (${size} > ${settings.maxSize}) on response`);
req.destroy();
}
}
}).on('downloadProgress', (progress: Got.Progress) => {
if (progress.transferred > config.maxSize) {
console.log(`maxSize exceeded (${progress.transferred} > ${config.maxSize}) on downloadProgress`);
if (progress.transferred > settings.maxSize) {
console.log(`maxSize exceeded (${progress.transferred} > ${settings.maxSize}) on downloadProgress`);
req.destroy();
}
});
@ -75,8 +94,8 @@ export async function downloadUrl(url: string, path: string): Promise<void> {
}
function isPrivateIp(ip: string): boolean {
for (const net of config.allowedPrivateNetworks ?? []) {
function isPrivateIp(ip: string, allowedPrivateNetworks: string[]): boolean {
for (const net of allowedPrivateNetworks ?? []) {
const cidr = new IPCIDR(net);
if (cidr.contains(ip)) {
return false;

View File

@ -2,7 +2,6 @@ import * as http from 'node:http';
import * as https from 'node:https';
import CacheableLookup from 'cacheable-lookup';
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent';
import config from "../config.js";
const cache = new CacheableLookup({
maxTtl: 3600, // 1hours
@ -22,24 +21,31 @@ const _https = new https.Agent({
lookup: cache.lookup,
} as https.AgentOptions);
export const httpAgent = config.proxy
? new HttpProxyAgent({
keepAlive: true,
keepAliveMsecs: 30 * 1000,
maxSockets: 256,
maxFreeSockets: 256,
scheduling: 'lifo',
proxy: config.proxy,
})
: _http;
export function getAgents(proxy?: string) {
const httpAgent = proxy
? new HttpProxyAgent({
keepAlive: true,
keepAliveMsecs: 30 * 1000,
maxSockets: 256,
maxFreeSockets: 256,
scheduling: 'lifo',
proxy: proxy,
})
: _http;
export const httpsAgent = config.proxy
? new HttpsProxyAgent({
keepAlive: true,
keepAliveMsecs: 30 * 1000,
maxSockets: 256,
maxFreeSockets: 256,
scheduling: 'lifo',
proxy: config.proxy,
})
: _https;
const httpsAgent = proxy
? new HttpsProxyAgent({
keepAlive: true,
keepAliveMsecs: 30 * 1000,
maxSockets: 256,
maxFreeSockets: 256,
scheduling: 'lifo',
proxy: proxy,
})
: _https;
return {
httpAgent,
httpsAgent,
};
}

View File

@ -1,4 +1,6 @@
import * as fs from 'node:fs';
import * as http from 'node:http';
import * as https from 'node:https';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
import fastifyStatic from '@fastify/static';
@ -9,14 +11,60 @@ import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOption
import { detectType, isMimeImage } from './file-info.js';
import sharp from 'sharp';
import { StatusError } from './status-error.js';
import { downloadUrl } from './download.js';
import { DownloadConfig, defaultDownloadConfig, downloadUrl } from './download.js';
import { getAgents } from './http.js';
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
const assets = `${_dirname}/../../server/file/assets/`;
export default function (fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
export type MediaProxyOptions = {
userAgent?: string;
allowedPrivateNetworks?: string[];
maxSize?: number;
} & ({
proxy?: string;
} | {
httpAgent: http.Agent;
httpsAgent: https.Agent;
});
let config: DownloadConfig = defaultDownloadConfig;
export function setMediaProxyConfig(setting?: MediaProxyOptions | null) {
const proxy = process.env.HTTP_PROXY ?? process.env.http_proxy;
if (!setting) {
config = {
...defaultDownloadConfig,
...(proxy ? getAgents(proxy) : {}),
proxy: !!proxy,
};
console.log(config);
return;
}
config = {
userAgent: setting.userAgent ?? defaultDownloadConfig.userAgent,
allowedPrivateNetworks: setting.allowedPrivateNetworks ?? defaultDownloadConfig.allowedPrivateNetworks,
maxSize: setting.maxSize ?? defaultDownloadConfig.maxSize,
...('proxy' in setting ?
{ ...getAgents(setting.proxy), proxy: !!setting.proxy } :
'httpAgent' in setting ? {
httpAgent: setting.httpAgent,
httpsAgent: setting.httpsAgent,
proxy: true,
} :
{ ...getAgents(proxy), proxy: !!proxy }),
};
console.log(config);
}
export default function (fastify: FastifyInstance, options: MediaProxyOptions | null | undefined, done: (err?: Error) => void) {
setMediaProxyConfig(options);
fastify.addHook('onRequest', (request, reply, done) => {
reply.header('Content-Security-Policy', `default-src 'none'; img-src 'self'; media-src 'self'; style-src 'unsafe-inline'`);
done();
@ -57,9 +105,9 @@ function errorHandler(request: FastifyRequest<{ Params?: { [x: string]: any }; Q
}
async function proxyHandler(request: FastifyRequest<{ Params: { url: string; }; Querystring: { url?: string; }; }>, reply: FastifyReply) {
const url = 'url' in request.query ? request.query.url : 'https://' + request.params.url;
const url = 'url' in request.query ? request.query.url : (request.params.url && 'https://' + request.params.url);
if (typeof url !== 'string') {
if (!url || typeof url !== 'string') {
reply.code(400);
return;
}
@ -171,7 +219,7 @@ async function downloadAndDetectTypeFromUrl(url: string): Promise<
> {
const [path, cleanup] = await createTemp();
try {
await downloadUrl(url, path);
await downloadUrl(url, path, config);
const { mime, ext } = await detectType(path);