mirror of
https://github.com/withastro/astro.git
synced 2024-12-16 21:46:22 -05:00
Keep clientAddress on cloned requests (#12613)
* Keep clientAddress on cloned requests User observed that calling actions resulted in an error about not having clientRequest available. This is because the user had a middleware that cloned the request, which loses all of the symbols. The fix is to pass the clientAddress directly into the RenderContext. This deprecates the `clientAddressSymbol`, but we need to keep it for now because some adapters set the clientAddress that way. Note that similar fixes should be done for other symbol usage on the Request object (locals is one). * changeset * fix build stuff * Update packages/astro/src/core/render-context.ts * Update changeset
This commit is contained in:
parent
5b9b618183
commit
306c9f9a9a
8 changed files with 55 additions and 22 deletions
9
.changeset/wild-geckos-draw.md
Normal file
9
.changeset/wild-geckos-draw.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix use of cloned requests in middleware with clientAddress
|
||||||
|
|
||||||
|
When using `context.clientAddress` or `Astro.clientAddress` Astro looks up the address in a hidden property. Cloning a request can cause this hidden property to be lost.
|
||||||
|
|
||||||
|
The fix is to pass the address as an internal property instead, decoupling it from the request.
|
|
@ -508,6 +508,7 @@ export class experimental_AstroContainer {
|
||||||
pathname: url.pathname,
|
pathname: url.pathname,
|
||||||
locals: options?.locals ?? {},
|
locals: options?.locals ?? {},
|
||||||
partial: options?.partial ?? true,
|
partial: options?.partial ?? true,
|
||||||
|
clientAddress: ''
|
||||||
});
|
});
|
||||||
if (options.params) {
|
if (options.params) {
|
||||||
renderContext.params = options.params;
|
renderContext.params = options.params;
|
||||||
|
|
|
@ -72,6 +72,7 @@ export interface RenderErrorOptions {
|
||||||
* Allows passing an error to 500.astro. It will be available through `Astro.props.error`.
|
* Allows passing an error to 500.astro. It will be available through `Astro.props.error`.
|
||||||
*/
|
*/
|
||||||
error?: unknown;
|
error?: unknown;
|
||||||
|
clientAddress: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class App {
|
export class App {
|
||||||
|
@ -240,7 +241,7 @@ export class App {
|
||||||
let addCookieHeader: boolean | undefined;
|
let addCookieHeader: boolean | undefined;
|
||||||
|
|
||||||
addCookieHeader = renderOptions?.addCookieHeader;
|
addCookieHeader = renderOptions?.addCookieHeader;
|
||||||
clientAddress = renderOptions?.clientAddress;
|
clientAddress = renderOptions?.clientAddress ?? Reflect.get(request,clientAddressSymbol);
|
||||||
routeData = renderOptions?.routeData;
|
routeData = renderOptions?.routeData;
|
||||||
locals = renderOptions?.locals;
|
locals = renderOptions?.locals;
|
||||||
|
|
||||||
|
@ -256,13 +257,10 @@ export class App {
|
||||||
if (typeof locals !== 'object') {
|
if (typeof locals !== 'object') {
|
||||||
const error = new AstroError(AstroErrorData.LocalsNotAnObject);
|
const error = new AstroError(AstroErrorData.LocalsNotAnObject);
|
||||||
this.#logger.error(null, error.stack!);
|
this.#logger.error(null, error.stack!);
|
||||||
return this.#renderError(request, { status: 500, error });
|
return this.#renderError(request, { status: 500, error, clientAddress });
|
||||||
}
|
}
|
||||||
Reflect.set(request, clientLocalsSymbol, locals);
|
Reflect.set(request, clientLocalsSymbol, locals);
|
||||||
}
|
}
|
||||||
if (clientAddress) {
|
|
||||||
Reflect.set(request, clientAddressSymbol, clientAddress);
|
|
||||||
}
|
|
||||||
if (!routeData) {
|
if (!routeData) {
|
||||||
routeData = this.match(request);
|
routeData = this.match(request);
|
||||||
this.#logger.debug('router', 'Astro matched the following route for ' + request.url);
|
this.#logger.debug('router', 'Astro matched the following route for ' + request.url);
|
||||||
|
@ -271,7 +269,7 @@ export class App {
|
||||||
if (!routeData) {
|
if (!routeData) {
|
||||||
this.#logger.debug('router', "Astro hasn't found routes that match " + request.url);
|
this.#logger.debug('router', "Astro hasn't found routes that match " + request.url);
|
||||||
this.#logger.debug('router', "Here's the available routes:\n", this.#manifestData);
|
this.#logger.debug('router', "Here's the available routes:\n", this.#manifestData);
|
||||||
return this.#renderError(request, { locals, status: 404 });
|
return this.#renderError(request, { locals, status: 404, clientAddress });
|
||||||
}
|
}
|
||||||
const pathname = this.#getPathnameFromRequest(request);
|
const pathname = this.#getPathnameFromRequest(request);
|
||||||
const defaultStatus = this.#getDefaultStatusCode(routeData, pathname);
|
const defaultStatus = this.#getDefaultStatusCode(routeData, pathname);
|
||||||
|
@ -288,11 +286,12 @@ export class App {
|
||||||
request,
|
request,
|
||||||
routeData,
|
routeData,
|
||||||
status: defaultStatus,
|
status: defaultStatus,
|
||||||
|
clientAddress
|
||||||
});
|
});
|
||||||
response = await renderContext.render(await mod.page());
|
response = await renderContext.render(await mod.page());
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
this.#logger.error(null, err.stack || err.message || String(err));
|
this.#logger.error(null, err.stack || err.message || String(err));
|
||||||
return this.#renderError(request, { locals, status: 500, error: err });
|
return this.#renderError(request, { locals, status: 500, error: err, clientAddress });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -306,6 +305,7 @@ export class App {
|
||||||
// We don't have an error to report here. Passing null means we pass nothing intentionally
|
// We don't have an error to report here. Passing null means we pass nothing intentionally
|
||||||
// while undefined means there's no error
|
// while undefined means there's no error
|
||||||
error: response.status === 500 ? null : undefined,
|
error: response.status === 500 ? null : undefined,
|
||||||
|
clientAddress
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -353,6 +353,7 @@ export class App {
|
||||||
response: originalResponse,
|
response: originalResponse,
|
||||||
skipMiddleware = false,
|
skipMiddleware = false,
|
||||||
error,
|
error,
|
||||||
|
clientAddress,
|
||||||
}: RenderErrorOptions,
|
}: RenderErrorOptions,
|
||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
const errorRoutePath = `/${status}${this.#manifest.trailingSlash === 'always' ? '/' : ''}`;
|
const errorRoutePath = `/${status}${this.#manifest.trailingSlash === 'always' ? '/' : ''}`;
|
||||||
|
@ -386,6 +387,7 @@ export class App {
|
||||||
routeData: errorRouteData,
|
routeData: errorRouteData,
|
||||||
status,
|
status,
|
||||||
props: { error },
|
props: { error },
|
||||||
|
clientAddress
|
||||||
});
|
});
|
||||||
const response = await renderContext.render(await mod.page());
|
const response = await renderContext.render(await mod.page());
|
||||||
return this.#mergeResponses(response, originalResponse);
|
return this.#mergeResponses(response, originalResponse);
|
||||||
|
@ -397,6 +399,7 @@ export class App {
|
||||||
status,
|
status,
|
||||||
response: originalResponse,
|
response: originalResponse,
|
||||||
skipMiddleware: true,
|
skipMiddleware: true,
|
||||||
|
clientAddress
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -444,6 +444,7 @@ async function generatePath(
|
||||||
pathname: pathname,
|
pathname: pathname,
|
||||||
request,
|
request,
|
||||||
routeData: route,
|
routeData: route,
|
||||||
|
clientAddress: undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
let body: string | Uint8Array;
|
let body: string | Uint8Array;
|
||||||
|
|
|
@ -48,6 +48,7 @@ export class RenderContext {
|
||||||
public request: Request,
|
public request: Request,
|
||||||
public routeData: RouteData,
|
public routeData: RouteData,
|
||||||
public status: number,
|
public status: number,
|
||||||
|
public clientAddress: string | undefined,
|
||||||
protected cookies = new AstroCookies(request),
|
protected cookies = new AstroCookies(request),
|
||||||
public params = getParams(routeData, pathname),
|
public params = getParams(routeData, pathname),
|
||||||
protected url = new URL(request.url),
|
protected url = new URL(request.url),
|
||||||
|
@ -71,10 +72,11 @@ export class RenderContext {
|
||||||
pipeline,
|
pipeline,
|
||||||
request,
|
request,
|
||||||
routeData,
|
routeData,
|
||||||
|
clientAddress,
|
||||||
status = 200,
|
status = 200,
|
||||||
props,
|
props,
|
||||||
partial = undefined,
|
partial = undefined,
|
||||||
}: Pick<RenderContext, 'pathname' | 'pipeline' | 'request' | 'routeData'> &
|
}: Pick<RenderContext, 'pathname' | 'pipeline' | 'request' | 'routeData' | 'clientAddress'> &
|
||||||
Partial<
|
Partial<
|
||||||
Pick<RenderContext, 'locals' | 'middleware' | 'status' | 'props' | 'partial'>
|
Pick<RenderContext, 'locals' | 'middleware' | 'status' | 'props' | 'partial'>
|
||||||
>): Promise<RenderContext> {
|
>): Promise<RenderContext> {
|
||||||
|
@ -88,6 +90,7 @@ export class RenderContext {
|
||||||
request,
|
request,
|
||||||
routeData,
|
routeData,
|
||||||
status,
|
status,
|
||||||
|
clientAddress,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
|
@ -309,7 +312,7 @@ export class RenderContext {
|
||||||
routePattern: this.routeData.route,
|
routePattern: this.routeData.route,
|
||||||
isPrerendered: this.routeData.prerender,
|
isPrerendered: this.routeData.prerender,
|
||||||
get clientAddress() {
|
get clientAddress() {
|
||||||
return renderContext.clientAddress();
|
return renderContext.getClientAddress();
|
||||||
},
|
},
|
||||||
get currentLocale() {
|
get currentLocale() {
|
||||||
return renderContext.computeCurrentLocale();
|
return renderContext.computeCurrentLocale();
|
||||||
|
@ -490,7 +493,7 @@ export class RenderContext {
|
||||||
isPrerendered: this.routeData.prerender,
|
isPrerendered: this.routeData.prerender,
|
||||||
cookies,
|
cookies,
|
||||||
get clientAddress() {
|
get clientAddress() {
|
||||||
return renderContext.clientAddress();
|
return renderContext.getClientAddress();
|
||||||
},
|
},
|
||||||
get currentLocale() {
|
get currentLocale() {
|
||||||
return renderContext.computeCurrentLocale();
|
return renderContext.computeCurrentLocale();
|
||||||
|
@ -519,14 +522,22 @@ export class RenderContext {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
clientAddress() {
|
getClientAddress() {
|
||||||
const { pipeline, request } = this;
|
const { pipeline, request, routeData, clientAddress } = this;
|
||||||
if (clientAddressSymbol in request) {
|
|
||||||
return Reflect.get(request, clientAddressSymbol) as string;
|
if(routeData.prerender) {
|
||||||
|
throw new AstroError(AstroErrorData.PrerenderClientAddressNotAvailable);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.body === null) {
|
if(clientAddress) {
|
||||||
throw new AstroError(AstroErrorData.PrerenderClientAddressNotAvailable);
|
return clientAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Legacy, should not need to get here.
|
||||||
|
// Some adapters set this symbol so we can't remove support yet.
|
||||||
|
// Adapters should be updated to provide it via RenderOptions instead.
|
||||||
|
if (clientAddressSymbol in request) {
|
||||||
|
return Reflect.get(request, clientAddressSymbol) as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pipeline.adapterName) {
|
if (pipeline.adapterName) {
|
||||||
|
|
|
@ -24,7 +24,6 @@ export interface CreateRequestOptions {
|
||||||
routePattern: string;
|
routePattern: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const clientAddressSymbol = Symbol.for('astro.clientAddress');
|
|
||||||
const clientLocalsSymbol = Symbol.for('astro.locals');
|
const clientLocalsSymbol = Symbol.for('astro.locals');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,7 +36,6 @@ const clientLocalsSymbol = Symbol.for('astro.locals');
|
||||||
export function createRequest({
|
export function createRequest({
|
||||||
url,
|
url,
|
||||||
headers,
|
headers,
|
||||||
clientAddress,
|
|
||||||
method = 'GET',
|
method = 'GET',
|
||||||
body = undefined,
|
body = undefined,
|
||||||
logger,
|
logger,
|
||||||
|
@ -93,9 +91,6 @@ export function createRequest({
|
||||||
_headers = newHeaders;
|
_headers = newHeaders;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else if (clientAddress) {
|
|
||||||
// clientAddress is stored to be read by RenderContext, only if the request is for a page that will be on-demand rendered
|
|
||||||
Reflect.set(request, clientAddressSymbol, clientAddress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Reflect.set(request, clientLocalsSymbol, locals ?? {});
|
Reflect.set(request, clientLocalsSymbol, locals ?? {});
|
||||||
|
|
|
@ -172,7 +172,6 @@ export async function handleRoute({
|
||||||
method: incomingRequest.method,
|
method: incomingRequest.method,
|
||||||
body,
|
body,
|
||||||
logger,
|
logger,
|
||||||
clientAddress: incomingRequest.socket.remoteAddress,
|
|
||||||
isPrerendered: route.prerender,
|
isPrerendered: route.prerender,
|
||||||
routePattern: route.component,
|
routePattern: route.component,
|
||||||
});
|
});
|
||||||
|
@ -191,6 +190,7 @@ export async function handleRoute({
|
||||||
middleware: isDefaultPrerendered404(matchedRoute.route) ? undefined : middleware,
|
middleware: isDefaultPrerendered404(matchedRoute.route) ? undefined : middleware,
|
||||||
request,
|
request,
|
||||||
routeData: route,
|
routeData: route,
|
||||||
|
clientAddress: incomingRequest.socket.remoteAddress
|
||||||
});
|
});
|
||||||
|
|
||||||
let response;
|
let response;
|
||||||
|
@ -248,6 +248,7 @@ export async function handleRoute({
|
||||||
middleware: isDefaultPrerendered404(fourOhFourRoute.route) ? undefined : middleware,
|
middleware: isDefaultPrerendered404(fourOhFourRoute.route) ? undefined : middleware,
|
||||||
request,
|
request,
|
||||||
routeData: fourOhFourRoute.route,
|
routeData: fourOhFourRoute.route,
|
||||||
|
clientAddress: incomingRequest.socket.remoteAddress
|
||||||
});
|
});
|
||||||
response = await renderContext.render(fourOhFourRoute.preloadedComponent);
|
response = await renderContext.render(fourOhFourRoute.preloadedComponent);
|
||||||
}
|
}
|
||||||
|
|
12
packages/astro/test/fixtures/client-address/src/middleware.ts
vendored
Normal file
12
packages/astro/test/fixtures/client-address/src/middleware.ts
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { defineMiddleware } from 'astro:middleware';
|
||||||
|
|
||||||
|
export const onRequest = defineMiddleware(async (ctx, next) => {
|
||||||
|
// Clone a request, losing all symbols
|
||||||
|
const clonedRequest = ctx.request.clone();
|
||||||
|
const safeInternalRequest = new Request(clonedRequest, {
|
||||||
|
method: clonedRequest.method,
|
||||||
|
headers: clonedRequest.headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
return next(safeInternalRequest);
|
||||||
|
});
|
Loading…
Reference in a new issue