Мои контакты


четверг, 17 декабря 2015 г.

Работа с кастомными поддоменами в веб-приложении Django

В нашей компании сейчас идет разработка SaaS-системы для вендоров, чье программное обеспечение продается по подписке. Зарегистрировавшись в нашей системе, вендор может создать поддомен, на котором будет отображаться сайт с информацией об их компании и решении, которое они предлагают. Этот сайт можно всячески кастомизировать, но сейчас речь пойдет не об этом. Я хотел бы рассказать о том, как мы реализовали возможность создания кастомных поддоменов и их обработку.

Разработка проекта ведется на фреймворке Django/Python. Работает все на сервере Apache через WSGI. В этой статье мы частично коснемся настройки Apache для обеспечения возможности добавления поддоменов "на лету", но в основной части статья будет про программирование.

Поддомены очень удобны, когда информация о клиентах сервиса может и должна быть представлена на обозрение в интернете. Да и просто URI "antida.service.com" выглядит лучше, чем "service.com/partner/antida" или "service.com/antida". Если поддомены в нашей системе заранее определены и известны, то в таком случае можно просто использовать обычные CNAME- или A-записи при настройке DNS.

А что делать, если мы хотим создавать или управлять поддоменами во время работы нашего веб-приложения? Нужно выполнить три основных шага:
  1. настроить DNS;
  2. настроить веб-сервер;
  3. в код вашего веб-приложения добавить функционал для работы с поддоменами.

Настройка DNS


Чтобы DNS-сервер обрабатывал запросы на любые поддомены вашего сайта, нужно добавить "*" как поддомен. Это может выглядеть примерно так:

*.service.com 14400 IN A 200.100.50.10

Такая запись будет соответствовать и www.service.com, и beta.service.com, и т.д.

Настройка веб-сервера


В конфигурации веб-сервера нужно выполнить похожую по сути операцию — нужно, чтобы веб-сервер понимал, что требуется отвечать на запросы к любому поддомену. В нашем случае был Apache, настройка которого выглядит так:
   
   <VirtualHost 200.100.50.10>
      ServerName service.com
      ServerAlias *.service.com
      ...

Конфигурация для Nginx будет выглядеть примерно так:
   server {
      listen       200.100.50.10:80;
      server_name  service.com *.service.com;
   ...

Этой конфигурации сервера и DNS должно хватить для того, чтобы ваше веб-приложение уже могло отвечать на запросы на любой поддомен. Теперь только остается научить наш софт понимать, на какой домен обращается пользователь.

Программирование


Здесь есть несколько вариантов, как можно реализовать обработку поддоменов. Первый вариант, он же вариант "в лоб", — мы понимаем, как Django обрабатывает входящие запросы и можем встроить свой класс в слой Middleware, который "выдернет" поддомен из URI и запишет его в объект request. Далее этот объект мы уже сможем использовать во время обработки запросов.

Такой подход прост, однако, если у нас сложное приложение, которому нужна детальная настройка URL-адресов, такой вариант не подойдет. Самым оптимальным решением нашей задачи будет подключение пакета django-subdomains, который решает все вопросы.

$ pip install django-subdomains

Нам остается только сделать несколько настроек нашего приложения.

Сначала нужно добавить  subdomains.middleware.SubdomainURLRoutingMiddleware  в список  MIDDLEWARE_CLASSES  в вашем файле настроек проекта Django. Если у вас используется  django.middleware.common.CommonMiddleware , то  subdomains.middleware.SubdomainURLRoutingMiddleware  должен стоять перед ним.

Затем нужно определить  ROOT_URLCONF  и  SUBDOMAIN_URLCONFS  в файле настроек проекта Django.

ROOT_URLCONF = 'service.urls.client'

SUBDOMAIN_URLCONFS = {
   None: 'service.urls.marketing',
   'www': 'service.urls.marketing',

   'app': 'service.urls.app'
}

 ROOT_URLCONF  будет использоваться в случаях, когда запрос на текущий поддомен не определен ни в одном из конфигов в  SUBDOMAIN_URLCONFS . Этот вариант как раз подходит для нашего случая, когда поддоменом может являться название клиента и оно не определено изначально в нашем проекте.

Осталось определить в настройках  SITE_ID  и ваше веб-приложение готово для обработки запросов на любые кастомные поддомены. Пример использования поддомена:

...

def client_profile(request):
   try:
      client = Client.objects.get(name=request.subdomain)
   except Client.DoesNotExist:
      raise Http404

...


Статьи по теме


Handling subdomains in Django, Red Robot Studios
Django "sites" framework documentation
Django-subdomains documentation