Files
cnpmcore/app/port/controller/package/ShowPackageController.ts
fengmk2 4427a4fca5 feat: use egg v4 (#747)
BREAKING CHANGE: only support egg >= 4.0.0

the first app on egg v4

https://github.com/eggjs/egg/issues/3644
2025-02-09 15:43:24 +08:00

119 lines
4.5 KiB
TypeScript

import {
HTTPController,
HTTPMethod,
HTTPMethodEnum,
HTTPParam,
Inject,
Context,
EggContext,
} from '@eggjs/tegg';
import { AbstractController } from '../AbstractController';
import { getScopeAndName, FULLNAME_REG_STRING } from '../../../common/PackageUtil';
import { isSyncWorkerRequest } from '../../../common/SyncUtil';
import { PackageManagerService } from '../../../core/service/PackageManagerService';
import { CacheService } from '../../../core/service/CacheService';
import { ABBREVIATED_META_TYPE, SyncMode } from '../../../common/constants';
import { ProxyCacheService } from '../../../core/service/ProxyCacheService';
import { calculateIntegrity } from '../../../common/PackageUtil';
import { DIST_NAMES } from '../../../core/entity/Package';
@HTTPController()
export class ShowPackageController extends AbstractController {
@Inject()
private packageManagerService: PackageManagerService;
@Inject()
private cacheService: CacheService;
@Inject()
private proxyCacheService: ProxyCacheService;
@HTTPMethod({
// GET /:fullname
// https://www.npmjs.com/package/path-to-regexp#custom-matching-parameters
path: `/:fullname(${FULLNAME_REG_STRING})`,
method: HTTPMethodEnum.GET,
})
async show(@Context() ctx: EggContext, @HTTPParam() fullname: string) {
const [ scope, name ] = getScopeAndName(fullname);
const isSync = isSyncWorkerRequest(ctx);
const isFullManifests = ctx.accepts([ 'json', ABBREVIATED_META_TYPE ]) !== ABBREVIATED_META_TYPE;
// handle cache
// fallback to db when cache error
try {
const cacheEtag = await this.cacheService.getPackageEtag(fullname, isFullManifests);
if (!isSync && cacheEtag) {
let requestEtag = ctx.request.get<string>('if-none-match');
if (requestEtag.startsWith('W/')) {
requestEtag = requestEtag.substring(2);
}
if (requestEtag === cacheEtag) {
// make sure CDN cache header set here
this.setCDNHeaders(ctx);
// match etag, set status 304
ctx.status = 304;
return;
}
// get cache pkg data
const cacheBytes = await this.cacheService.getPackageManifests(fullname, isFullManifests);
if (cacheBytes && cacheBytes.length > 0) {
ctx.set('etag', `W/${cacheEtag}`);
ctx.type = 'json';
this.setCDNHeaders(ctx);
return cacheBytes;
}
}
} catch (e) {
this.logger.error(e);
this.logger.error('[ShowPackageController.show:error] get cache error, ignore');
}
// handle cache miss
let result: { etag: string; data: any, blockReason: string };
if (this.config.cnpmcore.syncMode === SyncMode.proxy) {
// proxy mode
const fileType = isFullManifests ? DIST_NAMES.FULL_MANIFESTS : DIST_NAMES.ABBREVIATED_MANIFESTS;
const { data: sourceManifest } = await this.proxyCacheService.getProxyResponse(ctx, { dataType: 'json' });
const pkgManifest = this.proxyCacheService.replaceTarballUrl(sourceManifest, fileType);
const nfsBytes = Buffer.from(JSON.stringify(pkgManifest));
const { shasum: etag } = await calculateIntegrity(nfsBytes);
result = { data: pkgManifest, etag, blockReason: '' };
} else {
// sync mode
if (isFullManifests) {
result = await this.packageManagerService.listPackageFullManifests(scope, name, isSync);
} else {
result = await this.packageManagerService.listPackageAbbreviatedManifests(scope, name, isSync);
}
}
const { etag, data, blockReason } = result;
// 404, no data
if (!etag) {
const allowSync = this.getAllowSync(ctx);
// don't set cdn header, no cdn cache for new package to sync as soon as possible
throw this.createPackageNotFoundErrorWithRedirect(fullname, undefined, allowSync);
}
if (blockReason) {
this.setCDNHeaders(ctx);
throw this.createPackageBlockError(blockReason, fullname);
}
const cacheBytes = Buffer.from(JSON.stringify(data));
// only set cache with normal request
// sync request response with no bug version fixed
if (!isSync) {
ctx.runInBackground(async () => {
await this.cacheService.savePackageEtagAndManifests(fullname, isFullManifests, etag, cacheBytes);
});
}
// set etag
// https://forum.nginx.org/read.php?2,240120,240120#msg-240120
// should set weak etag avoid nginx remove it
ctx.set('etag', `W/${etag}`);
ctx.type = 'json';
this.setCDNHeaders(ctx);
return cacheBytes;
}
}