Reputation: 3563
Setting response headers with API Gateway with AWS Lambda proxy integration is straight forward and looks like this:
import zlib from 'zlib';
exports.handler = async (event, context, callback) => {
const body = zlib.gzipSync(JSON.stringify({ data: 'mock' }));
const headers = {};
headers['Content-Type'] = 'application/json';
headers['Content-Encoding'] = 'gzip';
const responseObject = {
statusCode: 200,
headers,
body: body.toString('base64'),
isBase64Encoded: true
};
return callback(null, responseObject);
}
Everything is returned gzipped as expected. Because we set the content-encoding the browser decompresses the response.
Question is how can headers be similarly set when the Lambda function is invoked from the browser directly using AWS SDK JS? API Gateway is the service which implements the headers in the previous setup, without having API Gateway in front of AWS Lambda headers are ignored and being generically set to:
access-control-allow-origin: *
access-control-expose-headers: x-amzn-RequestId,x-amzn-ErrorType,x-amzn-ErrorMessage,Date,x-amz-log-result,x-amz-function-error
content-length: 1242
content-type: application/json
date: Fri, 26 Apr 2019 00:36:35 GMT
status: 200
x-amz-executed-version: $LATEST
x-amzn-remapped-content-length: 0
x-amzn-requestid: <REDACTED>
x-amzn-trace-id: <REDACTED>
AWS SDK JS browser invoke code looks something like this:
import AWS from 'aws-sdk';
AWS.config.region = 'us-east-1'; // Region
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: '<SOME IDENTITY>',
});
const AWSLambda = new AWS.Lambda({region: REGION, apiVersion: '2015-03-31'});
const parameters = {
FunctionName : 'MyFunctionName',
InvocationType : 'RequestResponse',
LogType : 'None',
Payload: JSON.stringify({msg: 'hello lambda'})
};
(async () => {
const response = await AWSLambda.invoke(shopParameters).promise();
console.log(response);
})();
The returned response is the response object above as a string with the generic headers. Browser does not decompress the gzipped content, presumably because the content-encoding header is not being set. AWS Lambda when invoked from the browser, treats the whole Lambda response object as the response and performs no transformations that happen with API Gateway. For instance, API Gateway would pick up on the response object structure and map response object headers to the response before sending to the client.
Is there no way to set AWS Lambda headers without API Gateway? Or is the only option to decompress the gzip content manually on the client using something like https://github.com/nodeca/pako (sigh).
Idea to not use API Gateway came from AWS docs found here, like to avoid the API Gateway costs: https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/browser-invoke-lambda-function-example.html
Any guidance, expertise, thoughts are much appreciated!
Upvotes: 1
Views: 4042
Reputation: 764
Alternatively, depending on your requirements, a strategy I have used is have lambda write the json to s3 where its public and can be fetched easily.
Upvotes: 1
Reputation: 179064
No, there is no way to do this without something in front of the Lambda service API -- typically API Gateway.
Your const responseObject
is actually creating the response using the format API Gateway has specified -- the Lambda service performs no interpretation of the response. That's why it has no impact on the headers in your response, and why the base64 remains untranslated -- that entire structure is API Gateway specific. Lambda is just returning JSON.
If the responses are small enough, that "something" could alternately be an Application Load Balancer which might or might not be easy enough to use with Cognito, though authentication would be different. It uses essentially the same response format as API Gateway, and the balancer decodes the base64 before returning it to the browser.
You can also invoke a Lambda function over HTTP(S) with CloudFront's Lambda@Edge feature, and set custom headers and decode base64 automatically, but this service doesn't have a built-in Cognito integration and is notably different than the full-featured Lambda service, supporting only Node.js and running the Lambda function in whichever AWS region is closest to the browser rather than in the region where it was created for better global performance. Lambda@Edge also expects a different output format -- it's a much more well-engineered design than the response structure that API Gateway expects, but as such it's also not interchangeable.
Upvotes: 4