From 140c19612d6a64b174d379ea4d8f1f7636bfc28d Mon Sep 17 00:00:00 2001 From: Severin Ibarluzea Date: Thu, 21 Jul 2022 17:39:19 -0700 Subject: [PATCH] fix(#11930): rewritten api routes can correctly handle cors in dev mode --- packages/next/server/dev/hot-reloader.ts | 32 +++++++++++-- .../api-cors-with-rewrite/index.test.ts | 45 +++++++++++++++++++ 2 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 test/development/api-cors-with-rewrite/index.test.ts diff --git a/packages/next/server/dev/hot-reloader.ts b/packages/next/server/dev/hot-reloader.ts index a04315a4291..f216ddc6464 100644 --- a/packages/next/server/dev/hot-reloader.ts +++ b/packages/next/server/dev/hot-reloader.ts @@ -49,7 +49,8 @@ import ws from 'next/dist/compiled/ws' import { promises as fs } from 'fs' import { getPageStaticInfo } from '../../build/analysis/get-page-static-info' import { serverComponentRegex } from '../../build/webpack/loaders/utils' -import { stringify } from 'querystring' +import { ParsedUrlQuery, stringify } from 'querystring' +import resolveRewrites from '../../shared/lib/router/utils/resolve-rewrites' const wsServer = new ws.Server({ noServer: true }) @@ -77,8 +78,27 @@ export async function renderScriptError( res.end('500 - Internal Error') } -function addCorsSupport(req: IncomingMessage, res: ServerResponse) { - const isApiRoute = req.url!.match(API_ROUTE) +function isApiRouteAfterRewrites( + parsedUrl: UrlObject, + rewrites: HotReloader['rewrites'] +): boolean { + const rewrittenRoute = resolveRewrites( + parsedUrl.pathname as string, + [], + rewrites, + parsedUrl.query as ParsedUrlQuery, + (s) => s + ).resolvedHref + if (!rewrittenRoute) + return Boolean((parsedUrl.pathname || '').match(API_ROUTE)) + return Boolean(rewrittenRoute.match(API_ROUTE)) +} + +function addCorsSupport( + req: IncomingMessage, + res: ServerResponse, + isApiRoute: boolean +) { // API routes handle their own CORS headers if (isApiRoute) { return { preflight: false } @@ -234,7 +254,11 @@ export default class HotReloader { // With when the app runs for multi-zones support behind a proxy, // the current page is trying to access this URL via assetPrefix. // That's when the CORS support is needed. - const { preflight } = addCorsSupport(req, res) + const { preflight } = addCorsSupport( + req, + res, + isApiRouteAfterRewrites(parsedUrl, this.rewrites) + ) if (preflight) { return {} } diff --git a/test/development/api-cors-with-rewrite/index.test.ts b/test/development/api-cors-with-rewrite/index.test.ts new file mode 100644 index 00000000000..ea76a423507 --- /dev/null +++ b/test/development/api-cors-with-rewrite/index.test.ts @@ -0,0 +1,45 @@ +import { createNext } from 'e2e-utils' +import { NextInstance } from 'test/lib/next-modes/base' +import { fetchViaHTTP } from 'next-test-utils' + +describe('Rewritten API Requests should pass OPTIONS requests to the api function', () => { + let next: NextInstance + + beforeAll(async () => { + next = await createNext({ + files: { + 'pages/api/some-endpoint.js': ` + export default (req, res) => { + res.end("successfully hit some-endpoint!") + } + `, + }, + nextConfig: { + rewrites: () => + Promise.resolve({ + beforeFiles: [ + // Nextjs by default requires a /api prefix, let's remove that + { + source: '/:path*', + destination: '/api/:path*', + }, + ], + afterFiles: [], + fallback: [], + }), + }, + dependencies: {}, + }) + }) + afterAll(() => next.destroy()) + + it('should pass OPTIONS requests to the api function', async () => { + const res = await fetchViaHTTP(next.url, '/some-endpoint', null, { + method: 'OPTIONS', + headers: { + Origin: 'http://localhost:3000', + }, + }) + expect(await res.text()).toContain('successfully hit some-endpoint!') + }) +})