I am writing an AWS Lambda Python 3.6 function to use as a Lambda proxy on my API in API Gateway. When writing the Lambda, I am calling a helper function, where if there is an error, raises an exception. API Gateway doesn't like this, as it expects "body," "statusCode," and "headers" in the response from the Lambda, and when an exception is raised in Python, those keys are not provided.
I am wondering if it's possible to raise my custom exception with Lambda proxy in mind, so that I can break out of whatever callee I am in and return from the program fluidly, without having to check for errors from the callee in the caller. Basically, I want to raise an exception, provide my status code, headers, and body, and completely return from the Lambda function with API Gateway recognizing the error.
If you're using Lambda Proxy integration, you are responsible for returning a proper response whether its a success or an exception.
You can do so by catching the exception.
def handler(event, context):
try:
return {
'statusCode': 200,
'body': json.dumps({
'hello': 'world'
})
}
except BadRequestError:
return {
'statusCode': 400,
'body': json.dumps({
'error': 'Bad Request Error'
})
}
except:
return {
'statusCode': 500,
'body': json.dumps({
'error': 'Internal Server Error'
})
}
in node.js you can use:
callback(null, RESPONSE_NO_SUCCESS);
where RESPONSE_NO_SUCCESS is like this:
import json
return {
statusCode: 200,
body: json.dumps({YOUR_ERROR_HERE})
};
This should work as you want you just need to look up how the callback works in python
Related
I use raise Exception (" message ") and it returns this on browser
{"errorMessage": "{"httpStatus": 200, "message": "Exception: {\"httpStatus\": 200, \"message\": \"Exception: [InternalServerError] Project not found\"}"}", "errorType": "Exception", "stackTrace": [the stack trace]}
The stack trace causes security issue
If you are using API gateway in front of lambda. You can set reponse mapping template like below, this will override the response error message and response code as well.
#if($inputRoot.toString().contains('InternalServerError'))
{
"message": "Internal Server Error"
}
#set($context.responseOverride.status = 500)
Alternately you can also catch all the exceptions in the lambda and return whatever you like. However this will not override the status code and you would still get 200 even in case of error.
def handler(event, context):
try:
dosomething(event)
except Exception:
retunrn { "message": "Internal Server error" }
def dosomething(event):
.... You business logic.
I've written a small lambda function and connected it to the API Gateway. I added an error regex to match .*Error.* and return status 400 in the API Gateway. The problem I face is that the regex seems to match only if the lambda failed, as this thread suggests.
My lambda function:
import logging
import boto3
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
if int(event['event_id']) == 1:
return {
'statusCode': 200,
"status": "success"
}
elif int(event['event_id']) == 2:
return {
'statusCode': 400,
"status": "Error"
}
else:
raise Exception("Error")
It looks like case 1 works well, it returns status 200 by default. With event_id=3 it returns status 400 from the API Gateway (with the stack trace in the data, which I would like to avoid), but with event_id=2 it returns status 200 with the Error string in the data.
How can I mark the lambda as failed without throwing an exception?
A failed Lambda means there was some code section in Lambda Function that was failed to be executed. In the current code only the third path has such code section (artificially generated Exception).
It is not possible to execute all your code sections in Lambda and still fail.
If you want to handle API Gateway return status code via program, you'll have to use Lambda Integration. With Lambda integration you may return a response like:
{
"statusCode": "400",
body: json.dumps({ "error": "you messed up!" }),
headers: {
"Content-Type": "application/json",
}
}
and you'll get 400 in API response.
here is the line of code returning the error message from flask api
return jsonify(message='wrong username or password'),400
reading it from here in react js
axios
.post("http://127.0.0.1:5000/authentication/login", body)
.then((res) => {
console.log(res)
})
.catch((error) => {
console.log(error);
});
and this is what i see in the console
{"message":"Request failed with status code 400","name":"Error","stack":"Error: Request failed with status code 400\n at createError (http://localhost:3000/static/js/1.chunk.js:854:15)\n at settle (http://localhost:3000/static/js/1.chunk.js:1075:12)\n at XMLHttpRequest.handleLoad (http://localhost:3000/static/js/1.chunk.js:329:7)","config":{"url":"http://127.0.0.1:5000/auth/login","method":"post","data":"{\"phone\":\"\",\"password\":\"\"}","headers":{"Accept":"application/json, text/plain, */*","Content-Type":"application/json"},"transformRequest":[null],"transformResponse":[null],"timeout":0,"xsrfCookieName":"XSRF-TOKEN","xsrfHeaderName":"X-XSRF-TOKEN","maxContentLength":-1}}
The results doesn't not contain the custom message 'wrong username or password'
have actually gotten the solution, something simple
the error data can be accessed from
console.log(error.response.data);
flask by default returns html page instead of json when error is thrown. to make run return normal json as with 200's responses write this in your flask app:
from werkzeug.exceptions import HTTPException
#app.errorhandler(HTTPException)
def handle_exception(e):
"""Return JSON instead of HTML for HTTP errors."""
print(e)
# start with the correct headers and status code from the error
response = e.get_response()
# replace the body with JSON
response.data = json.dumps({
"code": e.code,
"name": e.name,
"description": e.description,
})
response.content_type = "application/json"
return response
then for example for such return in flask
...
return json.dumps(str(e)), 409
and then you can catch in your js:
...
}).then(function(response) {
...
}).catch((error) => {
console.log(error.response.data); // will log your error as string
})
From what I understand, when using Lambda proxy integration one must return statusCode and body in order to return any meaningful error information to the HTTP client.
My python code is below:
import json
import traceback
def lambda_handler(event, context):
try:
result = business_logic()
return {
'statusCode': 200,
'body': json.dumps(result)
}
except Exception as e:
error = {
"type": e.__class__.__name__,
"message": str(e),
"traceback": traceback.format_exc()
}
return {
'statusCode': 502,
'body': json.dumps(error)
}
My problem is, I'd like to make use of Lambda and Cloudwatch's handy monitoring interface (e.g. being able to trigger off lambda errors) but since lambda in this code is handling all errors and returning a value, I think lambda will have 0% error rate.
Is there an alternative approach which still uses proxy integration and passes error info?
I think if you want to check your metrics, it's better to check API Gateway's cloudwatch than Lambda's.
Unless you really really want Lambda's cloudwatch to monitor errorrs, you might have through non-proxy integration
It's a bit messier, but since the lambda is responsible for the error handling in the code(response codes/etc.) it might have to also be responsible for creating the metrics than.
You can simply generate the metric via the SDK, for example:
def handler(event: Dict[str, Any], context: LambdaContext) -> Dict:
try:
raise Exception( "")
except Exception:
client = boto3.client('cloudwatch')
response = client.put_metric_data(Namespace=‘my_namespace’, MetricData=[
{
'MetricName': f”my_metric",
'Value': 1,
'Timestamp': time.time()
}
])
return ResponseUtil.build_exception_response(logger)
I'm currently writing a REST API for an app I'm working on. The app is written in python using flask. I have the following:
try:
_profile = profile(
name=request.json['name'],
password=profile.get_salted_password('blablabla'),
email=request.json['email'],
created_by=1,
last_updated_by=1
)
except AssertionError:
abort(400)
session = DatabaseEngine.getSession()
session.add(_profile)
try:
session.commit()
except IntegrityError:
abort(400)
The error handler looks like this:
#app.errorhandler(400)
def not_found(error):
return make_response(standard_response(None, 400, 'Bad request'), 400)
I'm using the error 400 to denote both a problem with a sqlalchemy model validator and a unique constraint when writing to the database and in both cases the following error is sent to the client:
{
"data": null,
"error": {
"msg": "Bad request",
"no": 400
},
"success": false
}
Is there a way to still use abort(400) but also set the error somehow so that the error handler can take care of adding additional information for the error object in the result?
I would like it to be more in line with:
{
"data": null,
"error": {
"msg": "(IntegrityError) duplicate key value violates unique constraint profile_email_key",
"no": 400
},
"success": false
}
you can directly put a custom response in abort() function:
abort(make_response("Integrity Error", 400))
Alternatively, you can put it in the error handler function
#app.errorhandler(400)
def not_found(error):
resp = make_response("Integrity Error", 400)
return resp
errorhandler can take an exception type as well:
#app.errorhandler(AssertionError)
def handle_sqlalchemy_assertion_error(err):
return make_response(standard_response(None, 400, err.message), 400)
i know am late to the game, but for anyone who wants another solution, mine is based on the answer by #codegeek.
i was able to accomplish something similar with the following in my ServerResponse.py module:
def duplicate(message=""):
response = make_response()
response.status_code = 409
response.headers = {
"X-Status-Reason" : message or "Duplicate entry"
}
abort(response)
then i can call
ServerResponse.duplicate('Duplicate submission. An article with a similar title already exists.')
this makes it easy in my AngularJS app to check for a response status and display the X-Status-Reason default or customized message