How to catch all exceptions with CherryPy? - python

I use CherryPy to run a very simple web server. It is intended to process the GET parameters and, if they are correct, do something with them.
import cherrypy
class MainServer(object):
def index(self, **params):
# do things with correct parameters
if 'a' in params:
print params['a']
index.exposed = True
cherrypy.quickstart(MainServer())
For example,
http://127.0.0.1:8080/abcde:
404 Not Found
The path '/abcde' was not found.
Traceback (most recent call last):
File "C:\Python27\lib\site-packages\cherrypy\_cprequest.py", line 656, in respond
response.body = self.handler()
File "C:\Python27\lib\site-packages\cherrypy\lib\encoding.py", line 188, in __call__
self.body = self.oldhandler(*args, **kwargs)
File "C:\Python27\lib\site-packages\cherrypy\_cperror.py", line 386, in __call__
raise self
NotFound: (404, "The path '/abcde' was not found.")
Powered by CherryPy 3.2.4
I am trying to catch this exception and show a blank page because the clients do not care about it. Specifically, the result would be an empty body, no matter the url or query string that resulted in an exception.
I had a look at documentation on error handling cherrypy._cperror, but I did not find a way to actually use it.
Note: I gave up using CherryPy and found a simple solution using BaseHTTPServer (see my answer below)

Docs somehow seem to miss this section. This is what I found while looking for detailed explanation for custom error handling from the source code.
Custom Error Handling
Anticipated HTTP responses
The 'error_page' config namespace can be used to provide custom HTML output for
expected responses (like 404 Not Found). Supply a filename from which the
output will be read. The contents will be interpolated with the values
%(status)s, %(message)s, %(traceback)s, and %(version)s using plain old Python
string formatting.
_cp_config = {
'error_page.404': os.path.join(localDir, "static/index.html")
}
Beginning in version 3.1, you may also provide a function or other callable as
an error_page entry. It will be passed the same status, message, traceback and
version arguments that are interpolated into templates
def error_page_402(status, message, traceback, version):
return "Error %s - Well, I'm very sorry but you haven't paid!" % status
cherrypy.config.update({'error_page.402': error_page_402})
Also in 3.1, in addition to the numbered error codes, you may also supply
error_page.default to handle all codes which do not have their own error_page
entry.
Unanticipated errors
CherryPy also has a generic error handling mechanism: whenever an unanticipated
error occurs in your code, it will call
Request.error_response to
set the response status, headers, and body. By default, this is the same
output as
HTTPError(500). If you want to provide
some other behavior, you generally replace "request.error_response".
Here is some sample code that shows how to display a custom error message and
send an e-mail containing the error
from cherrypy import _cperror
def handle_error():
cherrypy.response.status = 500
cherrypy.response.body = [
"<html><body>Sorry, an error occurred</body></html>"
]
sendMail('error#domain.com',
'Error in your web app',
_cperror.format_exc())
#cherrypy.config(**{'request.error_response': handle_error})
class Root:
pass
Note that you have to explicitly set
response.body
and not simply return an error message as a result.

Choose what's most suitable for you: Default Methods, Custom Error Handling.
I don't think you should use BaseHTTPServer. If your app is that simple, just get a lightweight framework (e. g. Flask), even though it might be a bit overkill, OR stay low level but still within the WSGI standard and use a WSGI-compliant server.

CherryPy IS catching your exception. That's how it returns a valid page to the browser with the caught exception.
I suggest you read through all the documentation. I realize it isn't the best documentation or organized well, but if you at least skim through it the framework will make more sense. It is a small framework, but does almost everything you'd expect from a application server.
import cherrypy
def show_blank_page_on_error():
"""Instead of showing something useful to developers but
disturbing to clients we will show a blank page.
"""
cherrypy.response.status = 500
cherrypy.response.body = ''
class Root():
"""Root of the application"""
_cp_config = {'request.error_response': show_blank_page_on_error}
#cherrypy.expose
def index(self):
"""Root url handler"""
raise Exception
See this for the example in the documentation on the page mentioned above for further reference.

You can simply use a try/except clause:
try:
cherrypy.quickstart(MainServer())
except: #catches all errors, including basic python errors
print("Error!")
This will catch every single error. But if you want to catch only cherrypy._cperror:
from cherrypy import _cperror
try:
cherrypy.quickstart(MainServer())
except _cperror.CherryPyException: #catches only CherryPy errors.
print("CherryPy error!")
Hope this helps!

import cherrypy
from cherrypy import HTTPError
def handle_an_exception():
cherrypy.response.status = 500
cherrypy.response.headers['content-type'] = 'text/plain;charset=UTF-8'
cherrypy.response.body = b'Internal Server Error'
def handle_a_404(status=None, message=None, version=None, traceback=None):
cherrypy.response.headers['content-type'] = 'text/plain;charset=UTF-8'
return f'Error page for 404'.encode('UTF-8')
def handle_default(status=None, message=None, version=None, traceback=None):
cherrypy.response.headers['content-type'] = 'text/plain;charset=UTF-8'
return f'Default error page: {status}'.encode('UTF-8')
class Root:
"""Root of the application"""
_cp_config = {
# handler for an unhandled exception
'request.error_response': handle_an_exception,
# specific handler for HTTP 404 error
'error_page.404': handle_a_404,
# default handler for any other HTTP error
'error_page.default': handle_default
}
#cherrypy.expose
def index(self):
"""Root url handler"""
raise Exception("an exception")
#cherrypy.expose
def simulate400(self):
raise HTTPError(status=400, message="Bad Things Happened")
cherrypy.quickstart(Root())
Test with:
http://127.0.0.1:8080/
http://127.0.0.1:8080/simulate400
http://127.0.0.1:8080/missing

Though this was the one of the top results when I searched for cherrypy exception handling, accepted answer did not fully answered the question. Following is a working code against cherrypy 14.0.0
# Implement handler method
def exception_handler(status, message, traceback, version)
# Your logic goes here
class MyClass()
# Update configurations
_cp_config = {"error_page.default": exception_handler}
Note the method signature. Without this signature your method will not get invoked.Following are the contents of method parameters,
status : HTTP status and a description
message : Message attached to the exception
traceback : Formatted stack trace
version : Cherrypy version

Maybe you could use a 'before_error_response' handler from cherrypy.tools
#cherrypy.tools.register('before_error_response', priority=90)
def handleexception():
cherrypy.response.status = 500
cherrypy.response.body = ''
And don't forget to enable it:
tools.handleexception.on = True

I gave up using CherryPy and ended up using the follwing code, which solves the issue in a few lines with the standard BaseHTTPServer:
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from urlparse import urlparse, parse_qs
class GetHandler(BaseHTTPRequestHandler):
def do_GET(self):
url = urlparse(self.path)
d = parse_qs(url[4])
if 'c' in d:
print d['c'][0]
self.send_response(200)
self.end_headers()
return
server = HTTPServer(('localhost', 8080), GetHandler)
server.serve_forever()

Related

How to use original_exception of InternalServerError

according to werkzeug docs, InternalServerError has original_exception parameter. That looks cool and I'would like to use it in following way to handle exceptions:
#app.errorhandler(Exception)
def handle_errors(err):
return err if isinstance(err, HTTPException) else InternalServerError(original_exception=err)
and log it in #app.after_request method to pack all additional info from request and response, where standard request logging occurs. Unfortunatelly I'm not able to find out how to use stored original_exception. Where is it stored?
When I check sys.exc_info() in #app.after_request, it's also empty (or (None, None, None)).
One way is probably log exceptions directly in #app.errorhandler, request info should be accessible there and response is not ready (because of error) anyways, but what happens with original_exception after storing in InternalServerError???
Thanks for answer, Michal
I found myself here looking for the same thing - how to access the original_exception, but I eventually gave up and settled on traceback to give me the info I was seeking.
This is the key component, capturing the traceback stack as a list:
formatted_lines = traceback.format_exc().splitlines()
Here's my final code in which I extracted the line number of the error and the specific trigger, then passed them to the page:
from werkzeug.exceptions import InternalServerError
import traceback
#app.errorhandler(InternalServerError)
def handle_500(e):
templateData = {
'errorAt' : 'Internal Server Error',
'errorIs' : 'Check ~/www/gunicorn.error for details'
}
try:
formatted_lines = traceback.format_exc().splitlines()
for error_location in formatted_lines:
if 'intvlm8r.py' in error_location:
templateData['errorAt'] = error_location
templateData['errorIs'] = formatted_lines[-1]
except Exception as e:
app.logger.debug(f'handle_500: Unhandled exception: {e}')
return render_template('500_generic.html', **templateData)

Flask and error handling BadRequest exception

I have simple view like that:
#blueprint.route('/')
def index():
'''CMS splash page!'''
print request.form['no-key-like-that']
return render_template('home/splash.html', title='Welcome')
The goal of that view is to cause BadRequest error on Flask. That happens and I got very generic error page in the process that says:
Bad Request
The browser (or proxy) sent a request that this server could not understand.
However I want to intercept all errors and serve them wrapped in our templates for better look and feel, I do this like that:
#app.errorhandler(TimeoutException)
def handle_timeout(error):
if utils.misc.request_is_xhr(request):
return jsonify({'api_timeout': True}), 503
return redirect(url_for('errors.api_timeout', path=request.path))
However the same cannot be done for BadRequest exception, I tried:
#app.errorhandler(werkzeug.exceptions.BadRequestError)
def handle_bad_request(error):
return render_template(
'errors/bad_request.html',
exception_message=unicode(error),
return_path=request.path), 400
And:
#app.errorhandler(werkzeug.exceptions.BadRequestKeyError)
def handle_bad_request(error):
return render_template(
'errors/bad_request.html',
exception_message=unicode(error),
return_path=request.path), 400
And:
#app.errorhandler(werkzeug.exceptions.HttpError)
def handle_bad_request(error):
return render_template(
'errors/bad_request.html',
exception_message=unicode(error),
return_path=request.path), 400
In each case, instead of my bad_request.html there is raw response I mentioned above.
Bad Request
The browser (or proxy) sent a request that this server could not understand.
What actually works for me:
# NOTE: for some reason we cannot intercpet BadRequestKeyError, didn't
# figure out why. Thus I have added check if given 400 is
# BadRequestKeyError if so display standard api error page. This happens
# often when dev tries to access request.form that does not exist.
#app.errorhandler(400)
def handle_bad_request(error):
if isinstance(error, werkzeug.exceptions.BadRequestKeyError):
if utils.misc.request_is_xhr(request):
return jsonify({
'api_internal_error': True,
'error': unicode(error)
}), 500
return render_template(
'errors/api_internal.html',
exception_message=unicode(error),
return_path=request.path), 500
However as you can see it's far from perfection as error 400 is not necessarily always BadRequestKeyError.
Because exception handling works for any other exception but not BadRequest family it keeps me wondering, is it a bug? Or perhaps I am doing something wrong.

Raising exceptions with django_rest_framework

I have written a view that decrypts a GPG encrypted file and returns it as plain text. This works fine in general. The problem is, if the file is empty or otherwise contains invalid GPG data, gnupg returns an empty result rather than throw an exception.
I need to be able to do something like this inside decrypt_file to check to see if the decryption failed and raise an error:
if data.ok:
return str(data)
else:
raise APIException(data.status)
If I do this, I see the APIException raised in the Django debug output, but it's not translating to a 500 response to the client. Instead the client gets a 200 response with an empty body. I can raise the APIException in my get method and it sends a 500 response, but there I don't have access to the gnupg error message.
Here is a very simplified version of my view:
from rest_framework.views import APIView
from django.http import FileResponse
from django.core import files
from gnupg import GPG
class FileDownload(APIView):
def decrypt_file(self, file):
gpg = GPG()
data = gpg.decrypt(file.read())
return data
def get(self, request, id, format=None):
f = open('/tmp/foo', 'rb')
file = files.File(f)
return FileResponse(self.decrypt_file(file))
I have read the docs on DRF exception handling here, but it doesn't seem to provide a solution to this problem. I am fairly new to Django and python in general, so it's entirely possible I'm missing something obvious. Helpful advice would be appreciated. Thanks.
If you raise an error in python, every function in the trace re-raise it until someone catch it. You can first declare a exception :
from rest_framework.exceptions import APIException
class ServiceUnavailable(APIException):
status_code = 503
default_detail = 'Service temporarily unavailable, try again later.'
Then in your decrypt_file function, raise this exception if decryption is not successful. You can pass an argument to modify the message. Then, in the get method, you should call decrypt_file function and pass your file as an argument. If any things goes wrong, your function raise that exception and then, get method re-raise it until Django Rest Framework exception handler catch it.
EDIT:
In your decrypt function, do something like this:
from rest_framework.response import Response
def decrypt_file(self, file):
... # your codes
if data.ok:
return str(data)
else:
raise ServiceUnavailable
def get(self, request, id, format=None):
f = open('/tmp/foo', 'rb')
result = decrypt_file(f)
f.close()
return Response({'data': result})

How can I get consistent results for my error messages?

I have the following little script running as a standalone app based on CherryPy 3.2.2 (alright, actually it's a condensed version to demonstrate what I'm trying to correct):
import cherrypy
from cherrypy import _cperror
class MyApp:
def __init__(self, **kwargs):
if ('recursive' in kwargs) and kwargs['recursive']:
return
cherrypy.tree.mount(MyApp(recursive=True), '/cgi-bin', {})
def error_page(status, message, traceback, version):
error_template = """\
<html><head><title>%s</title></head>
<body><h1>%s</h1><p>%s</p></body>
</html>"""
return error_template % (status, status, message)
#cherrypy.expose
def script_name(self, **kwargs):
return str(kwargs)
favicon_ico = None
_cp_config = {'error_page.404' : error_page}
if __name__ == '__main__':
cherrypy.quickstart(
MyApp(),
config = {
'global':{
'server.socket_host':'0.0.0.0',
'server.socket_port':8080,
}})
When I fetch a URL underneath /cgi-bin or generally anywhere, I will get the expected:
404 Not Found
The path '/' was not found.
however, when I fetch anything that passes a "PATH_INFO" such as /cgi-bin/script_name/foobar I get:
404 Not Found
Nothing matches the given URI
How can I ensure that in the latter case I get the same error message (only variable being the actual path mentioned in the message) as in the former, while still being able to serve content under the URI /cgi-bin/script_name?

Python -- Send Email When Exception Is Raised?

I have a python class with many methods():
Method1()
Method2()
...........
...........
MethodN()
All methods -- while performing different tasks -- have the same scheme:
do something
do something else
has anything gone wrong?
raise an exception
I want to be able to get an email whenever an exception is raised anywhere in the class.
Is there some easy way to combine this logic into the class, rather than calling SendEmail() before every raise Exception statement? what is the right, pythonic way to deal with such a case? canh a 'generalized' Exception handler be the solution? I'd be glad for any ideas you may have.
like #User said before Python has logging.handlers.SMTPHandler to send logged error message. Use logging module! Overriding exception class to send an email is a bad idea.
Quick example:
import logging
import logging.handlers
smtp_handler = logging.handlers.SMTPHandler(mailhost=("smtp.example.com", 25),
fromaddr="from#example.com",
toaddrs="to#example.com",
subject=u"AppName error!")
logger = logging.getLogger()
logger.addHandler(smtp_handler)
try:
break
except Exception as e:
logger.exception('Unhandled Exception')
Note: Although this is a simple, obvious solution to the problem as stated, the below answer is probably better in most cases.
If the alternative is this:
if problem_test():
SendEmail()
raise Exception
Then why don't you just define a custom raise_email method?
def raise_email(self, e):
SendEmail()
raise e
Python stdlib has dedicated class to do what you want. See logging.handlers.SMTPHandler
Gist Link
The most important trick is here if secure parameter is not passed, the default value is None which raises exception if you are trying to authenticate with TLS/SSL enabled STMP Servers lik Gmail's, Yahoo's, Yandex's, STMP servers.
We passed an empty tuple to trigger smtp.ehlo() to authenticate correctly with SSL.
...
if self.secure is not None:
smtp.ehlo()
smtp.starttls(*self.secure)
smtp.ehlo()
...
import logging
import logging.handlers
__author__ = 'Ahmed Şeref GÜNEYSU'
def foo():
raise Exception("Foo Bar")
def main():
logger = logging.getLogger()
logger.addHandler(logging.handlers.SMTPHandler(
mailhost=("smtp.mail.yahoo.com", 587),
fromaddr="boss#example.com",
toaddrs="me#example.com",
subject="EXCEPTION",
credentials=('smtpuser#example.com', 'MY SECRET PASSWORD'),
secure=()))
try:
foo()
except Exception, e:
logging.exception(e)
if __name__ == '__main__':
main()
Beware the wizard's apprentice!
It would be better to log those errors, then check to see when the last email was sent, and if the timespan is too short, do not send another message because the human being will already be looking at the log file. For many things, one message per day would be enough, but even for system critical things, if you have already had one failure, what could go wrong if you wait two hours to send the next email?
If you send one email per two hour timespan, then the maximum number of emails per day is 12. And if you get a cascading failure (you will!) then it will most likely happen within a couple of hours of the first failure event.
Most large networking companies offer an SLA of 4 hour to fix a failure, measured from the time it first occurs (because cascading failures tend to repeat) until the customer is satisified that it is fixed. If you have a tighter SLA than that, then unless it is some finance industry service, you probably are offering too high of a service level.
But if you do have a 4 hour SLA, then I would make sure that any email sent within 2 - 4 hours of the last email, should use whatever bells and whistles you can to prioritise it, highlight it, etc. For instance use the X-Priority header and put the word URGENT in the subject so that your mail client can display it in large bold red letters.
How about this:
class MyException(Exception):
def __init__(self):
SendEmail()
Something like this, perhaps?
def mailexception(ex):
# Be creative.
print 'Mailing... NOW!'
def pokemontrainer(cls):
class Rye(cls):
def __getattribute__(self, name):
def catcher(func):
def caller(*args, **kwargs):
try:
func(*args, **kwargs)
except Exception, e:
mailexception(e)
raise
return caller
ref = cls.__getattribute__(self, name)
if hasattr(cls, name) and hasattr(getattr(cls, name), '__call__'):
return catcher(ref)
return Rye
#pokemontrainer
class Exceptor(object):
def toss(self, e):
raise e('Incoming salad!')
ex = Exceptor()
ex.toss(ValueError)
By 2019, the easiest and best option seems to be sentry.
You need just two lines of code:
import sentry_sdk
sentry_sdk.init("https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx#sentry.io/xxxxxxx")
and it will send you a detailed email with any raised error.
After this you can further inspect the error on the Sentry website where there is impressive debug info - exceptions traceback, logs, data passed to functions, packages versions etc...
I'd just subclass Exception and send the e-mail in the custom Exception.
You can use an except hook to send an email when an exception is not caught.
see sys.excepthook
You can use python alerting library. It supports email (via mailgun and sendgrid), telegram and slack notifications for sending alerts.
https://github.com/sinarezaei/alerting
Sample code:
from alerting import Alerting
from alerting.clients import AlertingMailGunClient, AlertingSlackClient, AlertingTelegramClient
alerts = Alerting(
clients=[
AlertingMailGunClient(your_mailgun_api_key, your_domain, from_email, target_email),
AlertingSlackClient(your_bot_user_oauth, target_channel),
AlertingTelegramClient(bot_token, chat_id)
]
)
try:
# something
except Exception as ex:
alerting.send_alert(title='some bad error happened', message=str(ex))
I like the answers that use the logging module, but I use the smtp library (smtplib) for this. To send error message, I do something like the following in the exception branch of the try/except block:
import smtplib as smtp
s = smtplib.SMTP('smtp.gmail.com', 587)
s.starttls()
from_user = r"foo#gmail.com"
to_user = r"bar#gmail.com"
password = "xxxxxxxx"
s.login(from_user, password)
subject = "Uh oh"
text = "XYZ error message you blew it!"
message = f"Subject: {subject}\n\n{text}"
s.sendmail(from_user, to_user, message);
This works well, but isn't the most secure option in the world. You actually have to tell google you want to let less secure apps connect (you can change this setting here: https://myaccount.google.com/lesssecureapps).

Categories

Resources