-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #116 from sima-land/38-examples-bun
Шаг 69 #38
- Loading branch information
Showing
13 changed files
with
360 additions
and
335 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { Resolve } from '../../../di'; | ||
import { KnownToken } from '../../../tokens'; | ||
import { LogMiddlewareHandlerInit } from '../../../utils/axios'; | ||
import { AxiosLogging } from '../../isomorphic/utils/axios-logging'; | ||
|
||
/** | ||
* Провайдер обработчика логирования axios. | ||
* @param resolve Функция для получения зависимости по токену. | ||
* @return Обработчик логирования. | ||
*/ | ||
export function provideAxiosLogHandler(resolve: Resolve): LogMiddlewareHandlerInit { | ||
const logger = resolve(KnownToken.logger); | ||
const abortController = resolve(KnownToken.Http.Fetch.abortController); | ||
|
||
return data => { | ||
const logHandler = new AxiosLogging(logger, data); | ||
|
||
// ВАЖНО: отключаем логирование если запрос прерван | ||
logHandler.disabled = () => abortController.signal.aborted; | ||
|
||
return logHandler; | ||
}; | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { Middleware } from 'middleware-axios'; | ||
import { Resolve } from '../../../di'; | ||
import { KnownToken } from '../../../tokens'; | ||
import { HttpStatus } from '../../isomorphic/utils/http-status'; | ||
import { axiosTracingMiddleware } from '../../node/utils/axios-tracing-middleware'; | ||
import { cookieMiddleware, logMiddleware } from '../../../utils/axios'; | ||
import { getForwardedHeaders } from '../../node/utils/get-forwarded-headers'; | ||
|
||
/** | ||
* Провайдер фабрики http-клиентов. | ||
* @param resolve Функция для получения зависимости по токену. | ||
* @return Фабрика. | ||
*/ | ||
export function provideAxiosMiddleware(resolve: Resolve): Middleware<any>[] { | ||
const appConfig = resolve(KnownToken.Config.base); | ||
const tracer = resolve(KnownToken.Tracing.tracer); | ||
const context = resolve(KnownToken.ExpressHandler.context); | ||
const logHandler = resolve(KnownToken.Axios.Middleware.Log.handler); | ||
const cookieStore = resolve(KnownToken.Http.Fetch.cookieStore); | ||
const abortController = resolve(KnownToken.Http.Fetch.abortController); | ||
|
||
return [ | ||
// пробрасываемые заголовки по соглашению | ||
async (config, next) => { | ||
await next({ | ||
...config, | ||
headers: { | ||
...getForwardedHeaders(appConfig, context.req), | ||
|
||
// ВАЖНО: заголовки из конфига важнее, поэтому идут в конце | ||
...config.headers, | ||
}, | ||
}); | ||
}, | ||
|
||
// обрывание по сигналу из обработчика входящего запроса и по сигналу из конфига исходящего запроса | ||
async (config, next) => { | ||
const innerController = new AbortController(); | ||
|
||
abortController.signal.addEventListener( | ||
'abort', | ||
() => { | ||
innerController.abort(); | ||
}, | ||
{ once: true }, | ||
); | ||
|
||
config.signal?.addEventListener?.( | ||
'abort', | ||
() => { | ||
innerController.abort(); | ||
}, | ||
{ once: true }, | ||
); | ||
|
||
await next({ ...config, signal: innerController.signal }); | ||
}, | ||
|
||
HttpStatus.axiosMiddleware(), | ||
axiosTracingMiddleware(tracer, context.res.locals.tracing.rootContext), | ||
logMiddleware(logHandler), | ||
cookieMiddleware(cookieStore), | ||
]; | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { createCookieStore } from '../../../http'; | ||
import type { Resolve } from '../../../di'; | ||
import { KnownToken } from '../../../tokens'; | ||
|
||
/** | ||
* Провайдер хранилища cookie для исходящих запросов. | ||
* @param resolve Функция для получения зависимости по токену. | ||
* @return Хранилище cookie. | ||
*/ | ||
export function provideCookieStore(resolve: Resolve) { | ||
const context = resolve(KnownToken.ExpressHandler.context); | ||
|
||
const store = createCookieStore(context.req.header('cookie')); | ||
|
||
// @todo | ||
// store.subscribe(setCookieList => { | ||
// for (const setCookie of setCookieList) { | ||
// const parsed = parseSetCookieHeader(setCookie); | ||
|
||
// if (!parsed) { | ||
// return; | ||
// } | ||
|
||
// context.res.cookie(parsed.name, parsed.value, parsed.attrs); | ||
// } | ||
// }); | ||
|
||
return store; | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { LogHandler, LogHandlerFactory } from '../../../http'; | ||
import { Resolve } from '../../../di'; | ||
import { KnownToken } from '../../../tokens'; | ||
import { FetchLogging } from '../../isomorphic/utils/fetch-logging'; | ||
|
||
/** | ||
* Провайдер обработчика логирования axios. | ||
* @param resolve Функция для получения зависимости по токену. | ||
* @return Обработчик логирования. | ||
*/ | ||
export function provideFetchLogHandler(resolve: Resolve): LogHandler | LogHandlerFactory { | ||
const logger = resolve(KnownToken.logger); | ||
const abortController = resolve(KnownToken.Http.Fetch.abortController); | ||
|
||
const logHandler = new FetchLogging(logger); | ||
|
||
// ВАЖНО: отключаем логирование если запрос прерван | ||
logHandler.disabled = () => abortController.signal.aborted; | ||
|
||
return logHandler; | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { Resolve } from '../../../di'; | ||
import { Middleware, cookie, defaultHeaders } from '../../../http'; | ||
import { KnownToken } from '../../../tokens'; | ||
import { getFetchErrorLogging } from '../../isomorphic/utils/get-fetch-error-logging'; | ||
import { getFetchExtraAborting } from '../../isomorphic/utils/get-fetch-extra-aborting'; | ||
import { getFetchLogging } from '../../isomorphic/utils/get-fetch-logging'; | ||
import { getFetchTracing } from '../../server/utils/get-fetch-tracing'; | ||
import { getForwardedHeaders } from '../../node/utils/get-forwarded-headers'; | ||
|
||
/** | ||
* Провайдер промежуточных слоев для fetch. | ||
* @param resolve Функция для получения зависимости по токену. | ||
* @return Массив промежуточных слоев. | ||
*/ | ||
export function provideFetchMiddleware(resolve: Resolve): Middleware[] { | ||
const config = resolve(KnownToken.Config.base); | ||
const tracer = resolve(KnownToken.Tracing.tracer); | ||
const context = resolve(KnownToken.ExpressHandler.context); | ||
const logHandler = resolve(KnownToken.Http.Fetch.Middleware.Log.handler); | ||
const cookieStore = resolve(KnownToken.Http.Fetch.cookieStore); | ||
const abortController = resolve(KnownToken.Http.Fetch.abortController); | ||
|
||
return [ | ||
// ВАЖНО: слой логирования ошибки ПЕРЕД остальными слоями чтобы не упустить ошибки выше | ||
getFetchErrorLogging(logHandler), | ||
|
||
// пробрасываемые заголовки по соглашению | ||
defaultHeaders(getForwardedHeaders(config, context.req)), | ||
|
||
// обрывание по сигналу из обработчика входящего запроса и по сигналу из конфига исходящего запроса | ||
getFetchExtraAborting(abortController), | ||
|
||
cookie(cookieStore), | ||
|
||
getFetchTracing(tracer, context.res.locals.tracing.rootContext), | ||
|
||
// ВАЖНО: слой логирования запроса и ответа ПОСЛЕ остальных слоев чтобы использовать актуальные данные | ||
getFetchLogging(logHandler), | ||
]; | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import type { Resolve } from '../../../di'; | ||
import { KnownToken } from '../../../tokens'; | ||
import { renderToString } from 'react-dom/server'; | ||
import { PAGE_HANDLER_EVENT_TYPE } from '../../server'; | ||
import { HelmetContext } from '../../server/utils/regular-helmet'; | ||
import { getPageResponseFormat } from '../../node/utils/get-page-response-format'; | ||
import { ConventionalJson } from '../../isomorphic'; | ||
import { ResponseError } from '../../../http'; | ||
|
||
/** | ||
* Провайдер главной функции обработчика входящего http-запроса. | ||
* @param resolve Функция для получения зависимости по токену. | ||
* @return Главная функция. | ||
*/ | ||
export function provideHandlerMain(resolve: Resolve): VoidFunction { | ||
const config = resolve(KnownToken.Config.base); | ||
const logger = resolve(KnownToken.logger); | ||
const assetsInit = resolve(KnownToken.Http.Handler.Page.assets); | ||
const render = resolve(KnownToken.Http.Handler.Page.render); | ||
const extras = resolve(KnownToken.Http.Handler.Response.specificExtras); | ||
const Helmet = resolve(KnownToken.Http.Handler.Page.helmet); | ||
const { req, res } = resolve(KnownToken.ExpressHandler.context); | ||
const abortController = resolve(KnownToken.Http.Fetch.abortController); | ||
|
||
const getAssets = typeof assetsInit === 'function' ? assetsInit : () => assetsInit; | ||
|
||
/** | ||
* Рендер JSX-элемента в строку. | ||
* @param element Элемент. | ||
* @return Строка. | ||
*/ | ||
const elementToString = (element: JSX.Element) => { | ||
res.emit(PAGE_HANDLER_EVENT_TYPE.renderStart); | ||
const result = renderToString(element); | ||
res.emit(PAGE_HANDLER_EVENT_TYPE.renderFinish); | ||
|
||
return result; | ||
}; | ||
|
||
return async () => { | ||
try { | ||
const assets = await getAssets(); | ||
const meta = extras.getMeta(); | ||
|
||
const jsx = ( | ||
<HelmetContext.Provider value={{ title: config.appName, assets }}> | ||
<Helmet>{await render()}</Helmet> | ||
</HelmetContext.Provider> | ||
); | ||
|
||
switch (getPageResponseFormat(req)) { | ||
case 'html': { | ||
res.setHeader('simaland-bundle-js', assets.js); | ||
res.setHeader('simaland-bundle-css', assets.css); | ||
|
||
if (assets.criticalJs) { | ||
res.setHeader('simaland-critical-js', assets.criticalJs); | ||
} | ||
|
||
if (assets.criticalCss) { | ||
res.setHeader('simaland-critical-css', assets.criticalCss); | ||
} | ||
|
||
if (meta) { | ||
res.setHeader('simaland-meta', JSON.stringify(meta)); | ||
} | ||
|
||
// ВАЖНО: DOCTYPE обязательно нужен так как влияет на то как браузер будет парсить html/css | ||
// ВАЖНО: DOCTYPE нужен только когда отдаем полноценную страницу | ||
if (config.env === 'development') { | ||
res.send(`<!DOCTYPE html>${elementToString(jsx)}`); | ||
} else { | ||
res.send(elementToString(jsx)); | ||
} | ||
break; | ||
} | ||
|
||
case 'json': { | ||
res.json({ | ||
markup: elementToString(jsx), | ||
bundle_js: assets.js, | ||
bundle_css: assets.css, | ||
critical_js: assets.criticalJs, | ||
critical_css: assets.criticalCss, | ||
meta, | ||
} satisfies ConventionalJson); | ||
break; | ||
} | ||
} | ||
} catch (error) { | ||
let message: string; | ||
let statusCode = 500; // по умолчанию, если на этапе подготовки страницы что-то не так, отдаем 500 | ||
|
||
if (error instanceof Error) { | ||
message = error.message; | ||
|
||
if (error instanceof ResponseError) { | ||
statusCode = error.statusCode; | ||
} | ||
} else { | ||
message = String(error); | ||
} | ||
|
||
res.status(statusCode).send(message); | ||
logger.error(error); | ||
} | ||
|
||
// ВАЖНО: прерываем исходящие в рамках обработчика http-запросы | ||
abortController.abort(); | ||
}; | ||
} | ||
|
||
// @todo а что если привести все зависимости к виду: | ||
// const getAppConfig = resolve.lazy(KnownToken.Config.base); |
Oops, something went wrong.