Статьи Архив статей

Автор: Мациевский Николай aka sunnybear
Опубликована: 6 марта 2009

Подключаем Google в качестве CDN за полчаса

Наткнувшись на заметку "10 Easy Steps to use Google App Engine as your own CDN", мне тут же захотелось применить ее для Web Optimizator (чтобы сделать самый быстрый сайт еще быстрее). Благо у меня уже был аккаунт на Google Apps Engine (его выдавали в порядке очереди где-то полгода назад, сейчас вроде можно получить свободно). Ниже я постарался свести все описанные в статье шаги к минимуму и дополнить своими размышлениями на тему.

Итак, мы готовы использовать Google для хостинга своих файлов? Тогда вперед!

Все по порядку

  1. Для загрузки файлов на CDN нам нужен будет Python. Ну просто потому, что нам нем работает сам Google Apps. Для корректной работы рекомендуют версию 2.5 (у меня на 3.0 Google SDK не запустился, на 2.5 работал исправно). Загружаем Python отсюда: http://www.python.org/download/, устанавливаем, запоминаем директорию установки (она нам пригодится в дальнейшем).

  2. Загружаем последнюю версию Google Aps SDK.

    Google Aps Engine SDK

    Устанавливаем ее (так как мы выполнили уже п.1 и поставили Python, то проблем у нас не возникнет). Если в ходе установки выбираем нестандартную директорию, то опять-таки запоминаем к ней путь.

  3. Регистрируемся на appengine.google.com (для этого понадобится аккаунт Google). Если в Google Apps Engine аккаунт уже был, то пропускаем этот пункт.

  4. После регистрации заходим и создаем свое приложение. Нужно выбрать уникальный URL (поддомен appspot.com) и название:

    Выбор имени приложения в Google Apps

    Дополнительно нужно будет подтвердить аккаунт через SMS, но ведь мы собираемся там просто CDN развернуть, а не спамить, правда? :)

  5. Теперь (или параллельно ожиданию подтверждения от Google) готовим рабочую директорию с файлами у себя на машине (ведь мы для этого устанавливали сначала Pyhon, а потом SDK). Называем ее произвольным образом, в корне создаем файл app.yaml, в который записываем:

    application: ваш_идентификатор_приложения
    version: 1
    runtime: python
    api_version: 1
    
    handlers:
    - url: /favicon.ico
      static_files: favicon.ico
      upload: favicon.ico
    
    - url: /.*
      script: cacheheaders.py

    В моем случае идентификатор был просто webo, он соответствует адресу webo.appspot.com. version соответствует версии приложения. Очень удобно отлаживать новую версию, в то время как более старая замечательно работает. Переключение между версиями происходит из панели управления Google Apps.

  6. Сюда же в директорию закидываем файлик favicon.ico от рабочего сайта и создаем еще один файл, cacheheaders.py (оригинальная идея этой Python-скрипта):

    import wsgiref.handlers
    from google.appengine.ext import webapp
    
    class MainPage(webapp.RequestHandler):
    
      def output_file(self, path, lastmod):
        import datetime
        try:
          self.response.headers['Last-Modified'] = lastmod.strftime("%a, %d %b %Y %H:%M:%S GMT")
          expires=lastmod+datetime.timedelta(days=365)
          self.response.headers['Expires'] = expires.strftime("%a, %d %b %Y %H:%M:%S GMT")
          fh = open(path, 'r')
          self.response.out.write(fh.read())
          fh.close
          return
        except IOError:
          self.error(404)
          return
    
      def get(self, dir, file, extension):
        if (dir != 'i' and extension != 'jpg' and extension != 'png' and extension != 'gif' and extension != 'css' and extension != 'js'):
          self.error(404)
          return
    
        if extension == "jpg":
          self.response.headers['Content-Type'] = "image/jpeg"
        elif extension == "gif":
          self.response.headers['Content-Type'] = "image/gif"
        elif extension == "png":
          self.response.headers['Content-Type'] = "image/png"
        elif extension == "css":
          self.response.headers['Content-Type'] = "text/css"
        elif extension == "js":
          self.response.headers['Content-Type'] = "application/javascript"
    
        try:
          import os
          import datetime
          path = dir+"/"+file+"."+extension
          info = os.stat(path)
          lastmod = datetime.datetime.fromtimestamp(info[8])
          self.response.headers['Cache-Control'] = 'public, max-age=31536000'
          if self.request.headers.has_key('If-Modified-Since'):
            dt = self.request.headers.get('If-Modified-Since').split(';')[0]
            modsince = datetime.datetime.strptime(dt, "%a, %d %b %Y %H:%M:%S %Z")
            if modsince >= lastmod:
            # The file is older than the cached copy (or exactly the same)
              self.error(304)
              return
            else:
            # The file is newer
              self.output_file(path, lastmod)
          else:
            self.output_file(path, lastmod)
        except:
          self.error(404)
          return
    
    def main():
      application = webapp.WSGIApplication([(r'/(.*)/([^.]*).(.*)', MainPage)], debug=False)
      wsgiref.handlers.CGIHandler().run(application)
    
    if __name__ == "__main__":
      main()

    По поводу этого файла небольшое лирическое отступление. Как выяснилось в ходе исследования, Google Apps по умолчанию не поддерживает Last-Modified / ETag (только Expires, который настраивается простой строкой в app.yamldefault_expiration: "365d"). Чтобы обеспечить поддержку этого необходимого для CDN функционала (для 304-ответов), мы и заводим обработчик cacheheaders.py.

    Конечно, можно обойтись простым кэшированием, но мы же хотим максимально правильный CDN? Сам файл cacheheaders.py просто проверяет, что запрос пришел к папке i для нашего приложения и расширение у файла .png, .gif или .jpg, после этого он отдает либо сам файл, либо 304-ответ (сравнивая заголовок браузера If-Modified-Since с меткой времени файла).

  7. Теперь настроим скрипт для загрузки файлов из нашей директории на Google. Для этого нужно завести в нашей папочке (или еще где-нибудь, это уже не важно) файл upload.bat (если вы собираетесь загружать файлы из-под другой операционной системы нежели Windows, то логику файла придется переписать на соответствующем скриптовом языке). В файле записываем:

    "путь_к_установленному_Python_из_пункта_1" "C:\Program Files\Google\google_appengine\appcfg.py" update "путь_к_рабочей_папочке_с_файлами"

    Если в пункте 2 вы выбрали нестандартную директорию для Google Apps Engine SDK, то ее придется подставить вместо C:\Program Files\Google\google_appengine.

  8. Создаем папку i в рабочей директории, в которую можно загрузить все файлы, которые предполагается отдавать с CDN. В имени файла должна отсутствовать точка (иначе cacheheaders.py будет некорректно обрабатывать расширение для файла — и его придется подправить).

  9. Запускаем наш upload.bat, вводим логин / пароль от Google Apps Engine (только в первый раз), и смотрим за процессом загрузки файлов на CDN.

  10. И вот сейчас уже любой файл по адресу ваш_идентификатор.appspot.com/i/ будет отдаваться через сеть серверов Google по всему миру (для меня это, например, http://webo.appspot.com/i/b.png). Радуемся!

В итоге, у меня в рабочей папке оказалась следующая структура.

  • app.yaml — конфигурационный файл, описывающий приложение.
  • cacheheaders.py — скрипт на Python для выставления кэширующих заголовков.
  • favicon.ico — просто для красоты.
  • i — собственно, сама директория с файлами, которые отдаются через CDN.
  • upload.bat — скрипт для загрузки файлов на Google.

Подводим итоги

Если ваш проект не создает большой статической нагрузки (оценочно не более 250–500 Кб/с), то вы с легкостью можете воспользоваться серверами Google для выдачи своих файлов.

Отмеченные минусы:

  • По умолчанию доступно только большое время кэша, настройка Last-Modified требует дополнительной логики и нагрузки на процессор (может стать критичной при большом количестве мелких файлов).
  • Google CDN не позволяет изменять заголовок Content-Encoding. Это означает, что текстовые файлы можно отдавать либо стандартным образом (без обработчиков, т.е. без Last-Modified), но в сжатом виде. Либо без gzip, но со всеми кэширующими заголовками. На данный момент лично я не вижу смысла отдавать CSS/JS-файлы через CDN при таком раскладе событий. Google нормально обрабатывает эту ситуацию и позволяет установить кэширующие заголовки, отдать текстовый контент файла, а потом уже сам фронтенд накладывает на него сжатие. Очень удобно.
  • Процесс обновления сайта может стать достаточно трудоемким, если его не автоматизировать (но автоматизируется он довольно просто). Также в бесплатной версии присутствует ограничение на число ежедневных обновлений файловой системы.

Во всем остальном — это идеальный выбор. Web Optimizator уже использует эту CDN для выдачи всех фоновых изображений (они обслуживаются с адреса webo.appspot.com/i/). Вообще говоря, можно подключить свои домены как альтернатива хостам на appspot.com — но это уже тема для отдельной дискуссии.

Читать дальше

Все комментарии (habrahabr.ru)