How to catch all exceptions with CherryPy?
Asked Answered
T

8

6

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)

Tress answered 5/12, 2013 at 9:1 Comment(4)
why not use try, except?Todo
@ZagorulkinDmitry: I tried but apparently this does not work (see my update above)Tress
If clients don't care, what's the reason for this? Do you still want the 404 status and just no content? Is your concern the traceback text?Tuggle
@jwalker: the clients sent a query with information but this is a one-way message. They do not expect anything back (not even a code - I could send back a 200 at all times instance). I then use what they sent in my script. And yes - my concern is the traceback text in the response (I want an empty response).Tress
C
5

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('[email protected]',
             '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.

Chill answered 28/2, 2019 at 19:12 Comment(1)
Thanks - I am not using CherryPy for a few years now so I trust you that this is correct :)Tress
D
3

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.

Disrate answered 5/12, 2013 at 17:27 Comment(11)
I checked Google and I did see the solution. I just did not manage to make it work. If you know how to solve the problem the relevant piece of code would be great.Tress
@Tress The StackOverflow community is not about handouts to people who show clear signs of lack of effort and understanding of the subject matter. However, I feel generous. Also, I am a bit perturbed that you decided to modify your question, and answer your own new question. Now, you've accepted an answer and commented about a solution for processing GET parameters and finding an appropriate handler instead of catching exceptions. This is why more then one question at once is also, generally, a bad idea. You also double posted your question in the forums... bad form.Disrate
Thank you for being generous, I appreciate. First: I initially asked a complete question with code and examples of what does not work. How should I have asked? What research is missing? (referring to the "clear lack of effort" -- which you cannot judge, with all due respect, from behind your screen). Second: None of the answers, before you decided to post actual code (which is great, don't get me wrong) answered my problem. So after more research I found a solution which does not use CherryPy - and modified my question + added something which works. (next part follows...)Tress
@Tress If you really did try to get it to work, you should have posted that not working code. Seeing code that people are writing the does not work helps developers of tools to understand what mistakes people are making so they can better understand how to design things people can use. It's generally a good learning experience for everyone as well.Disrate
@Tress That is true, you need to present you effort in a clear way is a better way to say what I meant.Disrate
(followup from previous part). So now that I see working code which exactly solves the problem I will change the best answer (despite the downvotes). This is called "helping each other" which is the core of places like SO.Tress
@Tress Yeah, don't do that. You should simply let the question die out if no one can answer it. We really want to make a good reference for future coders with all of the questions, so we don't have to answer them again. It's confusing if people have to piece things together.Disrate
@Tress I agree, this is all helpful in a way, but I think this type of help and conversation is better suited to the Forums. The direction SO has decided to take since its inception is towards answering very specific coding problems.Disrate
I did post the non-working code. If you start it and then replicate the steps I also added in my code you see the issue as described in my question. Your comment about the fact that the issues are caught and displayed in the browser is useful -- this is exactly what I wanted to avoid (displaying a backtrack)Tress
let us continue this discussion in chatDisrate
I will therefore change the accepted answer -- because the one you gave is an exact answer, leaving mine downvoted one for the sake of those who could stumble upon it one day.Tress
T
3

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.

Tuggle answered 5/12, 2013 at 21:9 Comment(0)
P
2

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!

Proximate answered 5/12, 2013 at 9:24 Comment(10)
Ah of course - I did not think about putting the try, except there. I updated my code but it does not work (when trying http://127.0.0.1:8080/abcde I get a 404 and the Python backtrace indicating a NotFoundexception)Tress
Hm.. I don't think that should happen. Is this your whole functional code?Proximate
I updated my question with actual code which includes your solution. You can see that the error is not caught.Tress
No, there is of course more code (where you see the print params['a']). But the code I put is fully functional, including the lack of exception catching. What more would you need?Tress
An error cannot escape the except clause. Please post your whole code.Proximate
This is my whole code. I removed the parts irrelevant to my question and the script is a correct, working Python script. On that script I would like to catch the exceptions. If it helps let's assume I want a script which prints the value of the parameter a in the URL, discarding any exception (that would be the code I posted)Tress
The raised exception is a NotFound- which is correct as the page indeed does not exist. I want to catch it (as well as all potential others) to send back an empty page.Tress
@Tress Why do you want to send back an empty page? That will be confusing to the users and developers. Just change the server configuration to 'production' instead of 'development' and get the default error page without the stack trace. If you want to implement your own page, see my answer for the documentation. If you want to get better at being a developer and ever want to invent your own abstractions to solve new problems well, understanding other people's well done abstractions is a good place to start.Disrate
@DerekLitz: this is a helper web site, I am the user and the developer (albeit a beginner one). I need it to get some asynchronous information and process it (it is reused by another script).I just tried your solution and the default dispatcher works! ThanksTress
I'm not sure this works because CherryPy handles the exception before it gets to this stage. I've tried it out and that seems to be the case.Responsibility
S
1
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

Su answered 5/1, 2022 at 23:1 Comment(0)
R
0

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
Ruphina answered 2/4, 2018 at 14:27 Comment(0)
B
0

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
Bazar answered 19/9, 2018 at 22:22 Comment(0)
T
-2

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()
Tress answered 5/12, 2013 at 21:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.