1
votes

I just cannot seem to get multiple handlers working. I'm using Google App Engine with Python. The base URL returns "Hello world!", as expected, but I keep getting a 404 error when I try to visit "/girl".

As far as I can tell I'm doing exactly what is specified in the docs:

https://cloud.google.com/appengine/docs/python/config/appconfig

And in this similar question:

YAML file url and script in GAE python

and yet trying all variants I can think of on these models does not resolve my 404 problem. I am a beginner and don't really understand how the app.yaml file works, thus I'm pretty sure that I'm not specifying the handlers correctly. But I don't know how to fix it.

app.yaml

application: multiapp
version: 1
runtime: python27
api_version: 1
threadsafe: yes

handlers:

- url: ./girl/.*
  script: girl.app

- url: .*
  script: main.app

libraries:
- name: webapp2
  version: "2.5.2"

main.py

import webapp2

class MainHandler(webapp2.RequestHandler):
    def get(self):
        self.response.write('Hello world!')

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

girl.py

import webapp2

class MainHandler(webapp2.RequestHandler):
        def get(self):
            self.response.write('Hey girl!')

app = webapp2.WSGIApplication([
    ('/',MainHandler),
    ('/girl/', MainHandler)
], debug=True)

Added: Different organization of the project could definitely avoid having to solve this problem, but I would also like to know why setting these multiple handlers isn't working to begin with.

4

4 Answers

4
votes

I know this is an old thread, and that you sort of got your answer, but I want to give a better explanation of what was happening, as I have just found the answer to your fundamental question: How to split handlers into different files?

The key problem is that you are working with regular expressions, both when you declare your WSGIApplication

app = webapp2.WSGIApplication([
    ('/', MainHandler),
    ('/girl/', GirlHandler)
], debug=True)

and in your yaml file

handlers:

- url: ./girl/.*
  script: girl.app

- url: .*
  script: main.app

The path in your yaml url: regex must be the full path from your webapp domain. So if your domain is localhost:8080 then in your yaml file yo should add the url from localhost:8080 onwards. If you type url: /girl/.* you're asking the browser to match domain+regex: localhost:8080/girl/.*

This means:

  1. match this part exactly: localhost:8080/girl/
  2. match any characters that follow the first part (as that's what ".*" means in a regex)

So the following yaml statement:

handlers:

- url: /girl/.*
  script: girl.app

means that for any url of the form domain/girl/ anything (or nothing) use the the app variable in girl.py file app = webapp2.WSGIApplication(...)

The first implication of this, is there is no purpose in writing a handler for a url that girl.py will never handle, such as when you coded:

girl.py

import webapp2

class MainHandler(webapp2.RequestHandler):
        def get(self):
            self.response.write('Hey girl!')

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

You will never use this main handler, as it will only 'activate' when you browse for domain/, but you said in your yaml file that you only wanted girl.py to handler urls of the form domain/girl/something. It's a contradiction

This means that for your setup to work, in girl.py you should only write handlers for urls that match the regex in your yaml. in this case, any regex that is also accepted by /girl/.*.

As a sidenote, if you wanted your girlHandler to work on either domain/girl and domain/girl/ you should use this regex in your yaml file:

handlers:

- url: ./girl(?:/.*)?
  script: girl.app

- url: .*
  script: main.app

as this makes the /.* part optional

Hope this helps anyone reaching this question, as an insight of how webapp2 calls each handler depending on the url given

1
votes

You should bundle all your routes together in one file, and have different handlers for different routes.

main.py

import webapp2

class MainHandler(webapp2.RequestHandler):
    def get(self):
        self.response.write('Hello world!')

class GirlHandler(webapp2.RequestHandler):
    def get(self):
        self.response.write('Hey Girl!')

app = webapp2.WSGIApplication([
    ('/', MainHandler),
    ('/girl/', GirlHandler)
], debug=True)

then in your app.yaml you only have to link to main.app

application: multiapp
version: 1
runtime: python27
api_version: 1
threadsafe: yes

handlers:
- url: .*
  script: main.app

libraries:
- name: webapp2
  version: "2.5.2"

You don't need multiple webapp2.WSGIApplication instances.

1
votes

You could have central routing while keeping handlers in separate files with something along these lines in main.py:

app = webapp2.WSGIApplication([
    ('/girl/.*', "girl.MainHandler")
    ('/.*', MainHandler),
], debug=True)

Another possibility of running largely independent "apps" while still be able to share some info across them (like authentication) would be to make them modules of the main app. While offering more flexibility in the long run they do have a non-neglijible learning curve (and docs aren't always up2date for them)

0
votes

Organizational issues aside, the reason this wasn't working was that I didn't include a trailing slash.

"/girl" (no slash) returns 404; "/girl/" (with slash) renders the page.

Also the lines in app.yaml should be:

handlers:
- url: /girl/.*
  script: girl.app