diff --git a/packages/astro/src/core/base-pipeline.ts b/packages/astro/src/core/base-pipeline.ts index 614748cd81..4f87112c18 100644 --- a/packages/astro/src/core/base-pipeline.ts +++ b/packages/astro/src/core/base-pipeline.ts @@ -9,9 +9,12 @@ import type { SSRManifest, SSRResult, } from '../types/public/internal.js'; +import { createOriginCheckMiddleware } from './app/middlewares.js'; import { AstroError } from './errors/errors.js'; import { AstroErrorData } from './errors/index.js'; import type { Logger } from './logger/core.js'; +import { NOOP_MIDDLEWARE_FN } from './middleware/noop-middleware.js'; +import { sequence } from './middleware/sequence.js'; import { RouteCache } from './render/route-cache.js'; import { createDefaultRoutes } from './routing/default.js'; @@ -22,82 +25,107 @@ import { createDefaultRoutes } from './routing/default.js'; * Thus, a `Pipeline` is created once at process start and then used by every `RenderContext`. */ export abstract class Pipeline { - readonly internalMiddleware: MiddlewareHandler[]; - - constructor( - readonly logger: Logger, - readonly manifest: SSRManifest, - /** - * "development" or "production" - */ - readonly mode: RuntimeMode, - readonly renderers: SSRLoadedRenderer[], - readonly resolve: (s: string) => Promise, - /** - * Based on Astro config's `output` option, `true` if "server" or "hybrid". - */ - readonly serverLike: boolean, - readonly streaming: boolean, - /** - * Used to provide better error messages for `Astro.clientAddress` - */ - readonly adapterName = manifest.adapterName, - readonly clientDirectives = manifest.clientDirectives, - readonly inlinedScripts = manifest.inlinedScripts, - readonly compressHTML = manifest.compressHTML, - readonly i18n = manifest.i18n, - readonly middleware = manifest.middleware, - readonly routeCache = new RouteCache(logger, mode), - /** - * Used for `Astro.site`. - */ - readonly site = manifest.site ? new URL(manifest.site) : undefined, - readonly callSetGetEnv = true, - /** - * Array of built-in, internal, routes. - * Used to find the route module - */ - readonly defaultRoutes = createDefaultRoutes(manifest), - ) { - this.internalMiddleware = []; - // We do use our middleware only if the user isn't using the manual setup - if (i18n?.strategy !== 'manual') { - this.internalMiddleware.push( - createI18nMiddleware(i18n, manifest.base, manifest.trailingSlash, manifest.buildFormat), - ); + readonly internalMiddleware: MiddlewareHandler[]; + resolvedMiddleware: MiddlewareHandler | undefined = undefined; + + constructor( + readonly logger: Logger, + readonly manifest: SSRManifest, + /** + * "development" or "production" + */ + readonly mode: RuntimeMode, + readonly renderers: SSRLoadedRenderer[], + readonly resolve: (s: string) => Promise, + /** + * Based on Astro config's `output` option, `true` if "server" or "hybrid". + */ + readonly serverLike: boolean, + readonly streaming: boolean, + /** + * Used to provide better error messages for `Astro.clientAddress` + */ + readonly adapterName = manifest.adapterName, + readonly clientDirectives = manifest.clientDirectives, + readonly inlinedScripts = manifest.inlinedScripts, + readonly compressHTML = manifest.compressHTML, + readonly i18n = manifest.i18n, + readonly middleware = manifest.middleware, + readonly routeCache = new RouteCache(logger, mode), + /** + * Used for `Astro.site`. + */ + readonly site = manifest.site ? new URL(manifest.site) : undefined, + readonly callSetGetEnv = true, + /** + * Array of built-in, internal, routes. + * Used to find the route module + */ + readonly defaultRoutes = createDefaultRoutes(manifest), + ) { + this.internalMiddleware = []; + // We do use our middleware only if the user isn't using the manual setup + if (i18n?.strategy !== 'manual') { + this.internalMiddleware.push( + createI18nMiddleware(i18n, manifest.base, manifest.trailingSlash, manifest.buildFormat), + ); + } + // In SSR, getSecret should fail by default. Setting it here will run before the + // adapter override. + if (callSetGetEnv && manifest.envGetSecretEnabled) { + setGetEnv(() => { + throw new AstroError(AstroErrorData.EnvUnsupportedGetSecret); + }, true); + } } - // In SSR, getSecret should fail by default. Setting it here will run before the - // adapter override. - if (callSetGetEnv && manifest.envGetSecretEnabled) { - setGetEnv(() => { - throw new AstroError(AstroErrorData.EnvUnsupportedGetSecret); - }, true); + + abstract headElements(routeData: RouteData): Promise | HeadElements; + + abstract componentMetadata( + routeData: RouteData, + ): Promise | void; + + /** + * It attempts to retrieve the `RouteData` that matches the input `url`, and the component that belongs to the `RouteData`. + * + * ## Errors + * + * - if not `RouteData` is found + * + * @param {RewritePayload} rewritePayload The payload provided by the user + * @param {Request} request The original request + */ + abstract tryRewrite( + rewritePayload: RewritePayload, + request: Request, + ): Promise; + + /** + * Tells the pipeline how to retrieve a component give a `RouteData` + * @param routeData + */ + abstract getComponentByRoute(routeData: RouteData): Promise; + + /** + * Resolves the middleware from the manifest, and returns the `onRequest` function. If `onRequest` isn't there, + * it returns a no-op function + */ + async getMiddleware(): Promise { + if (this.resolvedMiddleware) { + return this.resolvedMiddleware; + } else { + const middlewareInstance = await this.middleware(); + const onRequest = middlewareInstance.onRequest ?? NOOP_MIDDLEWARE_FN; + if (this.manifest.checkOrigin) { + this.resolvedMiddleware = sequence(createOriginCheckMiddleware(), onRequest); + } else { + this.resolvedMiddleware = onRequest; + } + return this.resolvedMiddleware; + } } } - abstract headElements(routeData: RouteData): Promise | HeadElements; - - abstract componentMetadata(routeData: RouteData): Promise | void; - - /** - * It attempts to retrieve the `RouteData` that matches the input `url`, and the component that belongs to the `RouteData`. - * - * ## Errors - * - * - if not `RouteData` is found - * - * @param {RewritePayload} rewritePayload The payload provided by the user - * @param {Request} request The original request - */ - abstract tryRewrite(rewritePayload: RewritePayload, request: Request): Promise; - - /** - * Tells the pipeline how to retrieve a component give a `RouteData` - * @param routeData - */ - abstract getComponentByRoute(routeData: RouteData): Promise; -} - // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface HeadElements extends Pick {} diff --git a/packages/astro/src/core/middleware/noop-middleware.ts b/packages/astro/src/core/middleware/noop-middleware.ts index bf5f10d890..2f55962d1f 100644 --- a/packages/astro/src/core/middleware/noop-middleware.ts +++ b/packages/astro/src/core/middleware/noop-middleware.ts @@ -1,3 +1,3 @@ -import type { MiddlewareHandler } from '../../@types/astro.js'; +import type { MiddlewareHandler } from "../../types/public/common.js"; export const NOOP_MIDDLEWARE_FN: MiddlewareHandler = (_, next) => next();