Сегодня решил поразбираться с тем как устроен Google App Engine, а точнее хранилище данных в App Engine и попробовать написать на Python'е небольшое приложение, которое будет получать что-либо от Delphi-приложения и выводить на веб-страницу.
В качестве Delphi-приложения я взял свой Link Compressor и сделал для него небольшую удаленную БД в которой будут храниться все сжатые мной ссылки. При загрузке страницы веб-приложения App Engine будем выводить последние 10 сжатых ссылок с заголовками страниц.
Первое, что нам необходимо - это зарегистрировать новое приложение в своем аккаунте Goole App Engine. Для нового приложения необходимо задать идентификатор, который будет в дальнейшем использоаться в URL и заголовок приложения. После этого Вас попросят ввести номер сотового телефона на который Вам пришлют SMS с кодом активации. SMS отправляют 1 раз за всё время работы с App Engine - подтвердили себя и потом создавайте до 10 бесплатных приложений и спокойно работайте.
Инструментарий
Теперь определимся с инструментами.
Обязательно требуется скачать SDK Google App Engine, который собран в двух вариантах - для Java и Python.
App Engine поддерживает Python 2.5 поэтому скачиваем его с офф.сайта.
Вообще я использую Python 2.7. и пока каих-либо проблем в работе не замечал, а вот с Python 3.х App Engine работать не захотел.
В целом этих инструментов будет достаточно, чтобы начать работу, но после IDE Delphi мне как-то стало тоскливо смотреть на весьма аскетичный "родной" GUI Python'а, поэтому в довесок ко всему я скачал Eclipse и установил для него плагин PyDev - надо сказать работать стало намного удобнее и превычнее. А т.к. последняя версия Eclipse в довесок ко всему без проблем работает с GitHub'ом, то вообще стал доволен как слон :).
В итоге я получил для себя полностью удовлетвряющий моим нынешним потребностям инструмент работы - Eclipse + PyDev. PyDev прекрасно осведомлен о том, что такое App Engine - поэтому выгрузка приложения на сервер осуществляется буквально в два клика, Eclipse помогает не напортачить с написанием приложения - по ходу работы отмечает все ошибки, предупреждает о неиспользуемых переменных, расставляет правильно отсупы и, по требованию, опять же в пару кликов, выгружает исходники в репозиторий на GitHub.
Кстати, было бы неплохо увидеть в будущих версиях RAD Studio "фишку" как в Eclipse по выявлению ошибок и предупреждений. Вот, например, как это выглядит:
Слева в редакторе по ходу написания кода появляются метки предупреждений и ошибок. Так в строке 1 мне говорят, что импортирован неиспользуемый модуль - надобно удалить либо что-нибудь из него использовать, на строке 9 я вообще ошибся - упустил пробелы, которые в Python'е играют роль операторных скобок, на 10 строке всё прекрасно, НО вместо 4 пробелов поставил 2 - не критично, но лучше исправить. И так далее. Конечно эта "фишка" не такая уж и важная, но всё-таки хотелось бы такую штуку в Delphi :). Но это всё мечты-мечты...
Инструменты определили - двигаемся дальше.
Пишем первое веб-приложение App Engine
Как я упоминал, использование инфраструктуры webapp в значиельно сепени облегчает нашу работу по созданию нового приложения, т.к. в том случае отпадает необходимость прописывать весь код приложения и можно сосредоточится на его создании функций приложения, а всю рутину возьмет на себя webapp.
Приложение webapp состоит из трех частей:
* один или несколько классов RequestHandler, обрабатывающих запросы и создающих ответы;
* экземпляр WSGIApplication, направляющий входящие запросы обработчикам в зависимости от URL;
* основная часть, выполняющая WSGIApplication с помощью CGI-адаптера.
Элеменарный пример того, как выглядит простое приложение webapp можно увидеть здесь.
Теперь определим, что будет делать наше приложение.
Приложение должно работать следующим образом:
1. Клиент (Delphi-приложение) отправляет POST-запрос на определенный URL. В запросе содержаться данные о ссылке - URL и анкор
2. Наше приложение берет данные клиента и заносит из в хранилище
3. При заходе на главную страницу веб-приложения пользователю показывается последние 10 ссылок
Все просто. Сразу оговорюсь - веб-приложение никаким образом не проверяет коректность данных клиента. Можно было бы написать соответствующие обработчики, но я пока этим ханиматься не буду.
Пойдем по порядку. Вначале импорируем в наше приложение следующие модули и объекты:
from google.appengine.ext import webapp from google.appengine.ext.webapp.util import run_wsgi_app from google.appengine.ext import db import os from google.appengine.ext.webapp import template
Теперь пишем первый класс, который будем использовать для записи данных в хранилище, назовем его DataClass:
class DataClass(db.Model): link_url = db.StringProperty(multiline=True) #URL ссылки link_title = db.StringProperty(multiline=True) #Анкор date = db.DateProperty(auto_now_add=True) #Дата добавления в БД
Так, всего в 4 строки мы определили как будут выглядеть наши объекты в хранилище. Здесь:
db.StringProperty - строка длиной до 500 байт, допускающая наличие символов перевода строки.
db.DateProperty(auto_now_add=True) - дата. Значение полю присваиваеся автоматически при добавлении объекта в хранилище, а также автоматически и обновляется вместе с изменением объекта.
Следующий шаг - работа с хранилищем. Получим последние 10 записей из хранилища и выведем их на сранице. Готовый класс выглядит следующим образом:
class MainPage(webapp.RequestHandler): def get(self): lclink_query = DataClass.all().order('-date') lclinks = lclink_query.fetch(10) template_values = {'urls': lclinks,} path = os.path.join(os.path.dirname(__file__), 'index.html') self.response.out.write(template.render(path, template_values))
В двух слова, чо здесь делается.
lclink_query = DataClass.all().order('-date')
Получили объект Query со всеми данными, которые соответствуют модели DataClass и отсортированные по дате добавления.
lclinks = lclink_query.fetch(10)
Выполнили запрос, получив в lclinks, говоря привычными терминами, первый 10 записей.
template_values = {'urls': lclinks,}
Создали словарь значений для шаблон страницы результотов
path = os.path.join(os.path.dirname(__file__), 'index.html')
Определили путь до шаблона страницы. В данном случае шаблон находится в корне.
self.response.out.write(template.render(path, template_values))
"Нарисовали" пользователю страничку с результатами.
Заметьте - при работе с данными из хранилища я нигде в явном виде не указывал текст запроса, нигде не выполнял коннект к БД и прочие привычные операции. Все эти операции выполняются в классах и объектах, которые разработаны для нас в Google. Даже при выводе результатов пользователю нам не потребовалось писать слишком много - 1 строка. А шаблон с HTML-кодом лежит отдельно. Кстати шаблон тоже выглядит достаточно просто:
<html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Ссылки LinkCompressor</title>
<body>
<h1 align="center">Последние ссылки</h1>
<ol>
{% for link in urls %}
<li>{{ link.date }}: <a href="{{ link.link_url }}" target="_blank">{{ link.link_title }}</a></li>
{% endfor %}
</ol>
</body>
</html>Обратите внимание - внутри шаблона присутсвует цикл for..in, которые перебирает все значения из словаря (в нашем случае 10 штук) и "рисует" список ссылок.
И наконец, переходим к третьей части нашего приложения - получение данных от клиента и запись их в хранилище. Как уже понятно из представленных выше фрагментов кода - этот также будет достаточно прост и лаконичен. Выглядит он следующим образом:
class AddLinks(webapp.RequestHandler): def post(self): SingleLink = DataClass() SingleLink.link_url = self.request.get('lnk_url') SingleLink.link_title =self.request.get('lnk_text') SingleLink.put() self.redirect('/')
Здесь мы обрабатываем только POST-запросы. При желании можно тут же написать и обработчик GET-ов. Итак, что мы тут наделали.
SingleLink.link_url = self.request.get('lnk_url')
Получили из запроса пользователя данные по URL'у. На следующей строке - также само получили и анкор. А дальше:
SingleLink.put() self.redirect('/')
Записали данные в хранилище и перенапавили клиента на главную страницу. Опять же никакой суеты с текстами запросов и пр. Все достаточно внятно и ясно даже для человека впервые увидившего код. Кстати, если очень уж хочется написать свой запрос к хранилищу, то нет проблем - можно воспользоваться GQL - язык запросов к хранилищу данных Google, очень напоминающий MySQL.
Теперь нам остается мелочь - определить URL'ы и сопоставить с ними обработчики нашего приложения. Делается это так:
application = webapp.WSGIApplication( [('/', MainPage), ('/sign', AddLinks)], debug=True) def main(): run_wsgi_app(application) if __name__ == "__main__": main()
Здесь мы сопоставили с двумя URL'ами приложения два обработчика. Так, при загрузке главной страницы приложения сработает обработчик MainPage, который выведет пользователю 10 ссылок, а при GET-запросе на /sign ничего не произойдет, так как в AddLinks мы не определяли ничего для GET, а вот POST-запрос инициирует запись данных в хранилище. При этом т.к. мы не использовали никаких проверок данных запроса, то в случае отправки на URL даже пустого докумена запись всё равно произойдет - в хранилище попадет объект DataClass с одним определенным значением Date - т.к. оно заполняется автоматически.
Теперь осталась всего одна небольшая, но очень важная для App Engine деталь - создать файл app.yaml - файл конфигурации, который помимо прочей служебной информации определяет также какие сценарии обработчиков для каких URL'ов использовать. Создаем пустой файл и записываем туда следующие строки:
application: myapp version: 1 runtime: python api_version: 1 handlers: - url: /.* script: script.py
Здесь стоит обратить внимание на следующие детали.
Во-первых application в файле должен совпадать с названием Вашего зарегистрированного приложения. Т.е. в данном случае я написал файл конфигурации для веб-приложения доступ к которому осуществляется по URL http://myapp.appspot.com/
Во-вторых, version. С помощью этой опции можно создавать различные версии своего приложения и, в случае необходимости, делать откат на более старые версии.
И, наконец, в представленном файле я "объяснил" серверу, что вне зависимости от того какой URL будет набран всё будет обрабатываться с помощью script.py, в котором и определены все наши классы.
Теперь закачиваем приложение на сервер. Для этого необходимо в SDK Google App Engine нажать кнопку Deploy и ввести логин и пароль к аккаунту. Я же в Eclipse выбираю опцию PyDev : Google App Engine - Upload.
На этом работу с Python можно считать законченной. Переходим в Delphi.
Delphi-приложение для отправки данных
Собственно, наш мини-клиент будет выполнять всего одну функцию - выполнять POST-запрос к URL приложения Google App Engine.
Что у нас есть на входе?
1. Целевой URL, который, судя по данным app.yaml и webapp.WSGIApplication, выглядит как http://myapp.appspot.com/sign
2. Есть необходимые данные для составления запроса, а именно запрос должен содержать два параметра: lnk_url и lnk_text.
Этого нам будет вполне достаточно для работы. Открываем Delphi, создаем новое приложение и укладываем на форму компоненты как показано на рисунке:

В Memo1 будем выводить, в случае успешного запроса, содержание главной страницы.
Теперь пишем обработчик кнопки. Для работы с HTTP я, как уже стало обычным, использую библиотеку Synapse:
procedure TForm6.Button1Click(Sender: TObject); const cParams = 'lnk_url=%s&lnk_text=%s'; var ParamStr: AnsiString; begin ParamStr:=Format(cParams,[EncodeURLElement(AnsiToUTF8(Edit1.Text)), EncodeURLElement(AnsiToUTF8(Edit2.Text))]); with THTTPSend.Create do begin Document.Write(Pointer(ParamStr)^, Length(ParamStr)*SizeOf(AnsiChar)); Document.Seek(0,soBeginning); MimeType:='application/x-www-form-urlencoded'; if HTTPMethod('POST','http://myapp.appspot.com/sign') then begin HTTPMethod('GET','http://myapp.appspot.com/'); Memo1.Lines.LoadFromStream(Document); end else Memo1.Lines.Add(ResultString); end; end;
По большому счёты, если Вы хотя бы 1 раз в жизни работали с веб-формами в Delphi, то этот код для Вас должен выглядеть абсолютно обычно - мы просто отправили данные формы на сервер. Ничего сверхъестественного.
Теперь можно зайти на главную страницу приложения и посмотреть как выглядит наше импровизированное веб-приложение.
Конечно до серъезного этому приложению ещё далеко - это просто небольшая демонстрация того как можно организовать работы с данными.
Хотите ноутбук купить, но не знаете какую модель выбрать? Загляните на NovaTek.com.ua. Там Вы можете найти информацию по ноутбукам, фотоаппаратам, нетбукам и прочей техники. Ну, а жителям украинского региона уж грех туда не заглянуть, а тем более купить там ноутбук ;)
------------------------------------------------
| Делись! | Загружай! | Плюсуй! |
| | |









29 Авг 2010 в 2:06 пп
Понравилась статья. Интересно.
30 Авг 2010 в 8:44 пп
Очень интересно, продолжайте. :)
30 Авг 2010 в 10:40 пп
Продолжу :)
14 Сен 2010 в 4:33 дп
может не туда смотрю, а какой адрес страницы с этими 10 первыми записями?
и еще, сам нулевый в питоне и в яве. Что лучше посоветуете для «освоения» Google App Engine?
14 Сен 2010 в 5:57 дп
Страница http://linkcompressor.appspot.com — нижняя часть отведена под ссылки. В плане совета чего-либо я, видимо, советчик плохой, т.к. уже говорил, что Ява меня очень напрягает :) Мне больше понравился Python — как-то лекго пошло обучение после Delphi. А по поводу App Engine могу сказать, что информации, что по Яве, что по питону в доках — валом.