19

I'm using the new webapp2 (now the default webapp in 1.6), and I haven't been able to figure out how to make the trailing slash optional in code like this:

webapp.Route('/feed', handler = feed)

I've tried /feed/?, /feed/*, /feed\/* and /feed\/?, all to no avail.

Sudhir Jonathan
  • 16,998
  • 13
  • 66
  • 90

9 Answers9

13

To avoid creating duplicate URL:s to the same page, you should use a RedirectRoute with strict_slash set to True to automatically redirect /feed/ to /feed, like this:

from webapp2_extras.routes import RedirectRoute

route = RedirectRoute('/feed', handler=feed, strict_slash=True)

Read more at http://webapp2.readthedocs.io/en/latest/api/webapp2_extras/routes.html

Sam
  • 5,997
  • 5
  • 46
  • 66
Aneon
  • 816
  • 5
  • 18
  • 1
    Thanks. Do you mean to say that if I have a hundred routes, I need a hundred redirectroutes as well? Seems a terribly messy solution. – Mark Simpson May 20 '12 at 11:14
  • 1
    Hi. No, you replace the Routes with RedirectRoutes, so you don't need both. RedirectRoute does all that Route does, but also adds the "strict_slash" option, as well as some other redirect functionality. Check out the link I posted above for details. – Aneon May 30 '12 at 15:04
  • This is not always a good idea. If your client isn't a web browser (eg, if you're writing an http-accessible api for your website), it may not follow the redirect. – Ponkadoodle Aug 25 '13 at 21:30
7

I don't like the RedirectRoute class because it causes an unnecessary HTTP Redirect.
Based on the documentation for webapp2 Route class, here is a more detailed answer in this webapp2.Route with optional leading part thread.

Short Answer

My route patterns works for the following URLs.

  1. /
  2. /feed
  3. /feed/
  4. /feed/create
  5. /feed/create/
  6. /feed/edit/{entity_id}
SITE_URLS = [
    webapp2.Route(r'/', handler=HomePageHandler, name='route-home'),

    webapp2.Route(r'/feed/<:(create/?)|edit/><entity_id:(\d*)>',
        handler=MyFeedHandler,
        name='route-entity-create-or-edit'),

    webapp2.SimpleRoute(r'/feed/?',
        handler=MyFeedListHandler,
        name='route-entity-list'),
]

Hope it helps :-)

Community
  • 1
  • 1
stun
  • 1,684
  • 3
  • 16
  • 25
4

Here's how I handle these routes.

from webapp2 import Route
from webapp2_extras.routes import PathPrefixRoute
import handlers

urls = [
  Route('/foo<:/?>', handlers.Foo),
  Route('/bars', handlers.BarList),
  PathPrefixRoute('/bar', [
    Route('/', handlers.BarList),
    Route('/<bar_id:\w+><:/?>', handlers.Bar),
  ]),
]
...

It's important to note that your handlers will need to define *args and **kwargs to deal with the potential trailing slash, which gets sent to them as an argument using this method.

class Bar(webapp2.RequestHandler):

  def get(bar_id, *args, **kwargs):
    # Lookup and render this Bar using bar_id.
    ...
erichiggins
  • 586
  • 1
  • 4
  • 9
2

webapp2.Route template is not a regular expressions and your value is being escaped with re.escape. You can use old style rules which provides regular expression templates:

 webapp2.SimpleRoute('^/feed/?$', handler = feed)
Ski
  • 14,197
  • 3
  • 54
  • 64
  • I want to continue using webapps' own syntax without dropping down to regular regular expressions... in that sense the `strict_slash` gives me exactly what I want with the least code changed. – Sudhir Jonathan Jan 19 '12 at 05:39
1

I was looking for a way to make the trailling slash on the root of a PathPrefixRoute block optional.

If you have, say:

from webapp2_extras.routes import RedirectRoute, PathPrefixRoute

from webapp2 import Route

app = webapp2.WSGIApplication([
  PathPrefixRoute('admin', [
      RedirectRoute('/', handler='DashboardHandler', name='admin-dashboard', strict_slash=True),
      RedirectRoute('/sample-page/', handler='SamplePageHandler', name='sample-page', strict_slash=True),
  ]),
])

You will be able to access /admin/, but not /admin.

Since I couldn't find any better solution, I've added a redirect_to_name to an extra route, like:

from webapp2_extras.routes import RedirectRoute, PathPrefixRoute

from webapp2 import Route

app = webapp2.WSGIApplication([
  Route('admin', handler='DashboardHandler', name='admin-dashboard'),
  PathPrefixRoute('admin', [
      RedirectRoute('/', redirect_to_name='admin-dashboard'),
      RedirectRoute('/sample-page/', handler='SamplePageHandler', name='sample-page', strict_slash=True),
  ]),
])

I'm interested in better solutions to this problem.

Should I go for Stun's solution and simply not use RedirectRoute?

Ulf Gjerdingen
  • 1,414
  • 3
  • 16
  • 20
Ivan Chaer
  • 6,980
  • 1
  • 38
  • 48
1

This works for me and is very simple. It uses the template format for URI routing in the webapp2 Route class. Trailing slash in this example is optional with no redirection:

webapp2.Route('/your_url<:/?>', PageHandler)

Everything after the colon between the chevrons is considered to be a regex: <:regex>

HorseloverFat
  • 3,290
  • 5
  • 22
  • 32
1

If you don't want to use redirects (and you probably don't), you can override Route.match():

from webapp2 import Route, _get_route_variables
import urllib
from webob import exc


class SloppyRoute(Route):
    """
    A route with optional trailing slash.
    """
    def __init__(self, *args, **kwargs):
        super(SloppyRoute, self).__init__(*args, **kwargs)

    def match(self, request):
        path = urllib.unquote(request.path)
        match = self.regex.match(path)
        try:
            if not match and not path.endswith('/'):
                match = self.regex.match(path + '/')
        except:
            pass
        if not match or self.schemes and request.scheme not in self.schemes:
            return None

        if self.methods and request.method not in self.methods:
            # This will be caught by the router, so routes with different
            # methods can be tried.
            raise exc.HTTPMethodNotAllowed()

        args, kwargs = _get_route_variables(match, self.defaults.copy())
        return self, args, kwargs
Roman Levin
  • 461
  • 3
  • 18
0

I came up with a sort of hacky way. I define the following class:

class UrlConf(object):

    def __init__(self, *args, **kwargs):
        self.confs = []
        for arg in args:
            if isinstance(arg, webapp2.Route):
                slash_route = webapp2.Route(arg.template + '/', arg.handler)
                self.confs += [arg, slash_route]

    def __iter__(self):
        for route in self.confs:
            yield route

Then I set up my routes like the following:

MIRROR_URLS = list(UrlConf(
    Route('/path/to/stuff', handler=StuffHandler, name='stuff.page'),
    Route('/path/to/more/stuff', handler= MoreStuffHandler, name='more.stuff.page')
))

If you do choose to go this route, you can obviously improve upon it to be more flexible with other types of BaseRoute objects.

-4

I am not familiar with webapp2, but if the first parameter is a regular expression, try:

/feed(/)?
Krystian Cybulski
  • 10,789
  • 12
  • 67
  • 98