Gascon Pardieu
Gascon Pardieu

Reputation: 231

How to make an asynchronous API call to a Chalice app?

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?

Upvotes: 1

Views: 771

Answers (1)

Gascon Pardieu
Gascon Pardieu

Reputation: 231

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.

Upvotes: 2

Related Questions