Skip to content

Instantly share code, notes, and snippets.

@zerkalica
Created August 15, 2021 16:51
Show Gist options
  • Save zerkalica/4c965792913df612da32ae5e8c75e8d7 to your computer and use it in GitHub Desktop.
Save zerkalica/4c965792913df612da32ae5e8c75e8d7 to your computer and use it in GitHub Desktop.
bikeServer
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