I'm using Python and Webtest to test a WSGI application. I found that exceptions raised in the handler code tend to be swallowed by Webtest, which then raises a generic:
AppError: Bad response: 500 Internal Server Error
How do I tell it to raise or print the original error that caused this?
While clj's answer certainly works, you may still want to access the response in your test case. To do this, you can use expect_errors=True (from the webtest documentation) when you make your request to the TestApp, and that way no AppError will be raised. Here is an example where I am expecting a 403 error:
# attempt to access secure page without logging in
response = testapp.get('/secure_page_url', expect_errors=True)
# now you can assert an expected http code,
# and print the response if the code doesn't match
self.assertEqual(403, response.status_int, msg=str(response))
Your WSGI framework and server contains handlers which catch exceptions and performs some action (render a stacktrace in the body, log the backtrace to a logfile, etc). Webtest, by default, does not show the actual response, which might be useful if your framework renders a stacktrace in the body. I use the following extension to Webtest when I need to look at the body of the response:
class BetterTestApp(webtest.TestApp):
"""A testapp that prints the body when status does not match."""
def _check_status(self, status, res):
if status is not None and status != res.status_int:
raise webtest.AppError(
"Bad response: %s (not %s)\n%s", res.status, status, res)
super(BetterTestApp, self)._check_status(status, res)
Getting more control over what happens to the exception depends on what framework and server you are using. For the built in wsgiref module you might be able to override error_output to achieve what you want.
Related
I am trying to create a simple HTTP server that uses the Python HTTPServer which inherits BaseHTTPServer. [https://github.com/python/cpython/blob/main/Lib/http/server.py][1]
There are numerous examples of this approach online and I don't believe I am doing anything unusual.
I am simply importing the class via:
"from http.server import HTTPServer, BaseHTTPRequestHandler"
in my code.
My code overrides the do_GET() method to parse the path variable to determine what page to show.
However, if I start this server and connect to it locally (ex: http://127.0.0.1:50000) the first page loads fine. If I navigate to another page (via my first page links) that too works fine, however, on occasion (and this is somewhat sporadic), there is a delay and the server log shows a Request timed out: timeout('timed out') error. I have tracked this down to the handle_one_request method in the BaseHTTPServer class:
def handle_one_request(self):
"""Handle a single HTTP request.
You normally don't need to override this method; see the class
__doc__ string for information on how to handle specific HTTP
commands such as GET and POST.
"""
try:
self.raw_requestline = self.rfile.readline(65537)
if len(self.raw_requestline) > 65536:
self.requestline = ''
self.request_version = ''
self.command = ''
self.send_error(HTTPStatus.REQUEST_URI_TOO_LONG)
return
if not self.raw_requestline:
self.close_connection = True
return
if not self.parse_request():
# An error code has been sent, just exit
return
mname = 'do_' + self.command ## the name of the method is created
if not hasattr(self, mname): ## checking that we have that method defined
self.send_error(
HTTPStatus.NOT_IMPLEMENTED,
"Unsupported method (%r)" % self.command)
return
method = getattr(self, mname) ## getting that method
method() ## finally calling it
self.wfile.flush() #actually send the response if not already done.
except socket.timeout as e:
# a read or a write timed out. Discard this connection
self.log_error("Request timed out: %r", e)
self.close_connection = True
return
You can see where the exception is thrown in the "except socket.timeout as e:" clause.
I have tried overriding this method by including it in my code but it is not clear what is causing the error so I run into dead ends. I've tried creating very basic HTML pages to see if there was something in the page itself, but even "blank" pages cause the same sporadic issue.
What's odd is that sometimes a page loads instantly, and almost randomly, it will then timeout. Sometimes the same page, sometimes a different page.
I've played with the http.timeout setting, but it makes no difference. I suspect it's some underlying socket issue, but am unable to diagnose it further.
This is on a Mac running Big Sur 11.3.1, with Python version 3.9.4.
Any ideas on what might be causing this timeout, and in particular any suggestions on a resolution. Any pointers would be appreciated.
After further investigation, this particular appears to be an issue with Safari. Running the exact same code and using Firefox does not show the same issue.
I have a flask app that receives bad requests from another software. I would like to manually handle these requests as I can then still call the relevant functions. A sample bad request looks like this GET GET / HTTP/1.1 with the additional GET in front
Hence, I tried to add a custom error handler as shown in the documentation. However, I am unable to make it work and the default error handler is used instead. This is my code:
from flask import Flask
app = Flask(__name__)
#app.route('/')
def hello_world():
return 'Hello, World!'
#app.errorhandler(400)
def handle_bad_request(e):
print('Custom handler active')
return 'bad request!', 400
if __name__ == '__main__':
app.run()'
I used Packetsender to send similar bad requests, and Flask receives them and uses the default error handler as I can see in my console:
127.0.0.1 - - [18/Jan/2021 17:49:28] code 400, message Bad request syntax ('GET GET / HTTP/1.1')
127.0.0.1 - - [18/Jan/2021 17:49:28] "GET GET / HTTP/1.1" HTTPStatus.BAD_REQUEST
So, why is my custom error handler not used? Is this somehow related to the ridiculously incorrect syntax of the requests? Any feedback would be helpful
Error handlers are for unhandled errors in the application code. Sending a bad HTTP request causes an error at the HTTP server layer (the Werkzeug development server in your case, or an HTTP server like Nginx in production). There is no way for Flask or any other WSGI application to handle the errors you're seeing, as the request is invalid, so it doesn't make it to the application layer.
I am trying to catch an exception when using a Response object. My use case is that I want to stream data like described in the official documentation and that there is the possibility of an exception when I get the data that I want to stream:
# test.py
from flask import Flask, Response, jsonify
import werkzeug
app = Flask(__name__)
#app.route("/")
def hello():
def generate():
raise Exception
for i in range(0,10):
yield '1'
try:
return Response(generate())
except Exception:
return jsonify("error") # expected this but instead 500 server error is returned
if __name__ == '__main__':
app.run()
When I then run the server and request the data, I would expect to see "error" but instead an Internal Server Error message from Flask is shown.
FLASK_APP=test.py flask run
curl http://localhost:5000/
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to
complete your request. Either the server is overloaded or there is
an error in the application.</p>
In some way the exception gets raised, as we can see it in stdout but does not get catched by Flask.
I also tried to make sure that the exception does not get catched by setting passthrough_errors option of the werkzeug server. Then the exception does not get catched in Werkzeug e.g. here or here. Unfortunately this did not helped for catching it in the Flask application above:
app.run(passthrough_errors=True)
Output of flask --version:
Flask 1.0.2
Python 3.7.0 (default, Aug 22 2018, 15:22:33)
[Clang 9.1.0 (clang-902.0.39.2)]
UPDATE:
This problem also occurs when I am not using the streaming:
from flask import Flask, Response, jsonify
app = Flask(__name__)
#app.route("/")
def hello():
try:
return Response(2)
except Exception as error:
return jsonify({'error': error})
if __name__ == '__main__':
app.run(passthrough_errors=False)
A TypeError gets raised on Flask side, because the Integer is not iterable but the exception does not get catched.
FLASK_APP=test.py flask run
curl http://localhost:5000/
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to
complete your request. Either the server is overloaded or there is
an error in the application.</p>
In case you come here from a web search because your exceptions in a generator don't get rendered on the client side: That's because the pattern of generators in responses is really HTTP chunked streaming. This technique results in one HTTP response being sent to the client in chunks. The HTTP response code (e.g. 200 for success) is returned first. So if any of the following chunks trigger an exception in your code, Flask has no possibility of returning that to the browser.
Let's change your code and run it again:
#app.route("/")
def hello():
def generate():
raise Exception
yield '1'
try:
resp = Response(generate())
data = resp.data
return resp
except Exception:
return jsonify("error") # expected this but instead 500 server error is returned
This code will correctly return "error", the previous one also "correctly" raises a 500 server error. To understand why, you have to think of the execution flow. In your initial code, return Response(generate()) will return immediately without executing the generator function. The consumer, in this case probably werkzeug, will try to read data or a similar property which will then cause the generator to run. By that time your function is already executed and of course no exception has happened, since you are returning the generator wrapped in a Response. Hope this helps.
Update
The above code only demonstrates the problem and explains why the exception is not caught in the original code. If you have an error in the middle of your stream IMHO the application should give a 500 server error and die. Moving the exception to the generator however will let you catch such errors:
#app.route("/")
def hello():
def generate():
try:
raise Exception('some error')
for i in range(0,10):
yield '1'
except Exception as e:
yield str(e)
return Response(generate())
And you can't use jsonify because the generator is being consumed outside of your application context.
I wrote a general error handler for a flask application like this
def error_handler(error):
if isinstance(error, HTTPException):
description = error.get_description(request.environ)
code = error.code
name = error.name
else:
description = ("We encountered an error "
"while trying to fulfill your request")
code = 500
name = 'Internal Server Error'
templates_to_try = ['errors/error{}.html'.format(code), 'errors/generic_error.html']
return render_template(templates_to_try,
code=code,
name=Markup(name),
description=Markup(description),
error=error)
def init_app(app):
''' Function to register error_handler in app '''
for exception in default_exceptions:
app.register_error_handler(exception, error_handler)
app.register_error_handler(Exception, error_handler)
which I registered with the app as
error_handler.init_app(app)
but in case of a 413 error (Request Entity Too Large) I do not get redirected to the error handler? Instead, I can create an additional error handler like this
#app.errorhandler(413)
def request_entity_too_large(error):
return 'File Too Large', 413
which catches the error fine.
I found that when I raise the RequestEntityTooLarge error artificially within my app, the error handler works fine. So it must have to do with the fact that the error gets raise within the werkzeuge package
RequestBase._load_form_data(self)
File "/usr/local/lib/python2.7/site-packages/werkzeug/wrappers.py", line 385, in _load_form_data
mimetype, content_length, options)
File "/usr/local/lib/python2.7/site-packages/werkzeug/formparser.py", line 197, in parse
raise exceptions.RequestEntityTooLarge()
RequestEntityTooLarge: 413 Request Entity Too Large: The data value transmitted exceeds the capacity limit.
Does anybody know why my first solution cannot capture 413 errors? But my second solution can? How would I need to modify my error_handler to capture the 413 error?
ok I found the solution. Changing the error_handler to
return render_template(templates_to_try,
code=code,
name=Markup(name),
description=Markup(description),
error=error), code
does solve the problem... not sure exactly why though
The problem lies in the flask development server. It is not really a fully fledged server and falls short in that aspect. You don't have to worry about it because in production WSGI server etc it will work as expected with a normal error handler.
To quote flask documentation:
When using the local development server, you may get a connection
reset error instead of a 413 response. You will get the correct status
response when running the app with a production WSGI server.
I'm trying to setup email error logging and I want to test it.
Whats an easy way to trigger a 500 error in Django? This surprisingly has not been discussed here yet.
A test view like this will work:
from django.http import HttpResponse
def my_test_500_view(request):
# Return an "Internal Server Error" 500 response code.
return HttpResponse(status=500)
or use the baked in error class:
from django.http import HttpResponseServerError
def my_test_500_view(request):
# Return an "Internal Server Error" 500 response code.
return HttpResponseServerError()
Raising an appropriate Exception is the most robust way to test this. Returning an HttpResponse of any variety, as in #agconti 's answer, will not allow you to test the behavior of error handling. You'll just see a blank page. Raising an exception triggers both the correct response code and the internal Django behavior you expect with a "real" error.
Response code 500 represents the server not knowing what to do. The easiest way is to write into your test or test-view raise Exception('Make response code 500!').
Most other errors should be raised with exceptions designed to trigger specific response codes. Here's the complete list of Django exceptions that you might raise.
Old question, but I hope this helps someone in the future. The accepted answer doesn't really answer the original question...
You can do this with django rest framework easily by raising ApiException:
from rest_framework.exceptions import APIException
try:
...
except ...:
raise APIException('custom exception message')
This will return a response with status 500 from your view. Pretty useful if you are calling another function from your API and you don't want to manage return values from this function to decide if returning a response with status 500.
You can use it like this:
raise ApiException
And the default response message (at the time of writing this) will be 'A server error occurred.'.
There's a few predefined ApiException subclasses to raise different kinds of errors, check the file rest_framework.exceptions, if you need one that is not in there, you can extend ApiException like this:
from rest_framework.exceptions import APIException
class YourCustomApiExceptionName(APIException):
def __init__(self, status, detail):
self.status_code = status
self.detail = detail
Usage:
raise YourCustomApiExceptionName(100, 'continue')
You can also define custom status and detail but wouldn't make much sense in most cases.