Commit fc9c3eaf authored by nanahira's avatar nanahira

support inm

parent 5d1f5135
Pipeline #40986 passed with stages
in 3 minutes and 7 seconds
import { Body, Controller, Get, Header, Ip, Param, ParseArrayPipe, Post, Query, Render, Res, ValidationPipe } from '@nestjs/common'; import { Body, Controller, Get, Ip, Param, ParseArrayPipe, Post, Query, Render, Req, Res, ValidationPipe } from '@nestjs/common';
import { UpdateService } from './update.service'; import { UpdateService } from './update.service';
import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger';
import { DepotDto } from '../dto/Depot.dto'; import { DepotDto } from '../dto/Depot.dto';
import { Response } from 'express'; import { Request, Response } from 'express';
import { createHash } from 'crypto'; import { createHash } from 'crypto';
function md5HexOfString(s: string) {
return createHash('md5').update(s).digest('hex');
}
/**
* 以“稳定字节串”为准计算 ETag,并发送响应。
* - 不捕获异常,让 Nest 接管错误处理
* - 若 If-None-Match 命中,返回 304
* - 若未显式设置 Cache-Control,则使用传入的默认值
*/
async function stableSend(
req: Request,
res: Response,
body: any | Promise<any>,
defaultCache = 'public, max-age=31536000, immutable',
contentType = 'application/json; charset=utf-8'
): Promise<void> {
const bodyResolved = await body;
const bodyString = typeof bodyResolved === 'string' ? bodyResolved : JSON.stringify(bodyResolved);
const hash = md5HexOfString(bodyString);
const quoted = `"${hash}"`;
// 协商缓存:If-None-Match 命中则 304
const inm = req.headers['if-none-match'];
if (typeof inm === 'string' && inm === quoted) {
res.setHeader('ETag', quoted);
if (res.getHeader('Cache-Control') === undefined) {
res.setHeader('Cache-Control', defaultCache);
}
res.status(304).end();
return;
}
// 设置头
res.setHeader('ETag', quoted);
if (res.getHeader('Cache-Control') === undefined) {
res.setHeader('Cache-Control', defaultCache);
}
if (!res.getHeader('Content-Type')) {
res.setHeader('Content-Type', contentType);
}
// 发送“刚刚参与了 MD5 计算”的同一份字节串
res.end(bodyString);
}
@Controller('update') @Controller('update')
@ApiTags('update') @ApiTags('update')
export class UpdateController { export class UpdateController {
constructor(private readonly updateService: UpdateService) {} constructor(private readonly updateService: UpdateService) {}
@Get('apps.json')
@ApiOperation({ summary: '获取 apps.json', description: '懒得解释这是啥了……' })
async getAppsJson(@Res({ passthrough: true }) res: Response) {
const result = await this.updateService.getAppsJson();
this.addETag(res, JSON.stringify(result));
res.setHeader('Cache-Control', 'public, max-age=600, stale-while-revalidate=600, stale-if-error=604800');
return result;
}
private addETag(res: Response, data: any) { private async stableReturn<T>(res: Response, prom: Promise<T> | T, cache = 'public, max-age=31536000, immutable') {
const hash = createHash('md5').update(JSON.stringify(data)).digest('hex'); const result = await prom;
const stringified = JSON.stringify(result);
const hash = createHash('md5').update(stringified).digest('hex');
res.setHeader('ETag', `"${hash}"`); res.setHeader('ETag', `"${hash}"`);
if (res.getHeader('Cache-Control') === undefined) res.setHeader('Cache-Control', cache);
res.end(stringified);
// return result;
} }
private async cacheResult<T>(res: Response, prom: Promise<T>) { @Get('apps.json')
const result = await prom; @ApiOperation({ summary: '获取 apps.json', description: '懒得解释这是啥了……' })
this.addETag(res, result); async getAppsJson(@Req() req: Request, @Res({ passthrough: true }) res: Response) {
if (res.getHeader('Cache-Control') === undefined) res.setHeader('Cache-Control', 'public, max-age=31536000, immutable'); await stableSend(req, res, this.updateService.getAppsJson(), 'public, max-age=600, stale-while-revalidate=600, stale-if-error=604800');
return result; return;
} }
@Get('checksums/:id/:version') @Get('checksums/:id/:version')
@Render('checksums')
@ApiOperation({ summary: '获取 app 校验和', description: '是 shasum 的格式' }) @ApiOperation({ summary: '获取 app 校验和', description: '是 shasum 的格式' })
@ApiParam({ name: 'id', description: 'APP 的 id' }) @ApiParam({ name: 'id', description: 'APP 的 id' })
@ApiParam({ name: 'version', description: 'APP 的版本号' }) @ApiParam({ name: 'version', description: 'APP 的版本号' })
@ApiOkResponse({ type: String }) @ApiOkResponse({ type: String })
// @Header('Cache-Control', 'public, max-age=31536000, immutable') // @Header('Cache-Control', 'public, max-age=31536000, immutable') // 可留给 stableSend 兜底
async getChecksum( async getChecksum(
@Req() req: Request,
@Res({ passthrough: true }) res: Response,
@Param('id') id: string, @Param('id') id: string,
@Query(new ValidationPipe({ transform: true })) depot: DepotDto, @Query(new ValidationPipe({ transform: true })) depot: DepotDto,
@Param('version') version: string, @Param('version') version: string,
@Res({ passthrough: true }) res: Response
) { ) {
return this.cacheResult(res, this.updateService.getChecksum(id, depot, version)); await stableSend(req, res, this.updateService.getChecksum(id, depot, version));
return;
} }
@Get('metalinks/:id/:version') @Get('metalinks/:id/:version')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment