Created
August 15, 2021 16:51
-
-
Save zerkalica/4c965792913df612da32ae5e8c75e8d7 to your computer and use it in GitHub Desktop.
bikeServer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { IncomingMessage, ServerResponse } from 'http' | |
import fetchRaw from 'node-fetch' | |
import { AcmeRouterLocation } from '@acme/router/location' | |
import { PsyContext } from '@psy/psy/context/Context' | |
import { PsyErrorMix } from '@psy/psy/error/Mix' | |
import { PsyErrorNotFound } from '@psy/psy/error/NotFound' | |
import { PsyFetcher } from '@psy/psy/fetcher/Fetcher' | |
import { PsyLog } from '@psy/psy/log/log' | |
import { psySsrClient } from '@psy/psy/ssr/client.node' | |
import { PsySsrHydrator } from '@psy/psy/ssr/Hydrator' | |
import { PsySsrHydratorNode } from '@psy/psy/ssr/Hydrator.node' | |
import { PsySsrTemplate } from '@psy/psy/ssr/Template' | |
import { PsyReactRenderNode } from '@psy/react/render.node' | |
import { AcmeServerManifest, AcmeServerManifestLoader } from './Manifest' | |
export class AcmeServerRequest { | |
constructor(protected parentCtx: PsyContext, protected req: IncomingMessage, protected res: ServerResponse) {} | |
protected $ = new PsyContext(this.parentCtx) | |
protected idCached = undefined as undefined | string | |
get id() { | |
return this.idCached ?? (this.idCached = this.req.headers['x-request-id'] as string | undefined) ?? PsyFetcher.requestId() | |
} | |
protected sidCached = undefined as undefined | string | |
get sid() { | |
return this.sidCached ?? (this.sidCached = this.req.headers['x-session-id'] as string | undefined) ?? PsyFetcher.requestId() | |
} | |
protected isJson() { | |
return /^application\/json/.test(this.req.headers['content-type'] ?? '') | |
} | |
logger() { | |
const cli = psySsrClient(this.req) | |
const rid = this.id | |
const sid = this.sid | |
const PsyLogParent = this.$.get(PsyLog) | |
return class PsyLogNodeConfgured extends PsyLogParent { | |
static context() { | |
return { | |
ua: cli.navigator.userAgent, | |
url: cli.location.href, | |
rid, | |
sid, | |
} | |
} | |
} as typeof PsyLog | |
} | |
fetcher() { | |
const requestId = () => this.id | |
const $ = this.$ | |
const fallbackConfig = {} as any | |
const ParentFetcher = this.$.get(PsyFetcher) | |
return class PsyFetcherNodeConfigured extends ParentFetcher { | |
static $ = $ | |
static baseUrl = fallbackConfig.apiUrl | |
static fetch = fetchRaw as unknown as typeof fetch | |
static requestId = requestId | |
} | |
} | |
hydrator() { | |
const fallbackConfig = {} as any | |
return new PsySsrHydratorNode({ __config: fallbackConfig.browser, __files: this.$.get(AcmeServerManifest).files }) | |
} | |
location() { | |
const cli = psySsrClient(this.req) | |
return new AcmeRouterLocation(cli) | |
} | |
outDir() { | |
return '' | |
} | |
manifestLoader() { | |
const manifestLoader = new AcmeServerManifestLoader(this.$) | |
manifestLoader.outDir = () => this.outDir() | |
return manifestLoader | |
} | |
async context() { | |
this.$.set(AcmeServerManifest, await this.manifestLoader().load()) | |
.set(PsyFetcher, this.fetcher()) | |
.set(PsySsrHydrator.instance, this.hydrator()) | |
.set(AcmeRouterLocation.instance, this.location()) | |
.set(PsyLog, this.logger()) | |
} | |
protected isDev() { | |
return this.$.get(AcmeServerManifest).isDev | |
} | |
failTemplateJson(error: Error) { | |
return JSON.stringify(this.isDev() ? error : { message: error.message, name: error.name }) | |
} | |
failTemplateHtml(error: Error) { | |
const t = new PsySsrTemplate() | |
t.error = error | |
return t | |
} | |
failTemplate(error: Error) { | |
if (this.isJson()) return this.failTemplateJson(error) | |
return this.failTemplateHtml(error).render() | |
} | |
fail(error: Error = new PsyErrorNotFound('Request not handlered')) { | |
let chunk = error.stack | |
const log = this.$.get(PsyLog) | |
try { | |
chunk = this.failTemplate(error) | |
} catch (e) { | |
log.error({ place: 'AcmeServerRequest.fail#template', message: e }) | |
} finally { | |
log.error({ place: 'AcmeServerRequest.fail', message: error }) | |
} | |
const nf = error instanceof PsyErrorMix ? error.filter(PsyErrorNotFound)?.[0] : error | |
const httpCode = nf instanceof PsyErrorNotFound ? nf.httpCode : 500 | |
const contentType = this.isJson() ? 'application/json' : 'text/html' | |
this.res.writeHead(httpCode, { 'Content-Type': contentType }) | |
this.res.end(chunk) | |
return true | |
} | |
middleware() { | |
return undefined as undefined | ((req: IncomingMessage, res: ServerResponse, next: (e?: any) => any) => void) | |
} | |
protected middlewareConverted() { | |
const mdl = this.middleware() | |
if (!mdl) return undefined | |
return new Promise<true | undefined>((resolve, reject) => { | |
this.res.once('close', () => resolve(true)) | |
mdl(this.req, this.res, e => (e ? reject(e) : resolve(undefined))) | |
}) | |
} | |
async version() { | |
if (this.req.url !== '/version') return | |
const manifest = this.$.get(AcmeServerManifest) | |
this.res.end(manifest.version) | |
return true | |
} | |
async favIcon() { | |
if (this.req.url !== '/favicon.ico') return | |
this.res.statusCode = 200 | |
this.res.end() | |
return true | |
} | |
pkgName() { | |
return '' | |
} | |
protected renderTemplate() { | |
const manifest = this.$.get(AcmeServerManifest) | |
const config = this.fallbackConfig() | |
const template = new PsySsrTemplate() | |
template.titleText = () => 'test' | |
template.pkgName = () => this.pkgName() | |
template.bodyJs = () => Object.values(manifest.entries).map(src => ({ src: config.browser.publicUrl + src })) | |
if (this.node) template.node = this.node.bind(this) | |
return Promise.resolve(template) | |
} | |
async renderer() { | |
const renderer = new PsyReactRenderNode(this.$) | |
renderer.write = val => this.res.write(val) | |
const template = await this.renderTemplate() | |
renderer.template = () => template | |
return renderer | |
} | |
async render() { | |
const renderer = await this.renderer() | |
const stat = await renderer.run() | |
const httpCode = stat.error?.find(PsyErrorNotFound)?.httpCode ?? 200 | |
if (stat.chunk) { | |
this.res.writeHead(httpCode, { 'Content-Type': 'text/html' }) | |
this.res.end(stat.chunk) | |
return true | |
} | |
} | |
// prettier-ignore | |
async process() { | |
await this.middlewareConverted() ?? | |
await this.context() ?? | |
await this.version() ?? | |
await this.favIcon() ?? | |
await this.render() ?? | |
this.fail() | |
} | |
async run() { | |
try { | |
await this.process() | |
} catch (error) { | |
this.fail(error) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment