Пишем на Python'e снифер используя Google App Engine
Представлял ли ты когда-нибудь, что Google будет ловить лично для тебя сессии пользователей и тем помогать "исследовать", к примеру, чужие почтовые аккаунты? Я тоже нет, пока не появился Google App Engine. Сегодня затестим его создавая супер-крутой снифер.
В моей статье за прошлый месяц с тобою обсудили некоторые сервисы Гугла, но один я сознательно оставил на отдельную статью. Ведь Google App Engine(GAE) одна из перспективнейших масштабируемых "облачных" технологий, которую можно бесплатно заюзать. Это такой себе крутой хостинг, что автоматически увеличивает свою "скорость" в зависимости от количества юзеров. Если сегодня у нас 100 юзеров, то хостинг работает. Если завтра придет еще 1 миллион сверху, то Гугл автоматически подкинет серверов, и все будет также стабильно. Еще в GAE есть куча дополнительных API по работе с базой данных, memcache, почтой, джабером, также огромным плюсом является возможность бесплатного использования (правда с некоторыми мелкими ограничениями). Поэтому нам не затестить такую штуку прям грех ;)
В качестве примера разработки напишем снифер, который будет сохранять данные о зашедших юзерах, а страничка с админкой показывать собранные логи.
Начало операции
Специально для разработки сайтов для GAE создан пакет SDK, который позволяет писать и тестировать приложение у себя на компе. А лишь потом уже заливать на App Engine. Инсталяху этой штуки можно скачать с http://code.google.com/intl/ru/appengine/downloads.html или с диска.
Архитектура нашего снифера следующая:
- админка для просмотра логов, что находится по адресу /admin/ с авторизацией через Гугл-акки;
- логер, который на все запросы после логирования данных о юзере, будет перенаправлять его на какой-то другой сайт.
Роутинг
Главный файл при создании сайтов в Google App Engine - app.yaml, который определяет все настройки приложения. Это текстовый файл с синтаксисом YAML, что содержит вначале общие настройки о названии приложения, его версии, и среде исполнения:
application: spirt40
version: 2
runtime: python
api_version: 1
Особенно полезен пункт "версия". Когда, к примеру, мы будем заливать новый релиз приложения, то стоит увеличить версию. И Гугл автоматически сохранит старые и новые версии файлов, да к тому же он предоставляет удобнейшую систему по переключению на любую версию в любой момент.
Последним пунктом в файле app.yaml будет handlers, который устанавливает как нужно реагировать на определенные URL.
Для нас он такой:
handlers:
- url: /favicon\.ico
upload: /
static_files: favicon.ico
- url: /.*
script: index.py
В первом пункте в параметре url регуляркой обозначаем, что если браузер запрашивает favicon.ico, то отдаем статический файл с корневой папки. Это правило можно б пропустить, но браузеры данную иконку часто запрашивают автоматически и посему будут засорять нам логи, что не совсем красиво.
Во втором пункте настроек, все остальные запросы направляем на index.py.
Хелло Ворлд>
Скрипты на GAE, для работы с WEB, используют CGI, о котором мы уже говорили при создании сплойтпака. Так давай уже перейдем к программингу, к выводу простого текста. Для этого сохраним файл index.py со следующим содержанием:
print 'Content-Type: text/plain'
print ''
print 'Hello, ][akep!'
Можем посмотреть на наше первое приложение в браузере. Для старта тестового сервера запусти скрипт dev_appserver.py с папкой со скриптами в качестве параметра. На Windows будет что-то такое:
C:\>"C:\Program Files\Google\google_appengine\dev_appserver.py" d:\snifer
Теперь перейди в браузере по URL http://localhost:8080/ и можешь убедиться в работоспособности приложения.
webapp
Все с CGI стабильно работает, но это несколько неудобно для сложных приложений. Лучше перейдем на WSGI инфраструктуру реализуемую в модуле webapp. Саму возможность использовать WCGI дает нам функция run_wsgi_app(), что по сути преобразует WCGI к CGI. Посмотрим теперь на каркас кода нашего снифера:
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
class AdminPage(webapp.RequestHandler):
def get(self):
self.response.out.write('Hello Admin, ][akep')
class MainPage(webapp.RequestHandler):
def get(self):
self.response.out.write('Hello, ][akep')
application = webapp.WSGIApplication(
[('/admin/', AdminPage),
('.*', MainPage)],
debug=True)
if __name__ == "__main__":
run_wsgi_app(application)
В конце скрипта мы запускаем webapp.WSGIApplication, в конструктор которого передаем параметры, что определяют связь между URI и классами обработчиками запросов. Если URI соответствует регулярке '/admin/', то используем класс AdminPage. Все остальные запросы направляем на обработчик MainPage.
Обработчики представляют собою класс наследованный от webapp.RequestHandler. В них названия методов соответствуют типам запросов. Мы как видим используем лишь GET. Также нам пригодиться то, что из свойства request класса можно получить входные параметры запроса, а через свойства response будем управлять выводом, к примеру так:
self.response.out.write('Hello, ][akep')
Авторизация
Сейчас нашей админкой могут все кому не лень пользоваться, поэтому давай в класс AdminPage добавим авторизацию. Для этого заюзаем API для использования Гугл-авторизации доступное в модуле users. В нем нам интересны для разработки снифера два метода get_current_user() и create_login_url(). Первый возвращает обьект, который описывает текущего пользователя или None, если юзер не авторизирован. А create_login_url() возвращает URL перейдя на который можно авторизироваться. Теперь добавим в начало AdminPage строки, которыми получим текущего юзера, а если он не авторизирован, то направим его на нужную страницу:
from google.appengine.api import users
user = users.get_current_user()
if not user:
self.redirect(users.create_login_url(self.request.uri))
В create_login_url мы передаем URI на который юзера перешлют после авторизации. Сам редирект как видим вызываем методом redirect обьекта AdminPage.
Но на этом авторизация не завершена, ведь любой у кого есть Гугл-акк сможет смотреть логи. Для избавления от этой беды можно заюзать примерно такой код:
if user != 'your_login':
self.redirect('http://googel.com')
Эти строчки в финальное приложения добавлять не буду, чтобы ты смог беспрепятственно зайти и посмотреть в админку.
BigTable
Google для своих целей по хранению инфы использует масштабируемую базу данных BigTable и дает нам возможность из GAE затестить ее в работе. Правда API настолько высокоуровневое, что врядли получится прочувствовать внутреннюю архитектуру. Само же API для доступа к БД сделано как в Django. И для работы нашего снифера нужно описать класс таблицы БД наследованный от db.Model и в нем описать все поля:
from google.appengine.ext import db
class Sniffer(db.Model):
ip = db.StringProperty()
request_uri = db.StringProperty()
ua = db.StringProperty()
referer = db.StringProperty()
date = db.DateTimeProperty(auto_now_add=True)
Видим, что поля таблицы при описании представляют простые свойства обьекта, которым присваиваем соответствующие классы. Для текстового поля используем db.StringProperty(), для даты db.DateTimeProperty(). Заметим, что мы при описании поля можем передавать дополнительные параметры, как, к примеру, в поле с датой передаем параметр auto_now_add=True, чем заставляем БД автоматически записывать текущую дату в это поле при создании записи.
Теперь когда в файл index.py добавили описание базы, ее можно использовать. Для записи данных, нужно создать на основе этого класса обьект и заполнить поля. Для снифера на страницу MainPage добавим строчки:
snif = Sniffer()
snif.ip = self.request.remote_addr
snif.request_uri = self.request.uri
snif.ua = self.request.user_agent
snif.referer = self.request.referer
snif.put()
Информацию для заполнения полей берем из self.request. Именно этим мы сохраняем IP пользователя, адрес по которому он пришел, юзер-агент и реферер. Вызов метода snif.put() сохраняет данные в базу.
В классе AdminPage нам нужно чтения лога из базы. Делается еще проще, чем запись:
log = Sniffer.all().order("-date")
Тут мы выбираем все элементы, сортируем по дате в обратном порядке и сохраняем результат в переменную log.
Темплейты
Для класса MainPage вывод HTML не нужен, достаточно лишь после получения данных дальше перенаправить пользователя уже знакомым redirect'ом например на страницу Гугла.
Но класс AdminPage требует вывод статистики, поэтому для формирования HTML-вывода заюзаем шаблонизатор. Но сначала сформируем данные для него в виде словаря. Думаю список логов и имя юзера хватит:
template_values = {
'user': user,
'log': log,
}
GAE использует шаблонизатор такой же как и в Django, и находится он в модуле templates. В нем самая главная функция render, которая принимает путь к шаблону и переменные для передачи в шаблон, а результат - сформированный HTML - возвращается простой строкой:
self.response.out.write(
template.render('template/admin.html', template_values)
)
Давай теперь создадим файл template/admin.html и сохраним с таким содержанием:
Hello, {{ user }}
{% for item in log %}
Как видишь сам шаблон - простой HTML, лишь с некоторыми вставками. Строкой {{ user }}, мы выводим переменную, что передали в шаблон. Также можно использовать управляющие конструкции, и они помещаются в {% %}. В нашем случае мы используем for для перебора построчно всех логов - {% for item in log %}{% endfor %}. В самом теле цикле выводим переменную item, что является одной строкой из лога. Для вывода на экран полей из этой строчки используем ".", чтобы получить доступ к свойствам. И к примеру {{ item.ip }} выведет айпишник.
Deploy
Кажется невероятно, но вот так легко и почти незаметно мы маленькими кусочками уже создали снифер на платформе Google App Engine. Осталось лишь задеплоить его, тоесть запустить на сервере. Для этого сначала зарегистрируемся на https://appengine.google.com/, изменим в файле app.yaml параметр application на тот что нам дали при регистрации. И запустим загрузчик: appcfg.py update d:/snifer/.
Готовое приложение снифера находится по адресу http://spirt40.appspot.com/, так что можешь использовать в "научных" целях ;).
Надеюсь этой маленькой статьи хватит, чтобы заинтересовать тебя в углубленном изучении и использовании платформы GAE.
