пятница, 29 января 2010 г.

Google App Engine. Обработка запросов с ошибками в webapp фреймворке

Идея переработать блог летала вокруг уже давно, назрело так сказать. Разделив работу на этапы, для начала решил перенести все ресурсы на CDN. Но как всегда захотелось халявы :).

Вспомнил о своем аккаунте на Google App Engine, промелькнула мысль "Можно же построить свой CDN, практически без затрат денег и управлять им так, как душа пожелает". Реализации, которые есть на текущий момент меня немного не устроили и работа закипела.

В качестве основы был выбран webapp фреймворк. Но речь как раз не о моей реализации, а о тонкостях самого фреймворка. Краткий пост хотел посвятить небольшому хаку по обработке запросов, в которых произошла ошибка.

В документации данная тема раскрыта в статье "Перенаправления, заголовки и коды статуса". И приведен следующий пример.
class MyHandler(webapp.RequestHandler):
  def get(self):
    self.response.out.write("Вы попросили что-то сделать.")
    try:
      doSomething()
      self.response.out.write("Это сделано!")

    except Error:
      # Очистить вывод и вернуть сообщение об ошибке.
      self.error(500)
Вроде бы все понятно, если вызвать self.error(500), то все данные которые записаны в ответ будут удалены и будет возвращено сообщение об ошибке.

Но при выполнении сразу можно заметить один маленький нюанс, сервер нам вернет пустую страницу в случае, когда отработает команда self.error(500) или self.error(404), или будет вызван данный метод с любой другой ошибкой. Но как и написано в документации, ответ будет имеет код ошибки 500 или 404, или любой другой, соответственно.

Поведение метода можно посмотреть в исходниках.
google.appengine.ext.webapp.RequestHandler
class Request(webob.Request):  
  ...
  def error(self, code):
    """Clears the response output stream and sets the given HTTP error code.

    Args:
      code: the HTTP status error code (e.g., 501)
    """
    self.response.set_status(code)
    self.response.clear()
  ...
Он всего лишь устанавливает нужный статус и очищает ответ. А хотелось же получить страницу с сообщением об ошибке :(!!!

Просто редиректить на страницу ошибки как-то не интересно. Добавлять после каждого вызова "self.error" рендер страницы ошибки тоже как-то не кошерно. Решил добавить небольшой хак
import os
import logging
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from google.appengine.ext.webapp import RequestHandler

template_path = os.path.join(os.path.dirname(__file__), "templates")

def get_template_path(file_name):    
  return os.path.join(template_path, file_name)

def error_decorator(func):    
    def wrapper(*args, **kwarg):
        handler = args[0]
        status_code = args[1]
        result = func(*args, **kwarg)
        try:
            html = {
                404 : "404.html",
                500 : "500.html"
                }[status_code]
            if not html:
                return;
            handler.response.out.write(template.render(get_template_path(html), template_values))
        except Exception, e:
            logging.error('Raise error in error_decorator. %s' % e)
        return result
    return wrapper

def main():
    RequestHandler.error = error_decorator(RequestHandler.error)
    application = webapp.WSGIApplication(
          [('/', MainHandler),
          ], debug=True)
    util.run_wsgi_app(application)

if __name__ == '__main__':
    main()

При вызове метода "self.error(404)" кроме установки статуса и очистки данных response в ответ будет добавлена страница с сообщением об ошибке. Такой подход имеет ряд преимуществ.

Пользуйтесь! :)

Комментариев нет:

Отправить комментарий