I have my API Gateway set up with application/json Content-Type mapped to
{
"param1": "$input.params('param1')"
}
and application/pdf mapped to
{
"content": "$input.body"
}
Both of these work on their own when I use postman and specify the Content-Type as one or the other.
But, I want to be able to make a request that can take both pdf binary data along with some parameters from JSON using one call and use both in lambda.
I've tried adding both of these in a content-type and setting the template as
{
"content": "$input.body",
"param1": "$input.params('param1')"
}
But this returns back an error when I call it with the params and the pdf:
"message": "Could not parse request body into json: Illegal unquoted character ((CTRL-CHAR, code 10)): has to be escaped using backslash to be included in string value\n
The "param1" is a query string parameter I have added.
Related
I'm trying to understand how to correctly format the JSON that is sent back from my AWS rest API. I am using Api-Gateway to create an API that return the result of a Lambda.
I am used to API responses in the following format:
{
"name" : "John",
"age" : 34
}
But, in the AWS example for integrating lambda and api-gateway, they said to use the following response format:
// The output from a Lambda proxy integration must be
// in the following JSON object. The 'headers' property
// is for custom response headers in addition to standard
// ones. The 'body' property must be a JSON string. For
// base64-encoded payload, you must also set the 'isBase64Encoded'
// property to 'true'.
let response = {
statusCode: responseCode,
headers: {
"x-custom-header" : "my custom header value"
},
body: JSON.stringify(responseBody)
};
return response;
Is there a right way to do this, or can you just return whatever response style you like?
I am trying send an image from POSTMAN. I am able to send a message but image is not getting posted.
https://slack.com/api/chat.postMessage
Used POST type headers i am passing token, channel and i have an Image URL but not sure how to send that. Can anyone please help me on this.
There are two alternative approaches on how to send your message to the API endpoint chat.postMessage:
Body as form-urlencoded
Here is how to include an image in a message send as x-www-form-urlencoded:
The image has to be send as part of an attachment by setting the property called image_url.
The attachments are set with the attachments key in the API call, which requires you define your attachments as array of attachment object in JSON.
In addition to the image_url your attachment needs to contain a fallback property, which is used to display a text to the user in case the image can not be displayed.
Your attachments object then looks like this in JSON:
{
"fallback": "this did not work",
"image_url": "https://secure.gravatar.com/avatar/d6ada88a40de8504c6b6068db88266ad.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2F27b6e%2Fimg%2Favatars%2Fsmiley_blobs%2Fava_0016-512.png"
}
Now you have to put that into an array (by adding [] around it) and this is what you get as value for your attachments key:
[{"fallback":"did not work","image_url":"https://secure.gravatar.com/avatar/d6ada88a40de8504c6b6068db88266ad.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2F27b6e%2Fimg%2Favatars%2Fsmiley_blobs%2Fava_0016-512.png"}]
In addition you need to add the keys token, channel, text key to your message. Voila.
Body as JSON
An alternative and probably easier approach is to send your data as JSON instead of x-www-form-urlencoded to the API.
This requires you to send the token in the Auth header and switch to JSON for the body.
To do that in Postman put your token as "Bearer Token" under "Authorization".
In "Body" switch to "raw" and select "JSON".
Then you can define the whole message as follows:
{
"channel": "test",
"text": "Hi there!",
"attachments":
[
{
"fallback": "this did not work",
"image_url": "https://secure.gravatar.com/avatar/d6ada88a40de8504c6b6068db88266ad.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2F27b6e%2Fimg%2Favatars%2Fsmiley_blobs%2Fava_0016-512.png"
}
]
}
Of course you can do the same in your code. Since your are working in JavaScript using JSON would be the natural approach.
Note that not all API methods support sending the body in JSON. Check out this documentation for more info.
I am attempting to use a pre-signed URL to upload as described in the docs (https://docs.aws.amazon.com/AmazonS3/latest/dev/PresignedUrlUploadObject.html) I can retrieve the pre-signed URL but when I attempt to do a PUT in Postman, I receive the following error:
<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
Obviously, the way my put call is structured doesn't match with the way AWS is calculating the signature. I can't find a lot of information on what this put call requires.
I've attempted to modify the header for Content-Type to multipart/form-data and application/octet-stream. I've also tried to untick the headers section in postman and rely on the body type for both form-data and binary settings where I select the file. The form-data setting results in the following added to the call:
Content-Disposition: form-data; name="thefiletosend.txt"; filename="thefiletosend.txt
In addition, I noticed that postman is including what it calls "temporary headers" as follows:
Host: s3.amazonaws.com
Content-Type: text/plain
User-Agent: PostmanRuntime/7.13.0
Accept: /
Cache-Control: no-cache
Postman-Token: e11d1ef0-8156-4ca7-9317-9f4d22daf6c5,2135bc0e-1285-4438-bb8e-b21d31dc36db
Host: s3.amazonaws.com
accept-encoding: gzip, deflate
content-length: 14
Connection: keep-alive
cache-control: no-cache
The Content-Type header may be one of the issues, but I'm not certain how to exclude these "temporary headers" in postman.
I am generating the pre-signed URL in a lambda as follows:
public string FunctionHandler(Input input, ILambdaContext context)
{
_logger = context.Logger;
_key = input.key;
_bucketname = input.bucketname;
string signedURL = _s3Client.GetPreSignedURL(new GetPreSignedUrlRequest()
{
Verb = HttpVerb.PUT ,
Protocol = Protocol.HTTPS,
BucketName = _bucketname,
Key = _key,
Expires = DateTime.Now.AddMinutes(5)
});
returnObj returnVal = new returnObj() { url = signedURL };
return JsonConvert.SerializeObject(returnVal);
}
Your pre-signed url should be like https://bucket-name.s3.region.amazonaws.com/folder/filename.jpg?AWSAccessKeyId=XXX&Content-Type=image%2Fjpeg&Expires=XXX&Signature=XXX
You can upload to S3 with postman by
Set above url as endpoint
Select PUT request,
Body -> binary -> Select file
I was able to get this working in Postman using a POST request. Here are the details of what worked for me. When I call my lambda to get a presigned URL here is the json that comes back (after I masked sensitive and app-specific information):
{
"attachmentName": "MySecondAttachment.docx",
"url": "https://my-s3-bucket.s3.amazonaws.com/",
"fields": {
"acl": "public-read",
"Content-Type": "multipart/form-data",
"key": "attachment-upload/R271645/65397746_MySecondAttachment.docx",
"x-amz-algorithm": "AWS4-HMAC-SHA256",
"x-amz-credential": "WWWWWWWW/20200318/us-east-1/s3/aws4_request",
"x-amz-date": "20200318T133309Z",
"x-amz-security-token": "XXXXXXXX",
"policy": "YYYYYYYY",
"x-amz-signature": "ZZZZZZZZ"
}
}
In Postman, create a POST request, and use “form-data” to enter in all the fields you got back, with exactly the same field names you got back in the signedURL shown above. Do not set the content type, however. Then add one more key named “file”:
To the right of the word file if you click the drop-down you can browse to your file and attach it:
In case it helps, I’m using a lambda written in python to generate a presigned URL so a user can upload an attachment. The code looks like this:
signedURL = self.s3.generate_presigned_post(
Bucket= "my-s3-bucket",
Key=putkey,
Fields = {"acl": "public-read", "Content-Type": "multipart/form-data"},
ExpiresIn = 15,
Conditions = [
{"acl": "public-read"},
["content-length-range", 1, 5120000]
]
)
Hope this helps.
Your pre-signed url should be like https://bucket-name.s3.region.amazonaws.com/folder/filename.jpg?AWSAccessKeyId=XXX&Content-Type=image%2Fjpeg&Expires=XXX&Signature=XXX
You can upload to S3 with postman by
Set above url as endpoint
Select PUT request,
Body -> binary -> Select file
I was facing the same problem and below is how it worked for me.
Note, I am making signed URL by using AWS S3 Java SDK as my backend is in Java. I gave content type as "application/octet-stream" while creating this signed Url so that any type of content can be uploaded. Below is my java code generating signed url.
public String createS3SignedURLUpload(String bucketName, String objectKey) {
try {
PutObjectRequest objectRequest = PutObjectRequest.builder().bucket(bucketName).key(objectKey)
.contentType("**application/octet-stream**").build();
S3Presigner presigner = S3Presigner.builder().region(s3bucketRegions.get(bucketName))
.credentialsProvider(StaticCredentialsProvider.create(awsBasicCredentials)).build();
PutObjectPresignRequest presignRequest = PutObjectPresignRequest.builder()
.signatureDuration(Duration.ofMinutes(presignedURLTimeoutInMins)).putObjectRequest(objectRequest)
.build();
PresignedPutObjectRequest presignedRequest = presigner.presignPutObject(presignRequest);
return presignedRequest.url().toString();
} catch (Exception e) {
throw new CustomRuntimeException(e.getMessage());
}
}
## Now to upload file using Postman
Set the generated url as endpoint
Select PUT request,
Body -> binary -> Select file
Set header Content-Type as application/octet-stream (This point I was missing earlier)
It's actually depends in how you generated URL,
If you generated using JAVA,
Set the generated url as endpoint
Select PUT request,
Body -> binary -> Select file
If you generated using PYTHON,
Create a POST request, and use form-data to enter in all the fields you got back, with exactly the same field names you got back in the signedURL shown above.
Do not set the content type, however. Then add one more key named “file”:
Refer the accepted answer picture
I am trying to see how to access the request header and body values from with in the lambda code. If the request body is in JSON format, it automatically seems to be parsed and made available in the event object.
How can I access the complete query string, request body, request headers (cookies) for any type of incoming "Content-Type" request inside Lambda ?
The edits below are information I have gathered to help solve the question that may or may not be relevant. Please ignore them if you wish to.
EDIT:
I went through the existing questions on SE here and here.
As per this thread, using $input.json('$') should do the trick. I guess the answers from these links above are already out-dated as API gateway by default seems to recognize JSON in the request and if so makes it available in the event object without any mapping templates being configured.
Setting the mapping as suggested does not work for me. It does not contain the request header information.
Here are screen shots on how it is configured.
The "headers" key returns a blank value. Using $input.params('$') or "$input.params('$')" errors out.
EDIT 2
Tried defining the headers in Method Request. Still not getting the User-Agent value inside lambda.
EDIT 3
I used the following template mapping at the API Gateway
{
"request": $input.json('$'),
"headers": "$input.params()"
}
and the below code in lambda
context.succeed("event.key32:"+JSON.stringify(event, null, 2) );
And the response generated by the API gateway shows this
Looking at the "headers" value in the response, it looks like the AWS-SDK/API gateway/cloudfront strips off all headers received from the HTTP client ? Here is the full text from the JSON returned by the $input.params().header
header={CloudFront-Forwarded-Proto=https, CloudFront-Is-Desktop-Viewer=true, CloudFront-Is-Mobile-Viewer=false, CloudFront-Is-SmartTV-Viewer=false, CloudFront-Is-Tablet-Viewer=false, Content-Type=application/json, Via=1.1 5d53b9570d94ce920abbd471.cloudfront.net (CloudFront), 1.1 95eea7baa7ec95c9a41eca9e3ab7.cloudfront.net (CloudFront), X-Amz-Cf-Id=GBqmObLRy6Iem9bJbVPrrW1K3YoWRDyAaMpv-UkshfCsHAA==, X-Forwarded-For=172.35.96.199, 51.139.183.101, X-Forwarded-Port=443, X-Forwarded-Proto=https}}
It doesn't have the User-Agent string in the header, although as the screenshot shows above, it was sent by the REST client.
Interestingly, the entire query string is made available. Not sure if this is an intended way to access it.
The request headers can be accessed using $input.params('header-name')
Surprisingly, the User-Agent header cannot be accessed with above code. You need to jump through the following hoop to retrieve it:
$context.identity.userAgent
The request body/payload should be accessible using the following code. More reference here, here and here:
{
"reqbody": "$input.path('$')"
}
It is not yet clear if the request body is expected to be in JSON. It needs to be noted that the request is treated as UTF-8 according to this post.
There currently seems to be two bugs:
The "User-Agent" header is missing/being stripped off by the Amazon API.
When the header values contain a double quote ("), the lambda function is not executed. (I do not see a log entry in the cloudwatch logs for such requests). Instead, the http response body contains the following:
{
"Type": "User",
"message": "Could not parse request body into json."
}
An example request that fails in Amazon API
I believe this would need to be corrected to be able to implement the ETag mechanism for caching.
References:
An Etag is expected to be enclosed within double quotes. The browser is expected to send this exact value back through the If-None-Match header, and this is where Amazon API breaks.
Syntax for ETag?
HTTP: max length of etag
http://gsnedders.com/http-entity-tags-confusion
Seems like if no "Content-Type" is sent, AWS API Gateway defaults it to "application/json":
https://forums.aws.amazon.com/thread.jspa?threadID=215471
So just define the Mapping Template for "application/json".
You have to get the information you need in the template mapping and send them back your Lambda function, this is one of the template I used to send information to the Lambda function:
{
"params" : "$input.params()",
"content-type-value" : "$input.params().header.get('Content-Type')",
"body" : "$input.json('$')",
"request-id": "$context.requestId",
"method": "$context.httpMethod",
"resource": "$context.resourcePath",
"id": "$input.params('id')" //This is a path parameter in my case
}
You can do the same, or you can access params.path.id (again in my case). Here is the link to the documentation.
Cheers,
I updated the mapping template I used in the answer to one of the referenced questions to contain the userAgent property.
{
"method": "$context.httpMethod",
"body": $input.json('$'),
"userAgent": "$context.identity.userAgent",
"headers": {
#foreach($param in $input.params().header.keySet())
"$param": "$util.escapeJavaScript($input.params().header.get($param))" #if($foreach.hasNext),#end
#end
},
"queryParams": {
#foreach($param in $input.params().querystring.keySet())
"$param": "$util.escapeJavaScript($input.params().querystring.get($param))" #if($foreach.hasNext),#end
#end
},
"pathParams": {
#foreach($param in $input.params().path.keySet())
"$param": "$util.escapeJavaScript($input.params().path.get($param))" #if($foreach.hasNext),#end
#end
}
}
A detailed explanation of the template is available here:
http://kennbrodhagen.net/2015/12/06/how-to-create-a-request-object-for-your-lambda-event-from-api-gateway/
I am calling a web service from Classic ASP/VBScript. The call expects 3 parameters, 1 which is form data and 2 which are optional file data. Currently not sending the file data.
The form data is multiple fields, wrapped up in json.
So I'm setting the content-type on the ServerXMLHTTP object to be multipart/form-data, and I'm creating the json segment as such and sending it as the data
Content-Type: application/json; charset=utf-8
{
"Token": "...",
"FirstName": "First Name",
I keep getting Request must be Content-type: multipart/form-data.
I've tried adding a boundary and same thing.
I know I can do this in C# using MultipartFormDataContent, but it has to be Classic unfortunately.
What's the correct way to send? Thanks!