From 8e2cae4990c9629702d02a3dacf5365fea0b7833 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Thu, 24 Mar 2022 17:00:50 -0400 Subject: [PATCH] Implement the Astro.request RFC --- examples/ssr/src/pages/products/[id].astro | 2 +- packages/astro/src/@types/astro.ts | 9 ++-- packages/astro/src/core/build/generate.ts | 5 +- packages/astro/src/core/endpoint/index.ts | 7 ++- packages/astro/src/core/render/core.ts | 9 ++-- packages/astro/src/core/render/dev/index.ts | 11 ++-- packages/astro/src/core/render/request.ts | 50 ------------------- packages/astro/src/core/render/result.ts | 13 ++--- packages/astro/src/core/request.ts | 32 ++++++++++++ .../src/vite-plugin-astro-server/index.ts | 18 +++++-- .../src/pages/blog/[year]/[slug].astro | 4 +- .../src/pages/pizza/[...pizza].astro | 4 +- .../src/pages/pizza/[cheese]-[topping].astro | 4 +- .../fixtures/ssr-dynamic/src/pages/[id].astro | 2 +- 14 files changed, 79 insertions(+), 91 deletions(-) delete mode 100644 packages/astro/src/core/render/request.ts create mode 100644 packages/astro/src/core/request.ts diff --git a/examples/ssr/src/pages/products/[id].astro b/examples/ssr/src/pages/products/[id].astro index 37fe7e0b46..317cea6352 100644 --- a/examples/ssr/src/pages/products/[id].astro +++ b/examples/ssr/src/pages/products/[id].astro @@ -5,7 +5,7 @@ import AddToCart from '../../components/AddToCart.svelte'; import { getProduct } from '../../api'; import '../../styles/common.css'; -const id = Number(Astro.request.params.id); +const id = Number(Astro.params.id); const product = await getProduct(id); --- diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 9e83af22d3..b77bbe4e14 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -4,7 +4,6 @@ import type * as vite from 'vite'; import type { z } from 'zod'; import type { AstroConfigSchema } from '../core/config'; import type { AstroComponentFactory, Metadata } from '../runtime/server'; -import type { AstroRequest } from '../core/render/request'; export type { SSRManifest } from '../core/app/types'; export interface AstroBuiltinProps { @@ -47,10 +46,14 @@ export interface BuildConfig { * Docs: https://docs.astro.build/reference/api-reference/#astro-global */ export interface AstroGlobal extends AstroGlobalPartial { + /** get the current canonical URL */ + canonicalURL: URL; + /** get page params (dynamic pages only) */ + params: Params; /** set props for this astro component (along with default values) */ props: Record; /** get information about this page */ - request: AstroRequest; + request: Request; /** see if slots are used */ slots: Record & { has(slotName: string): boolean; render(slotName: string): Promise }; } @@ -624,7 +627,7 @@ export interface EndpointOutput { } export interface EndpointHandler { - [method: string]: (params: any, request: AstroRequest) => EndpointOutput | Response; + [method: string]: (params: any, request: Request) => EndpointOutput | Response; } export interface AstroRenderer { diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index d3d2365b41..794a715439 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -16,6 +16,7 @@ import { createLinkStylesheetElementSet, createModuleScriptElementWithSrcSet } f import { getOutFile, getOutFolder, getOutRoot } from './common.js'; import type { PageBuildData, StaticBuildOptions } from './types'; import { getTimeStat } from './util.js'; +import { createRequest } from '../request.js'; // Render is usually compute, which Node.js can't parallelize well. // In real world testing, dropping from 10->1 showed a notiable perf @@ -195,6 +196,7 @@ async function generatePath(pathname: string, opts: StaticBuildOptions, gopts: G } try { + const url = new URL(origin + pathname); const options: RenderOptions = { legacyBuild: false, links, @@ -222,8 +224,7 @@ async function generatePath(pathname: string, opts: StaticBuildOptions, gopts: G const fullyRelativePath = relPath[0] === '.' ? relPath : './' + relPath; return fullyRelativePath; }, - method: 'GET', - headers: new Headers(), + request: createRequest(url, new Headers()), route: pageData.route, routeCache, site: astroConfig.buildOptions.site, diff --git a/packages/astro/src/core/endpoint/index.ts b/packages/astro/src/core/endpoint/index.ts index 745811354f..fb525066c9 100644 --- a/packages/astro/src/core/endpoint/index.ts +++ b/packages/astro/src/core/endpoint/index.ts @@ -2,9 +2,9 @@ import type { EndpointHandler } from '../../@types/astro'; import type { RenderOptions } from '../render/core'; import { renderEndpoint } from '../../runtime/server/index.js'; import { getParamsAndProps, GetParamsAndPropsError } from '../render/core.js'; -import { createRequest } from '../render/request.js'; -export type EndpointOptions = Pick; +export type EndpointOptions = Pick; type EndpointCallResult = | { @@ -23,9 +23,8 @@ export async function call(mod: EndpointHandler, opts: EndpointOptions): Promise throw new Error(`[getStaticPath] route pattern matched, but no matching static path found. (${opts.pathname})`); } const [params] = paramsAndPropsResp; - const request = createRequest(opts.method, opts.pathname, opts.headers, opts.origin, opts.site, opts.ssr); - const response = await renderEndpoint(mod, request, params); + const response = await renderEndpoint(mod, opts.request, params); if (response instanceof Response) { return { diff --git a/packages/astro/src/core/render/core.ts b/packages/astro/src/core/render/core.ts index a1ea94f65d..3cb109b768 100644 --- a/packages/astro/src/core/render/core.ts +++ b/packages/astro/src/core/render/core.ts @@ -1,6 +1,5 @@ import type { ComponentInstance, EndpointHandler, MarkdownRenderOptions, Params, Props, SSRLoadedRenderer, RouteData, SSRElement } from '../../@types/astro'; import type { LogOptions } from '../logger.js'; -import type { AstroRequest } from './request'; import { renderHead, renderPage } from '../../runtime/server/index.js'; import { getParams } from '../routing/index.js'; @@ -71,12 +70,11 @@ export interface RenderOptions { routeCache: RouteCache; site?: string; ssr: boolean; - method: string; - headers: Headers; + request: Request; } export async function render(opts: RenderOptions): Promise<{ type: 'html'; html: string } | { type: 'response'; response: Response }> { - const { headers, legacyBuild, links, logging, origin, markdownRender, method, mod, pathname, scripts, renderers, resolve, route, routeCache, site, ssr } = opts; + const { legacyBuild, links, logging, origin, markdownRender, mod, pathname, scripts, renderers, request, resolve, route, routeCache, site, ssr } = opts; const paramsAndPropsRes = await getParamsAndProps({ logging, @@ -107,11 +105,10 @@ export async function render(opts: RenderOptions): Promise<{ type: 'html'; html: pathname, resolve, renderers, + request, site, scripts, ssr, - method, - headers, }); let page = await renderPage(result, Component, pageProps, null); diff --git a/packages/astro/src/core/render/dev/index.ts b/packages/astro/src/core/render/dev/index.ts index d16cb8923a..97cb402672 100644 --- a/packages/astro/src/core/render/dev/index.ts +++ b/packages/astro/src/core/render/dev/index.ts @@ -29,10 +29,8 @@ export interface SSROptions { routeCache: RouteCache; /** Vite instance */ viteServer: vite.ViteDevServer; - /** Method */ - method: string; - /** Headers */ - headers: Headers; + /** Request */ + request: Request; } export type ComponentPreload = [SSRLoadedRenderer[], ComponentInstance]; @@ -65,7 +63,7 @@ export async function preload({ astroConfig, filePath, viteServer }: Pick { - const { astroConfig, filePath, logging, mode, origin, pathname, method, headers, route, routeCache, viteServer } = ssrOpts; + const { astroConfig, filePath, logging, mode, origin, pathname, request, route, routeCache, viteServer } = ssrOpts; const legacy = astroConfig.buildOptions.legacyBuild; // Add hoisted script tags @@ -145,12 +143,11 @@ export async function render(renderers: SSRLoadedRenderer[], mod: ComponentInsta } }, renderers, + request, route, routeCache, site: astroConfig.buildOptions.site, ssr: astroConfig.buildOptions.experimentalSsr, - method, - headers, }); if (route?.type === 'endpoint' || content.type === 'response') { diff --git a/packages/astro/src/core/render/request.ts b/packages/astro/src/core/render/request.ts deleted file mode 100644 index 5956c78679..0000000000 --- a/packages/astro/src/core/render/request.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { Params } from '../../@types/astro'; -import { canonicalURL as utilCanonicalURL } from '../util.js'; - -type Site = string | undefined; - -export interface AstroRequest { - /** get the current page URL */ - url: URL; - - /** get the current canonical URL */ - canonicalURL: URL; - - /** get page params (dynamic pages only) */ - params: Params; - - headers: Headers; - - method: string; -} - -export type AstroRequestSSR = AstroRequest; - -export function createRequest(method: string, pathname: string, headers: Headers, origin: string, site: Site, ssr: boolean): AstroRequest { - const url = new URL('.' + pathname, new URL(origin)); - - const canonicalURL = utilCanonicalURL('.' + pathname, site ?? url.origin); - - const request: AstroRequest = { - url, - canonicalURL, - params: {}, - headers, - method, - }; - - if (!ssr) { - // Headers are only readable if using SSR-mode. If not, make it an empty headers - // object, so you can't do something bad. - request.headers = new Headers(); - - // Disallow using query params. - request.url = new URL(request.url); - - for (const [key] of request.url.searchParams) { - request.url.searchParams.delete(key); - } - } - - return request; -} diff --git a/packages/astro/src/core/render/result.ts b/packages/astro/src/core/render/result.ts index 8b14c99bdd..6f03d48061 100644 --- a/packages/astro/src/core/render/result.ts +++ b/packages/astro/src/core/render/result.ts @@ -3,7 +3,7 @@ import type { AstroGlobal, AstroGlobalPartial, MarkdownParser, MarkdownRenderOpt import { renderSlot } from '../../runtime/server/index.js'; import { LogOptions, warn } from '../logger.js'; import { isCSSRequest } from './dev/css.js'; -import { createRequest } from './request.js'; +import { canonicalURL as utilCanonicalURL } from '../util.js'; import { isScriptRequest } from './script.js'; function onlyAvailableInSSR(name: string) { @@ -26,8 +26,7 @@ export interface CreateResultArgs { site: string | undefined; links?: Set; scripts?: Set; - headers: Headers; - method: string; + request: Request; } class Slots { @@ -72,10 +71,10 @@ class Slots { } export function createResult(args: CreateResultArgs): SSRResult { - const { legacyBuild, markdownRender, method, origin, headers, params, pathname, renderers, resolve, site } = args; + const { legacyBuild, markdownRender, origin, params, pathname, renderers, request, resolve, site } = args; - const request = createRequest(method, pathname, headers, origin, site, args.ssr); - request.params = params; + const url = new URL(request.url); + const canonicalURL = utilCanonicalURL('.' + pathname, site ?? url.origin); // Create the result object that will be passed into the render function. // This object starts here as an empty shell (not yet the result) but then @@ -90,6 +89,8 @@ export function createResult(args: CreateResultArgs): SSRResult { const Astro = { __proto__: astroGlobal, + canonicalURL, + params, props, request, redirect: args.ssr diff --git a/packages/astro/src/core/request.ts b/packages/astro/src/core/request.ts new file mode 100644 index 0000000000..74ce72eec3 --- /dev/null +++ b/packages/astro/src/core/request.ts @@ -0,0 +1,32 @@ +import type { IncomingHttpHeaders } from 'http'; + +type HeaderType = Headers | Record | IncomingHttpHeaders; + +export function createRequest(url: URL | string, headers: HeaderType, method: string = 'GET'): Request { + let headersObj = headers instanceof Headers ? headers : + new Headers(Object.entries(headers as Record)); + + const request = new Request(url.toString(), { + method: method, + headers: headersObj + }); + + Object.defineProperties(request, { + canonicalURL: { + get() { + console.warn(`Astro.request.canonicalURL has been moved to Astro.canonicalURL`); + return undefined; + } + }, + params: { + get() { + console.warn(`Astro.request.params has been moved to Astro.params`); + return undefined; + } + } + }); + + // TODO warn + + return request; +} diff --git a/packages/astro/src/vite-plugin-astro-server/index.ts b/packages/astro/src/vite-plugin-astro-server/index.ts index bc45596dae..ec45ba8d51 100644 --- a/packages/astro/src/vite-plugin-astro-server/index.ts +++ b/packages/astro/src/vite-plugin-astro-server/index.ts @@ -10,11 +10,11 @@ import { createSafeError } from '../core/util.js'; import { ssr, preload } from '../core/render/dev/index.js'; import { call as callEndpoint } from '../core/endpoint/dev/index.js'; import * as msg from '../core/messages.js'; +import { createRequest } from '../core/request.js'; import notFoundTemplate, { subpathNotUsedTemplate } from '../template/4xx.js'; import serverErrorTemplate from '../template/5xx.js'; import { RouteCache } from '../core/render/route-cache.js'; -import { AstroRequest } from '../core/render/request.js'; interface AstroPluginOptions { config: AstroConfig; @@ -117,9 +117,19 @@ async function handleRequest( const site = config.buildOptions.site ? new URL(config.buildOptions.site) : undefined; const devRoot = site ? site.pathname : '/'; const origin = `${viteServer.config.server.https ? 'https' : 'http'}://${req.headers.host}`; + const buildingToSSR = !!config._ctx.adapter?.serverEntrypoint; const url = new URL(origin + req.url); const pathname = decodeURI(url.pathname); const rootRelativeUrl = pathname.substring(devRoot.length - 1); + if(!buildingToSSR) { + // Prevent user from depending on search params when not doing SSR. + for(const [key] of url.searchParams) { + url.searchParams.delete(key); + } + } + + // Headers are only available when using SSR. + const request = createRequest(url, buildingToSSR ? req.headers : new Headers(), req.method); try { if (!pathname.startsWith(devRoot)) { @@ -166,10 +176,9 @@ async function handleRequest( filePath: filePathCustom404, logging, mode: 'development', - method: 'GET', - headers: new Headers(Object.entries(req.headers as Record)), origin, pathname: rootRelativeUrl, + request, route: routeCustom404, routeCache, viteServer, @@ -190,8 +199,7 @@ async function handleRequest( route, routeCache, viteServer, - method: req.method || 'GET', - headers: new Headers(Object.entries(req.headers as Record)), + request, }; // Route successfully matched! Render it. diff --git a/packages/astro/test/fixtures/astro-get-static-paths/src/pages/blog/[year]/[slug].astro b/packages/astro/test/fixtures/astro-get-static-paths/src/pages/blog/[year]/[slug].astro index b5c8ec282e..12e686366c 100644 --- a/packages/astro/test/fixtures/astro-get-static-paths/src/pages/blog/[year]/[slug].astro +++ b/packages/astro/test/fixtures/astro-get-static-paths/src/pages/blog/[year]/[slug].astro @@ -6,7 +6,7 @@ export async function getStaticPaths() { ] } -const { year, slug } = Astro.request.params +const { year, slug } = Astro.params --- @@ -14,4 +14,4 @@ const { year, slug } = Astro.request.params {year} | {slug} - \ No newline at end of file + diff --git a/packages/astro/test/fixtures/astro-get-static-paths/src/pages/pizza/[...pizza].astro b/packages/astro/test/fixtures/astro-get-static-paths/src/pages/pizza/[...pizza].astro index 02ef8ef478..a58b314e36 100644 --- a/packages/astro/test/fixtures/astro-get-static-paths/src/pages/pizza/[...pizza].astro +++ b/packages/astro/test/fixtures/astro-get-static-paths/src/pages/pizza/[...pizza].astro @@ -8,7 +8,7 @@ export function getStaticPaths() { params: { pizza: 'grimaldis/new-york' }, }] } -const { pizza } = Astro.request.params +const { pizza } = Astro.params --- @@ -19,4 +19,4 @@ const { pizza } = Astro.request.params

Welcome to {pizza ?? 'The landing page'}

- \ No newline at end of file + diff --git a/packages/astro/test/fixtures/astro-get-static-paths/src/pages/pizza/[cheese]-[topping].astro b/packages/astro/test/fixtures/astro-get-static-paths/src/pages/pizza/[cheese]-[topping].astro index 353805c5cd..a698a76d78 100644 --- a/packages/astro/test/fixtures/astro-get-static-paths/src/pages/pizza/[cheese]-[topping].astro +++ b/packages/astro/test/fixtures/astro-get-static-paths/src/pages/pizza/[cheese]-[topping].astro @@ -6,7 +6,7 @@ export function getStaticPaths() { params: { cheese: 'provolone', topping: 'sausage' }, }] } -const { cheese, topping } = Astro.request.params +const { cheese, topping } = Astro.params --- @@ -18,4 +18,4 @@ const { cheese, topping } = Astro.request.params

🍕 It's pizza time

{cheese}-{topping}

- \ No newline at end of file + diff --git a/packages/astro/test/fixtures/ssr-dynamic/src/pages/[id].astro b/packages/astro/test/fixtures/ssr-dynamic/src/pages/[id].astro index b976757e21..e646261729 100644 --- a/packages/astro/test/fixtures/ssr-dynamic/src/pages/[id].astro +++ b/packages/astro/test/fixtures/ssr-dynamic/src/pages/[id].astro @@ -1,5 +1,5 @@ --- -const val = Number(Astro.request.params.id); +const val = Number(Astro.params.id); ---