diff --git a/preserve-query-strings-on-redirect/README.md b/preserve-query-strings-on-redirect/README.md new file mode 100644 index 0000000..086d768 --- /dev/null +++ b/preserve-query-strings-on-redirect/README.md @@ -0,0 +1,83 @@ +## Preserve Query Strings on Redirect + +**CloudFront Functions event type: viewer request** + +This function provides an example of how to preserve query strings when performing a redirect. + +**Testing the function** + +To validate that the function is working as expected, you can use the JSON test objects in the `test-objects` directory. To test, use the `test-function` CLI command as shown in the following examples: + +**Preserve Query Strings** + +This test validates that requests with query strings will be redirected to the `www.example.com` domain preserving the requested URL and query strings. + +```shell +# Get the current ETag value +$ aws cloudfront describe-function --name preserve-query-strings-on-redirect +# Run the test +$ aws cloudfront test-function --if-match EXXXXXXXXXXXX --name preserve-query-strings-on-redirect --event-object fileb://preserve-query-strings-on-redirect/test-objects/query-strings.json +``` + +If the function has been set up correctly, you should see a result similar to the following with the redirect being issued (`location` header being returned) in the `FunctionOutput` JSON object. Notice that the requested URL and query strings are included in the `value`: + +```json +{ + "TestResult": { + "FunctionSummary": { + "Name": "preserve-query-strings-on-redirect", + "Status": "UNPUBLISHED", + "FunctionConfig": { + "Comment": "", + "Runtime": "cloudfront-js-2.0" + }, + "FunctionMetadata": { + "FunctionARN": "arn:aws:cloudfront::060232822672:function/preserve-query-strings-on-redirect", + "Stage": "DEVELOPMENT", + "CreatedTime": "2024-10-02T14:08:21.623000+00:00", + "LastModifiedTime": "2024-10-02T14:08:44.844000+00:00" + } + }, + "ComputeUtilization": "10", + "FunctionExecutionLogs": [], + "FunctionErrorMessage": "", + "FunctionOutput": "{\"response\":{\"headers\":{\"location\":{\"value\":\"https://www.example.com/example-page?name=example-name&test=example-test-query-string&querymv=val1&querymv=val2,val3\"}},\"statusDescription\":\"Moved Permanently\",\"cookies\":{},\"statusCode\":301}}" + } +} +``` + +**Redirect still works when no query strings in request** + +```shell +# Get the current ETag value +$ aws cloudfront describe-function --name preserve-query-strings-on-redirect +# Run the test +$ aws cloudfront test-function --if-match EXXXXXXXXXXXX --name preserve-query-strings-on-redirect --event-object fileb://preserve-query-strings-on-redirect/test-objects/no-query-strings.json +``` + +If the function has been set up correctly, you should see a result similar to the following with the redirect being issued (`location` header being returned) in the `FunctionOutput` JSON object. Notice that the requested URL is included in the `value`: + +```json +{ + "TestResult": { + "FunctionSummary": { + "Name": "preserve-query-strings-on-redirect", + "Status": "UNPUBLISHED", + "FunctionConfig": { + "Comment": "", + "Runtime": "cloudfront-js-2.0" + }, + "FunctionMetadata": { + "FunctionARN": "arn:aws:cloudfront::060232822672:function/preserve-query-strings-on-redirect", + "Stage": "DEVELOPMENT", + "CreatedTime": "2024-10-02T14:08:21.623000+00:00", + "LastModifiedTime": "2024-10-02T14:08:44.844000+00:00" + } + }, + "ComputeUtilization": "7", + "FunctionExecutionLogs": [], + "FunctionErrorMessage": "", + "FunctionOutput": "{\"response\":{\"headers\":{\"location\":{\"value\":\"https://www.example.com/example-page\"}},\"statusDescription\":\"Moved Permanently\",\"cookies\":{},\"statusCode\":301}}" + } +} +``` diff --git a/preserve-query-strings-on-redirect/index.js b/preserve-query-strings-on-redirect/index.js new file mode 100644 index 0000000..a2cf2bc --- /dev/null +++ b/preserve-query-strings-on-redirect/index.js @@ -0,0 +1,34 @@ +function handler(event) { + const request = event.request; + const uri = request.uri; + const customRedirectLogic = true; + var queryStringAsString = ''; + + const qs = [] + for (const key in request.querystring) { + if (request.querystring[key].multiValue) { + request.querystring[key].multiValue.forEach((mv) => { qs.push(key + '=' + mv.value) }) + } else { + qs.push(key + '=' + request.querystring[key].value) + } + } + + if (qs.length !== 0) { + queryStringAsString = '?' + qs.join('&') + } + + if (customRedirectLogic) { + const response = { + statusCode: 301, + statusDescription: 'Moved Permanently', + headers: + { + location: { value: `https://www.example.com${uri}${queryStringAsString}` }, + } + } + + return response; + } + + return request; +} \ No newline at end of file diff --git a/preserve-query-strings-on-redirect/test-objects/no-query-strings.json b/preserve-query-strings-on-redirect/test-objects/no-query-strings.json new file mode 100644 index 0000000..183e20c --- /dev/null +++ b/preserve-query-strings-on-redirect/test-objects/no-query-strings.json @@ -0,0 +1,17 @@ +{ + "version": "1.0", + "context": { + "eventType": "viewer-request" + }, + "viewer": { + "ip": "0.0.0.0" + }, + "request": { + "method": "GET", + "uri": "/example-page", + "headers": { + "host": { "value": "www.example.com" }, + "accept": { "value": "text/html", "multivalue": [ { "value": "text/html" }, { "value": "application/xhtml+xml" } ] } + } + } +} diff --git a/preserve-query-strings-on-redirect/test-objects/query-strings.json b/preserve-query-strings-on-redirect/test-objects/query-strings.json new file mode 100644 index 0000000..559e587 --- /dev/null +++ b/preserve-query-strings-on-redirect/test-objects/query-strings.json @@ -0,0 +1,27 @@ +{ + "version": "1.0", + "context": { + "eventType": "viewer-request" + }, + "viewer": { + "ip": "0.0.0.0" + }, + "request": { + "method": "GET", + "uri": "/example-page", + "querystring": { + "name": { "value": "example-name"}, + "test": {"value": "example-test-query-string"}, + "querymv": { + "value": "val1", + "multiValue": [ + {"value": "val1"}, + {"value": "val2,val3"} + ]} + }, + "headers": { + "host": { "value": "www.example.com" }, + "accept": { "value": "text/html", "multivalue": [ { "value": "text/html" }, { "value": "application/xhtml+xml" } ] } + } + } +}