Compare commits
3 commits
main
...
refactor-e
Author | SHA1 | Date | |
---|---|---|---|
|
5debbc74a1 | ||
|
e14e9dd1d9 | ||
|
d60c16a839 |
4 changed files with 115 additions and 118 deletions
|
@ -1,4 +1,3 @@
|
||||||
import mime from 'mime';
|
|
||||||
import type {
|
import type {
|
||||||
EndpointHandler,
|
EndpointHandler,
|
||||||
ManifestData,
|
ManifestData,
|
||||||
|
@ -8,7 +7,7 @@ import type {
|
||||||
SSRManifest,
|
SSRManifest,
|
||||||
} from '../../@types/astro';
|
} from '../../@types/astro';
|
||||||
import type { SinglePageBuiltModule } from '../build/types';
|
import type { SinglePageBuiltModule } from '../build/types';
|
||||||
import { attachToResponse, getSetCookiesFromResponse } from '../cookies/index.js';
|
import { getSetCookiesFromResponse } from '../cookies/index.js';
|
||||||
import { callEndpoint, createAPIContext } from '../endpoint/index.js';
|
import { callEndpoint, createAPIContext } from '../endpoint/index.js';
|
||||||
import { consoleLogDestination } from '../logger/console.js';
|
import { consoleLogDestination } from '../logger/console.js';
|
||||||
import { error, type LogOptions } from '../logger/core.js';
|
import { error, type LogOptions } from '../logger/core.js';
|
||||||
|
@ -44,7 +43,6 @@ export class App {
|
||||||
#manifest: SSRManifest;
|
#manifest: SSRManifest;
|
||||||
#manifestData: ManifestData;
|
#manifestData: ManifestData;
|
||||||
#routeDataToRouteInfo: Map<RouteData, RouteInfo>;
|
#routeDataToRouteInfo: Map<RouteData, RouteInfo>;
|
||||||
#encoder = new TextEncoder();
|
|
||||||
#logging: LogOptions = {
|
#logging: LogOptions = {
|
||||||
dest: consoleLogDestination,
|
dest: consoleLogDestination,
|
||||||
level: 'info',
|
level: 'info',
|
||||||
|
@ -299,36 +297,16 @@ export class App {
|
||||||
mod: handler as any,
|
mod: handler as any,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await callEndpoint(handler, this.#env, ctx, page.onRequest);
|
const response = await callEndpoint(handler, this.#env, ctx, page.onRequest);
|
||||||
|
|
||||||
if (result.type === 'response') {
|
if (response.headers.get('X-Astro-Response') === 'Not-Found') {
|
||||||
if (result.response.headers.get('X-Astro-Response') === 'Not-Found') {
|
const fourOhFourRequest = new Request(new URL('/404', request.url));
|
||||||
const fourOhFourRequest = new Request(new URL('/404', request.url));
|
const fourOhFourRouteData = this.match(fourOhFourRequest);
|
||||||
const fourOhFourRouteData = this.match(fourOhFourRequest);
|
if (fourOhFourRouteData) {
|
||||||
if (fourOhFourRouteData) {
|
return this.render(fourOhFourRequest, fourOhFourRouteData);
|
||||||
return this.render(fourOhFourRequest, fourOhFourRouteData);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return result.response;
|
|
||||||
} else {
|
|
||||||
const body = result.body;
|
|
||||||
const headers = new Headers();
|
|
||||||
const mimeType = mime.getType(url.pathname);
|
|
||||||
if (mimeType) {
|
|
||||||
headers.set('Content-Type', `${mimeType};charset=utf-8`);
|
|
||||||
} else {
|
|
||||||
headers.set('Content-Type', 'text/plain;charset=utf-8');
|
|
||||||
}
|
|
||||||
const bytes = this.#encoder.encode(body);
|
|
||||||
headers.set('Content-Length', bytes.byteLength.toString());
|
|
||||||
|
|
||||||
const response = new Response(bytes, {
|
|
||||||
status: 200,
|
|
||||||
headers,
|
|
||||||
});
|
|
||||||
|
|
||||||
attachToResponse(response, result.cookies);
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import type {
|
||||||
AstroSettings,
|
AstroSettings,
|
||||||
ComponentInstance,
|
ComponentInstance,
|
||||||
EndpointHandler,
|
EndpointHandler,
|
||||||
EndpointOutput,
|
|
||||||
GetStaticPathsItem,
|
GetStaticPathsItem,
|
||||||
ImageTransform,
|
ImageTransform,
|
||||||
MiddlewareHandler,
|
MiddlewareHandler,
|
||||||
|
@ -556,18 +555,13 @@ async function generatePath(
|
||||||
if (pageData.route.type === 'endpoint') {
|
if (pageData.route.type === 'endpoint') {
|
||||||
const endpointHandler = mod as unknown as EndpointHandler;
|
const endpointHandler = mod as unknown as EndpointHandler;
|
||||||
|
|
||||||
const result = await callEndpoint(
|
const result = await callEndpoint(endpointHandler, env, renderContext, onRequest, true);
|
||||||
endpointHandler,
|
|
||||||
env,
|
|
||||||
renderContext,
|
|
||||||
onRequest as MiddlewareHandler<Response | EndpointOutput>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.type === 'response') {
|
if (result instanceof Response) {
|
||||||
throwIfRedirectNotAllowed(result.response, opts.settings.config);
|
throwIfRedirectNotAllowed(result, opts.settings.config);
|
||||||
// If there's no body, do nothing
|
// If there's no body, do nothing
|
||||||
if (!result.response.body) return;
|
if (!result.body) return;
|
||||||
const ab = await result.response.arrayBuffer();
|
const ab = await result.arrayBuffer();
|
||||||
body = new Uint8Array(ab);
|
body = new Uint8Array(ab);
|
||||||
} else {
|
} else {
|
||||||
body = result.body;
|
body = result.body;
|
||||||
|
|
|
@ -9,6 +9,7 @@ import type {
|
||||||
} from '../../@types/astro';
|
} from '../../@types/astro';
|
||||||
import type { Environment, RenderContext } from '../render/index';
|
import type { Environment, RenderContext } from '../render/index';
|
||||||
|
|
||||||
|
import mime from 'mime';
|
||||||
import { isServerLikeOutput } from '../../prerender/utils.js';
|
import { isServerLikeOutput } from '../../prerender/utils.js';
|
||||||
import { renderEndpoint } from '../../runtime/server/index.js';
|
import { renderEndpoint } from '../../runtime/server/index.js';
|
||||||
import { ASTRO_VERSION } from '../constants.js';
|
import { ASTRO_VERSION } from '../constants.js';
|
||||||
|
@ -16,20 +17,16 @@ import { AstroCookies, attachToResponse } from '../cookies/index.js';
|
||||||
import { AstroError, AstroErrorData } from '../errors/index.js';
|
import { AstroError, AstroErrorData } from '../errors/index.js';
|
||||||
import { warn } from '../logger/core.js';
|
import { warn } from '../logger/core.js';
|
||||||
import { callMiddleware } from '../middleware/callMiddleware.js';
|
import { callMiddleware } from '../middleware/callMiddleware.js';
|
||||||
|
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
|
||||||
const clientAddressSymbol = Symbol.for('astro.clientAddress');
|
const clientAddressSymbol = Symbol.for('astro.clientAddress');
|
||||||
const clientLocalsSymbol = Symbol.for('astro.locals');
|
const clientLocalsSymbol = Symbol.for('astro.locals');
|
||||||
|
|
||||||
type EndpointCallResult =
|
type SimpleEndpointObject = {
|
||||||
| {
|
body: string;
|
||||||
type: 'simple';
|
encoding?: BufferEncoding;
|
||||||
body: string;
|
};
|
||||||
encoding?: BufferEncoding;
|
|
||||||
cookies: AstroCookies;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: 'response';
|
|
||||||
response: Response;
|
|
||||||
};
|
|
||||||
|
|
||||||
type CreateAPIContext = {
|
type CreateAPIContext = {
|
||||||
request: Request;
|
request: Request;
|
||||||
|
@ -100,12 +97,29 @@ export function createAPIContext({
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return response only
|
||||||
export async function callEndpoint<MiddlewareResult = Response | EndpointOutput>(
|
export async function callEndpoint<MiddlewareResult = Response | EndpointOutput>(
|
||||||
mod: EndpointHandler,
|
mod: EndpointHandler,
|
||||||
env: Environment,
|
env: Environment,
|
||||||
ctx: RenderContext,
|
ctx: RenderContext,
|
||||||
onRequest?: MiddlewareHandler<MiddlewareResult> | undefined
|
onRequest?: MiddlewareHandler<MiddlewareResult> | undefined
|
||||||
): Promise<EndpointCallResult> {
|
): Promise<Response>;
|
||||||
|
// Return response or a simple endpoint object (used for SSG)
|
||||||
|
export async function callEndpoint<MiddlewareResult = Response | EndpointOutput>(
|
||||||
|
mod: EndpointHandler,
|
||||||
|
env: Environment,
|
||||||
|
ctx: RenderContext,
|
||||||
|
onRequest?: MiddlewareHandler<MiddlewareResult> | undefined,
|
||||||
|
returnObjectFormIfAvailable?: boolean
|
||||||
|
): Promise<Response | SimpleEndpointObject>;
|
||||||
|
// Base implementation
|
||||||
|
export async function callEndpoint<MiddlewareResult = Response | EndpointOutput>(
|
||||||
|
mod: EndpointHandler,
|
||||||
|
env: Environment,
|
||||||
|
ctx: RenderContext,
|
||||||
|
onRequest?: MiddlewareHandler<MiddlewareResult> | undefined,
|
||||||
|
returnObjectFormIfAvailable?: boolean
|
||||||
|
): Promise<Response | SimpleEndpointObject> {
|
||||||
const context = createAPIContext({
|
const context = createAPIContext({
|
||||||
request: ctx.request,
|
request: ctx.request,
|
||||||
params: ctx.params,
|
params: ctx.params,
|
||||||
|
@ -128,38 +142,71 @@ export async function callEndpoint<MiddlewareResult = Response | EndpointOutput>
|
||||||
response = await renderEndpoint(mod, context, env.ssr);
|
response = await renderEndpoint(mod, context, env.ssr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response instanceof Response) {
|
// If return simple endpoint object, convert to response
|
||||||
attachToResponse(response, context.cookies);
|
if (!(response instanceof Response)) {
|
||||||
return {
|
// Validate properties not available in SSR
|
||||||
type: 'response',
|
if (env.ssr && !ctx.route?.prerender) {
|
||||||
response,
|
if (response.hasOwnProperty('headers')) {
|
||||||
};
|
warn(
|
||||||
}
|
env.logging,
|
||||||
|
'ssr',
|
||||||
|
'Setting headers is not supported when returning an object. Please return an instance of Response. See https://docs.astro.build/en/core-concepts/endpoints/#server-endpoints-api-routes for more information.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (env.ssr && !ctx.route?.prerender) {
|
if (response.encoding) {
|
||||||
if (response.hasOwnProperty('headers')) {
|
warn(
|
||||||
warn(
|
env.logging,
|
||||||
env.logging,
|
'ssr',
|
||||||
'ssr',
|
'`encoding` is ignored in SSR. To return a charset other than UTF-8, please return an instance of Response. See https://docs.astro.build/en/core-concepts/endpoints/#server-endpoints-api-routes for more information.'
|
||||||
'Setting headers is not supported when returning an object. Please return an instance of Response. See https://docs.astro.build/en/core-concepts/endpoints/#server-endpoints-api-routes for more information.'
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.encoding) {
|
// Passed during SSG where we don't need to return a full response, as we only need
|
||||||
warn(
|
// to write the `body` to a file directly.
|
||||||
env.logging,
|
if (returnObjectFormIfAvailable) {
|
||||||
'ssr',
|
return {
|
||||||
'`encoding` is ignored in SSR. To return a charset other than UTF-8, please return an instance of Response. See https://docs.astro.build/en/core-concepts/endpoints/#server-endpoints-api-routes for more information.'
|
body: response.body,
|
||||||
);
|
encoding: response.encoding,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let body: BodyInit;
|
||||||
|
const headers = new Headers();
|
||||||
|
|
||||||
|
const pathname = ctx.route
|
||||||
|
? // Try the static route `pathname`
|
||||||
|
ctx.route.pathname ??
|
||||||
|
// Dynamic routes don't include `pathname`, so synthesize a path for these (e.g. 'src/pages/[slug].svg')
|
||||||
|
ctx.route.segments.map((s) => s.map((p) => p.content).join('')).join('/')
|
||||||
|
: // Fallback to pathname of the request
|
||||||
|
ctx.pathname;
|
||||||
|
const mimeType = mime.getType(pathname) || 'text/plain';
|
||||||
|
headers.set('Content-Type', `${mimeType};charset=utf-8`);
|
||||||
|
|
||||||
|
if (typeof Buffer !== 'undefined' && Buffer.from) {
|
||||||
|
body = Buffer.from(response.body, response.encoding);
|
||||||
|
} else if (
|
||||||
|
response.encoding == null ||
|
||||||
|
response.encoding === 'utf8' ||
|
||||||
|
response.encoding === 'utf-8'
|
||||||
|
) {
|
||||||
|
body = encoder.encode(response.body);
|
||||||
|
headers.set('Content-Length', body.byteLength.toString());
|
||||||
|
} else {
|
||||||
|
body = response.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
response = new Response(body, {
|
||||||
|
status: 200,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
attachToResponse(response, context.cookies);
|
||||||
type: 'simple',
|
|
||||||
body: response.body,
|
return response;
|
||||||
encoding: response.encoding,
|
|
||||||
cookies: context.cookies,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRedirect(statusCode: number) {
|
function isRedirect(statusCode: number) {
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import type http from 'http';
|
import type http from 'http';
|
||||||
import mime from 'mime';
|
|
||||||
import type { ComponentInstance, ManifestData, RouteData } from '../@types/astro';
|
import type { ComponentInstance, ManifestData, RouteData } from '../@types/astro';
|
||||||
import { attachToResponse } from '../core/cookies/index.js';
|
|
||||||
import { call as callEndpoint } from '../core/endpoint/dev/index.js';
|
import { call as callEndpoint } from '../core/endpoint/dev/index.js';
|
||||||
import { throwIfRedirectNotAllowed } from '../core/endpoint/index.js';
|
import { throwIfRedirectNotAllowed } from '../core/endpoint/index.js';
|
||||||
import { AstroErrorData, isAstroError } from '../core/errors/index.js';
|
import { AstroErrorData, isAstroError } from '../core/errors/index.js';
|
||||||
|
@ -176,43 +174,23 @@ export async function handleRoute(
|
||||||
}
|
}
|
||||||
// Route successfully matched! Render it.
|
// Route successfully matched! Render it.
|
||||||
if (route.type === 'endpoint') {
|
if (route.type === 'endpoint') {
|
||||||
const result = await callEndpoint(options);
|
const response = await callEndpoint(options);
|
||||||
if (result.type === 'response') {
|
if (response.headers.get('X-Astro-Response') === 'Not-Found') {
|
||||||
if (result.response.headers.get('X-Astro-Response') === 'Not-Found') {
|
const fourOhFourRoute = await matchRoute('/404', env, manifest);
|
||||||
const fourOhFourRoute = await matchRoute('/404', env, manifest);
|
return handleRoute(
|
||||||
return handleRoute(
|
fourOhFourRoute,
|
||||||
fourOhFourRoute,
|
new URL('/404', url),
|
||||||
new URL('/404', url),
|
'/404',
|
||||||
'/404',
|
body,
|
||||||
body,
|
origin,
|
||||||
origin,
|
env,
|
||||||
env,
|
manifest,
|
||||||
manifest,
|
req,
|
||||||
req,
|
res
|
||||||
res
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
throwIfRedirectNotAllowed(result.response, config);
|
|
||||||
await writeWebResponse(res, result.response);
|
|
||||||
} else {
|
|
||||||
let contentType = 'text/plain';
|
|
||||||
// Dynamic routes don't include `route.pathname`, so synthesize a path for these (e.g. 'src/pages/[slug].svg')
|
|
||||||
const filepath =
|
|
||||||
route.pathname ||
|
|
||||||
route.segments.map((segment) => segment.map((p) => p.content).join('')).join('/');
|
|
||||||
const computedMimeType = mime.getType(filepath);
|
|
||||||
if (computedMimeType) {
|
|
||||||
contentType = computedMimeType;
|
|
||||||
}
|
|
||||||
const response = new Response(Buffer.from(result.body, result.encoding), {
|
|
||||||
status: 200,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': `${contentType};charset=utf-8`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
attachToResponse(response, result.cookies);
|
|
||||||
await writeWebResponse(res, response);
|
|
||||||
}
|
}
|
||||||
|
throwIfRedirectNotAllowed(response, config);
|
||||||
|
await writeWebResponse(res, response);
|
||||||
} else {
|
} else {
|
||||||
const result = await renderPage(options);
|
const result = await renderPage(options);
|
||||||
throwIfRedirectNotAllowed(result, config);
|
throwIfRedirectNotAllowed(result, config);
|
||||||
|
|
Loading…
Reference in a new issue