I have API Gateway set up to serve some files from S3 bucket with Lambda. When I try to request non-existing files, API Gateway sometimes responds with 403 Forbidden (most of the times and doesn't even trigger Lambda function) and sometimes with 404 Not Found error (I'd like to trigger 404 in such cases).
My Lambda function is very simple:
exports.handler = async event => {
try {
const Bucket = 'testing-bucket';
const Key = `${event.documentType}/${event.requestParams.document}`;
const file = await s3.getObject({ Bucket, Key }).promise();
return {
body: file.Body.toString('base64'),
headers: {
'Content-Disposition': `attachment; filename=test.jpg`,
'Content-Length': file.ContentLength,
'Content-Type': file.ContentType,
},
statusCode: 200,
isBase64Encoded: true,
};
} catch (e) {
return {
body: JSON.stringify({ message: `[${e.code}] ${e.message}` }),
statusCode: e.statusCode,
};
}
};
IAM Role attached to Lambda function is configured in this way:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::testing-bucket",
"arn:aws:s3:::testing-bucket/*"
]
}
]
}
Caching is completely disabled in API Gateway and the command I've been trying to test this out is:
curl -X GET -H 'Authorization: 123xyz' -H 'Accept: image/jpeg' -H 'Cache-Control: no-cache' -I https://test.com/existing_folder/non-existing-file.xxx
Responses are:
HTTP/2 403
content-type: application/json
content-length: 60
date: Mon, 07 Oct 2019 10:32:30 GMT
x-amzn-requestid: ae870104-9045-4c23-9794-226992bad591
x-amzn-errortype: AccessDeniedException
x-amz-apigw-id: BMAZwGSyoAMFftw=
x-cache: Error from cloudfront
via: 1.1 ccf34ecc11e5579d8083b17d9d39a622.cloudfront.net (CloudFront)
x-amz-cf-pop: LHR62-C2
x-amz-cf-id: zgtgfJX9TQLcI8F2RLWdgTz-RN_1j7MXblQ1498ucoeFY3dhjitOdg==
and
HTTP/2 404
content-type: application/json
content-length: 59
date: Mon, 07 Oct 2019 10:32:31 GMT
x-amzn-requestid: 2de49681-4f21-4cd1-989c-9b36327badb1
x-amz-apigw-id: BMAZ5E52IAMFwEg=
x-amzn-trace-id: Root=1-5d9b143f-aadf0a24a5f60f4c939b77c0;Sampled=0
x-cache: Error from cloudfront
via: 1.1 be00537a2361673ea48963d6e04d04a1.cloudfront.net (CloudFront)
x-amz-cf-pop: LHR62-C2
x-amz-cf-id: 9VI26GH3-ZuJSQrEt5Fc7EjuMt8IV0TPzPwna8dvvr6UtsgiqwwIkw==
How to make API Gateway respond in consistent way?
UPDATE:
After observing API Gateway logs and trying to spam the same curl command for existing and non-existing files couple of times in a row, this was the output for non-existing file (timestamps are intact):
# curl -X GET -H 'Authorization: 123xyz' -H 'Accept: image/jpeg' -H 'cache-control: private, no-cache, no-store, max-age=1, s-maxage=1' https://my.url/foo/nobar
{
"requestId": "d19602e8-3a32-4445-b9e6-99f05a59fac4",
"ip": "redacted",
"caller": "-",
"user": "-",
"requestTime": "08/Oct/2019:00:05:03 +0000",
"httpMethod": "GET",
"resourcePath": "/foo/{bar}",
"status": "404",
"protocol": "HTTP/1.1",
"responseLength": "59"
}
# and
{
"requestId": "b33bf6c7-55db-4e1f-b4e4-b1e826139556",
"ip": "redacted",
"caller": "-",
"user": "-",
"requestTime": "08/Oct/2019:00:05:05 +0000",
"httpMethod": "GET",
"resourcePath": "/foo/{bar}",
"status": "403",
"protocol": "HTTP/1.1",
"responseLength": "60"
}
and for existing file:
# curl -X GET -H 'Authorization: 123xyz' -H 'Accept: image/jpeg' -H 'cache-control: private, no-cache, no-store, max-age=1, s-maxage=1' https://my.url/foo/bar
{
"requestId": "122ef31e-c587-470c-a0b5-51c6d9838fe4",
"ip": "redacted",
"caller": "-",
"user": "-",
"requestTime": "07/Oct/2019:23:58:35 +0000",
"httpMethod": "GET",
"resourcePath": "/foo/{bar}",
"status": "403",
"protocol": "HTTP/1.1",
"responseLength": "60"
}
# and then later
{
"requestId": "c8ad1b40-006f-4d03-9d10-c6d91e366380",
"ip": "redacted",
"caller": "-",
"user": "-",
"requestTime": "07/Oct/2019:23:59:58 +0000",
"httpMethod": "GET",
"resourcePath": "/foo/{bar}",
"status": "200",
"protocol": "HTTP/1.1",
"responseLength": "80280"
}
I finally got some time to get back to this issue and it looks like the problem was all along in "authorizer" function which had caching enabled, once disabled, my responses started to respond in a consistent way.
You can manage the response yourself. You can check if the file doesn't exist after your await and respond a 404. Something like this: (Code not tested)
exports.handler = async event => {
try {
const Bucket = 'testing-bucket';
const Key = `${event.documentType}/${event.requestParams.document}`;
const file = await s3.getObject({ Bucket, Key }).promise();
if (!file) {
return {
body: {error: 'File not found'},
headers: {
'Content-Type': 'application/json'
}
statusCode: 400,
};
}
return {
body: file.Body.toString('base64'),
headers: {
'Content-Disposition': `attachment; filename=test.jpg`,
'Content-Length': file.ContentLength,
'Content-Type': file.ContentType,
},
statusCode: 200,
isBase64Encoded: true,
};
} catch (e) {
return {
body: JSON.stringify({ message: `[${e.code}] ${e.message}` }),
statusCode: e.statusCode,
};
}
};
So when I wrote my lambda to process s3 downloads, I did stick with promise chaining so that I could debug easier...have you tried the other method?
return s3.getObject({ Bucket, Key }).promise().then((s3Response) => {
console.log(s3Response.Body.toString());
}).catch((err) => {
console.log(err);
});
I have a feeling there is something happening in your promise that's causing it to come back to quick and it's failing or something along those lines.
Related
I made this function to make it possible to send announcements to my google home mini but im getting error code 400 (Bad Request) nonstop. I tried using multiple approaches to this.
In Home-Assistant, i have setup my Google Home and can play media through it but whenever i try it with the "google_say" API of Home-Assistant it doesn't work.
I also tried calling the Home-Assistant API over my Phone using an App called "API Client" but i got the same response.
function ttsGoogleHome(text) {
var ttsUrl = "http://127.0.0.1:8123/api/services/tts/google_say?api_password=<MY_PASSWORD>"
var varToken = "<MY_TOKEN>"
var postData = {"entity_id": "media_player.david", "message": `${text}`};
let axiosConfig = {
headers: {
'authorization': `Bearer ${varToken}`,
'Content-Type': 'application/json',
"Access-Control-Allow-Origin": "*",
}
};
axios.post(ttsUrl, postData, axiosConfig)
.then((res) => {
console.log("RESPONSE RECEIVED: ", JSON.stringify(res));
})
.catch((err) => {
console.log("AXIOS ERROR: ", JSON.stringify(err));
})
}
This is the response i get in the server:
{
"message": "Request failed with status code 400",
"name": "Error",
"stack": "Error: Request failed with status code 400\n
at createError (/home/pi/nodejs/node_modules/axios/lib/core/createError.js:16:15)\n
at settle (/home/pi/nodejs/node_modules/axios/lib/core/settle.js:17:12)\n
at IncomingMessage.handleStreamEnd (/home/pi/nodejs/node_modules/axios/lib/adapters/http.js:260:11)\n
at IncomingMessage.emit (events.js:388:22)\n
at endReadableNT (internal/streams/readable.js:1336:12)\n
at processTicksAndRejections (internal/process/task_queues.js:82:21)",
"config": {
"url": "http://127.0.0.1:8123/api/services/tts/google_say?api_password=<MY_PASSWORD>",
"method": "post",
"data": "{\"entity_id\":\"media_player.david\",\"message\":\"Erste Stunde Fach Deutsch Lehrer Schemmer Raum Schemmer\"}",
"headers": {
"Accept": "application/json, text/plain, */*",
"Content-Type": "application/json;charset=UTF-8",
"authorization": "Bearer <MY_TOKEN>",
"Access-Control-Allow-Origin": "*",
"User-Agent": "axios/0.21.1",
"Content-Length": 103
},
"transformRequest": [
null
],
"transformResponse": [
null
],
"timeout": 0,
"xsrfCookieName": "XSRF-TOKEN",
"xsrfHeaderName": "X-XSRF-TOKEN",
"maxContentLength": -1,
"maxBodyLength": -1
}
}
I found my error.
I used the wrong api link
here is the correct way to call it.
function ttsGoogleHome(text) {
var ttsUrl = "http://127.0.0.1:8123/api/services/tts/google_translate_say?api_password=APIPASSWORD"
var varToken = "TOKEN"
var postData = `{"entity_id": "media_player.david", "message": "${text}", "language": "de"}`;
let axiosConfig = {
data: null,
headers: {
'authorization': `Bearer ${varToken}`,
'Content-Type': 'application/json',
"Access-Control-Allow-Origin": "*",
}
};
axios.post(ttsUrl, postData, axiosConfig)
.then((res) => {
console.clear();
console.info("RESPONSE RECEIVED: ", JSON.stringify(res));
})
.catch((err) => {
console.clear();
console.error("AXIOS ERROR: ", JSON.stringify(err));
})
}
also here is my configuration.yaml:
# Configure a default steup of Home Assistant (frontend, api, etc)
# Text to speech
tts:
- platform: google_translate
- language: "de"
- service_name: google_say
-base_url: http://192.168.0.176:8123
group: !include groups.yaml
automation: !include automations.yaml
script: !include scripts.yaml
scene: !include scenes.yaml
homeassistant:
auth_providers:
- type: legacy_api_password
api_password: !secret http_password
I am trying to call the createFeedDocument operation to sp-api. But I never get a response.
Here is my signedRequest:
{
path: '/feeds/2020-09-04/documents',
method: 'POST',
host: 'sellingpartnerapi-fe.amazon.com',
region: 'us-west-2',
service: 'execute-api',
headers: {
'User-Agent': 'MyAmazonApp/1.0 (Language=JavaScript;)',
'x-amz-access-token': 'Atzasd61as689d1a1f89a189198ea1f891ad89d1e891f89ae189f189165',
Accept: 'application/json',
'Accept-Charset': 'utf-8',
Host: 'sellingpartnerapi-fe.amazon.com',
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
'Content-Length': 58,
'X-Amz-Security-Token': 'Fwa***********************************************',
'X-Amz-Date': '20210404T230040Z',
Authorization: 'AWS4-HMAC-SHA256 Credential=ASIASEECTM3DHQOSTQ4M/20210404/us-west-2/execute-api/aws4_request, SignedHeaders=accept;accept-charset;content-length;content-type;host;x-amz-access-token;x-amz-date;x-amz-security-token, Signature=f***********************************************e'
},
body: '{"contentType":"text/tab-separated-values; charset=UTF-8"}'
}
And if I tried to call this operation without body it gives the error shown below:
{
"statusCode": 400,
"res": {
"errors": [
{
"code": "InvalidInput",
"message": "One or more required parameters missing",
"details": "contentType;"
}
]
}
}
Your Content-Type header should be application/json
To send a notification, you'll need to send the following HTTP request:
POST /fcm/send HTTP/1.1
Host: fcm.googleapis.com
Content-Type: application/json
Authorization: key=YOUR_SERVER_KEY
{
"notification": {
"title": "New chat message!",
"body": "There is a new message in FriendlyChat",
"icon": "/images/profile_placeholder.png",
"click_action": "http://localhost:5000"
},
"to":"YOUR_DEVICE_TOKEN"
}
how can I do this??
If you're using Node.JS, I suggest you look at the documentation for Firebase's Node.JS SDK instead of manually sending HTTP requests. There's the official documentation or this nice tutorial
If you still want to go for the plain HTTP method, you can use the request npm module
$ npm install request
then in your code :
const request = require('request');
request({
url: 'https://fcm.googleapis.com/fcm/send',
method: 'POST',
headers: {
"Content-Type": "application/json",
"Authorization": ['key', yourServerKey].join('=')
},
json: {
to: clientFirebaseToken,
notification: {
title: "Notification Title",
body: "This is a neat little notification, right ?"
}
});
Edit
From their GitHub
As of Feb 11th 2020, request is fully deprecated. No new changes are
expected to land. In fact, none have landed for some time.
If you use axios
axios({
method: 'post',
url: 'https://fcm.googleapis.com/fcm/send',
headers: {
"Content-Type": "application/json",
"Authorization": ['key', yourServerKey].join('=')
},
params: {
to: clientFirebaseToken,
notification: {
title: "Notification Title",
body: "Neat indeed !"
}
}
})
If you are using React JS/ React Native, using the Axios package it can be done very easily, the sample code is below, first, you have to register for firebase cloud messaging to get the authorization key
axios.post(
'https://fcm.googleapis.com/fcm/send',
{
data: {},
notification: {
title: "Sample text1",
body: "Sample text2",
image: "Sample text3",
},
to: '/topics/TopicName',
},
{
headers: {
Authorization:
'key=Authorization key from firebase',
},
},
)
.then(Response => {
console.log(Response.data);
});
How can I log cookies that are stored in a cookie jar using the request-promise npm module.
I have tried printing the cookie jar variable but as expected that does not work.
How I am creating the jar,
var request = require('request-promise');
var sess = request.jar()
The code sending the request,
request({url: myurl, jar: sess}, function () {
request(
{
url: 'myurl',
method: 'POST',
headers: [
{
"Accept": "application/json",
}
],
postData: {
"xqr":"1"
}
}
)
I expect all the cookies used to send my request to be printed out using console.log()
request uses tough-cookie internally. So you can easily access to tough-cookie store which is an abstract class and use its prototype function getAllCookies.
function logCookies(jar){
jar._jar.store.getAllCookies(function(err, cookieArray) {
if(err) throw new Error("Failed to get cookies");
console.log(JSON.stringify(cookieArray, null, 4));
});
}
And this will log all cookies and its properties.
[
{
"key": "1P_JAR",
"value": "1P_JAR_VALUE",
"expires": "2019-01-23T20:09:38.000Z",
"domain": "google.com",
"path": "/",
"hostOnly": false,
"creation": "2018-12-24T20:09:37.800Z",
"lastAccessed": "2018-12-24T20:09:38.097Z"
},
{
"key": "NID",
"value": "NID_VALUE",
"expires": "2019-06-25T20:09:38.000Z",
"domain": "google.com",
"path": "/",
"httpOnly": true,
"hostOnly": false,
"creation": "2018-12-24T20:09:37.802Z",
"lastAccessed": "2018-12-24T20:09:38.098Z"
}
]
If you only want to get raw cookie string you can just simply use
console.log(cookieArray.map(cookie => cookie.toString()))
And it will give you
[
'1P_JAR=1P_JAR_VALUE; Expires=Wed, 23 Jan 2019 20:15:02 GMT; Domain=google.com; Path=/',
'NID=NID_VALUE; Expires=Tue, 25 Jun 2019 20:15:02 GMT; Domain=google.com; Path=/; HttpOnly'
]
I have a marklogic database with the following JSON document named urlList.json
{
"test": {
"ip": "10.10.10.10",
"fqdn": "www.test.test"
}
}
I am trying to add to the test object with the marklogic rest API using patch. I am using Node with the request-promise module here is the code
var options = {
method: 'PATCH',
url: 'https://test:8000/v1/documents',
qs: { database: databaseName, uri: 'urlList.json' },
headers:
{
'Content-Type': 'application/json',
Accept: 'application/json'
},
strictSSL: false,
auth: {
user: userName,
pass: password,
sendImmediately: false
},
body: JSON.stringify({
"patch": [
{
"insert": {
"context": "/test/",
"position": "last-child",
"content": { "test": "test"}
}
}
]
})
};
request(options)
.then(results => {
return resolve(results);
})
.catch(err => {
return reject(err);
})
The desired outcome when it runs is
{
"test": {
"ip": "10.10.10.10",
"fqdn": "www.test.test",
"test": "test"
}
}
I get the following error every time I run it
"400 - "{\"errorResponse\":{\"statusCode\":400, \"status\":\"Bad
Request\", \"messageCode\":\"RESTAPI-INVALIDREQ\",
\"message\":\"RESTAPI-INVALIDREQ: (err:FOER0000) Invalid request:
reason: invalid patch for uri urlList.json: invalid path: /test/\"}}""
Here is the body that is sent
"{"patch":[{"insert":{"context":"/test/","position":"last-
child","content":{"test":"test"}}}]}"
A path must select a node. For that reason, a path can't end with a separator. That's what the message attempts to convey.
Does it work with a path of /test?
By the way, MarkLogic provides a Node.js API with support for promises. That might be easier to work with.
Hoping that helps,