Skip to content

Commit

Permalink
fix: middleware for API endpoints (#7106)
Browse files Browse the repository at this point in the history
Co-authored-by: Bjorn Lu <bjornlu.dev@gmail.com>
  • Loading branch information
ematipico and bluwy committed May 17, 2023
1 parent 826e028 commit 075eee0
Show file tree
Hide file tree
Showing 10 changed files with 77 additions and 24 deletions.
5 changes: 5 additions & 0 deletions .changeset/long-starfishes-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fix middleware for API endpoints that use `Response`, and log a warning for endpoints that don't use `Response`.
3 changes: 2 additions & 1 deletion packages/astro/src/core/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type { RouteInfo, SSRManifest as Manifest } from './types';

import mime from 'mime';
import { attachToResponse, getSetCookiesFromResponse } from '../cookies/index.js';
import { call as callEndpoint, createAPIContext } from '../endpoint/index.js';
import { callEndpoint, createAPIContext } from '../endpoint/index.js';
import { consoleLogDestination } from '../logger/console.js';
import { error, type LogOptions } from '../logger/core.js';
import { callMiddleware } from '../middleware/callMiddleware.js';
Expand Down Expand Up @@ -224,6 +224,7 @@ export class App {
let response;
if (onRequest) {
response = await callMiddleware<Response>(
this.#env.logging,
onRequest as MiddlewareResponseHandler,
apiContext,
() => {
Expand Down
7 changes: 2 additions & 5 deletions packages/astro/src/core/build/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,7 @@ import {
} from '../../core/path.js';
import { runHookBuildGenerated } from '../../integrations/index.js';
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
import {
call as callEndpoint,
createAPIContext,
throwIfRedirectNotAllowed,
} from '../endpoint/index.js';
import { callEndpoint, createAPIContext, throwIfRedirectNotAllowed } from '../endpoint/index.js';
import { AstroError } from '../errors/index.js';
import { debug, info } from '../logger/core.js';
import { callMiddleware } from '../middleware/callMiddleware.js';
Expand Down Expand Up @@ -495,6 +491,7 @@ async function generatePath(
const onRequest = middleware?.onRequest;
if (onRequest) {
response = await callMiddleware<Response>(
env.logging,
onRequest as MiddlewareResponseHandler,
apiContext,
() => {
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/core/endpoint/dev/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { EndpointHandler } from '../../../@types/astro';
import type { LogOptions } from '../../logger/core';
import type { SSROptions } from '../../render/dev';
import { createRenderContext } from '../../render/index.js';
import { call as callEndpoint } from '../index.js';
import { callEndpoint } from '../index.js';

export async function call(options: SSROptions, logging: LogOptions) {
const {
Expand Down
27 changes: 13 additions & 14 deletions packages/astro/src/core/endpoint/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export function createAPIContext({
return context;
}

export async function call<MiddlewareResult = Response | EndpointOutput>(
export async function callEndpoint<MiddlewareResult = Response | EndpointOutput>(
mod: EndpointHandler,
env: Environment,
ctx: RenderContext,
Expand All @@ -108,26 +108,25 @@ export async function call<MiddlewareResult = Response | EndpointOutput>(
adapterName: env.adapterName,
});

let response = await renderEndpoint(mod, context, env.ssr);
let response;
if (middleware && middleware.onRequest) {
if (response.body === null) {
const onRequest = middleware.onRequest as MiddlewareEndpointHandler;
response = await callMiddleware<Response | EndpointOutput>(onRequest, context, async () => {
const onRequest = middleware.onRequest as MiddlewareEndpointHandler;
response = await callMiddleware<Response | EndpointOutput>(
env.logging,
onRequest,
context,
async () => {
if (env.mode === 'development' && !isValueSerializable(context.locals)) {
throw new AstroError({
...AstroErrorData.LocalsNotSerializable,
message: AstroErrorData.LocalsNotSerializable.message(ctx.pathname),
});
}
return response;
});
} else {
warn(
env.logging,
'middleware',
"Middleware doesn't work for endpoints that return a simple body. The middleware will be disabled for this page."
);
}
return await renderEndpoint(mod, context, env.ssr);
}
);
} else {
response = await renderEndpoint(mod, context, env.ssr);
}

if (response instanceof Response) {
Expand Down
22 changes: 20 additions & 2 deletions packages/astro/src/core/middleware/callMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import type { APIContext, MiddlewareHandler, MiddlewareNext } from '../../@types/astro';
import { AstroError, AstroErrorData } from '../errors/index.js';
import type { EndpointOutput } from '../../@types/astro';
import { warn } from '../logger/core.js';
import type { Environment } from '../render';
import { bold } from 'kleur/colors';

/**
* Utility function that is in charge of calling the middleware.
Expand Down Expand Up @@ -36,6 +40,7 @@ import { AstroError, AstroErrorData } from '../errors/index.js';
* @param responseFunction A callback function that should return a promise with the response
*/
export async function callMiddleware<R>(
logging: Environment['logging'],
onRequest: MiddlewareHandler<R>,
apiContext: APIContext,
responseFunction: () => Promise<R>
Expand All @@ -56,6 +61,15 @@ export async function callMiddleware<R>(
let middlewarePromise = onRequest(apiContext, next);

return await Promise.resolve(middlewarePromise).then(async (value) => {
if (isEndpointOutput(value)) {
warn(
logging,
'middleware',
'Using simple endpoints can cause unexpected issues in the chain of middleware functions.' +
`\nIt's strongly suggested to use full ${bold('Response')} objects.`
);
}

// first we check if `next` was called
if (nextCalled) {
/**
Expand Down Expand Up @@ -99,6 +113,10 @@ export async function callMiddleware<R>(
});
}

function isEndpointResult(response: any): boolean {
return response && typeof response.body !== 'undefined';
function isEndpointOutput(endpointResult: any): endpointResult is EndpointOutput {
return (
!(endpointResult instanceof Response) &&
typeof endpointResult === 'object' &&
typeof endpointResult.body === 'string'
);
}
2 changes: 1 addition & 1 deletion packages/astro/src/core/render/dev/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ export async function renderPage(options: SSROptions): Promise<Response> {
});

const onRequest = options.middleware.onRequest as MiddlewareResponseHandler;
const response = await callMiddleware<Response>(onRequest, apiContext, () => {
const response = await callMiddleware<Response>(env.logging, onRequest, apiContext, () => {
return coreRenderPage({ mod, renderContext, env: options.env, apiContext });
});

Expand Down
7 changes: 7 additions & 0 deletions packages/astro/test/fixtures/middleware-dev/src/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ const first = defineMiddleware(async (context, next) => {
return new Response(null, {
status: 500,
});
} else if (context.request.url.includes('/api/endpoint')) {
const response = await next();
const object = await response.json();
object.name = 'REDACTED';
return new Response(JSON.stringify(object), {
headers: response.headers,
});
} else {
context.locals.name = 'bar';
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function get() {
const object = {
name: 'Endpoint!!',
};
return new Response(JSON.stringify(object), {
headers: {
'Content-Type': 'application/json',
},
});
}
16 changes: 16 additions & 0 deletions packages/astro/test/middleware.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,22 @@ describe('Middleware API in PROD mode, SSR', () => {
const $ = cheerio.load(html);
expect($('title').html()).to.not.equal('MiddlewareNoDataReturned');
});

it('should correctly work for API endpoints that return a Response object', async () => {
const app = await fixture.loadTestAdapterApp();
const request = new Request('http://example.com/api/endpoint');
const response = await app.render(request);
expect(response.status).to.equal(200);
expect(response.headers.get('Content-Type')).to.equal('application/json');
});

it('should correctly manipulate the response coming from API endpoints (not simple)', async () => {
const app = await fixture.loadTestAdapterApp();
const request = new Request('http://example.com/api/endpoint');
const response = await app.render(request);
const text = await response.text();
expect(text.includes('REDACTED')).to.be.true;
});
});

describe('Middleware with tailwind', () => {
Expand Down

0 comments on commit 075eee0

Please sign in to comment.