Skip to content
This repository was archived by the owner on Jan 28, 2025. It is now read-only.

Commit 98e963b

Browse files
authored
fix(lambda-at-edge): use 307 redirect for root -> locale URI instead … (#886)
1 parent 828b9dd commit 98e963b

File tree

11 files changed

+205
-129
lines changed

11 files changed

+205
-129
lines changed

packages/libs/lambda-at-edge/src/default-handler.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,19 @@ import type { Readable } from "stream";
2626
import {
2727
createRedirectResponse,
2828
getDomainRedirectPath,
29+
getLanguageRedirect,
2930
getRedirectPath
3031
} from "./routing/redirector";
3132
import {
3233
createExternalRewriteResponse,
33-
getLanguageRewrite,
3434
getRewritePath,
3535
isExternalRewrite
3636
} from "./routing/rewriter";
3737
import { addHeadersToResponse } from "./headers/addHeaders";
3838
import { isValidPreviewRequest } from "./lib/isValidPreviewRequest";
3939
import { getUnauthenticatedResponse } from "./auth/authenticator";
4040
import { buildS3RetryStrategy } from "./s3/s3RetryStrategy";
41+
import { isLocaleIndexUri } from "./routing/locale-utils";
4142

4243
const basePath = RoutesManifestJson.basePath;
4344

@@ -374,16 +375,19 @@ const handleOriginRequest = async ({
374375

375376
// Handle root language rewrite
376377
const languageHeader = request.headers["accept-language"];
377-
const languageRewriteUri = getLanguageRewrite(
378+
const languageRedirectUri = getLanguageRedirect(
378379
languageHeader ? languageHeader[0].value : undefined,
379380
uri,
380-
routesManifest
381+
routesManifest,
382+
manifest
381383
);
382384

383-
if (languageRewriteUri) {
384-
request.uri = languageRewriteUri;
385-
386-
uri = normaliseUri(request.uri);
385+
if (languageRedirectUri) {
386+
return createRedirectResponse(
387+
languageRedirectUri,
388+
request.querystring,
389+
307
390+
);
387391
}
388392

389393
const isStaticPage = pages.html.nonDynamic[uri]; // plain page without any props
@@ -417,7 +421,7 @@ const handleOriginRequest = async ({
417421
} else if (isHTMLPage || hasFallback) {
418422
s3Origin.path = `${basePath}/static-pages/${manifest.buildId}`;
419423
let pageName;
420-
if (languageRewriteUri) {
424+
if (isLocaleIndexUri(uri, routesManifest)) {
421425
pageName = `${uri}/index`;
422426
} else {
423427
pageName = uri === "/" ? "/index" : uri;

packages/libs/lambda-at-edge/src/headers/addHeaders.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { CloudFrontResultResponse } from "aws-lambda";
22
import { RoutesManifest } from "../types";
33
import { matchPath } from "../routing/matcher";
4-
import { addDefaultLocaleToPath } from "../routing/common-utils";
4+
import { addDefaultLocaleToPath } from "../routing/locale-utils";
55

66
export function addHeadersToResponse(
77
path: string,

packages/libs/lambda-at-edge/src/routing/common-utils.ts renamed to packages/libs/lambda-at-edge/src/routing/locale-utils.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,18 @@ export function addDefaultLocaleToPath(
2929

3030
return path;
3131
}
32+
33+
export function isLocaleIndexUri(
34+
normalisedUri: string,
35+
routesManifest: RoutesManifest
36+
) {
37+
if (routesManifest.i18n) {
38+
for (const locale of routesManifest.i18n.locales) {
39+
if (normalisedUri === `/${locale}`) {
40+
return true;
41+
}
42+
}
43+
}
44+
45+
return false;
46+
}

packages/libs/lambda-at-edge/src/routing/redirector.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
import * as http from "http";
1010
import { CloudFrontRequest } from "aws-lambda";
1111
import { CloudFrontResultResponse } from "aws-lambda";
12-
import { addDefaultLocaleToPath } from "./common-utils";
12+
import { addDefaultLocaleToPath } from "./locale-utils";
13+
import Accept from "@hapi/accept";
1314

1415
/**
1516
* Whether this is the default trailing slash redirect.
@@ -176,3 +177,41 @@ export function getDomainRedirectPath(
176177
}
177178
return null;
178179
}
180+
181+
export function getLanguageRedirect(
182+
acceptLanguageHeader: string | undefined,
183+
normalisedUri: string,
184+
routesManifest: RoutesManifest,
185+
manifest: OriginRequestDefaultHandlerManifest
186+
): string | undefined {
187+
// Only redirect root page and if accept language is defined
188+
if (normalisedUri !== "/" || !acceptLanguageHeader) {
189+
return undefined;
190+
}
191+
192+
if (routesManifest.i18n) {
193+
const locales = routesManifest.i18n.locales;
194+
const defaultLocale = routesManifest.i18n.defaultLocale;
195+
const basePath = routesManifest.basePath;
196+
197+
const preferredLanguage = Accept.language(acceptLanguageHeader);
198+
199+
let localeToUse = undefined;
200+
201+
// Find language in locale that matches preferred language
202+
for (const locale of locales) {
203+
if (preferredLanguage === locale) {
204+
localeToUse = locale;
205+
break;
206+
}
207+
}
208+
209+
if (!localeToUse || localeToUse === defaultLocale) {
210+
return undefined;
211+
} else {
212+
return `${basePath}/${localeToUse}${manifest.trailingSlash ? "/" : ""}`;
213+
}
214+
}
215+
216+
return undefined;
217+
}

packages/libs/lambda-at-edge/src/routing/rewriter.ts

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { compileDestination, matchPath } from "./matcher";
22
import { RewriteData, RoutesManifest } from "../types";
33
import { IncomingMessage, ServerResponse } from "http";
4-
import Accept from "@hapi/accept";
5-
import { addDefaultLocaleToPath } from "./common-utils";
4+
import { addDefaultLocaleToPath } from "./locale-utils";
65

76
/**
87
* Get the rewrite of the given path, if it exists. Otherwise return null.
@@ -129,40 +128,3 @@ export async function createExternalRewriteResponse(
129128
res.statusCode = fetchResponse.status;
130129
res.end(await fetchResponse.buffer());
131130
}
132-
133-
export function getLanguageRewrite(
134-
acceptLanguageHeader: string | undefined,
135-
normalisedUri: string,
136-
routesManifest: RoutesManifest
137-
): string | undefined {
138-
// Only rewrites root page and if accept language is defined
139-
if (normalisedUri !== "/" || !acceptLanguageHeader) {
140-
return undefined;
141-
}
142-
143-
if (routesManifest.i18n) {
144-
const locales = routesManifest.i18n.locales;
145-
const defaultLocale = routesManifest.i18n.defaultLocale;
146-
const basePath = routesManifest.basePath;
147-
148-
const preferredLanguage = Accept.language(acceptLanguageHeader);
149-
150-
let localeToUse = undefined;
151-
152-
// Find language in locale that matches preferred language
153-
for (const locale of locales) {
154-
if (preferredLanguage === locale) {
155-
localeToUse = locale;
156-
break;
157-
}
158-
}
159-
160-
if (!localeToUse || localeToUse === defaultLocale) {
161-
return undefined;
162-
} else {
163-
return `${basePath}/${localeToUse}`;
164-
}
165-
}
166-
167-
return undefined;
168-
}

packages/libs/lambda-at-edge/tests/default-handler/default-build-manifest-with-trailing-slash.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,10 @@
156156
"nonDynamic": {
157157
"/": "pages/index.html",
158158
"/terms": "pages/terms.html",
159-
"/nl": "pages/index.html",
160-
"/nl/terms": "pages/terms.html",
159+
"/en": "pages/en/index.html",
160+
"/en/terms": "pages/en/terms.html",
161+
"/nl": "pages/nl/index.html",
162+
"/nl/terms": "pages/nl/terms.html",
161163
"/fr": "pages/fr/index.html",
162164
"/fr/terms": "pages/fr/terms.html"
163165
},

packages/libs/lambda-at-edge/tests/default-handler/default-build-manifest.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,10 @@
156156
"nonDynamic": {
157157
"/": "pages/index.html",
158158
"/terms": "pages/terms.html",
159-
"/nl": "pages/index.html",
160-
"/nl/terms": "pages/terms.html",
159+
"/en": "pages/en/index.html",
160+
"/en/terms": "pages/en/terms.html",
161+
"/nl": "pages/nl/index.html",
162+
"/nl/terms": "pages/nl/terms.html",
161163
"/fr": "pages/fr/index.html",
162164
"/fr/terms": "pages/fr/terms.html"
163165
},

packages/libs/lambda-at-edge/tests/default-handler/default-handler-with-basepath.test.ts

Lines changed: 61 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ describe("Lambda@Edge", () => {
4545
expectedRedirect: string,
4646
statusCode: number,
4747
querystring?: string,
48-
host?: string
48+
host?: string,
49+
requestHeaders?: { [p: string]: { key: string; value: string }[] }
4950
) => Promise<void>;
5051
beforeEach(() => {
5152
jest.resetModules();
@@ -96,15 +97,17 @@ describe("Lambda@Edge", () => {
9697
expectedRedirect: string,
9798
statusCode: number,
9899
querystring?: string,
99-
host?: string
100+
host?: string,
101+
requestHeaders?: { [p: string]: { key: string; value: string }[] }
100102
): Promise<void> => {
101103
await runRedirectTestWithHandler(
102104
handler,
103105
path,
104106
expectedRedirect,
105107
statusCode,
106108
querystring,
107-
host
109+
host,
110+
requestHeaders
108111
);
109112
};
110113
});
@@ -757,48 +760,70 @@ describe("Lambda@Edge", () => {
757760
);
758761
});
759762

760-
describe("Root Language Rewrite", () => {
763+
describe("Root Locale Redirect", () => {
761764
it.each`
762-
acceptLanguageHeader | expectedPage
763-
${"nl"} | ${"/nl/index.html"}
764-
${"en"} | ${"/index.html"}
765-
${undefined} | ${"/index.html"}
766-
${"fr,nl,en"} | ${"/fr/index.html"}
767-
${"nl,fr"} | ${"/nl/index.html"}
768-
${"fr,nl"} | ${"/fr/index.html"}
769-
${"en,nl"} | ${"/index.html"}
770-
${"en;q=1.0,nl;q=0.8"} | ${"/index.html"}
771-
${"en;q=0.5,nl;q=0.8"} | ${"/nl/index.html"}
765+
acceptLanguageHeader | expectedRedirect
766+
${"nl"} | ${"/basepath/nl"}
767+
${"fr,nl,en"} | ${"/basepath/fr"}
768+
${"nl,fr"} | ${"/basepath/nl"}
769+
${"fr,nl"} | ${"/basepath/fr"}
770+
${"en;q=0.5,nl;q=0.8"} | ${"/basepath/nl"}
772771
`(
773-
"rewrites / with accept-language [$acceptLanguageHeader] to $expectedPage",
774-
async ({ acceptLanguageHeader, expectedPage }) => {
775-
const event = createCloudFrontEvent({
776-
uri: trailingSlash ? "/basepath/" : "/basepath",
777-
host: "mydistribution.cloudfront.net",
778-
requestHeaders: {
772+
"redirects path /basepath with accept-language [$acceptLanguageHeader] to $expectedRedirect",
773+
async ({ acceptLanguageHeader, expectedRedirect }) => {
774+
if (trailingSlash) {
775+
expectedRedirect += "/";
776+
}
777+
778+
await runRedirectTest(
779+
trailingSlash ? "/basepath/" : "/basepath",
780+
expectedRedirect,
781+
307,
782+
undefined,
783+
undefined,
784+
{
779785
"accept-language": [
780786
{ key: "Accept-Language", value: acceptLanguageHeader }
781787
]
782788
}
783-
});
784-
785-
const request = await handler(event);
786-
787-
expect(request.origin).toEqual({
788-
s3: {
789-
authMethod: "origin-access-identity",
790-
domainName: "my-bucket.s3.amazonaws.com",
791-
path: "/basepath/static-pages/build-id",
792-
region: "us-east-1"
793-
}
794-
});
795-
expect(request.uri).toEqual(expectedPage);
796-
expect(request.headers.host[0].key).toEqual("host");
797-
expect(request.headers.host[0].value).toEqual(
798-
"my-bucket.s3.amazonaws.com"
799789
);
800790
}
801791
);
802792
});
793+
794+
describe("Locale Pages", () => {
795+
it.each`
796+
path | expectedPage
797+
${"/basepath"} | ${"/index.html"}
798+
${"/basepath/en"} | ${"/en/index.html"}
799+
${"/basepath/fr"} | ${"/fr/index.html"}
800+
${"/basepath/nl"} | ${"/nl/index.html"}
801+
`("path $path renders $expectedPage", async ({ path, expectedPage }) => {
802+
if (trailingSlash && !path.endsWith("/")) {
803+
path += "/";
804+
}
805+
806+
const event = createCloudFrontEvent({
807+
uri: path,
808+
host: "mydistribution.cloudfront.net"
809+
});
810+
811+
const request = await handler(event);
812+
813+
expect(request.origin).toEqual({
814+
s3: {
815+
authMethod: "origin-access-identity",
816+
domainName: "my-bucket.s3.amazonaws.com",
817+
path: "/basepath/static-pages/build-id",
818+
region: "us-east-1"
819+
}
820+
});
821+
expect(request.uri).toEqual(expectedPage);
822+
expect(request.headers.host[0].key).toEqual("host");
823+
expect(request.headers.host[0].value).toEqual(
824+
"my-bucket.s3.amazonaws.com"
825+
);
826+
});
827+
});
803828
});
804829
});

0 commit comments

Comments
 (0)