fix(server): renderer path (#8305)

This commit is contained in:
forehalo
2024-09-19 08:16:13 +00:00
parent 5d57f53a06
commit 260104c933
2 changed files with 65 additions and 50 deletions

View File

@@ -60,15 +60,8 @@ spec:
name: affine-graphql name: affine-graphql
port: port:
number: {{ .Values.graphql.service.port }} number: {{ .Values.graphql.service.port }}
- path: /workspace/([^/]+)/(home|all|search|collection|tag|trash) - path: /workspace
pathType: ImplementationSpecific pathType: Prefix
backend:
service:
name: affine-web
port:
number: {{ .Values.web.service.port }}
- path: /workspace/([^/]+)/.+
pathType: ImplementationSpecific
backend: backend:
service: service:
name: affine-renderer name: affine-renderer
@@ -81,11 +74,4 @@ spec:
name: affine-web name: affine-web
port: port:
number: {{ .Values.web.service.port }} number: {{ .Values.web.service.port }}
- path: /js/worker.(.+).js
pathType: ImplementationSpecific
backend:
service:
name: affine-web
port:
number: {{ .Values.web.service.port }}
{{- end }} {{- end }}

View File

@@ -1,7 +1,7 @@
import { readFileSync } from 'node:fs'; import { readFileSync } from 'node:fs';
import { join } from 'node:path'; import { join } from 'node:path';
import { Controller, Get, Logger, Param, Req, Res } from '@nestjs/common'; import { Controller, Get, Logger, Req, Res } from '@nestjs/common';
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import isMobile from 'is-mobile'; import isMobile from 'is-mobile';
@@ -18,6 +18,7 @@ interface RenderOptions {
} }
interface HtmlAssets { interface HtmlAssets {
html: string;
css: string[]; css: string[];
js: string[]; js: string[];
publicPath: string; publicPath: string;
@@ -26,6 +27,7 @@ interface HtmlAssets {
} }
const defaultAssets: HtmlAssets = { const defaultAssets: HtmlAssets = {
html: '',
css: [], css: [],
js: [], js: [],
publicPath: '/', publicPath: '/',
@@ -33,6 +35,16 @@ const defaultAssets: HtmlAssets = {
description: '', description: '',
}; };
// TODO(@forehalo): reuse routes with frontend
const staticPaths = new Set([
'all',
'home',
'search',
'collection',
'tag',
'trash',
]);
@Controller('/workspace') @Controller('/workspace')
export class DocRendererController { export class DocRendererController {
private readonly logger = new Logger(DocRendererController.name); private readonly logger = new Logger(DocRendererController.name);
@@ -45,36 +57,23 @@ export class DocRendererController {
private readonly config: Config, private readonly config: Config,
private readonly url: URLHelper private readonly url: URLHelper
) { ) {
try { this.webAssets = this.readHtmlAssets(
const webConfigMapsPath = join( join(
this.config.projectRoot, this.config.projectRoot,
this.config.isSelfhosted ? 'static/selfhost' : 'static', this.config.isSelfhosted ? 'static/selfhost' : 'static'
'assets-manifest.json' )
); );
const mobileConfigMapsPath = join( this.mobileAssets = this.readHtmlAssets(
join(
this.config.projectRoot, this.config.projectRoot,
this.config.isSelfhosted ? 'static/mobile/selfhost' : 'static/mobile', this.config.isSelfhosted ? 'static/mobile/selfhost' : 'static/mobile'
'assets-manifest.json' )
); );
this.webAssets = JSON.parse(readFileSync(webConfigMapsPath, 'utf-8'));
this.mobileAssets = JSON.parse(
readFileSync(mobileConfigMapsPath, 'utf-8')
);
} catch (e) {
if (this.config.node.prod) {
throw e;
}
}
} }
@Public() @Public()
@Get('/:workspaceId/:docId') @Get('/*')
async render( async render(@Req() req: Request, @Res() res: Response) {
@Req() req: Request,
@Res() res: Response,
@Param('workspaceId') workspaceId: string,
@Param('docId') docId: string
) {
const assets: HtmlAssets = const assets: HtmlAssets =
this.config.affine.canary && this.config.affine.canary &&
isMobile({ isMobile({
@@ -84,14 +83,20 @@ export class DocRendererController {
: this.webAssets; : this.webAssets;
let opts: RenderOptions | null = null; let opts: RenderOptions | null = null;
try { // /workspace/:workspaceId/{:docId | staticPaths}
opts = const [, , workspaceId, subPath, ...restPaths] = req.path.split('/');
workspaceId === docId
? await this.getWorkspaceContent(workspaceId) // /:workspaceId/:docId
: await this.getPageContent(workspaceId, docId); if (workspaceId && !staticPaths.has(subPath) && restPaths.length === 0) {
metrics.doc.counter('render').add(1); try {
} catch (e) { opts =
this.logger.error('failed to render page', e); workspaceId === subPath
? await this.getWorkspaceContent(workspaceId)
: await this.getPageContent(workspaceId, subPath);
metrics.doc.counter('render').add(1);
} catch (e) {
this.logger.error('failed to render page', e);
}
} }
res.setHeader('Content-Type', 'text/html'); res.setHeader('Content-Type', 'text/html');
@@ -148,6 +153,10 @@ export class DocRendererController {
} }
_render(opts: RenderOptions | null, assets: HtmlAssets): string { _render(opts: RenderOptions | null, assets: HtmlAssets): string {
if (!opts && assets.html) {
return assets.html;
}
const title = opts?.title const title = opts?.title
? htmlSanitize(`${opts.title} | AFFiNE`) ? htmlSanitize(`${opts.title} | AFFiNE`)
: 'AFFiNE'; : 'AFFiNE';
@@ -199,4 +208,24 @@ export class DocRendererController {
</html> </html>
`; `;
} }
/**
* Should only be called at startup time
*/
private readHtmlAssets(path: string): HtmlAssets {
const manifestPath = join(path, 'assets-manifest.json');
const htmlPath = join(path, 'index.html');
try {
const assets = JSON.parse(readFileSync(manifestPath, 'utf-8'));
assets.html = readFileSync(htmlPath, 'utf-8');
return assets;
} catch (e) {
if (this.config.node.prod) {
throw e;
} else {
return defaultAssets;
}
}
}
} }