Pull to refresh

Google AppEngine с самого начала: Контроллер

Reading time 4 min
Views 4.2K
Мы двигаемся вперед со скоростью реактивного паравоза, и пока хабралюди читают и осмысливают первую и вторую части статьи, я со скоростью пулемета пишу продолжение. На этот раз речь пойдет о сердце любого веб-приложения —

Контроллер


Какое-то время назад мы уже определили несколько URL в app.yaml — пора разобраться, как заставить приложение правильно «реагировать» на них. Вот так выглядят наши маппинги:

# $Id: app.yaml 4 2010-01-25 12:14:48Z sigizmund $

application: helloworld
version: 1
runtime: python
api_version: 1

handlers:
- url: /(stats|login)
  script: main.py
  login: required

- url: .*
  script: main.py


Как мы видим, определены три типа URL — /stats, /login и «всё остальное». Все три, что характерно, будут обработаны одним и тем же скриптом main.py, однако настройки отличаются — /stats и /login требуют наличия активной пользовательской сессии, в то время как для остальных это не обязательно. Давайте рассмотрим содержимое скрипта main.py:

#!/usr/bin/env python

'''
$Id: main.py 4 2010-01-25 12:14:48Z sigizmund $
'''

import controller

from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from google.appengine.api import users

def main():
  application = webapp.WSGIApplication([('/', controller.DefaultRequestHandler), 
                                ('/stats', controller.StatsRequestController),
                                ('/login', controller.LoginController)],
                                       debug=True)
  util.run_wsgi_app(application)


if __name__ == '__main__':
  main()


Он довольно лаконичен. То есть он предельно лаконичен, но в то же время очень важен — все что он делает, это импортирует содержимое controllers и создает инстанс webapp.WSGIApplication, который сопоставляет URL'ы запросов и соответствующие обработчики. Эти обработчики как правило бывает удобно вынести в отдельный package дабы отделить код служебный от кода реального, который, собственно, и определяет поведение приложения. Рассмотрим эти обработчики по очереди.

DefaultRequestHandler — обработчик по умолчанию


Как видно из кода выше, данный обработчик будет использоваться для всех запросов, которые пришли на «главную страницу» приложения — а именно, "/". Его код размещен ниже:

class DefaultRequestHandler(webapp.RequestHandler): 
    '''
    Handles default requests - checks whether user is logged in; if it is - saves an information about
    his visit in the database.
    '''

    def get(self):
        user = users.get_current_user()

        page = None
        if not user:
            page = view.StartPage(self.request)
        else:
            page = view.WelcomePage(self.request)

        page.render(self.response.out)


Принципиально этот код ничем не отличается от кода MainHandler в первой части, за исключением того, что он не генерирует содержимое страницы самостоятельно, а использует какие-то вспомогательные классы. На данном этапе мы не будем рассматривать, что они делают — для нас достаточно знать, что они генерируют HTML соответствующий нашим требованиям — то есть предлагает залогиниться пользователям, которые еще этого не сделали и приветствует тех, кто с этим успешно справился.

Login Handler — между двух страниц


Напомню вам картинку со структурой нашего приложения:

gaehabr

Как следует из описания страницы /login, пользователь редиректится на нее автоматически и так же автоматически перенаправляется дальше, на станицу /. Что происходит на этой странице? Для этого нам потребуется рассмотреть сразу два класса.

class LoggedInRequestHandler(webapp.RequestHandler):
    def currentVisitor(self):
        user = users.get_current_user()

        # we shouldn't check user, as /login and /stats specifies 
        # login:required in app.yaml

        q = model.Visitor.all()
        q.filter('user = ', user)

        qr = q.fetch(2)

        if len(qr) == 0:
            u = model.Visitor()
        elif len(qr) > 1:
            # something is horribly wrong here, it shouldn't happen
            # but it still could
            logging.error("Duplicating user %s in datastore" % user.nickname())
            raise Exception("Duplicating user %s in datastore" % user.nickname())
        else:
            u = qr[0]

        self.currentVisitor = u
        return u


Возможно, это и не самое лучшее архитектурное решение, однако в иллюстрационных целях мы объявляем класс и наследуем его от webapp.RequestHandler. Как видно, описанный класс не определяет метода get — то есть в качестве независимого разработчика он будет довольно бесполезен. Все, что он делает — предоставляет метод currentVisitor(), который либо достает из Datastore либо создает новый инстанс Visitor и возвращает его. Рассмотрим этот код подробнее.

Описанный подход использует Datastore Query, который начинает со «всех» объектов данного типа, и потом постепенно «уточняет» окончательный набор данных посредством вызовов filter() и ancestor(). Приведенный пример очень прост, однако показывает практически все, что нужно, чтобы извлечь из Datastore необходимую запись; разумеется, в реальных приложениях этот запрос будет наверняка намного сложнее.

Теперь, имея такой полезный класс, мы можем легко описать наш обработчик:

class LoginController(LoggedInRequestHandler): 
    '''
    We use this controller just for handling the login event
    '''
    def get(self):
        u = self.currentVisitor()

        u.hits = u.hits + 1
        u.put()

        self.redirect('/')

Как видно из кода, обработчик получает инстанс Visitor, ассоциированный с текущим пользователем (возможно, создает его), увеличивает количество посещений на единицу и сохраняет инстанс. После этого пользователь без лишних слов перенаправляется на главную страницу. Пользователя проверять уже не надо, поскольку URL /login помечен как требущий обязательного логина (то есть если пользователь попытается зайти на него без аутентификации, он будет автоматически перенаправлен на страницу входа).

StatsRequestController — статистика по запросу


Код последнего обработчика предельно прост. Он прост настолько, что это навевает на мысли, что там явно должно быть что-то еще:

class StatsRequestController(LoggedInRequestHandler): 
    def get(self):
        u = self.currentVisitor()
        page = view.StatsPage(self.request, u)

        page.render(self.response.out)

Действительно, там есть что-то еще. Это что-то называется Представление, использует шаблоны Django и будет описано в следующей части нашей статьи ;-)

Напоминаю, что полный исходный код можно посмотреть в SVN, а я жду ваших вопросов и комментариев!

P.S. придумайте, пожалуйста, как исходники с подсветкой в Хабр выкладывать — а то я буду все картинками заливать!
Tags:
Hubs:
+19
Comments 13
Comments Comments 13

Articles