Skip to content

Commit

Permalink
#38
Browse files Browse the repository at this point in the history
- preset/node: добавлена возможность задать http proxy (minor)
- preset/bun: добавлена возможность задать http proxy (minor)
- preset/node: добавлена возможность задать метод маршрута (minor)
- preset/bun: добавлена возможность задать метод маршрута (minor)
  • Loading branch information
krutoo committed Apr 8, 2024
1 parent 4b20969 commit 39b251a
Show file tree
Hide file tree
Showing 11 changed files with 266 additions and 110 deletions.
221 changes: 149 additions & 72 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
},
"dependencies": {
"@humanwhocodes/env": "^3.0.2",
"@krutoo/fetch-tools": "^0.0.12",
"@krutoo/fetch-tools": "^0.0.14",
"@opentelemetry/api": "^1.4.1",
"@opentelemetry/exporter-prometheus": "^0.38.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.39.1",
Expand All @@ -63,6 +63,7 @@
"accepts": "^1.3.8",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"http-proxy-middleware": "^3.0.0",
"jsesc": "^3.0.2",
"middleware-axios": "^2.1.6",
"pino": "^8.14.1",
Expand Down
8 changes: 7 additions & 1 deletion src/http/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ export type {
ResponseErrorInit,
} from './types';
export { configureFetch, applyMiddleware } from '@krutoo/fetch-tools';
export { log, defaultHeaders, validateStatus } from '@krutoo/fetch-tools/middleware';
export {
type ProxyOptions,
log,
proxy,
defaultHeaders,
validateStatus,
} from '@krutoo/fetch-tools/middleware';
export { StatusError, ResponseError } from './errors';
export { FetchUtil } from './utils';
1 change: 1 addition & 0 deletions src/preset/bun/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export function PresetBun(customize?: PresetTuner) {
preset.set(KnownToken.Http.serve, provideServe);
preset.set(KnownToken.Http.Serve.serviceRoutes, provideServiceRoutes);
preset.set(KnownToken.Http.Serve.middleware, provideServeMiddleware);
preset.set(KnownToken.Http.Serve.Proxy.config, () => null);

// http api
preset.set(KnownToken.Http.Api.knownHosts, provideKnownHttpApiHosts);
Expand Down
52 changes: 34 additions & 18 deletions src/preset/bun/providers/serve.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,47 @@
/* eslint-disable require-jsdoc, jsdoc/require-jsdoc */
import { Resolve } from '../../../di';
import { KnownToken } from '../../../tokens';
import { Handler } from '../../../http';
import { route, router } from '@krutoo/fetch-tools';
import { Handler, proxy } from '../../../http';
import { router, applyMiddleware } from '@krutoo/fetch-tools';
import { applyServerMiddleware } from '../../server/utils/apply-server-middleware';

export function provideServe(resolve: Resolve): Handler {
const middleware = resolve(KnownToken.Http.Serve.middleware);
const routes = resolve(KnownToken.Http.Serve.routes);
const config = resolve(KnownToken.Config.base);
const pageRoutes = resolve(KnownToken.Http.Serve.pageRoutes);
const serviceRoutes = resolve(KnownToken.Http.Serve.serviceRoutes);
const middleware = resolve(KnownToken.Http.Serve.middleware);
const proxyConfig = resolve(KnownToken.Http.Serve.Proxy.config);

const enhance =
config.env === 'development' && proxyConfig
? applyMiddleware(
...(Array.isArray(proxyConfig) ? proxyConfig.map(proxy) : [proxy(proxyConfig)]),
)
: identity;

const enhance = applyServerMiddleware(...middleware);
const builder = router.builder();

Check failure on line 22 in src/preset/bun/providers/serve.ts

View workflow job for this annotation

GitHub Actions / test

Property 'builder' does not exist on type '(...routes: Route[]) => Handler'.

return router(
// маршруты с промежуточными слоями
...routes.map(([pattern, handler]) => {
const enhancedHandler = enhance(handler);
// маршруты с промежуточными слоями
for (const [pattern, handler] of pageRoutes) {
const path = typeof pattern === 'string' ? pattern : pattern.path;
const method = typeof pattern === 'string' ? 'get' : pattern.method;
const pageHandler = applyServerMiddleware(...middleware)(handler);

return route.get(pattern, request => enhancedHandler(request, { events: new EventTarget() }));
}),
builder[method](path, request => pageHandler(request, { events: new EventTarget() }));

Check failure on line 30 in src/preset/bun/providers/serve.ts

View workflow job for this annotation

GitHub Actions / test

Parameter 'request' implicitly has an 'any' type.
}

// @todo вместо routes обрабатывать pageRoutes с помощью route.get() из новой версии fetch-tools (для явности)
// @todo также добавить apiRoutes и обрабатывать их с помощью с помощью route()?
// служебные маршруты (к ним не применяются промежуточные слои)
for (const [pattern, handler] of serviceRoutes) {
const path = typeof pattern === 'string' ? pattern : pattern.path;
const method = typeof pattern === 'string' ? 'get' : pattern.method;

builder[method](path, request => handler(request, { events: new EventTarget() }));

Check failure on line 38 in src/preset/bun/providers/serve.ts

View workflow job for this annotation

GitHub Actions / test

Parameter 'request' implicitly has an 'any' type.
}

// @todo также добавить apiRoutes?
return enhance(builder.build());
}

// служебные маршруты (без промежуточных слоев)
...serviceRoutes.map(([pattern, handler]) =>
route(pattern, request => handler(request, { events: new EventTarget() })),
),
);
function identity<T>(value: T): T {
return value;
}
13 changes: 10 additions & 3 deletions src/preset/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { provideTracer } from './providers/tracer';
import { provideTracerProvider } from './providers/tracer-provider';
import { provideTracerProviderResource } from './providers/tracer-provider-resource';
import { provideMainExpressApp } from './providers/main-express-app';
import { ExpressRouteList } from './types';

/**
* Возвращает preset с зависимостями по умолчанию для frontend-микросервисов на Node.js.
Expand Down Expand Up @@ -50,6 +51,7 @@ export function PresetNode(customize?: PresetTuner): Preset {
// fetch
preset.set(KnownToken.Http.fetch, provideFetch);
preset.set(KnownToken.Http.Fetch.middleware, () => []);
preset.set(KnownToken.Http.Serve.Proxy.config, () => null);

// axios
preset.set(KnownToken.Axios.factory, provideAxiosFactory);
Expand All @@ -58,9 +60,12 @@ export function PresetNode(customize?: PresetTuner): Preset {
// express
preset.set(KnownToken.Express.app, provideMainExpressApp);
preset.set(KnownToken.Express.pageRoutes, () => []);
preset.set(KnownToken.Express.serviceRoutes, resolve => [
['/healthcheck', resolve(KnownToken.Express.Handlers.healthCheck)],
]);
preset.set(
KnownToken.Express.serviceRoutes,
(resolve): ExpressRouteList => [
['/healthcheck', resolve(KnownToken.Express.Handlers.healthCheck)],
],
);
preset.set(KnownToken.Express.middleware, resolve => [
resolve(KnownToken.Express.Middleware.request),
resolve(KnownToken.Express.Middleware.log),
Expand Down Expand Up @@ -91,6 +96,8 @@ export function PresetNode(customize?: PresetTuner): Preset {
return preset;
}

export type { ExpressHandlerContext, ExpressRouteList } from './types';

// доступные утилиты
export { getClientIp } from './utils/get-client-ip';
export { getForwardedHeaders } from './utils/get-forwarded-headers';
Expand Down
41 changes: 36 additions & 5 deletions src/preset/node/providers/main-express-app.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,62 @@
import express from 'express';
import type { Resolve } from '../../../di';
import { KnownToken } from '../../../tokens';
import express from 'express';
import { createProxyMiddleware } from 'http-proxy-middleware';

/**
* Провайдер основного express-приложения.
* @param resolve Функция для получения зависимости по токену.
* @return Основное express-приложение.
*/
export function provideMainExpressApp(resolve: Resolve): express.Application {
const config = resolve(KnownToken.Config.base);
const pageRoutes = resolve(KnownToken.Express.pageRoutes);
const serviceRoutes = resolve(KnownToken.Express.serviceRoutes);
const middleware = resolve(KnownToken.Express.middleware);
const endMiddleware = resolve(KnownToken.Express.endMiddleware);
const proxyConfig = resolve(KnownToken.Http.Serve.Proxy.config);

const app = express();

if (config.env === 'development' && proxyConfig) {
const proxyConfigs = Array.isArray(proxyConfig) ? proxyConfig : [proxyConfig];

for (const { target, filter } of proxyConfigs) {
// ВАЖНО: так как не можем предоставить web-интерфейс Request бросаем ошибку
if (typeof filter === 'function') {
throw new Error('Currently function is not supported for proxy "filter"');
}

const proxyPaths = Array.isArray(filter) ? filter : [filter];

app.use(
createProxyMiddleware({
target,
changeOrigin: true,
pathFilter: inputPath => proxyPaths.some(proxyPath => inputPath.startsWith(proxyPath)),
}),
);
}
}

// маршруты страниц
for (const [routePath, routeHandler] of pageRoutes) {
app.use(routePath, middleware);
app.get(routePath, routeHandler);
app.use(routePath, endMiddleware);
const path = typeof routePath === 'string' ? routePath : routePath.path;
const method = typeof routePath === 'string' ? 'get' : routePath.method;

app.use(path, middleware);
app[method](path, routeHandler);
app.use(path, endMiddleware);
}

// служебные маршруты (к ним не применяются промежуточные слои)
for (const [routePath, routeHandler] of serviceRoutes) {
app.get(routePath, routeHandler);
const path = typeof routePath === 'string' ? routePath : routePath.path;
const method = typeof routePath === 'string' ? 'get' : routePath.method;

app[method](path, routeHandler);
}

// @todo также добавить apiRoutes?
return app;
}
11 changes: 7 additions & 4 deletions src/preset/node/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import type { Request, Response, NextFunction } from 'express';
import type express from 'express';
import type { RouteInfo } from '../server/types';

/**
* Контекст обработчика express.
*/
export interface ExpressHandlerContext {
readonly req: Request;
readonly res: Response;
readonly next: NextFunction;
readonly req: express.Request;
readonly res: express.Response;
readonly next: express.NextFunction;
}

export type ExpressRouteList = Array<[string | RouteInfo, express.Handler]>;
2 changes: 2 additions & 0 deletions src/preset/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export type {
ServerHandlerContext,
PageResponseFormatter,
PageResponseFormatResult,
RouteInfo,
RouteList,
} from './types';
export { PAGE_HANDLER_EVENT_TYPE, PAGE_FORMAT_PRIORITY } from './constants';

Expand Down
7 changes: 7 additions & 0 deletions src/preset/server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,10 @@ export interface PageResponseFormatter {
export interface RenderToString {
(jsx: JSX.Element): string | Promise<string>;
}

export interface RouteInfo {
method: 'all' | 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options' | 'head';
path: string;
}

export type RouteList = Array<[string | RouteInfo, ServerHandler]>;
17 changes: 11 additions & 6 deletions src/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,19 @@ import type { BasicTracerProvider, SpanExporter } from '@opentelemetry/sdk-trace
import type { Resource } from '@opentelemetry/resources';
import type { ElementType, ReactNode, JSX } from 'react';
import type { KnownHttpApiKey, PageAssets } from './preset/isomorphic/types';
import type { ExpressHandlerContext } from './preset/node/types';
import type { ExpressHandlerContext, ExpressRouteList } from './preset/node/types';
import type { SpecificExtras } from './preset/server/utils/specific-extras';
import type { CreateAxiosDefaults } from 'axios';
import type { AxiosInstanceWrapper, Middleware as AxiosMiddleware } from 'middleware-axios';
import type { Handler, LogHandler, LogHandlerFactory, Middleware } from './http';
import type { Handler, LogHandler, LogHandlerFactory, Middleware, ProxyOptions } from './http';
import type { HttpApiHostPool } from './preset/isomorphic/utils/http-api-host-pool';
import type {
ServerHandlerContext,
ServerHandler,
ServerMiddleware,
PageResponseFormatter,
RenderToString,
RouteList,
} from './preset/server/types';

/**
Expand Down Expand Up @@ -98,13 +99,17 @@ export const KnownToken = {
/** Токены компонентов функции обработки входящего HTTP-запроса. */
Serve: {
/** Токен списка маршрутов. */
routes: createToken<Array<[string, ServerHandler]>>('serve/routes'),
pageRoutes: createToken<RouteList>('serve/page-routes'),

/** Токен списка "служебных" маршрутов. К "служебным" маршрутам не применяются промежуточные слои. */
serviceRoutes: createToken<Array<[string, ServerHandler]>>('serve/service-routes'),
serviceRoutes: createToken<RouteList>('serve/service-routes'),

/** Токен списка промежуточных слоев обработки входящего HTTP-запроса. */
middleware: createToken<ServerMiddleware[]>('serve/middleware'),

Proxy: {
config: createToken<null | undefined | ProxyOptions | ProxyOptions[]>('proxy/config'),
},
},

/** Токены компонентов обработчиков входящих HTTP-запросов. */
Expand Down Expand Up @@ -177,10 +182,10 @@ export const KnownToken = {
app: createToken<express.Application>('express/app'),

/** Токен списка маршрутов страниц. */
pageRoutes: createToken<Array<[string, express.Handler]>>('express/page-routes'),
pageRoutes: createToken<ExpressRouteList>('express/page-routes'),

/** Токен списка служебных маршрутов. */
serviceRoutes: createToken<Array<[string, express.Handler]>>('express/service-routes'),
serviceRoutes: createToken<ExpressRouteList>('express/service-routes'),

/** Токен списка промежуточных слоев для публичных маршрутов. */
middleware:
Expand Down

0 comments on commit 39b251a

Please sign in to comment.