How to make an asynchronous API call to a Chalice app? - aws-lambda

I need a user-request from a web app to call an AWS Chalice endpoint which triggers a long-running job. I can't let the user's browser wait for a response.
Chalice creates a REST API in API Gateway automatically, so this should be possible as it's not an HTTP API.
How to implement an endpoint in AWS Chalice which responds to the user immediately before executing the attached Lambda function?
I am aware of the InvocationType: Event header which should allow for this, but this has no effect, the endpoint does not call the Lambda asynchronously (the client gets a response when the job completes 20-30 seconds later).
Here's what the endpoint in the Chalice app roughly looks like:
#app.route('/api-v1/runLongJob', methods=['PUT'])
def run_long_job():
# do some stuff that takes a long time here
return {'status_code': 200}
I have also set InvocationType as a header in the Method request section in the API Gateway console:
See this screenshot for what that looks like
An example of how I call this endpoint from Python with the InvocationType header:
url = "https://----------.execute-api.xx-xxxx-x.amazonaws.com/api/v1-api/runLongJob"
data = {
'some parameter': 'some data'
}
headers = {
'Content-Type': 'application/json',
'InvocationType': 'Event'
}
r = requests.put(
url,
data=json.dumps(data),
headers=headers
)
How to create an asynchronous endpoint in a Chalice app?

The solution is to use the .lambda_function feature in Chalice along with the invocationType='Event' parameter in the invocation call, like this:
import boto3
from chalice import Chalice
lambda_client = boto3.client('lambda')
app = Chalice(app_name='api')
LOG.setLevel(logging.INFO)
#app.route('/v1/lambdaTest')
def test_endpoint():
LOG.info(datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
_ = lambda_client.invoke(
FunctionName="api-dev-testFunction",
InvocationType='Event',
Payload="{}"
)
LOG.info(datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
return {'status_code': 200}
#app.lambda_function(name='testFunction')
def test_lambda_function(event, context):
LOG.info(datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
time.sleep(20)
LOG.info(datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
This generates two lambda functions, one for the API and one for the asynchronous task. When the API is called, the response is immediate, but the task takes 20 seconds to complete.
Note that I had to manually permit the API lambda to invoke the other, in the AWS console.

Related

javascript client for oauth2 and google-oauth library's "redirect-uri" missing case

I am working on porting oauth2client library to google-auth and oauthlib library
Right now for oauth2 authentication, I am using javascript client and using grantofflineaccess method in it.
The flow for oauth2client library was:
using the js client, we get the authorization code and pass it in oauth2client.client.credentials_from_code to get the credentials object. In the paramaeters, we pass only clientid,secret,scopes and code. There was no mention of redirect_uri.
Right now I am replacing it with flow
https://google-auth-oauthlib.readthedocs.io/en/latest/reference/google_auth_oauthlib.flow.html
This means that I HAVE TO pass redirect_uri to create the flow object, following which I can use flow.fetch_token(code) to get flow.credentials.
Here is my javascript side code (angularJS) (Reference: https://developers.google.com/identity/sign-in/web/reference)
// init
gapi.load('auth2', () => {
const authScopes = {
client_id: $window.GOOGLE_API_CLIENT_ID,
scope: scopes.join(' '),
};
auth2 = gapi.auth2.init(authScopes);
// When signin button clicked
auth2.grantOfflineAccess({ redirect_uri: 'postmessage', prompt: 'consent' })
.then((response) => {
// We enter this when user signins into google account and 'ALLOWS' the scopes. We receive the code in response using which we get the access and refresh token. The below calls are being made to backend
Something.authenticateUser(response.code).then(() => {
$scope.sheetsWizard.onLoginSuccess();
The backend code: (django)
# Upon receiving the 'code', it calls oauth2client function to get credentials object
credentials = client.credentials_from_code(
GOOGLE_API_CLIENT_ID,
GOOGLE_API_CLIENT_SECRET,
scopes,
data['code']
The above was older code, How the new code looks in the backend (The frontend or angularJS remains the same)
flow = Flow.from_client_config(client_config=dictionary_with_client_credentials, scopes=scopes, redirect_uri='some_website.com')
flow.fetch_token(code = data['code'])
credentials = flow.credentials
My questions:
Is there a method in google-auth that I am missing, so I don't have to pass redirect_uri, and still get at least access_token and refresh_token, after receiving code?
in js gapi client, there is a mention of redirect_uri='postmessage', but could not find documentation related to it. What does it mean?

With Cypress, how to get the response body of an api call triggered by a non-request event

I am testing my login page with cypress. The call to my api /api/auth/login is triggered automatically when the input field of the password reaches 4 characters. So in my cypress spec file, the command cy.get("password-input").type("1234") is enough to trigger the api call. How can I get the response body of that api call? I would like to get the token my api sends back.
In classic api calls with the cy.request command I can easily handle the response body, but I couldn't find how to access the response body when the api request is triggered by another event like here with the type event.
Currently, I have a workaround because my website stores the response.body.token in the localStorage, so I access the token with window and after a wait:
it("should get token", () => {
cy.visit("/login")
cy.get("[data-cy="login-input"]).type("myLogin")
cy.get("[data-cy="password-input"]).type("0001")
cy.wait(5000)
cy.window().then(window => {
cy.log(window.localStorage.getItem("myToken"))
})
})
But this feels gross... Could you give me the proper way to access the response body of the api call triggered by the type event?
You can use cy.intercept(), aliasing, and cy.wait():
it("should get token", () => {
cy
.intercept('/api/auth/login')
.as('token');
cy
.visit("/login");
cy
.get('[data-cy="login-input"]')
.type("myLogin");
cy
.get('[data-cy="password-input"]')
.type("0001");
cy
.wait('#token')
.then(intercept => {
// you can now access the request body, response body, status, ...
});
});
Useful reading:
https://docs.cypress.io/api/commands/intercept
https://docs.cypress.io/api/commands/intercept#Aliasing-individual-requests
https://docs.cypress.io/api/commands/wait
https://docs.cypress.io/api/commands/wait#Alias

Capture raw axios request from AWS Lambda

I have code that calls a vendor API to do a formdata upload of a file by axios from inside an AWS Lambda. The call returns a 400 error. If I run the code locally using the same node version v14 it works. I want to capture both raw requests and compare them for differences. How do I capture both raw requests? I've tried using ngrok and pipedream but they don't show the raw but decode the request and the file.
let response = null;
try {
const newFile = fs.createReadStream(doc);
const formData = new FormData();
formData.append("file", newFile);
formData.append("url", url);
const headers = {
Authorization: "Bearer " + token,
...formData.getHeaders(),
};
console.log("Headers: ", headers);
response = await axios.post(`${APIBASE}/file/FileUpload`, formData, {
headers,
});
console.log("file upload response", response);
} catch (err) {
console.log("fileupload error at API", err);
}
You might be able to just use a custom request interceptor and interrogate at the requests that way.
https://axios-http.com/docs/interceptors
You're not able to capture the request on the network level, as this is totally controlled by AWS. Maybe there's a way to do this when running in a VPC, but I don't think so.
You could simply use a tool such as axios debug logger to print out all of the request and response contents (including headers etc) before the request is made/after the response has arrived. This might provide some more information as to where things are going wrong.
As to the cause of the problem, it is difficult to help you there since you haven't shared the error message nor do we know anything about the API you're trying to call.
There are multiple ways to debug
axios debug logger .
AWS cloud watch where you can see all the logs. you can capture the request
and response.
Use postman to call the prod lambda endpoint and verify the response.

How to consume other GraphQL API from Apollo Server implementation?

I have an Apollo Server implementation in which consume other REST APIs, I need to know, how can I use another GraphQL API inside a resolver?
I expect an Apollo Server implementation that works as an API Gateway to consume other APIs (REST or GraphQL)
A GraphQL request is made like most other REST calls, with POST with application/json header. It still hits an endpoint, passes data, and provides a body with the query. You don't need a fancy client like Apollo, but you will need to know the implementation;
A query parameter also needs to be passed as a string.
Here is an example call using axios in javascript:
const data = await axios.post(GRAPHQL_URL, {
query: `
getUser(userID: 1234){
id
name
age
}
`,
}, {
headers: {
'Content-Type': 'application/json'
}
})

Alexa sends SessionEndedRequest on timeout then displays error card to user

While testing my skill, if I do not reply and it times out, Alexa sends a SessionEndedRequest to my Lambda function.
Based on these docs: Handle Requests Sent by Alexa:
Your service cannot send back a response to a SessionEndedRequest.
Therefore, I am not responding to these requests.
But then my app shows a card with this message:
Skill response was marked as failure
(Skill Name)
Request Identifier: amzn1.echo-api.request.xxxxxxxxxxxxxxxxxxxxx
The target Lambda application returned a failure response
So what should we handle this request that does not give a response, and does not result in this error?
I use Node.js in Lambda, but a Python answer is fine too.
Are you sure the error in the card was for SessionEndedRequest only?
Generally, even if you send a response back to Alexa for a SessionEndedRequest, it won't be spoken.
You can handle a SessionEndedRequest like this in ask-nodejs-sdk-v2.
const SessionEndedRequestHandler = {
canHandle(handlerInput) {
console.log("Inside SessionEndedRequestHandler");
return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest';
},
handle(handlerInput) {
console.log(`Session ended with reason: ${JSON.stringify(handlerInput.requestEnvelope)}`);
return handlerInput.responseBuilder.getResponse();
},
};

Resources