From 25442f7ec72d8017b023c856ca76eff98b1cf16e Mon Sep 17 00:00:00 2001 From: krutoo Date: Thu, 16 Mar 2023 21:19:53 +0500 Subject: [PATCH] #38 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - пересмотрена константа KnownToken (major) - удален интерфейс DefaultMiddleware (major) - добавлены токены для основных промежуточных слоев обработчиков сервера (minor) - добавлена утилита composeMiddleware для express - добавлены провайдеры для промежуточных слоев express (minor) --- src/http-server/types.ts | 11 +-- src/http-server/utils.ts | 19 +++++- src/preset/browser/index.ts | 4 +- src/preset/node/{response.ts => handler.ts} | 47 +++++++------ src/preset/node/index.ts | 67 ++++++++++++------ src/tokens.ts | 76 +++++++++++---------- 6 files changed, 133 insertions(+), 91 deletions(-) rename src/preset/node/{response.ts => handler.ts} (75%) diff --git a/src/http-server/types.ts b/src/http-server/types.ts index bf08009..ea73f0f 100644 --- a/src/http-server/types.ts +++ b/src/http-server/types.ts @@ -1,5 +1,6 @@ -import type { Request, Response, NextFunction, Handler, ErrorRequestHandler } from 'express'; +import type { Request, Response, NextFunction } from 'express'; +// @todo переименовать в HandlerContext export interface ResponseContext { req: Request; res: Response; @@ -31,11 +32,3 @@ export interface ConventionalJson { critical_css?: string; meta?: any; } - -export interface DefaultMiddleware { - start: Handler[]; - logging: Handler[]; - tracing: Handler[]; - metrics: Handler[]; - finish: Array; -} diff --git a/src/http-server/utils.ts b/src/http-server/utils.ts index d541834..8b71388 100644 --- a/src/http-server/utils.ts +++ b/src/http-server/utils.ts @@ -1,8 +1,25 @@ -import type { Request, Response } from 'express'; +import type { Handler, Request, Response } from 'express'; import type { ConventionalJson, PageAssets, PageTemplate, PageTemplateData } from './types'; import type { BaseConfig } from '../config/types'; import { isIP } from 'net'; +/** + * Объединяет промежуточные слои в один. + * @param list Промежуточные слои. + * @return Промежуточный слой. + */ +export function composeMiddleware(list: Handler[]) { + return list.reduce((a, b) => (req, res, next) => { + a(req, res, err => { + if (err) { + return next(err); + } + + b(req, res, next); + }); + }); +} + /** * Определяет IP входящего запроса. * @param req Входящий запрос. diff --git a/src/preset/browser/index.ts b/src/preset/browser/index.ts index 761612b..4b5aff4 100644 --- a/src/preset/browser/index.ts +++ b/src/preset/browser/index.ts @@ -30,7 +30,7 @@ export function PresetBrowser(): Preset { [KnownToken.logger, provideLogger], [KnownToken.sagaMiddleware, provideSagaMiddleware], [KnownToken.Http.Client.factory, provideHttpClientFactory], - [KnownToken.Http.Client.LogMiddleware.handler, provideHttpClientLogHandler], + [KnownToken.Http.Client.Middleware.Log.handler, provideHttpClientLogHandler], [KnownToken.SsrBridge.clientSide, provideBridgeClientSide], [KnownToken.Http.Api.knownHosts, provideKnownHttpApiHosts], ]); @@ -86,7 +86,7 @@ export function provideKnownHttpApiHosts(resolve: Resolve): StrictMap new PageResponse()], - [KnownToken.Response.render, provideRender], - [KnownToken.Response.template, provideTemplate], - [KnownToken.Response.main, provideMain], - [KnownToken.Response.params, provideParams], + + // http client [KnownToken.Http.Client.factory, provideHttpClientFactory], - [KnownToken.Http.Client.LogMiddleware.handler, provideHttpClientLogHandler], + [KnownToken.Http.Client.Middleware.Log.handler, provideHttpClientLogHandler], + + // http handler + [KnownToken.Http.Handler.main, provideMain], + [KnownToken.Http.Handler.Request.specificParams, provideSpecificParams], + [KnownToken.Http.Handler.Response.builder, () => new PageResponse()], + [KnownToken.Http.Handler.Response.Page.assets, () => ({ js: '', css: '' })], + [KnownToken.Http.Handler.Response.Page.template, provideTemplate], + [KnownToken.Http.Handler.Response.Page.render, provideRender], ]); } @@ -40,8 +46,8 @@ export function provideHttpClientFactory(resolve: Resolve): HttpClientFactory { const appConfig = resolve(KnownToken.Config.base); const tracer = resolve(KnownToken.Tracing.tracer); - const context = resolve(KnownToken.Response.context); - const logHandler = resolve(KnownToken.Http.Client.LogMiddleware.handler); + const context = resolve(KnownToken.Http.Handler.context); + const logHandler = resolve(KnownToken.Http.Client.Middleware.Log.handler); // @todo добавить при необходимости (но тогда в логе будет значительно больше ошибок) // const controller = new AbortController(); @@ -73,7 +79,7 @@ export function provideHttpClientFactory(resolve: Resolve): HttpClientFactory { } export function provideRender(resolve: Resolve): (element: JSX.Element) => string { - const { res } = resolve(KnownToken.Response.context); + const { res } = resolve(KnownToken.Http.Handler.context); return function render(element: JSX.Element): string { res.emit(RESPONSE_EVENT.renderStart); @@ -97,16 +103,17 @@ export function provideTemplate(resolve: Resolve): PageTemplate { } export function provideMain(resolve: Resolve): VoidFunction { - const context = resolve(KnownToken.Response.context); - const assets = resolve(KnownToken.Response.assets); - const prepare = resolve(KnownToken.Response.prepare); - const render = resolve(KnownToken.Response.render); - const template = resolve(KnownToken.Response.template); const logger = resolve(KnownToken.logger); - const builder = resolve(KnownToken.Response.builder); + const context = resolve(KnownToken.Http.Handler.context); + const assets = resolve(KnownToken.Http.Handler.Response.Page.assets); + const prepare = resolve(KnownToken.Http.Handler.Response.Page.prepare); + const render = resolve(KnownToken.Http.Handler.Response.Page.render); + const template = resolve(KnownToken.Http.Handler.Response.Page.template); + const builder = resolve(KnownToken.Http.Handler.Response.builder); return async function main() { try { + // @todo это билдер ответа но в ответе может не быть markup, assets и тд, подумать и переделать builder .markup(await render(await prepare())) .assets(assets) @@ -133,8 +140,8 @@ export function provideMain(resolve: Resolve): VoidFunction { }; } -export function provideParams(resolve: Resolve): Record { - const context = resolve(KnownToken.Response.context); +export function provideSpecificParams(resolve: Resolve): Record { + const context = resolve(KnownToken.Http.Handler.context); try { const headerValue = context.req.header('simaland-params'); @@ -164,8 +171,8 @@ export function HandlerProvider(appFactory: () => Application) { const app = appFactory(); app.attach(parent); - app.bind(KnownToken.Response.context).toValue({ req, res, next }); - app.get(KnownToken.Response.main)(); + app.bind(KnownToken.Http.Handler.context).toValue({ req, res, next }); + app.get(KnownToken.Http.Handler.main)(); }; }; } diff --git a/src/preset/node/index.ts b/src/preset/node/index.ts index 7807440..829e4b1 100644 --- a/src/preset/node/index.ts +++ b/src/preset/node/index.ts @@ -2,7 +2,6 @@ import path from 'path'; import type { Logger, LoggerEventHandler } from '../../log/types'; import type { Tracer } from '@opentelemetry/api'; -import type { DefaultMiddleware } from '../../http-server/types'; import { Resolve, Preset, createPreset } from '../../di'; import { BasicTracerProvider, @@ -23,7 +22,7 @@ import { import { createDefaultMetrics, createMetricsHttpApp } from '../../metrics/node'; import { JaegerExporter } from '@opentelemetry/exporter-jaeger'; import { create } from 'middleware-axios'; -import Express from 'express'; +import Express, { Handler } from 'express'; import { init, Handlers, getCurrentHub } from '@sentry/node'; import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; import { Resource } from '@opentelemetry/resources'; @@ -38,6 +37,7 @@ import { provideBaseConfig } from '../parts/providers'; import pino from 'pino'; import PinoPretty from 'pino-pretty'; import { config as applyDotenv } from 'dotenv'; +import { composeMiddleware } from '../../http-server/utils'; /** * Возвращает preset с зависимостями по умолчанию для frontend-микросервисов на Node.js. @@ -45,19 +45,38 @@ import { config as applyDotenv } from 'dotenv'; */ export function PresetNode(): Preset { return createPreset([ + // config [KnownToken.Config.source, provideConfigSource], [KnownToken.Config.base, provideBaseConfig], + + // log [KnownToken.logger, provideLogger], + + // tracing [KnownToken.Tracing.tracer, provideTracer], [KnownToken.Tracing.spanExporter, provideSpanExporter], [KnownToken.Tracing.tracerProvider, provideTracerProvider], [KnownToken.Tracing.tracerProviderResource, provideTracerProviderResource], + + // metrics + [KnownToken.Metrics.httpApp, createMetricsHttpApp], + + // http client [KnownToken.Http.Client.factory, () => create], + + // http server [KnownToken.Http.Server.factory, () => Express], - [KnownToken.Http.Server.Defaults.middleware, provideDefaultMiddleware], - [KnownToken.Metrics.httpApp, createMetricsHttpApp], - [KnownToken.SsrBridge.serverSide, provideBridgeServerSide], + [KnownToken.Http.Server.Middleware.request, () => Handlers.requestHandler()], + [KnownToken.Http.Server.Middleware.log, provideHttpServerLogMiddleware], + [KnownToken.Http.Server.Middleware.metrics, provideHttpServerMetricsMiddleware], + [KnownToken.Http.Server.Middleware.tracing, provideHttpServerTracingMiddleware], + [KnownToken.Http.Server.Middleware.error, () => Handlers.errorHandler()], + + // http api [KnownToken.Http.Api.knownHosts, provideKnownHttpApiHosts], + + // ssr bridge + [KnownToken.SsrBridge.serverSide, provideBridgeServerSide], ]); } @@ -164,28 +183,32 @@ export function provideTracerProviderResource(resolve: Resolve): Resource { ); } -export function provideDefaultMiddleware(resolve: Resolve): DefaultMiddleware { +export function provideHttpServerLogMiddleware(resolve: Resolve): Handler { const config = resolve(KnownToken.Config.base); const logger = resolve(KnownToken.logger); - const tracer = resolve(KnownToken.Tracing.tracer); + return logMiddleware(config, logger); +} + +export function provideHttpServerMetricsMiddleware(resolve: Resolve): Handler { + const config = resolve(KnownToken.Config.base); const metrics = createDefaultMetrics(); - return { - start: [Handlers.requestHandler()], - logging: [logMiddleware(config, logger)], - tracing: [tracingMiddleware(tracer)], - metrics: [ - responseMetricsMiddleware(config, { - counter: metrics.requestCount, - histogram: metrics.responseDuration, - }), - renderMetricsMiddleware(config, { - histogram: metrics.renderDuration, - }), - ], - finish: [Handlers.errorHandler()], - }; + return composeMiddleware([ + responseMetricsMiddleware(config, { + counter: metrics.requestCount, + histogram: metrics.responseDuration, + }), + renderMetricsMiddleware(config, { + histogram: metrics.renderDuration, + }), + ]); +} + +export function provideHttpServerTracingMiddleware(resolve: Resolve): Handler { + const tracer = resolve(KnownToken.Tracing.tracer); + + return tracingMiddleware(tracer); } export function provideBridgeServerSide(resolve: Resolve): BridgeServerSide { diff --git a/src/tokens.ts b/src/tokens.ts index 1a9e9b8..9ab1a4b 100644 --- a/src/tokens.ts +++ b/src/tokens.ts @@ -1,11 +1,6 @@ import { createToken } from './di'; -import type { Application } from 'express'; -import type { - DefaultMiddleware, - PageAssets, - PageTemplate, - ResponseContext, -} from './http-server/types'; +import type { Application, ErrorRequestHandler, Handler } from 'express'; +import type { PageAssets, PageTemplate, ResponseContext } from './http-server/types'; import type { SagaExtendedMiddleware } from './utils/redux-saga'; import type { Logger } from './log/types'; import type { HttpClientFactory } from './http-client/types'; @@ -22,8 +17,8 @@ import type { LogMiddlewareHandlerInit } from './http-client/middleware/log'; export const KnownToken = { // config Config: { - source: createToken('config.source'), - base: createToken('config.base'), + source: createToken('config/source'), + base: createToken('config/base'), }, // cache @@ -37,51 +32,58 @@ export const KnownToken = { // tracing Tracing: { - tracer: createToken('tracing.tracer'), - spanExporter: createToken('tracing.span-exporter'), - tracerProvider: createToken('tracing.tracer-provider'), - tracerProviderResource: createToken('tracing.tracer-provider-resource'), + tracer: createToken('tracing/tracer'), + spanExporter: createToken('tracing/span-exporter'), + tracerProvider: createToken('tracing/tracer-provider'), + tracerProviderResource: createToken('tracing/resource'), }, // metrics Metrics: { - httpApp: createToken('metrics.http-app'), + httpApp: createToken('metrics/http-app'), }, // http Http: { + Api: { + knownHosts: createToken>('http/api/known-hosts'), + }, Client: { - factory: createToken('http.client.factory'), - LogMiddleware: { - handler: createToken('http.client.log-middleware.handler'), + factory: createToken('client/factory'), + Middleware: { + Log: { + handler: createToken('log/handler'), + }, }, }, Server: { - factory: createToken<() => Application>('http.server.factory'), - Defaults: { - middleware: createToken('http.server.defaults.middleware'), + factory: createToken<() => Application>('server/factory'), + Middleware: { + request: createToken('middleware/request'), + log: createToken('middleware/log'), + tracing: createToken('middleware/tracing'), + metrics: createToken('middleware/metrics'), + error: createToken('middleware/error'), }, }, - Api: { - knownHosts: createToken>('http.api.known-hosts'), + Handler: { + main: createToken<() => void>('handler/main'), + context: createToken('handler/context'), + Request: { + specificParams: createToken>('request/specific-params'), + }, + Response: { + builder: createToken('response/builder'), + Page: { + assets: createToken('page/assets'), + template: createToken('page/template'), + prepare: createToken<() => JSX.Element | Promise>('page/prepare'), + render: createToken<(element: JSX.Element) => string | Promise>('page/render'), + }, + }, }, }, - // scope: page response - Response: { - builder: createToken('response/builder'), - - // @todo переименовать в requestSimaParams или убрать - params: createToken>('response/params'), - - template: createToken('response/template'), - context: createToken('response/context'), - assets: createToken('response/assets'), - prepare: createToken<() => JSX.Element | Promise>('response/prepare'), - render: createToken<(element: JSX.Element) => string | Promise>('response/render'), - main: createToken<() => void>('response/main'), - }, - // SSR SsrBridge: { clientSide: createToken>('ssr-bridge/client-side'),