Может ли один обработчик обрабатывать сообщения и получать разные URL в торнадо - PullRequest
1 голос
/ 06 ноября 2019

Может ли один обработчик обрабатывать и отправлять, и получать запросы (с немного разными URL) в торнадо?

Например, я хотел бы иметь:

  • /app/user (POST) для созданияновый пользователь, использующий данные из тела запроса
  • /app/user/<user_id> (GET) для возврата пользователя с заданным идентификатором

В настоящее время у меня есть два обработчика GetUser & PostUser и два маршрута.

1 Ответ

1 голос
/ 09 ноября 2019

Да, вы можете сделать это, однако, все быстрые решения, которые я видел до сих пор, всегда имели незначительные неудобства при обработке позиционных аргументов. Взгляните на этот пример:

import tornado.ioloop
import tornado.web

class UserHandler(tornado.web.RequestHandler):
    def get(self, __):
        self.write("Get request")

    def post(self, user_id):
        self.write(f"Post request for user id {user_id}")

app = tornado.web.Application([
    (r"/app/user/?(\d+)?", UserHandler),
])

app.listen(8000)
tornado.ioloop.IOLoop.current().start()

Это создаст правило маршрутизации, которое будет соответствовать обоим шаблонам URL и назначит UserHandler. Группа захвата в URL-адресе является необязательной, поэтому None может передаваться как user_id, когда запрос POST отправляется на /app/user. При таком подходе торнадо всегда будет вызывать метод get с аргументом для второй группы в регулярном выражении. В зависимости от вашего проекта, вы можете захотеть сделать более обширную проверку ошибок (проверьте заданный аргумент, и если это не None, отправьте 405 Method Not Allowed или что-то еще подходящее в вашем случае - возможно, вы хотите разрешить GETдля одного пользователя?)


Это также возможно сделать, добавив два правила с одним и тем же обработчиком:

import tornado.ioloop
import tornado.web

class UserHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Get request")

    def post(self, user_id):
        self.write(f"Post request for user id {user_id}")

app = tornado.web.Application([
    (r"/app/user", UserHandler),
    (r"/app/user/(\d+)", UserHandler),
])
app.listen(8000)
tornado.ioloop.IOLoop.current().start()

Имейте в виду, что оба маршрута будут совпадать для POSTи получает запросы от торнадо, что приводит к таким неудобствам:

  • Если кто-то отправляет запрос POST на /app/user, он выдаст TypeError: post() missing 1 required positional argument: 'user_id'.
  • Если кто-то отправит GETзапрос /app/user/123, он выдаст TypeError: get() takes 1 positional argument but 2 were given

Вы можете обойти это, но я думаю, что первое решение решает это более чистым способом.


Редактировать: Конечно, вы также можете реализовать свой собственный класс Matcher, который позволяет не только различать URL-адреса, но и запрашивать методы, используя подклассы tornado.routing.PathMatches - хотя это немного больше кода. Основным недостатком здесь является то, что для допустимых маршрутов сервер вернет 404 ответа, где он должен быть 405 с (поскольку сам маршрут / «ресурс» действителен, это просто неправильный метод)

import tornado.ioloop
import tornado.web
import tornado.routing

class MethodAndPathMatch(tornado.routing.PathMatches):
    def __init__(self, method, path_pattern):
        super().__init__(path_pattern)
        self.method = method

    def match(self, request):
        if request.method != self.method:
            return None

        return super().match(request)

class UserHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Get request")

    def post(self, user_id):
        self.write(f"Post request for user id {user_id}")

app = tornado.web.Application([
    (MethodAndPathMatch("GET", r"/app/user"), UserHandler),
    (MethodAndPathMatch("POST", r"/app/user/(\d+)"), UserHandler),
])

app.listen(8000)
tornado.ioloop.IOLoop.current().start()

cURL призывает к тестированию:

$ curl -w '\n' -X POST localhost:8000/app/user
$ curl -w '\n' -X POST localhost:8000/app/user/123
$ curl -w '\n' -X GET localhost:8000/app/user
$ curl -w '\n' -X GET localhost:8000/app/user/123
...