colinjwebb
colinjwebb

Reputation: 4378

Tornado URL query parameters

I've been playing around with Tornado, and I've written some code that doesn't seem very nice.

I'm writing an app to store recipes as an example. These are my handlers:

handlers = [
    (r"/recipes/", RecipeHandler),
    (r"/recipes", RecipeSearchHandler), #so query params can be used to search
]

This lead me to writing this:

class RecipeHandler(RequestHandler):      
    def get(self):
        self.render('recipes/index.html')

class RecipeSearchHandler(RequestHandler):    
    def get(self):
        try:
            name = self.get_argument('name', True)
            self.write(name)
        # will do some searching
        except AssertionError:
            self.write("no params")
            # will probably redirect to /recipes/

Is there a better way to approach these URLs without a try/except? I'd like /recipes and /recipes/ to show the same thing, whereas /recipes?name=something would do a search, and ideally be a different handler.

Upvotes: 46

Views: 81082

Answers (4)

frmdstryr
frmdstryr

Reputation: 21382

If you want to use a more dynamic approach for filtering (instead of a hard coded URL) you can get all the passed URL parameters/arguments using self.request.arguments in the request handler.

class ApiHandler(RequestHandler):
    def get(self, path):
        filters = self.request.arguments
        for k,v in filters.items():
            # Do filtering etc...

See http://www.tornadoweb.org/en/stable/httputil.html#tornado.httputil.HTTPServerRequest.arguments

Upvotes: 6

Aravindh
Aravindh

Reputation: 555

Tornado also has a get_arguments function. It returns a list of arguments with the given name. If not present, it returns an empty list ( [] ). I found it cleaner this way to sanitize your web service inputs instead of try..catch blocks.

Sample:
Assume I have a following URL handler:

(r"/recipe",GetRecipe)

And the request handler:

class GetRecipe(RequestHandler):
    def get(self):
        recipe_id = self.get_arguments("rid")
        if recipe_id == []:
            # Handle me
            self.set_status(400)
            return self.finish("Invalid recipe id")
        self.write({"recipe_id":self.get_argument("rid")})


recipe_id list will also hold the value but I found self.get_argument usage convenient this way.

Now for the results:

curl "http://localhost:8890/recipe" -v

*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8890 (#0)
> GET /recipe HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:8890
> Accept: */*
> 
< HTTP/1.1 400 Bad Request
< Content-Length: 17
< Content-Type: text/html; charset=UTF-8
* Server TornadoServer/1.1.1 is not blacklisted
< Server: TornadoServer/1.1.1
< 
* Connection #0 to host localhost left intact
Invalid recipe id

curl "http://localhost:8890/recipe?rid=230" -v
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8890 (#0)
> GET /recipe?rid=230 HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:8890
> Accept: */*
> 
< HTTP/1.1 200 OK
< Content-Length: 20
< Etag: "d69ecb9086a20160178ade6b13eb0b3959aa13c6"
< Content-Type: text/javascript; charset=UTF-8
* Server TornadoServer/1.1.1 is not blacklisted
< Server: TornadoServer/1.1.1
< 
* Connection #0 to host localhost left intact
{"recipe_id": "230"}

Upvotes: 16

mfussenegger
mfussenegger

Reputation: 3971

There is a better way for GET requests. There is a demo in the tornado source on github here

# url handler
handlers = [(r"/entry/([^/]+)", EntryHandler),]

class EntryHandler(BaseHandler):
    def get(self, slug):
        entry = self.db.get("SELECT * FROM entries WHERE slug = %s", slug)
        if not entry: raise tornado.web.HTTPError(404)
        self.render("entry.html", entry=entry)

Any "text" that matches the regular expression will be passed to the EntryHandler's get method as slug argument. If the url doesn't match any handler, the user will receive a 404 error.

If you wanted to provide another fallback, you could make the parameter optional

(r"/entry/([^/]*)", EntryHandler),

class EntryHandler(BaseHandler):
    def get(self, slug=None):
        pass

Update:

+1 for the link. However does this URL pattern extend to include more parameters if I wanted to search like this... /recipes?ingredient=chicken&style=indian – colinjameswebb

Yes it does.

handlers = [
     (r'/(\d{4})/(\d{2})/(\d{2})/([a-zA-Z\-0-9\.:,_]+)/?', DetailHandler)
]

class DetailHandler(BaseHandler):
    def get(self, year, month, day, slug):
        pass

Upvotes: 55

Casebash
Casebash

Reputation: 119012

get_argument allows you to provide a default value:

details=self.get_argument("details", None, True)

If it is provided, then no exception will occur if the argument isn't provided

Upvotes: 46

Related Questions