Skip to content

Commit

Permalink
feat: use stream instead of temp file.
Browse files Browse the repository at this point in the history
  • Loading branch information
hezhengxu2018 committed May 2, 2024
1 parent b77ef75 commit ec6d85d
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 80 deletions.
16 changes: 8 additions & 8 deletions app/core/service/ProxyCacheService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { readFile, rm } from 'node:fs/promises';
import { EggHttpClient } from 'egg';
import { EggHttpClient, HttpClientResponse } from 'egg';
import { InternalServerError, ForbiddenError, HttpError, NotFoundError } from 'egg-errors';
import { SingletonProto, AccessLevel, Inject } from '@eggjs/tegg';
import { BackgroundTaskHelper } from '@eggjs/tegg-background-task';
Expand All @@ -13,7 +12,6 @@ import { ProxyCache } from '../entity/ProxyCache';
import { Task, UpdateProxyCacheTaskOptions, CreateUpdateProxyCacheTask } from '../entity/Task';
import { ProxyCacheRepository } from '../../repository/ProxyCacheRepository';
import { TaskType, TaskState } from '../../common/enum/Task';
import { downloadToTempfile } from '../../common/FileUtil';
import { calculateIntegrity } from '../../common/PackageUtil';
import { PROXY_CACHE_DIR_NAME } from '../../common/constants';
import { DIST_NAMES } from '../entity/Package';
Expand Down Expand Up @@ -52,15 +50,17 @@ export class ProxyCacheService extends AbstractService {
@Inject()
private readonly backgroundTaskHelper:BackgroundTaskHelper;

async getPackageVersionTarBuffer(fullname: string, url: string): Promise<Buffer| null> {
async getPackageVersionTarResponse(fullname: string, url: string): Promise<HttpClientResponse> {
if (this.config.cnpmcore.syncPackageBlockList.includes(fullname)) {
throw new ForbiddenError(`stop proxy by block list: ${JSON.stringify(this.config.cnpmcore.syncPackageBlockList)}`);
}
const requestTgzURL = `${this.npmRegistry.registry}${url}`;
const { tmpfile } = await downloadToTempfile(this.httpclient, this.config.dataDir, requestTgzURL);
const tgzBuffer = await readFile(tmpfile);
await rm(tmpfile, { force: true });
return tgzBuffer;
return await this.httpclient.request(requestTgzURL, {
timeout: 60000 * 10,
streaming: true,
timing: true,
followRedirect: true,
}) as HttpClientResponse;
}

async getPackageManifest(fullname: string, fileType: DIST_NAMES.FULL_MANIFESTS| DIST_NAMES.ABBREVIATED_MANIFESTS): Promise<AbbreviatedPackageManifestType|PackageManifestType> {
Expand Down
114 changes: 49 additions & 65 deletions app/port/config.ts
Original file line number Diff line number Diff line change
@@ -1,181 +1,165 @@
import {
SyncDeleteMode,
SyncMode,
ChangesStreamMode,
} from '../common/constants';
import { SyncDeleteMode, SyncMode, ChangesStreamMode } from '../common/constants';

export { cnpmcoreConfig } from '../../config/config.default';

export type BaseCnpmcoreConfig = {
name: string;
export type CnpmcoreConfig = {
name: string,
/**
* enable hook or not
*/
hookEnable: boolean;
hookEnable: boolean,
/**
* mac custom hooks count
*/
hooksLimit: number;
hooksLimit: number,
/**
* upstream registry url
*/
sourceRegistry: string;
sourceRegistry: string,
/**
* upstream registry is base on `cnpmcore` or not
* if your upstream is official npm registry, please turn it off
*/
sourceRegistryIsCNpm: boolean;
sourceRegistryIsCNpm: boolean,
/**
* sync upstream first
*/
syncUpstreamFirst: boolean;
syncUpstreamFirst: boolean,
/**
* sync upstream timeout, default is 3mins
*/
sourceRegistrySyncTimeout: number;
sourceRegistrySyncTimeout: number,
/**
* sync task high water size, default is 100
*/
taskQueueHighWaterSize: number;
taskQueueHighWaterSize: number,
/**
* sync mode
* - none: don't sync npm package
* - admin: don't sync npm package,only admin can create sync task by sync contorller.
* - all: sync all npm packages
* - exist: only sync exist packages, effected when `enableCheckRecentlyUpdated` or `enableChangesStream` is enabled
* - proxy: don't sync npm package, create sync task when package requested, like nexus proxy repository. `redirectNotFound` must be false when syncMode is `proxy`.
* @see https://help.sonatype.com/repomanager3/using-nexus-repository/repository-manager-concepts/proxy-repository-concepts
*/
syncMode: SyncMode;
syncDeleteMode: SyncDeleteMode;
syncPackageWorkerMaxConcurrentTasks: number;
triggerHookWorkerMaxConcurrentTasks: number;
createTriggerHookWorkerMaxConcurrentTasks: number;
syncMode: SyncMode,
syncDeleteMode: SyncDeleteMode,
syncPackageWorkerMaxConcurrentTasks: number,
triggerHookWorkerMaxConcurrentTasks: number,
createTriggerHookWorkerMaxConcurrentTasks: number,
/**
* stop syncing these packages in future
*/
syncPackageBlockList: string[];
syncPackageBlockList: string[],
/**
* check recently from https://www.npmjs.com/browse/updated, if use set changesStreamRegistry to cnpmcore,
* maybe you should disable it
*/
enableCheckRecentlyUpdated: boolean;
enableCheckRecentlyUpdated: boolean,
/**
* mirror binary, default is false
*/
enableSyncBinary: boolean;
enableSyncBinary: boolean,
/**
* sync binary source api, default is `${sourceRegistry}/-/binary`
*/
syncBinaryFromAPISource: string;
syncBinaryFromAPISource: string,
/**
* enable sync downloads data from source registry https://github.com/cnpm/cnpmcore/issues/108
* all three parameters must be configured at the same time to take effect
*/
enableSyncDownloadData: boolean;
syncDownloadDataSourceRegistry: string;
enableSyncDownloadData: boolean,
syncDownloadDataSourceRegistry: string,
/**
* should be YYYY-MM-DD format
*/
syncDownloadDataMaxDate: string;
syncDownloadDataMaxDate: string,
/**
* @see https://github.com/npm/registry-follower-tutorial
*/
enableChangesStream: boolean;
checkChangesStreamInterval: number;
changesStreamRegistry: string;
enableChangesStream: boolean,
checkChangesStreamInterval: number,
changesStreamRegistry: string,
/**
* handle _changes request mode, default is 'streaming', please set it to 'json' when on cnpmcore registry
*/
changesStreamRegistryMode: ChangesStreamMode;
changesStreamRegistryMode: ChangesStreamMode,
/**
* registry url
*/
registry: string;
registry: string,
/**
* https://docs.npmjs.com/cli/v6/using-npm/config#always-auth npm <= 6
* if `alwaysAuth=true`, all api request required access token
*/
alwaysAuth: boolean;
alwaysAuth: boolean,
/**
* white scope list
*/
allowScopes: string[];
allowScopes: string [],
/**
* allow publish non-scope package, disable by default
*/
allowPublishNonScopePackage: boolean;
allowPublishNonScopePackage: boolean,
/**
* Public registration is allowed, otherwise only admins can login
*/
allowPublicRegistration: boolean;
allowPublicRegistration: boolean,
/**
* default system admins
*/
admins: Record<string, string>;
admins: Record<string, string>,
/**
* use webauthn for login, https://webauthn.guide/
* only support platform authenticators, browser support: https://webauthn.me/browser-support
*/
enableWebAuthn: boolean;
enableWebAuthn: boolean,
/**
* http response cache control header
*/
enableCDN: boolean;
enableCDN: boolean,
/**
* if you are using CDN, can override it
* it meaning cache 300s on CDN server and client side.
*/
cdnCacheControlHeader: string;
cdnCacheControlHeader: string,
/**
* if you are using CDN, can set it to 'Accept, Accept-Encoding'
*/
cdnVaryHeader: string;
cdnVaryHeader: string,
/**
* store full package version manifests data to database table(package_version_manifests), default is false
*/
enableStoreFullPackageVersionManifestsToDatabase: boolean;
enableStoreFullPackageVersionManifestsToDatabase: boolean,
/**
* only support npm as client and npm >= 7.0.0 allow publish action
*/
enableNpmClientAndVersionCheck: boolean;
enableNpmClientAndVersionCheck: boolean,
/**
* sync when package not found, only effect when syncMode = all/exist
*/
syncNotFound: boolean;
syncNotFound: boolean,
/**
* redirect to source registry when package not found
*/
redirectNotFound: boolean;
redirectNotFound: boolean,
/**
* enable unpkg features, https://github.com/cnpm/cnpmcore/issues/452
*/
enableUnpkg: boolean;
enableUnpkg: boolean,
/**
* enable this would make sync specific version task not append latest version into this task automatically,it would mark the local latest stable version as latest tag.
* in most cases, you should set to false to keep the same behavior as source registry.
*/
strictSyncSpecivicVersion: boolean;
strictSyncSpecivicVersion: boolean,
/**
* enable elasticsearch
*/
enableElasticsearch: boolean;
* enable elasticsearch
*/
enableElasticsearch: boolean,
/**
* elasticsearch index. if enableElasticsearch is true, you must set a index to write es doc.
*/
elasticsearchIndex: string;
* elasticsearch index. if enableElasticsearch is true, you must set a index to write es doc.
*/
elasticsearchIndex: string,
/**
* strictly enforces/validates manifest and tgz when publish, https://github.com/cnpm/cnpmcore/issues/542
*/
strictValidateTarballPkg?: boolean;
};

// `redirectNotFound` must be false when syncMode is `proxy`.
type ProxyModeRestrict = { syncMode: SyncMode.proxy; redirectNotFound: false };

type BaseModeRestrict = {
syncMode: Exclude<SyncMode, SyncMode.proxy>;
redirectNotFound: boolean;
strictValidateTarballPkg?: boolean,
};

export type CnpmcoreConfig = BaseCnpmcoreConfig & (ProxyModeRestrict | BaseModeRestrict);
14 changes: 7 additions & 7 deletions app/port/controller/package/DownloadPackageVersionTar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,9 @@ export class DownloadPackageVersionTarController extends AbstractController {
} catch (error) {
if (this.config.cnpmcore.syncMode === SyncMode.proxy) {
// proxy mode package version not found.
const tgzBuffer = await this.#getTgzBuffer(ctx, fullname, version);
const tgzStream = await this.#getTgzStream(ctx, fullname, version);
this.packageManagerService.plusPackageVersionCounter(fullname, version);
ctx.attachment(`${filenameWithVersion}.tgz`);
return tgzBuffer;
return tgzStream;
}
throw error;
}
Expand Down Expand Up @@ -113,10 +112,11 @@ export class DownloadPackageVersionTarController extends AbstractController {
return await this.download(ctx, fullname, filenameWithVersion);
}

async #getTgzBuffer(ctx: EggContext, fullname: string, version: string) {
const tgzBuffer = await this.proxyCacheService.getPackageVersionTarBuffer(fullname, ctx.url);
async #getTgzStream(ctx: EggContext, fullname: string, version: string) {
const { res: tgzStream, headers, status } = await this.proxyCacheService.getPackageVersionTarResponse(fullname, ctx.url);
ctx.status = status;
ctx.set(headers as { [key: string]: string | string[] });
ctx.runInBackground(async () => {
// create sync task
const task = await this.packageSyncerService.createTask(fullname, {
authorIp: ctx.ip,
authorId: `pid_${process.pid}`,
Expand All @@ -127,6 +127,6 @@ export class DownloadPackageVersionTarController extends AbstractController {
ctx.logger.info('[DownloadPackageVersionTarController.createSyncTask:success] taskId: %s, fullname: %s',
task.taskId, fullname);
});
return tgzBuffer;
return tgzStream;
}
}

0 comments on commit ec6d85d

Please sign in to comment.