39

I've setup a static website on GAE using hints found elsewhere, but can't figure out how to return a 404 error. My app.yaml file looks like

- url: (.*)/
  static_files: static\1/index.html
  upload: static/index.html

- url: /
  static_dir: static

with all the static html/jpg files stored under the static directory. The above works for files that exist, but returns a null length file if they don't. The answer is probably to write a python script to return a 404 error, but how do you set things up to serve the static files that exist but run the script for files that don't?

Here is the log from fetching a non-existent file (nosuch.html) on the development application server:

ERROR    2008-11-25 20:08:34,084 dev_appserver.py] Error encountered reading file "/usr/home/ctuffli/www/tufflinet/static/nosuch.html":
[Errno 2] No such file or directory: '/usr/home/ctuffli/www/tufflinet/static/nosuch.html'
INFO     2008-11-25 20:08:34,088 dev_appserver.py] "GET /nosuch.html HTTP/1.1" 404 -
Aaron Butacov
  • 32,415
  • 8
  • 47
  • 61
ctuffli
  • 3,559
  • 4
  • 31
  • 43

9 Answers9

37

You need to register a catch-all script handler. Append this at the end of your app.yaml:

- url: /.*
  script: main.py

In main.py you will need to put this code:

from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

class NotFoundPageHandler(webapp.RequestHandler):
    def get(self):
        self.error(404)
        self.response.out.write('<Your 404 error html page>')

application = webapp.WSGIApplication([('/.*', NotFoundPageHandler)],
                                     debug=True)

def main():
    run_wsgi_app(application)

if __name__ == "__main__":
    main()

Replace <Your 404 error html page> with something meaningful. Or better use a template, you can read how to do that here.

Please let me know if you have problems setting this up.

Alexander Kojevnikov
  • 17,580
  • 5
  • 49
  • 46
  • It looks like the last line in my original app.yaml matches before the one you suggest adding – ctuffli Nov 25 '08 at 20:28
  • This works, but I prefer Zachary's suggestion to use a static handler for the 404 page (see his answer below). – David Underhill Apr 29 '10 at 19:31
  • 2
    I'm trying to do it this way but if I set the 404 error, the html is not displayed whether I use a template or not. – Richard Nienaber Jul 03 '10 at 15:08
  • does not work for cases when u've different urls like domain.com/admin/.* , domain.com/.* etc – rafek Oct 28 '10 at 11:59
  • To handle that (custom 404 for each section) case, you add a `('/admin/.*', NotFoundPageHandler')` in you app setup for each handler in app.yaml. – Robert Kluin Oct 28 '10 at 16:25
  • 3
    This doesn't seem to work for a static_dir clause. It seems that any missing file in a static_dir returns its own (empty) 404 page instead of dropping through to other handlers. – mgiuca May 30 '11 at 11:05
  • Do handlers in the list take ordered priority? Or matched by specificity? i.e. if you had `[('/.*', NFPH), ('/.login', LoginH)]`, would `LoginH` ever get hit? – OJFord Jul 08 '14 at 23:33
28

google app engine now has Custom Error Responses

so you can now add an error_handlers section to your app.yaml, as in this example:

error_handlers:

- file: default_error.html

- error_code: over_quota
    file: over_quota.html
husayt
  • 14,553
  • 8
  • 53
  • 81
jonmiddleton
  • 1,122
  • 14
  • 16
4

You can create a function to handle your errors for any of the status codes. You're case being 404, define a function like this:

def Handle404(request, response, exception):
     response.out.write("Your error message") 
     response.set_status(404)`

You can pass anything - HTML / plain-text / templates in the response.out.write function. Now, add the following declaration after your app declaration.

app.error_handlers[404] = Handle404

This worked for me.

TeknasVaruas
  • 1,480
  • 3
  • 15
  • 28
4

A significantly simpler way to do this without requiring any CPU cycles is to place this handler at the bottom of your app.yaml

- url: /.*
    static_files: views/404.html
    upload: views/404.html

This then allows you to place a static 404.html file in your views directory. No need for a python handler. Anything that isn't handled in your app.yaml already will hit that.

Zee Spencer
  • 3,460
  • 6
  • 29
  • 31
  • 5
    There isn't any way to return a 404 HTTP response code this way, is there? – Jeremy Logan Jun 05 '09 at 01:45
  • 2
    Yah, all you have to do is add the header to your HTML file. – Zee Spencer Jun 05 '09 at 12:47
  • 1
    Maybe it is brilliant.. but it does not work in case of when u have nadlers/controllers for i.e. domain.com/admin/ and different for domain.com/ and u want to handle domain.com/admin/notexistent and also domain.com/nonexistent 404 error – rafek Oct 28 '10 at 06:54
  • 25
    This is wrong. You can't make static content return a status code other than 200. – Nick Johnson Oct 28 '10 at 09:52
  • This also does not catch `/views/nonexistentpage.htm` when there is a previous directive `- url: /views \\ static_dir: html` – bobobobo Mar 23 '11 at 18:33
  • 1
    To add to @NickJohnson - it's not a limitation, it's a definition: if you're serving static content, it *was* found! – OJFord Jul 13 '15 at 21:34
3

webapp2 provides the error_handlers dictionary that you can use to serve custom error pages. Example below:

def handle_404(request, response, exception):
    logging.warn(str(exception))
    response.set_status(404)
    h = YourAppBaseHandler(request, response)
    h.render_template('notfound')

def handle_500(request, response, exception):
    logging.error(str(exception))
    response.set_status(500)
    h = YourAppBaseHandler(request, response)
    h.render_template('servererror')

app = webapp2.WSGIApplication([
    webapp2.Route('/', MainHandler, name='home')
    ], debug=True)
app.error_handlers[404] = handle_404
app.error_handlers[500] = handle_500

More details are available on webapp2's documentation pages: http://webapp-improved.appspot.com/guide/app.html#error-handlers

Romain
  • 3,718
  • 3
  • 32
  • 48
  • This was very helpful due to the nature of existing webapp2 routes (pages and wildcards) which caused other solutions to not work properly. Thank you. – DMTishler May 12 '17 at 17:44
2

I have reviewed all the above given answers and used the following at the end as the most universal 404 solution:

Add this link at the end of app.yaml

- url: /(.*) 
  script: 404.app

and create 404.py with the following content

import webapp2
from google.appengine.ext.webapp import template

class NotFound(webapp2.RequestHandler):
  def get(self):
    self.error(404)
    self.response.out.write(template.render('404.html', {}))

app = webapp2.WSGIApplication([
    ('/.*', NotFound)
], debug=True)

This will display contents of 404.html file with 404 error code.

The advantage of this solution is simplicity, correctness of bahaviour and flexibility, as it allows to use a static 404.html file as error page content.

I also want to warn against some of the solutions suggested above.

  • Custom Error Responses don not work with 404 error
  • Also don't use static files, as they would return 200 instead of 404 error. This can cause a lot of headache ahead.
husayt
  • 14,553
  • 8
  • 53
  • 81
2

The dev_appserver is already returning 404 responses for anything that doesn't match the mapping, or does match the mapping but doesn't exist. The 404 response itself has no body, but it's still a 404:

$ wget -O - http://127.0.0.1:8080/foo
--2010-10-28 10:54:51--  http://127.0.0.1:8080/foo
Connecting to 127.0.0.1:8080... connected.
HTTP request sent, awaiting response... 404 
2010-10-28 10:54:51 ERROR 404: (no description).

$ wget -O - http://127.0.0.1:8080/foo/
--2010-10-28 10:54:54--  http://127.0.0.1:8080/foo/
Connecting to 127.0.0.1:8080... connected.
HTTP request sent, awaiting response... 404 
2010-10-28 10:54:54 ERROR 404: (no description).

If you want to return a more user-friendly error page, follow jonmiddleton's advice and specify a custom 404 page.

Nick Johnson
  • 100,655
  • 16
  • 128
  • 198
  • It does not work that way I think error_code can be ^(default|over_quota|dos_api_denial|timeout)$ and default one does NOT handle 404 error – rafek Oct 28 '10 at 10:22
  • @rafek Then specify a catchall handler, as Alexander suggests. Either way, the correct response code is already being returned by the default handlers, just without an explanatory message for users. – Nick Johnson Oct 28 '10 at 11:43
  • @Nick: if I have /admin/.* handler and (all) /.* handler and do what Alexander suggests.. then the case with domain.com/admin/stuff-that-doesnt-exist is not handled.. coz /admin/.* handler in app.yaml handles it.. and then I should have catch all handler for every script - that's awfull code duplication. – rafek Oct 28 '10 at 12:03
  • 1
    Then limit your handlers to one, or have a module that contains a 404 handler that you import in each handler, and set as the catchall. – Nick Johnson Oct 28 '10 at 15:44
  • One handler _script_ - as many handlers as you want. Most people use one per 'app'. – Nick Johnson Oct 29 '10 at 07:36
0

My approach is to handle both 404 and permanent redirects in a catch all handler that I put as the last one. This is usefull when I redesign and app and rename/substitute urls:

app = webapp2.WSGIApplication([
    ...
    ...
    ('/.*', ErrorsHandler)
], debug=True)


class ErrorsHandler(webapp2.RequestHandler):
    def get(self):
        p = self.request.path_qs
        if p in ['/index.html', 'resources-that-I-removed']:  
            return self.redirect('/and-substituted-with-this', permanent=True)
        else: 
            self.error(404)
            template = jinja_environment.get_template('404.html')
            context =  {
                'page_title': '404',
            }
            self.response.out.write(template.render(context))
JackNova
  • 3,911
  • 5
  • 31
  • 49
0

I can't comment on jonmiddleton's answer, but the custom error responses is for App engine specific errors by the look of it. I don't see a way to specify a custom 404 page.

Django let's you specify one though.

zahanm
  • 993
  • 8
  • 11