Open joknoxy opened 3 years ago
@joknoxy do we need to handle the case where the query string has multi values?
I have modified objectToQueryString
as shown below.
function objectToQueryString(obj) {
var str = [];
for (var param in obj)
if (obj[param].multiValue)
str.push(encodeURIComponent(param) + "=" + encodeURIComponent(obj[param].multiValue.map((item) => item.value).join(',')))
else if (obj[param].value == '')
str.push(encodeURIComponent(param));
else
str.push(encodeURIComponent(param) + "=" + encodeURIComponent(obj[param].value));
return str.join("&");
}
While checking further, I noticed an issue with this approach. If the query string is already an encoded string, we will be encoding it again in the cloudfront function. So I end up using this:
function objectToQueryString(obj) {
var str = [];
for (var param in obj)
if (obj[param].multiValue)
str.push(param + "=" + obj[param].multiValue.map((item) => item.value).join(','));
else if (obj[param].value == '')
str.push(param);
else
str.push(param + "=" + obj[param].value);
return str.join("&");
}
While checking further, I noticed an issue with this approach. If the query string is already an encoded string, we will be encoding it again in the cloudfront function. So I end up using this:
Thank you @akarsh-k, this works perfectly with the CloudFront event example structure.
We ended up using a slight modification that preserves original grouping of multiValues and has some documentation:
/**
* Patches lack of
* https://developer.mozilla.org/en-US/docs/Web/API/Location/search in event.
* Inspired by
* https://github.com/aws-samples/amazon-cloudfront-functions/issues/11.
* @param obj The weird format exposed by CloudFront
* https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-event-structure.html#functions-event-structure-query-header-cookie
* @returns {string} Tries to return the same as
* https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/toString
*/
function getURLSearchParamsString(obj) {
var str = [];
for (var param in obj) {
if (obj[param].multiValue) {
str.push(
obj[param].multiValue.map((item) => param + "=" + item.value).join("&")
);
} else if (obj[param].value === "") {
str.push(param);
} else {
str.push(param + "=" + obj[param].value);
}
}
return str.join("&");
}
Now
?aa=11&bb=22&aa=33&bb=44,55&cc=66&dd&ee
results in
?aa=11&aa=33&bb=22&bb=44,55&cc=66&dd&ee
instead of
?aa=11,33&bb=22,44,55&cc=66&dd&ee
Adapted JSDoc types from https://www.npmjs.com/package/@types/aws-lambda
/**
* Patches lack of
* https://developer.mozilla.org/en-US/docs/Web/API/Location/search in event.
* Inspired by
* https://github.com/aws-samples/amazon-cloudfront-functions/issues/11.
* @param {import("aws-lambda"). CloudFrontFunctionsQuerystring} querystring The weird format exposed by CloudFront
* https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-event-structure.html#functions-event-structure-query-header-cookie
* @returns {string} Tries to return the same as
* https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/toString
*/
function getURLSearchParamsString(querystring) {
var str = [];
for (var param in querystring) {
var query = querystring[param];
var multiValue = query.multiValue;
if (multiValue) {
str.push(multiValue.map((item) => param + '=' + item.value).join('&'));
} else if (query.value === '') {
str.push(param);
} else {
str.push(param + '=' + query.value);
}
}
return str.join('&');
}
@longzheng Instead of CloudFrontFunctionsEvent
I believe it should be CloudFrontFunctionsEvent['request']['querystring']
.
@longzheng Instead of
CloudFrontFunctionsEvent
I believe it should beCloudFrontFunctionsEvent['request']['querystring']
.
Sorry I made a typo when I edited it, it can also be CloudFrontFunctionsQuerystring
since that's what CloudFrontFunctionsEvent['request']['querystring']
references. I'll update my comment.
It's especially confusing that AWS offers a querystring
helper package which exposes a stringify
method that doesn't actually work with their own representation of the event.request.querystring
object https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-javascript-runtime-features.html#writing-functions-javascript-features-builtin-modules-query-string
okay so I updated this a bit on my end because I was worried about double encoding, but also worried about not having encodings properly in query params. I'm also not redirecting here like in the example above. this was super helpful though, thank you @joknoxy !
function isEncoded(uri) {
uri = uri || '';
return uri !== decodeURIComponent(uri);
}
function fullyDecodeURI(uri){
while (isEncoded(uri)) {
uri = decodeURIComponent(uri);
}
return uri;
}
function encode(param) {
return encodeURIComponent(fullyDecodeURI(param));
}
function objectToQueryString(obj) {
var str = [];
for (var param in obj) {
if (obj[param].multiValue) {
str.push(encode(param) + "=" + obj[param].multiValue.map((item) => encode(item.value)).join(','));
} else if (obj[param].value == '') {
str.push(encode(param));
} else {
str.push(encode(param) + "=" + encode(obj[param].value));
}
}
return str.join("&");
}
function handler(event) {
var request = event.request;
request.headers["x-forwarded-host"] = request.headers.host;
request.querystring = objectToQueryString(request.querystring);
return request;
}
Thanks for the comments, it helps me to solve my issue.
Here is my code for redirect
with query params
+ adding missing /index.html
for SPA or SSG websites
https://gist.github.com/karpolan/ecce9c372bebb448ee04cc240ca5c8aa
thanks @longzheng for sharing https://github.com/aws-samples/amazon-cloudfront-functions/issues/11#issuecomment-1530752418, got this working thanks to it 🚀
for anyone else trying to build cloudfront function to redirect to non trailing slash url while preserving query string refer 👇
function handler(event) {
// Get the request object from the event
var request = event.request;
// Get the URI of the requested resource
var uri = request.uri;
// Get the query string parameters
var queryStringParameters = request.querystring;
// Remove all trailing slashes from the URI using a regular expression
var newUri = uri.replace(/\/+$/, "");
// Check if the URI had trailing slashes
if (newUri !== uri) {
// Check if querystring is not empty
var hasQueryString = Object.keys(queryStringParameters).length > 0;
// Construct the new URI without the trailing slashes and include the query parameters if querystring is not empty
var redirectUri = newUri + (hasQueryString ? '?' + getURLSearchParamsString(queryStringParameters) : '');
// Redirect to the new URI without the trailing slashes
var response = {
statusCode: 301,
statusDescription: "Moved Permanently",
headers: {
location: { value: redirectUri },
},
};
return response;
}
// If there's no trailing slash, proceed with the request as is
return request;
}
// Helper function to format query string parameters
function getURLSearchParamsString(querystring) {
var str = [];
for (var param in querystring) {
var query = querystring[param];
var multiValue = query.multiValue;
if (multiValue) {
str.push(multiValue.map((item) => param + '=' + item.value).join('&'));
} else if (query.value === '') {
str.push(param);
} else {
str.push(param + '=' + query.value);
}
}
return str.join('&');
}
This relates to issue #7 . I've used the code from there and made it more generic and fixed a couple of edge-cases that wouldn't have worked. Please add this to sample library as it's a very common use-case with a non-obvious solution (for those of us not js experts).
This saved me! I kept getting ?[object%20Object]
Is this issue still open?!
This relates to issue #7 . I've used the code from there and made it more generic and fixed a couple of edge-cases that wouldn't have worked. Please add this to sample library as it's a very common use-case with a non-obvious solution (for those of us not js experts).