I'm developing a Flask API that will read data from a cloud database.
I've noticed some strange behaviour in my error handling.
When I make a request that returns an error, and after that make a correct request, the error json will be returned once again in the correct request. Only in the body though, the API status code is correct (and the flask application doesn't throw an error at all, in fact it doesn't even seem to call the error handling module).
If I change the error from for example an invalid queryparam request to a 404 request, the error will change in the json, and the latest error that was returned will persist through all subsequent requests until I rerun the application.
I have an error handling module that I've registered in my app:
from server.errorhandling.errors import InvalidQueryParameterError
from flask import Blueprint, jsonify, request
from server.common.jsonresponse import set_error_jsonresponse
import traceback
handle_errors = Blueprint('errorhandler', __name__)
@handle_errors.app_errorhandler(InvalidQueryParameterError)
def handle_notfound_error(error):
traceback.print_exc()
message = [str(x) for x in error.args]
status_code = 400
response = set_error_jsonresponse(message,error.__class__.__name__)
return jsonify(response), status_code
@handle_errors.app_errorhandler(404)
def handle_notfound_error(error):
traceback.print_exc()
message = [str(x) for x in error.args]
status_code = 404
response = set_error_jsonresponse(message,error.__class__.__name__)
return jsonify(response), status_code
Registering in my app:
self.register_blueprint(errorhandler.handle_errors)
And I have a module for setting the json response, that is used both by the "normal" successful response code, and the errorhandling:
from flask import current_app,request,Blueprint
# - Static template for response json -
response_body = {
"domain": {
"datatype": {
"data": {
}
}
}
}
def set_jsonresponse(query_jobs):
for job in query_jobs:
response_tuple = queryjob_to_response_tuple(job)
response_data = response_tuple
response_obj = DataResponse(response_data)
response_body['domain']['datatype']['data'][get_data_entity_kvm(response_obj)[0]] = get_data_entity_kvm(response_obj)[1]
success_response_body = response_body
return success_response_body
def set_error_jsonresponse(message="An unexpected error has occurred.",errortype="InternalError"):
error_body = {
"error":{
"message":message,
"type":errortype
}
}
response_body['domain']['datatype'].update(error_body)
return response_body
Everything else seems to be working as expected. When I debug the code, the problem appears in the row where I set the response_body. But I don't think how I set the response in the code is the problem itself, I suspect the problem has more to do with some background-storing of response data that I haven't understood yet.
Though the issue is similar, I have tried the answer here: API serving up old responses in flask And it does not work. I've tried both sending the no-cache header values and hardcoding them in my application.
What am I not getting here?
CodePudding user response:
You're defining response_body
as a global variable.
# - Static template for response json -
response_body = {
"domain": {
"datatype": {
"data": {
}
}
}
}
In the set_error_jsonresponse
call, you update this global variable, then return it:
response_body['domain']['datatype'].update(error_body)
Any data stored within this structure is still there on subsequent requests, because a global variable is within the worker's memory.
I'm not sure what get_data_entity_kvm
is, but it looks like in the set_jsonresponse
function you edit the global again, without removing the error_body
which was added previously.
response_body['domain']['datatype']['data'][get_data_entity_kvm(response_obj)[0]] = get_data_entity_kvm(response_obj)[1]
Hence seeing the error message from the last request, even on subsequent successful requests. The status codes are correct because they are defined in the handler functions. When you restart the app the response_body
is set back to the original.
The quick fix for this is not to define response_body
as a global. So you could have a function like:
def get_response_body():
return {
"domain": {
"datatype": {
"data": {
}
}
}
}
Then in each function where it's required, something like:
def set_error_jsonresponse(message="An unexpected error has occurred.",errortype="InternalError"):
response_body = get_response_body()
# do something with that...
I also suggest investigating the Flask deployment docs, and learn about WSGI servers. In a production setup, a WSGI server like gunicorn
may have sevaral syncronous workers. In that configuration the behaviour observed from this app would be even stranger: The same as what you're seeing now, but non-consistent, as subsequent requests may be handled by any of the multiple workers, each with their own copy of the response_body
global. This is because globals are not threadsafe.