<?xml version="1.0" encoding="UTF-8"?><!-- generator="wordpress.com" -->
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	>

<channel>
	<title>google-app-engine &amp;laquo; WordPress.com Tag Feed</title>
	<link>http://wordpress.com/tag/google-app-engine/</link>
	<description>Feed of posts on WordPress.com tagged "google-app-engine"</description>
	<pubDate>Sat, 30 Aug 2008 10:49:35 +0000</pubDate>

	<generator>http://wordpress.com/tags/</generator>
	<language>en</language>

<item>
<title><![CDATA[Все рецепты для App Engine в одном месте]]></title>
<link>http://techworkru.wordpress.com/?p=330</link>
<pubDate>Sat, 30 Aug 2008 10:19:02 +0000</pubDate>
<dc:creator>techworkru</dc:creator>
<guid>http://techworkru.wordpress.com/?p=330</guid>
<description><![CDATA[Есть интересный фрагмент кода, нетривиально решающий ]]></description>
<content:encoded><![CDATA[<p>Есть интересный фрагмент кода, нетривиально решающий какую-нибудь задачу, и хотите поделиться им с сообществом? Совсем пару дней назад для этих целей Google открыла сайт <a href="http://appengine-cookbook.appspot.com/" target="_blank">Google App Engine Cookbook</a>, предназначенный для обмена кодом.</p>
<p>Вкратце, App Engine Cookbook (или Поваренная книга App Engine) позволяет публиковать, оценивать и комментировать рецепты, созданные разработчиками платформы по всему миру. Кроме того, есть возможность подписаться на новостную ленту и читать новые рецепты прямо через программу чтения новостей.</p>
<p>Мы также в своем блоге будем публиковать наиболее интересные из этих рецептов. Может быть уже у вас есть что-то, чем бы хотелось поделиться?</p>
]]></content:encoded>
</item>
<item>
<title><![CDATA[XML сервисы, часть вторая]]></title>
<link>http://techworkru.wordpress.com/?p=323</link>
<pubDate>Thu, 28 Aug 2008 13:03:10 +0000</pubDate>
<dc:creator>techworkru</dc:creator>
<guid>http://techworkru.wordpress.com/?p=323</guid>
<description><![CDATA[Недавно мы рассматривали создание приложения на платф]]></description>
<content:encoded><![CDATA[<p>Недавно <a href="http://techwork.ru/2008/08/09/xml-services-for-ip-phones/">мы рассматривали</a> создание приложения на платформе App Engine, которое позволяет IP телефонам Cisco получать информацию о прогнозе погоды, новостях и курсе валют.</p>
<p>К сожалению, пользователи, которые же в первые дни появились у этого сервиса, обнаружили в нем два существенных недостатка:</p>
<ul>
<li>Мы отображаем только погоду только по Москве, хотя сервис у нас глобальный</li>
<li>Существуют проблемы на разных версиях прошивок для телефонов, которые ожидают данные на русском языке в кодировке cp-1251.</li>
</ul>
<p>Первый недочет решено было исправить, добавив опциональный параметр <em>city</em> к URL сервиса, равный коду города на сайте <a href="http://gismeteo.ru" target="_blank">ГисМетео</a>, на котором мы берем данные погоды. Таким образом строка для телефонов Питера выглядит как <a href="http://xmlphones.appspot.com/?city=26063" target="_blank">http://xmlphones.appspot.com/?city=26063</a>. Второй аналогично будет называться <em>encode</em> и содержать при необходимости задание кодировки windows-1251.</p>
<p>Перекодировка в Python делается элегантно. Допустим, у нас через URLFetch получены данные в UTF-8, а вывод требуется в windows-1251:</p>
<pre>resp = unicode(result.content, 'utf-8')
resp = resp.encode("cp1251")</pre>
<p>Ну и наоборот:</p>
<pre>resp = unicode(result.content, 'cp1251')</pre>
<p>В первом случае мы из байтового массива конструируем объект unicode, который затем кодируем с указанием кодека cp1251. Во втором случае одним действием из байтового массива конструируем также объект unicode с заданием кодека cp1251, но далее просто отдаем его в таком виде платформе App Engine (подразумевается что весь вывод по умолчанию в кодировке UTF-8).</p>
<p>Все кажется замечательным, но тут опять обнаруживается криворукость писателей прошивок некоторых моделей IP телефонов, заключающаяся в том, что телефоны сами добавляют к параметрам запросов дополнительные переменные вида <strong>?locale=English_United_States&#38;name=SEP001B541415EE</strong>, что приводит к проблемам обработке меню сами же телефонами.</p>
<p>Проведенный небольшой эксперимент показал, что если, например, параметры задавать с помощью многоуровневых URL, то все работает отлично. Допустим, кодировку windows-1251 мы будем задавать как <a href="http://xmlphones.appspot.com/windows-1251/">http://xmlphones.appspot.com/windows-1251/</a> (подразумевая, по умолчанию корневой адрес аналогичный <a href="http://xmlphones.appspot.com/utf-8/">http://xmlphones.appspot.com/utf-8/</a>), а город как <a href="http://xmlphones.appspot.com/utf-8/city/26063">http://xmlphones.appspot.com/utf-8/city/26063</a> (и опять это Питер).</p>
<p>Выкидываем из кода несколько обработчиков, привязанных к URL, и заменяем их одним Dispatcher, который будет решать куда направить тот или иной запрос. Так как нашим сервисом уже пользуются люди, старые параметры <em>?encode</em> и <em>?city</em> также оставляем для совместимости, но делаем их второстепенными по сравнению со схемой параметров, заданных через URL.</p>
<p>Вот что получилось в итоге:</p>
<pre># coding=UTF-8
# -*- coding: utf-8 -*-

import wsgiref.handlers
from google.appengine.ext import webapp
from google.appengine.api import urlfetch
import os
from google.appengine.ext.webapp import template
from xml.dom import minidom
import re
import time

WEATHER_URL = 'http://informer.gismeteo.ru/rss/%s.xml'

def get_city(self):
    city = self.request.get("city")
    if city == '':
        # Москва
        city = '27612'
    ccode = re.search('/.+/meteo/(.+)', self.request.path)
    if ccode != None:
        return ccode.group(1)
    else:
        return city

def get_encode_templ(self):
    encode = self.request.get("encode")
    if encode == 'windows-1251':
        return encode
    else:
        return 'utf-8'

def render_template(self, templ, template_values={}, encode='utf-8'):
    path = os.path.join(os.path.dirname(__file__), 'templates/'+templ)
    resp = template.render(path, template_values)
    if encode == 'windows-1251':
        # Перекодируем из UTF-8 в CP1251
        resp = unicode(resp, 'utf-8')
        resp = resp.encode("cp1251")
    self.response.out.write(resp)

def is_ipphone(self):
    if re.match('Allegro', self.request.headers.get('User-Agent', '')) == None:
        return False
    else:
        return True      

def parse_rss_data(content):
    result = ''
    dom = minidom.parseString(content)
    for item in dom.getElementsByTagName('item'):
        result = result + item.getElementsByTagName('title')[0].firstChild.nodeValue + '\n' + item.getElementsByTagName('description')[0].firstChild.nodeValue + '\n\n'
    return result

def rss_to_text(self, url):
    result = urlfetch.fetch(url)
    if result.status_code == 200:
        resp = parse_rss_data(result.content)
        charset = get_uri_encode(self)
        if charset == 'windows-1251':
            # Перекодируем из UTF-8 в CP1251
            resp = resp.encode("cp1251")
        self.response.headers['Content-type']='text/plain; charset='+charset
        self.response.out.write(resp)
    else:
        self.redirect('/error.xml')

def cbrf(self):
    charset = get_uri_encode(self)
    self.response.headers['Content-type']='text/plain; charset='+charset
    result = urlfetch.fetch('http://www.cbr.ru/currency_base/D_print.asp?date_req='+time.strftime('%d.%m.%Y', time.localtime()))
    if result.status_code == 200:
        content = unicode(result.content, 'windows-1251')
        data=re.split('&#60;td align="right" &#62;036&#60;/td&#62;', content)[1]
        for block in re.split('&#60;tr bgcolor="#ffffff"&#62;', data):
            block = re.sub('\r\n', '', block)
            cells = re.search('&#60;td align="left" &#62;&#38;nbsp;&#38;nbsp;(.*?)&#60;/td&#62;&#60;td align="right" &#62;(.*?)&#60;/td&#62;&#60;td&#62;&#38;nbsp;&#38;nbsp;(.*?)&#60;/td&#62;&#60;td align="right"&#62;(.*?)&#60;/td&#62;', block)
            if cells != None:
                resp = cells.group(2)+' '+cells.group(3)+' = '+cells.group(4)+'\n'
                if charset == 'windows-1251':
                    # Перекодируем из UTF-8 в CP1251
                    resp = resp.encode("cp1251")
                self.response.out.write(resp)
    else:
        self.redirect('/error.xml')

def error(self):
    self.response.headers['Content-type]']='text/plain'
    self.response.out.write('Произошла ошибка при работе сервиса')

def gismeteo(self):
    city = get_city(self)
    rss_to_text(self, WEATHER_URL % city)

def yandex_news(self):
    rss_to_text(self, 'http://news.yandex.ru/Russia/index.rss')

def get_uri_encode(self, default = 'utf-8'):
    urcode = re.search('/windows-1251/.*', self.request.url)
    if (urcode != None):
        return 'windows-1251'
    else:
        return default

def main_page(self):
    if is_ipphone(self):
        citytmpl = get_city(self)
        encode = get_encode_templ(self)
        urlcity = re.search('/(.+)/city/(.+)', self.request.path)
        if (urlcity != None):
            citytmpl = urlcity.group(2)
        encode = get_uri_encode(self, encode)
        self.response.headers['Content-type']='text/xml; charset='+encode
        render_template(self, 'main.xml', { 'city': citytmpl, 'encode': encode }, encode=encode)
    else:
        self.response.headers['Content-type']='text/html; charset=utf-8'
        render_template(self, 'main.html')    

class Dispatcher(webapp.RequestHandler):
    def get(self):
        # Анализируем только URI до знака ?
        url = self.request.url.split('?')[0]
        if re.search('/$', url):
            main_page(self)
            return
        if re.search('/.+/city/.+', url):
            main_page(self)
            return
        if re.search('/utf-8/$', url):
            main_page(self)
            return
        if re.search('/windows-1251/$', url):
            main_page(self)
            return
        if re.search('/(.+)/yandex_news.xml', url):
            yandex_news(self)
            return
        if re.search('/(.+)/meteo.+', url):
            gismeteo(self)
            return
        if re.search('/(.+)/cbrf.xml', url):
            cbrf(self)
            return
        error(self)

def main():
    application = webapp.WSGIApplication(
                                         [('/.*', Dispatcher)
                                         ],
                                         debug=True)
    wsgiref.handlers.CGIHandler().run(application)

if __name__ == '__main__':
    main()</pre>
<p><strong>Какие еще будут предложения по добавлению новых сервисов?</strong> Напишите комментарий к этой статье.</p>
]]></content:encoded>
</item>
<item>
<title><![CDATA[Автоматизированное тестирование приложений Google App Engine]]></title>
<link>http://techworkru.wordpress.com/?p=320</link>
<pubDate>Thu, 28 Aug 2008 10:08:54 +0000</pubDate>
<dc:creator>techworkru</dc:creator>
<guid>http://techworkru.wordpress.com/?p=320</guid>
<description><![CDATA[В этой статье мы рассмотрим пример добавления среды те]]></description>
<content:encoded><![CDATA[<p>В этой статье мы рассмотрим пример добавления среды тестирования к <a href="http://googleappengine.ru/docs/gettingstarted/templates.html" target="_blank">учебному приложению Гостевая книга</a>, которое описано в Руководстве для начинающих. Вы можете <a href="https://sites.google.com/a/appenginefan.com/downloads/Home/guestbook_test.zip" target="_blank">загрузить все описываемые тесты по этой ссылке</a>.</p>
<h2>Начальная настройка</h2>
<p>Python уже имеет в своей стандартной поставке <a href="http://docs.python.org/lib/module-unittest.html" target="_blank">модуль unittest</a>, поэтому беспокоиться о его установке не нужно. К сожалению, существуют несколько специфических для App Engine интерфейсов (такие как datastore и users), которые должны быть либо правильно настроены, либо вообще убраны из тестов. Я решил использовать <a href="http://labix.org/mocker" target="_blank">фреймворк mocker</a> и поставить заглушки на специфический код от Google, вместо того, чтобы заниматься созданием самопального хранилища, объектов пользователей и тому подобного. Следующий код определяет пути интерпретатора и импортирует необходимые модули:</p>
<pre><code>import unittest
import sys
import os.path  

# Измените следующую строку для указания актуального пути,
# где установлен SDK и библиотека mocker
APPENGINE_PATH = '../google_appengine'
MOCKER_PATH = '../mocker-0.10.1'

# Добавление необходимых для app-engine библиотек к пути интерпретатора
paths = [
    APPENGINE_PATH,
    os.path.join(APPENGINE_PATH, 'lib', 'django'),
    os.path.join(APPENGINE_PATH, 'lib', 'webob'),
    os.path.join(APPENGINE_PATH, 'lib', 'yaml', 'lib'),
    MOCKER_PATH,
]
for path in paths:
  if not os.path.exists(path):
    raise 'Path does not exist: %s' % path
sys.path = paths + sys.path
import mocker
</code></pre>
<h2>Определение и запуск тестов</h2>
<p>Мы только что настроили нашу программу для использования с необходимыми компонентами для проведения тестирования и готовы к созданию первого теста. Сначала задаем класс-наследник от MockerTestCase, который содержит объект "self.mocker", представляющий собой ссылку на интерфейс, контролирующий проведение тестов и выполняющий после них очистку данных. В данном примере я создаю экземпляры классов обоих обработчиков и задаю объекты request и response как заглушки. Если будет вызван метод, который не ожидался, то тест будет считаться непройденным. Я также выполняю замену модулей users, templates и модель Greetings аналогичными заглушками (зачем выполнять проверку содержимого сгенерированного html вместо того, чтобы проверить сами параметры?):</p>
<pre><code>import helloworld

class UnitTests(mocker.MockerTestCase):
  """Тесты для обработчиков MainPage и GuestBook."""

  def setUp(self):
    self.request = self.mocker.mock()
    self.response = self.mocker.mock()
    self.handler1 = helloworld.MainPage()
    self.handler2 = helloworld.Guestbook()
    self.handler1.request = self.request
    self.handler1.response = self.response
    self.handler2.request = self.request
    self.handler2.response = self.response
    self.Greeting = self.mocker.replace('helloworld.Greeting')
    self.users = self.mocker.replace('google.appengine.api.users')
    self.template = self.mocker.replace('google.appengine.ext.webapp.template')
</code></pre>
<p>Теперь все готово для составления первого теста. Он будет проводить проверку поведения метода MainPage.get() при условии авторизации пользователя (если вы не знакомы с этим методом, <a href="http://googleappengine.ru/docs/gettingstarted/templates.html" target="_blank">то вам сюда)</a>. Простейшим способом сделать это является подробное описание в нашем фреймворке каждого вызова, которое выполняет приложение. Следующий код симулирует запрос к хранилищу, который загружает данные и выдает 10 первых результатов. Обратите внимание, что нам на самом деле не нужно получать какие-либо объекты -- они просто не требуются для этого теста. Мы будем возвращать строку с значением <span style="font-style:italic;">Query result for template</span>.</p>
<pre><code>  def testGetWhenLoggedIn(self):
    # Что может произойти во время обычного запроса?
    # Сначала мы загрузим все сообщения
    all_query = self.mocker.mock()
    self.Greeting.all()
    self.mocker.result(all_query)
    ordered_query = self.mocker.mock()
    all_query.order('-date')
    self.mocker.result(ordered_query)
    ordered_query.fetch(10)
    self.mocker.result('Query result for template')
</code></pre>
<p>Мы используем точно такой же шаблон, с каким приложение работает с запросом текущего пользователя (не правда ли здесь проявляется вся сила динамических языков?). В этом примере мы вместо случайной строки возвращаем объект-заглушку. Технически это не обязательно, но такой подход показывает, как мы можем вернуть такой объект при возникшей необходимости глубже управлять поведением среды тестирования.</p>
<pre><code>    fake_user = self.mocker.mock()
    self.users.get_current_user()
    self.mocker.result(fake_user)
    self.request.uri
    self.mocker.result('fake uri')
    self.users.create_logout_url('fake uri')
    self.mocker.result('fake logout uri')
</code></pre>
<p>Теперь после того как наш обработчик загрузил все необходимые ему данные, он обычно выполняет формирование ответа из шаблона Django. Следующий код демонстрирует как можно проверить его работу. Обратите внимание на функцию checkArgs, которая используется для проверки содержимого ожидаемых нами параметров. В конце мы возвращаем пустую html-страницу и сообщаем нашей среде тестирования о необходимости проверки того, что попало в поток вывода:</p>
<pre><code>    out = self.mocker.mock()
    self.response.out
    self.mocker.result(out)
    self.template.render(mocker.ANY, mocker.ANY)
    def checkArgs(path, params):
      template_values = {
        'greetings': 'Query result for template',
        'url': 'fake logout uri',
        'url_linktext': 'Logout',
        }
      self.assert_(path.endswith('index.html'))
      self.assertEqual(template_values, params)
      return '&#60;html/&#62;'
    self.mocker.call(checkArgs)
    out.write('&#60;html/&#62;')
</code></pre>
<p>Мы закончили моделировать ожидаемые данные: представили что текущий пользовать был авторизован и задали нормальное поведение обработчика.</p>
<p>Теперь нам остается переключиться в режим прогона тестов и запустить функцию на исполнение. Если обработчик вдруг поведет себя не так как ожидалось, то наш тест будет считаться непройденным. Мы воспользуемся готовым методом main, который будет определять среду тестирования и прогонять все наши тесты.</p>
<pre><code>    # Все что надо было - записано, теперь можно устроить прогон теста :-)
    self.mocker.replay()
    self.handler1.get()

# #####################
#  ЗАПУСК ВСЕХ ТЕСТОВ
# #####################
if __name__ == "__main__":
  unittest.main()
</code></pre>
<h2>Все ли правильно?</h2>
<p>После того, как мы выполним проверку всех тестов, сразу же увидим что забыли сделать. В коде, представленном ниже, браузер получает указание на перенаправление:</p>
<pre><code>    # TODO: переделать тест?
    greeting.put()
    self.handler2.redirect = lambda x: self.assertEquals('/', x)
</code></pre>
<p>В этом участке кода я заменил метод редиректа на свой обработчик с лямбда-функцией, которая выполняет проверку этого URL. Однако нет никакой гарантии, что редирект будет выполнен.  Мы можем улучшить программу, используя для редиректа объект среды тестирования:</p>
<pre><code>    # TODO: переделать тест?
    greeting.put()
    mock_redirect = self.mocker.mock()
    self.handler2.redirect = lambda x: mock_redirect.redirect(x)
    mock_redirect.redirect('/')
</code></pre>
<p>Хотя это, безусловно, улучшение кода, оно все еще далеко от идеала. Что будет, если кто-то изменит обработчик и добавит в него вызов метода error()? Все тесты пойдут насмарку! Или, что еще хуже, если кто-то решил перекрыть метод redirect в обработчике? В этом случае тест будет всегда признан состоявшимся, несмотря на то, что новый метод может быть нерабочим.</p>
<p>Я не уверен, что это идеальное решение, но я попробовал переделать тест и производить вызов метода post напрямую от mock-объекта, передавая ему ссылку на обработчик Guestbook:</p>
<pre><code>  def testPostWhenLoggedOut(self):
    <strong>handler3 = self.mocker.mock(helloworld.Guestbook)</strong>
    handler3.request
    self.mocker.count(0,10)
    self.mocker.result(self.request)
    handler3.response
    self.mocker.count(0,10)
    self.mocker.result(self.response)

    # Сначала создается новое приветствие
    greeting = self.mocker.mock()
    self.Greeting()
    self.mocker.result(greeting)

    # Пользователь не авторизован
    self.users.get_current_user()
    self.mocker.result(None)

    # Далее извлекается за запроса параметр content
    self.request.get('content')
    self.mocker.result('mock content')
    greeting.content = 'mock content'

    # Теперь сохраняем пост и выполняем перенаправление
    greeting.put()
    mock_redirect = self.mocker.mock()
    handler3.redirect('/')

    # </code><code>Все что надо было - записано, теперь можно устроить прогон теста :-)</code><code>
    self.mocker.replay()
    <strong>helloworld.Guestbook.post(handler3)</strong>
</code></pre>
<p>Этот код имеет преимущество в тех случаях, когда есть вероятность того, что будут вызваны такие методы как self.error().</p>
]]></content:encoded>
</item>
<item>
<title><![CDATA[Amazon Web Services vs. Google App Engine: The Race to the One-Click Cloud]]></title>
<link>http://adamfisk.wordpress.com/?p=55</link>
<pubDate>Wed, 27 Aug 2008 21:49:19 +0000</pubDate>
<dc:creator>adamfisk</dc:creator>
<guid>http://adamfisk.wordpress.com/?p=55</guid>
<description><![CDATA[Can Amazon Build the One-Click Cloud?
It&#8217;s a great time to program for the cloud, no matter wh]]></description>
<content:encoded><![CDATA[[caption id="attachment_59" align="alignright" width="163" caption="Can Amazon Build the One-Click Cloud?"]<img class="size-full wp-image-59" src="http://adamfisk.wordpress.com/files/2008/08/one_click.gif" alt="One-Click Shopping" width="163" height="31" />[/caption]
<p>It's a great time to program for the cloud, no matter what Ted Dziuba's entertaining but barely coherent <a href="http://www.theregister.co.uk/2008/08/25/cloud_dziuba/">rants</a> have to say (will someone get that guy some experience?). Amazon and Google are going toe-to-toe, with Amazon's addition of sorting in Simple DB bringing it up to par with Google App Engine's Datastore API. Sorting was the biggest missing piece in Simple DB and the most compelling reason to choose the Datastore API instead. No longer.  </p>
<p>But Google App Engine (GAE) and the Datastore API still win. Here's why:</p>
<ol>
<li>The Datastore API is projected to be 10x cheaper. $0.15-$0.18 per GB-month sounds a lot better than Simple DB's $1.50 per GB-month.</li>
<li>GQL. GAE's SQL subset is just brain dead simple. As adept as programmers are at learning new frameworks, it's nice to have something brain dead every once in awhile. Simple DB takes a few more cycles to learn (brain cycles that is -- more coffee and such. Modafinil perhaps? Anyone tried it? I'm curious).</li>
<li>GAE has better Object Relational Mapping (ORM). GAE basically uses Django's sweet ORM system. You've got to jump through a lot more hoops to get something as nice with Simple DB. </li>
<li>GAE automatically scales the web application, not just the database. With Amazon, you have to add load balancing and bring machines up and down yourself, even if you're using Simple DB. While there are third-party tools to help, they're not built-in. Again, GAE is brain dead here.  </li>
</ol>
<p>Sure, App Engine only supports Python. The ultimate question, though, is what functionality can you get in the end? For web apps, App Engine gives you more, particularly for scaling (which is kind of the whole point). Don't know Python? Learn it. It will save you time in the end. Instead of endlessly fiddling with your load balancer and custom scripts for bringing instances up and down, you'll spend your time adding the next killer feature your users will love.</p>
<p>In the end, the Amazon/Google "main event" is a huge win for you, me, and our users. The sorting announcement from Amazon comes on the heals of a flurry of other new features from both companies, including Amazon's impressive persistent storage addition for EC2 called the <a href="http://aws.typepad.com/aws/2008/08/amazon-elastic.html">Elastic Block Store</a>, <a title="querying by attributes" href="http://aws.typepad.com/aws/2008/08/amazon-simpledb.html">querying by attributes</a> on Simple DB, GAE's support for 10 applications per user instead of 3, GAE's <a href="http://googleappengine.blogspot.com/2008/08/couple-datastore-updates.html">batch writes</a>, etc. Neither one is pulling any punches, and the tools at our disposal as developers are progressing at a breathtaking pace as a result.</p>
<p>Amazon's is clearly the more complete offering (you can do anything on it, in any language), but it needs to learn from Google's focus on the dominant deployment scenarios.  Amazon could easily win if it does the following:</p>
<ol>
<li>Makes Simple DB pricing competitive with Google's projected prices.</li>
<li>Adds a query language for Simple DB along the lines of GQL.</li>
<li>Adds automatic scaling for web applications, not just the database.</li>
<li>Offers complete deployment solutions for the dominant web applications frameworks, from Tomcat/Spring/Hibernate to Django and Zend, with ORM models already adapted to Simple DB, instances automatically replicated with traffic, etc. Basically the same thing as App Engine for more web app frameworks than App Engine supports and adapted to the Amazon platform. Sure, there are third-party solutions for some of this stuff, but those will never be trusted as much as something offered directly from Amazon.</li>
</ol>
<p>I'm a big fan of Amazon and <a href="http://www.allthingsdistributed.com/">Werner Vogels</a> (one of the most innovative people in the industry, and also apparently a pretty nice guy), but Amazon desperately needs to learn from what Google has done. It's ultimately a question of "usability" for developers. The originators of "one-click shopping" are losing in the game they practically invented. </p>
<p>Amazon needs to turn on the one-click cloud.</p>
]]></content:encoded>
</item>
<item>
<title><![CDATA[GAE goodness and badness]]></title>
<link>http://codecrafter.wordpress.com/?p=93</link>
<pubDate>Tue, 26 Aug 2008 20:54:45 +0000</pubDate>
<dc:creator>Josh Heitzman</dc:creator>
<guid>http://codecrafter.wordpress.com/?p=93</guid>
<description><![CDATA[GAE&#8217;s inherent logging facilities are pretty good and easily brought a couple of bugs in my pr]]></description>
<content:encoded><![CDATA[<p>GAE's inherent logging facilities are pretty good and easily brought a couple of bugs in my prototype to my attention.  That's the goodness.</p>
<p>The badness is that GAE's datastore doesn't appear to have any sort of inherint journaling or rollback features, so if something gets funky in the data there aren't any breadcrumbs to follow to get a clue as to how it got that way, nor is there a way to rollback to the last known good state.  Even bulk downloading and uploading of data isn't inherently supported to try to do just a straight off-site backup of a good state and then a restore of that state from the off-site backup.  Using a standard hosting a site, at a minimum you could occasionally backup your SQLite file right on the server and just replace it if things got wonky.  Obviously SQLite doesn't scale very far.  Other SQL DBs have their scalability limites as well.  So it looks likes its either write your data distribution code or write your own journaling and rollback code.  GAE's datastore and API also don't appear to allow modifications of the existing schema either.  If you add a field to an entity in the code when you try to load an existing entity it will fail, so you can really only make additions.</p>
<p>I'll think I'll still forge ahead with GAE for my first web browser based strategy game just for the experience, but I'm definitely feeling more motivated to go back and work on the distributed app platform I've been wanting to work on.</p>
]]></content:encoded>
</item>
<item>
<title><![CDATA[Вопрос доверия]]></title>
<link>http://techworkru.wordpress.com/?p=310</link>
<pubDate>Tue, 26 Aug 2008 12:04:21 +0000</pubDate>
<dc:creator>techworkru</dc:creator>
<guid>http://techworkru.wordpress.com/?p=310</guid>
<description><![CDATA[Процесс разработки приложения под Google App Engine замечател]]></description>
<content:encoded><![CDATA[<p>Процесс разработки приложения под Google App Engine замечателен во многих смыслах: он интересен, а приложение легко масштабируется и доступно огромному числу пользователей по всему миру. Если у вас есть аккаунт Gmail, вы можете пройти авторизацию на сайте, использующем App Engine.</p>
<p>А что можно сделать, чтобы не пускать кого попало в наше приложение? К примеру, представьте, что вы создали небольшой сайт, который хотите открыть только для членов семьи и своих друзей. Каким образом можно предотвратить доступ для всех других?</p>
<p>Одним из решений проблемы является составление списка разрешенных пользователей (либо получения его из базы данных). Это годится в том случае, если вы знаете кто же допущен к сайту, но к сожалению требует постоянного изменения этого списка и поддержания его в актуальном состоянии. Другим интересным методом является использование системы инвайтов (приглашений), как той, какую на начальном этапе имел Gmail, но это тоже означает, что-то должен управлять этими приглашениями. Следующий пример показывает работу золотой середины между этими описанными методами -- технология, названная <a href="http://ru.wikipedia.org/wiki/HMAC" target="_blank">HMAC</a>, которая позволяет убедиться, конкретный аккаунт Google имеет право доступа.</p>
<p>Следующий код создает криптографический хэш для заданного имени порльзователя. Он использует удобный метод из модуля <a href="http://code.google.com/p/google-app-engine-samples/source/browse/trunk/openid-provider/openid/cryptutil.py?r=26" target="_blank">cryptutil</a>, который сам является частью <a href="http://code.google.com/p/google-app-engine-samples/source/browse/trunk/openid-provider/openid/" target="_blank">примера кода OpenID</a>. В процессе работы приложение оперирует секретным ключом, который ни в коем случае не должен быть опубликован. К конечному пользователю передается только сгенерированная хэш-строка:</p>
<pre><code>import base64
from openid import cryptutil

def sign(username):

  # В реальной жизни выберите лучший секретный ключ!
  secret='superSecretKey'
  return base64.encodestring(
      cryptutil.hmacSha1(secret, username))

</code></pre>
<p>Так как эта проверка работает на практике? Давайте посмотрим на простой обработчик запросов, который будет использовать ее:</p>
<pre><code>import cgi
import os
import urllib
import wsgiref.handlers

from google.appengine.api import users
from google.appengine.ext import webapp

class MainPage(webapp.RequestHandler):

  def isValid(self):
    """Определение, что заданный запрос валиден.

       Для выполнения этих критериев, текущий пользователь должен
       быть авторизован и быть либо администратором, либо передать
       в запросе зашифрованный параметр, совпадающий с его именем пользователя.
    """
    user = users.get_current_user()
    if not user:
      return False
    if not users.is_current_user_admin():
      expected_signature = sign(user.email().lower())
      given_signature = self.request.get('signature')
      if not given_signature == expected_signature:
        return False
    return True

</code></pre>
<p>Наш метод выполняет проверку, имеет ли данный запрос параметр с подписью. Если да, он сравнивает его с хэшем, вычисленным из адреса электронной почты текущего пользователя. И только в том случае, если оба этих ключа совпадают, пользователю предоставляется доступ.</p>
<p>Следующие методы get и post показывают работу простейшего приложения, отображающего важное сообщение только для приглашенных пользователей. Введя адрес электронной почты другого пользователя, мы позволяем системе создать подписанный URL, который затем будет передан приглашаемому пользователю:</p>
<pre><code>  def get(self):
    """Отображение сообщения только для приглашенных пользователей."""

    # Требуется авторизация
    user = users.get_current_user()
    if not user:
      self.redirect(users.create_login_url(self.request.uri))
      return

    # Если пользователь не администратор, проверить подпись
    if not self.isValid():
      self.error(403)
      return

    # Ok, пользователь допущен к просмотру страницы!
    # В реальной жизни мы воспользуемся в этом месте
    # шаблоном ;-)
    self.response.out.write("""
        &#60;html&#62;&#60;body&#62;
        &#60;h1&#62;Hello, %s&#60;/h1&#62;
        &#60;p&#62;Ниже секретное сообщение, которое вы ищете:
           &#38;nbsp;&#60;b&#62;APP ENGINE РУЛИТ!!!&#60;/b&#62;
        &#60;/p&#62;
        &#60;form method="POST"&#62;
        Передать сообщение другу
        &#60;input name="invitee"/&#62; &#38;nbsp;
        &#60;input type="submit"/&#62;&#60;/form&#62;
        &#60;p&#62;&#60;a href="%s"&#62;Log out&#60;/a&#62;&#60;/p&#62;
        &#60;/body&#62;&#60;/html
        """ % (cgi.escape(user.nickname()),
               users.create_logout_url(self.request.uri)))

  def post(self):
    """Создание строки URL для последующей отправки приглашенному."""

  </code><code>  # Если пользователь не администратор, проверить подпись</code>
<code>    if not self.isValid():
      self.error(403)
      return

    # Получить имя invitee
    invitee = self.request.get('invitee')
    if not invitee:
      invitee = 'anonymnous'
    invitee = invitee.lower()

    # подписать инвайт и вывести url
    signature = sign(invitee)
    self.response.out.write("""
        &#60;html&#62;&#60;body&#62;
        URL для друга:
        http://%s?signature=%s&#60;/body&#62;&#60;/html&#62;""" % (
            os.environ['SERVER_NAME'],
            urllib.quote(signature, '')))

</code></pre>
<p>Несмотря на то, что это выглядит игрушечным приложением, этот пример показывает практический подход, который может быть использован для современных веб-приложений в ответ на вопрос: как можно проверить, что запрос пришел из надежного источника. Для более комплексного алгоритма обратитесь к <a href="http://oauth.net/core/1.0#signing_process" target="_blank">спецификации OAuth</a>, в которой описываются подходы использования подписи веб-запросов с помощью HMAC.</p>
]]></content:encoded>
</item>
<item>
<title><![CDATA[Jaiku мигрирует на Google App Engine]]></title>
<link>http://techworkru.wordpress.com/?p=304</link>
<pubDate>Tue, 26 Aug 2008 09:30:04 +0000</pubDate>
<dc:creator>techworkru</dc:creator>
<guid>http://techworkru.wordpress.com/?p=304</guid>
<description><![CDATA[Неделю назад финский сервис микроблогинга Jaiku пребыва]]></description>
<content:encoded><![CDATA[<p>Неделю назад финский сервис микроблогинга <strong>Jaiku</strong> пребывал в состоянии недоступности на протяжении одного дня из-за сбоя электропитания в финском датацентре, в котором он размещается. Несмотря на то, что его год назад купила компания Google, серверы до сих пор не были переведены в датацентры компании, и это привело жарким дискуссиям в Интернете.</p>
<p>На выходных Jaiku снова стал недоступен, но в <a href="http://jaikuinvites.com/jaiku-is-now-on-the-google-app-engine/" target="_blank">официальном блоге</a> содержится информация о том, что это произошло из-за его переноса на инфраструктуру Google. "Теперь мы можем подтвердить, что сервис уже работает на платформе Google App Engine и мы снова доступны всему миру," говорится в сообщении JaikuInvites. Содержимое страниц, загружаемых с Jaiku.com, также сообщает о том, что они обслуживаются сервером 'Google Frontend'. Google Frontend - это сервер, который используется для работы всех приложений Google App Engine. GFE также задействован для хостинга проекта Blogger в домене blogspot.com, хотя пока Google для других сервисов все еще использует GWS (Google Web Server).</p>
]]></content:encoded>
</item>
<item>
<title><![CDATA[Великий китайский анонимный прокси-сервер]]></title>
<link>http://techworkru.wordpress.com/?p=302</link>
<pubDate>Mon, 25 Aug 2008 13:08:58 +0000</pubDate>
<dc:creator>techworkru</dc:creator>
<guid>http://techworkru.wordpress.com/?p=302</guid>
<description><![CDATA[Недавно мы рассматривали процесс создания простейшег]]></description>
<content:encoded><![CDATA[<p>Недавно <a href="http://techwork.ru/2008/08/21/anonymous-proxy-from-google/" target="_blank">мы рассматривали</a> процесс создания простейшего прокси сервера, который бы получал от нас запросы и через Google App Engine передавал конкретным сайтам для скрытия настоящего адреса пользователя. Оказывается, что братья-китайцы уже давно используют подобное решение и с помощью него обходят <strong>Великий китайский файрвол</strong>.</p>
<p>Проект называется <a href="http://code.google.com/p/gappproxy/" target="_blank"><strong>Gapproxy</strong></a>, распространяется в исходных кодах и уже имеет лояльную аудиторию преданных пользователей. Суть проста: типичный китаец, который хочет попасть на сайты, куда его не очень-то и хотели пускать (например, <strong>Китайскую Википедию</strong>) скачивает простой агент, который принимает HTTP запросы на локальном компьютере на порту 8000. На его же использование настраиваются все браузеры, аськи и прочие клиенты, которым необходимо преодолеть запретный барьер. Далее этот агент при поступлении запроса выполняет такой же к специальному приложению на App Engine, которое в свою очередь делает обращение к нужному ресурсу. Получается такая четырехзвенная структура, способная работать с любыми приложениями по протоколам HTTP и HTTPS.</p>
<p>При этом каждый китаец волен выбирать, использовать ли ему общее приложение-прокси на GAE, или загрузить код своего (благо все распространяется в исходных кодах).</p>
]]></content:encoded>
</item>
<item>
<title><![CDATA[Некоторые обновления Datastore API]]></title>
<link>http://techworkru.wordpress.com/?p=293</link>
<pubDate>Mon, 25 Aug 2008 05:31:59 +0000</pubDate>
<dc:creator>techworkru</dc:creator>
<guid>http://techworkru.wordpress.com/?p=293</guid>
<description><![CDATA[Ранее команда разработки App Engine произвела обновление н]]></description>
<content:encoded><![CDATA[<p>Ранее команда разработки App Engine произвела обновление некоторых компонентов системы, а теперь дошла очередь до улучшений в работе хранилища.</p>
<p>Сначала следует упомянуть пакетную запись. Теперь вы можете включить объекты из нескольких различных групп в одну операцию <a href="http://googleappengine.ru/docs/datastore/functions.html#put" target="_blank">db.put()</a> или <a href="http://googleappengine.ru/docs/datastore/functions.html#delete" target="_blank">db.delete()</a>. Изменения объектов будут являться атомарными операциями только в рамках отдельной группы объектов, но использование одного обращения к хранилищу вместо нескольких отдельных последовательных вызовов, которые требовались ранее, более эффективно.</p>
<p>Также ожидается, что в <em>следующем</em> релизе платформы, будут поддерживаться индексы с повторяющимися свойствами. К примеру:</p>
<pre>Kind: Post
 properties:
 - name: tag
 - name: tag</pre>
<p>Такие индексы будут полезны в некоторых случаях; смотрите раздел <a href="http://googleappengine.ru/docs/datastore/queriesandindexes.html" target="_blank">Запросы и индексы</a> для дополнительной информации.</p>
<p>Ну и как обычно, <a href="http://code.google.com/p/googleappengine/downloads/list" target="_blank">обновите свой SDK</a> и просмотрите включенный в него полный список изменений в системе.</p>
]]></content:encoded>
</item>
<item>
<title><![CDATA[Web game prototype]]></title>
<link>http://codecrafter.wordpress.com/?p=87</link>
<pubDate>Sun, 24 Aug 2008 09:18:28 +0000</pubDate>
<dc:creator>Josh Heitzman</dc:creator>
<guid>http://codecrafter.wordpress.com/?p=87</guid>
<description><![CDATA[I now have a prototype for a web based game up on Google App Engine.  Being a prototype there is ju]]></description>
<content:encoded><![CDATA[<p>I now have a prototype for a web based game <a href="http://webgamesbyjosh.appspot.com/prototype">up</a> on Google App Engine.  Being a prototype there is just enough implemented to persist one user interaction (take a turn), but that is enough know there aren't any technical barriers to what I want to do.  Now I can concentrate on finishing up game design and coding for my stock analysis software, which just finished up a run that I'll take an in depth look at tomorrow.</p>
]]></content:encoded>
</item>
<item>
<title><![CDATA[No Appcelerator for my first App Engine app]]></title>
<link>http://codecrafter.wordpress.com/?p=85</link>
<pubDate>Thu, 21 Aug 2008 18:47:35 +0000</pubDate>
<dc:creator>Josh Heitzman</dc:creator>
<guid>http://codecrafter.wordpress.com/?p=85</guid>
<description><![CDATA[Playing Travian inspired me to develop web based strategy, and after surveying a number of others to]]></description>
<content:encoded><![CDATA[<p>Playing Travian inspired me to develop web based strategy, and after surveying a number of others to get a feel for the market I've begun game design and technical prototyping.</p>
<p>I thought I'd give <a href="http://www.appcelerator.org/">Appcelerator</a> a try, since it can create project for Google's App Engine.  I noticed that they have a <a href="http://www.appcelerator.org/download#riadeveloper">plug-in</a> for Eclipse; however, after installing Eclipse and their plug-in I found that their project wizard was throwing an exception at the last step.  It may be that they expect the Eclipse install to include certain thing such as a Java development environment (I had installed the C/C++ only distro), but I they have a command line tool for creating projects as well, so I gave that a shot.  I was able to run the created project locally and deploy it to App Engine and run it successfully there as well.  However, I found that the project was not setup to use Pylons as I had mistakenly gotten the impression that it would be.  I created a Appcelerator project for Python rather then AppEngine with their command line tool and I found that that project did use Pylons and it shouldn't be terribly difficult to get that project up and going on App Engine by following these <a href="http://wiki.pylonshq.com/display/APPENGINE/Pylons+on+App+Engine">directions</a>.  I also found that the default generated index page was HTML 4.01 rather then XHTML 1.0 and after switching it over, I found that a number of the Appcelerator specific things wouldn't pass validation (the original HTML 4.01 version didn't pass validation either).</p>
<p>As I'd like my first web app to actually serve up pages that are valid, that means no Appcelerator for me.  I also have a strong desire to not be involved with javasript in any way shape or form.  I was willing to live loading a third party script I never needed to touch, but I noticed that the example for some of the Appcelerator widgets involved adding app specific javascript.</p>
<p>I'm going to start over with a pure Pylons project and then go back to trying to get my CSS stylings to work in both FireFox and IE.  IE doesn't seem to be handle things tagged with float as well as FireFox so I may just throw in the towel on having a dynamic layout that will adapt to different width windows and just go with a fixed width of that will work on a display 800 pixels wide.</p>
]]></content:encoded>
</item>
<item>
<title><![CDATA[Анонимный прокси сервер от Google]]></title>
<link>http://techworkru.wordpress.com/?p=288</link>
<pubDate>Thu, 21 Aug 2008 12:34:43 +0000</pubDate>
<dc:creator>techworkru</dc:creator>
<guid>http://techworkru.wordpress.com/?p=288</guid>
<description><![CDATA[Замечательный интерфейс URLFetch позволяет выполнять заг]]></description>
<content:encoded><![CDATA[<p>Замечательный интерфейс URLFetch позволяет выполнять загрузку страниц по протоколам HTTP и HTTPS. Может ли App Engine сама себя вести как клиентский браузер? Конечно может!</p>
<p>Ниже мы рассмотрим пример создания приложения, которое будет загружать заданный пользователем сайт и передавать ему. Наиболее очевидный способ его применения: персональный анонимный прокси сервер. Вы будете самостоятельно управлять своей приватностью, а, при необходимости, быть уверенным, что все логи посещений этого прокси сервера были уничтожены.</p>
<p>Сам код очень тривиален:</p>
<pre># coding=UTF-8
# -*- coding: utf-8 -*-

import wsgiref.handlers
from google.appengine.ext import webapp
from google.appengine.api import urlfetch
import re
import logging

allowed_headers = ('content-type', 'Content-Type', 'Set-Cookie', 'set-cookie')
HOST = 'gproxyru.appspot.com'

def add_header(self, header, info):
    if header == 'Content-Type':
        self.response.headers['Content-Type'] = info
        return
    if (header == 'Set-Cookie') or (header == 'set-cookie'):
        cookies = re.split(', ', info)
        for cookie in cookies:
            # Убираем требования по безопасности
            cookie = re.sub('secure; ', '', cookie)
            self.response.headers.add_header('Set-Cookie', cookie)
        return
    self.response.headers.add_header(header, info)
    logging.error(header + ': '+ info)

def process_page(self, url, method, mode='http'):
    hm = re.findall('//(.*)/', url)
    if len(hm) == 0:
        hostname = re.findall('//(.*)', url)[0]
    else:
        hostname = hm[0]
    result = urlfetch.fetch(url, method=urlfetch.GET)
    # меняем все ссылки в документе http:// на http://HOST/http/ и тому подобное
    content = re.sub('http://', 'http://' + HOST + '/http/', result.content)
    content = re.sub('https://', 'http://' + HOST + '/https/', content)
    # относительные источники изображений
    content = re.sub(' src=/', ' src=/'+ mode +'/' + hostname + '/', content)
    content = re.sub(' src="/', ' src="/'+ mode +'/' + hostname + '/', content)
    # href для CSS и A
    content = re.sub(' href=/', ' href=/'+ mode +'/' + hostname + '/', content)
    content = re.sub(' href="/', ' href="/'+ mode +'/' + hostname + '/', content)
    # ссылки для форм
    content = re.sub(' action=/', ' action=/'+ mode +'/' + hostname + '/', content)
    content = re.sub(' action="/', ' action="/'+ mode +'/' + hostname + '/', content)
    self.response.out.write(content)
    for header in result.headers.keys():
        if header in allowed_headers:
            add_header(self, header, result.headers[header])

class Http(webapp.RequestHandler):
    def url(self):
        res = re.search('/http/(.*)', self.request.url)
        if res != None:
            return 'http://' + res.group(1)

    def get(self):
        process_page(self, self.url(), urlfetch.GET)

    def post(self):
        process_page(self, self.url(), urlfetch.POST)

class Https(webapp.RequestHandler):
    def url(self):
        res = re.search('/https/(.*)', self.request.url)
        if res != None:
            return 'https://' + res.group(1)         

    def get(self):
        process_page(self, self.url(), urlfetch.GET, mode='https')

    def post(self):
        process_page(self, self.url(), urlfetch.POST, mode='https')

class MainPage(webapp.RequestHandler):
    def get(self):
        self.response.out.write("""&#60;h3&#62;Простенький анонимный прокси-сервер от Google&#60;/h3&#62;
                                &#60;form action='/go'&#62;
                                Введите сайт, например &#60;b&#62;www.google.ru&#60;/b&#62;: &#60;input name=q size=70&#62;
                                &#60;input type=submit value="Перейти"&#62;
                                &#60;/form&#62;
                                """)
class Q(webapp.RequestHandler):
    def get(self):
        q = self.request.get('q')
        if re.match('http://', q):
            self.redirect('/http/' + q[7:])
            return
        if re.match('https://', q):
            self.redirect('/https/' + q[8:])
            return
        self.redirect('/http/' + q)

def main():
    application = webapp.WSGIApplication(
                                         [('/', MainPage),
                                          ('/go', Q),
                                          ('/http/.*', Http),
                                          ('/https/.*', Https)
                                         ],
                                         debug=True)
    wsgiref.handlers.CGIHandler().run(application)

if __name__ == '__main__':
    main()</pre>
<p>Посмотреть работу вживую можно на сайте <a href="http://gproxyru.appspot.com" target="_blank"><strong>gproxyru.appspot.com</strong></a>. Ниже приведен пример, как Mail.Ru определил наше местоположение как <strong>Вашингтон</strong>:</p>
<p><a href="http://techworkru.wordpress.com/files/2008/08/gproxy.png"><img class="aligncenter size-medium wp-image-289" src="http://techworkru.wordpress.com/files/2008/08/gproxy.png?w=300" alt="" width="300" height="230" /></a></p>
<p>Недостатки:</p>
<ul>
<li>хотя сайты по протоколу HTTPS прекрасно открываются, передача данных на участке от клиента до датацентра Google проходит без шифрации</li>
<li>на данный момент никак не реализована работа с куками (хотя для анонимности, может быть это и хорошо ;-) )</li>
<li>замена ссылок в коде страницы для быстроты реакции производится регулярными выражениями, возможно, это не самый правильный способ</li>
<li>существуют мелкие недочеты в виде нарушения работы кода JavaScript, либо, наоборот, невозможности скрыть IP адрес от таких скриптов.</li>
</ul>
]]></content:encoded>
</item>
<item>
<title><![CDATA[Система мониторинга Google App Engine]]></title>
<link>http://techworkru.wordpress.com/?p=283</link>
<pubDate>Thu, 21 Aug 2008 11:19:10 +0000</pubDate>
<dc:creator>techworkru</dc:creator>
<guid>http://techworkru.wordpress.com/?p=283</guid>
<description><![CDATA[Компания Hyperic представила поддержку платформы Google App Eng]]></description>
<content:encoded><![CDATA[<p>Компания Hyperic представила поддержку платформы Google App Engine в своем веб-приложении для мониторинга систем Cloud Computing - <a href="http://www.cloudstatus.com/" target="_blank"><strong>CloudStatus</strong></a>. Оно позволяет в реальном времени следить за доступностью и производительностью платформы. Кроме того поддерживается анализ работоспособности отдельных компонентов системы: Datastore, Memcache и URLFetch:</p>
<p style="text-align:center;"><img class="size-medium wp-image-285 aligncenter" src="http://techworkru.wordpress.com/files/2008/08/cloudstatus.png?w=300" alt="" width="300" height="241" /></p>
<p>Все сообщения о проблемах кроме сайта дублируются в Twitter.</p>
]]></content:encoded>
</item>
<item>
<title><![CDATA[Как получать почту приложением?]]></title>
<link>http://techworkru.wordpress.com/?p=280</link>
<pubDate>Wed, 20 Aug 2008 13:58:51 +0000</pubDate>
<dc:creator>techworkru</dc:creator>
<guid>http://techworkru.wordpress.com/?p=280</guid>
<description><![CDATA[К сожалению, приложения платформы Google App Engine не могут н]]></description>
<content:encoded><![CDATA[<p>К сожалению, приложения платформы Google App Engine не могут напрямую получать почтовые сообщения от внешних источников. Существует сторонний проект <strong>smtp2web</strong>, который позволяет принимать сообщения и передавать их содержимое (включая заголовки) через метод POST приложению.</p>
<p>Вы можете запустить этот сервис либо на своем сервере, либо воспользоваться существующим (бесплатным) на сайте <strong><a href="http://www.smtp2web.com" target="_blank">www.smtp2web.com</a></strong>. Причем можно использовать как работу с ящиками всего домена, так и с отдельными.</p>
<p>Ниже приведен пример получения сообщения при использовании фреймворка webapp:</p>
<pre>from google.appengine.ext import webapp
import email

class EmailHandler(webapp.RequestHandler):
  def post(self):
    sender = self.request.GET.get("from", "")
    recipient = self.request.GET.get("to", "")
    message = email.message_from_string(self.request.body)
    # Произвести обработку сообщения</pre>
<p>Исходный код полностью <a href="http://code.google.com/p/smtp2web/" target="_blank">открыт</a>.</p>
]]></content:encoded>
</item>
<item>
<title><![CDATA[CloudStatus Keeps an Eye on The Clouds]]></title>
<link>http://gigaom.com/?p=18202</link>
<pubDate>Wed, 20 Aug 2008 13:00:27 +0000</pubDate>
<dc:creator>Om Malik</dc:creator>
<guid>http://gigaom.com/?p=18202</guid>
<description><![CDATA[The summer of 2008 has been the best of times and worst of times for cloud computing. Many companies]]></description>
<content:encoded><![CDATA[<p>The summer of 2008 has been the best of times and worst of times for cloud computing. Many companies –- big and small -- <a href="http://gigaom.com/2008/07/29/hp-yahoo-and-intel-create-compute-cloud/">decided to throw in their lot</a> with cloud computing, betting that it is the future of technology infrastructure. At the same time, cloud computing took its lumps as some of the early large-scale cloud applications hit the skids.</p>
<p><a href="http://gigaom.com/2008/07/10/i-cant-find-mobileme/">Apple's MobileMe went on the blink</a> for many while the <a href="http://gigaom.com/2008/08/11/gmails-outagain/">GMail blackout</a> that left millions angry and frustrated. Even Amazon's seemingly fool proof S3 service was down for an extended period of time, <a href="http://gigaom.com/2008/07/20/amazon-s3-outage-july-2008/">impacting thousands of its customers</a>. This isn't the last we have seen of these outages. As the size and scope of cloud computing grows, so will the problems and the need for tools to monitor the clouds.</p>
<p>Enter San Francisco-based web infrastructure monitoring service provider Hyperic, which recently launched <a href="http://cloudstatus.com">CloudStatus</a>, a hosted real-time cloud monitoring service to keep an eye on cloud–based services. Thus far CloudStatus is monitoring Amazon's web services, but <strong>sometime later today, the company will start monitoring Google's App Engine infrastructure</strong>, a move that has been blessed by the search engine giant. The service, still in beta testing phase, is free for near foreseeable future, but company might charge for premium services at a later date.</p>
<p><!--more--><a href="http://gigaom.files.wordpress.com/2008/08/cloudstatusscreenshot.jpg"><img class="alignleft size-full wp-image-18203" title="cloudstatusscreenshot" src="http://gigaom.wordpress.com/files/2008/08/cloudstatusscreenshot.jpg" alt="" width="325" height="370" /></a>To describe it in super-simplistic terms, this is how the service works: Hyperic has developed an application that runs on the Google App Engine and essentially sends all sorts of information to an agent sitting in Hyperic's data center, which in turn passes it onto Hyperic's main offering. Through a web-browser interface, folks can in turn keep an eye on the status of the cloud.</p>
<p>What will it do for Google App Engine users? Javier A. Soltero, CEO of Hyperic said that it will help users answer questions like "How fast is the App Engine cache service running?" or  "What's the response time to Facebook's API from App Engines perspective?" and other such questions that can help keep apps healthy.</p>
<p>The company can do this is because it is based on agents that are deployed both inside and outside the "cloud infrastructure" and are specialized for the kind of services they monitor. A storage agent, for instance may monitor latency, throughput and remaining capacity. A compute engine agent would monitor load, availability and response times.</p>
<p>Taking measurements from both sides of the wall -- that is, from inside the cloud providers operation, and from the outside looking in -- gives Hyperic an advantage, says Soltero, who claims that it "picked up the Amazon S3 problems about 20 minutes before Amazon announced (the outage.)" He explained that the service is capable of monitoring any number of different clouds, and it will add more cloud providers to the list.</p>
<p>For Hyperic, CloudStatus is a chance to stand out amongst its competitors. While companies like Stubhub, Comcast and CNET have adopted Hyperics web monitoring tools, the company has little traction inside the enterprises. On the web-side, several companies such as Gomez, Keynote and Webmetrics offer Hyperic-like services.</p>
<p>Cloud computing, however, is a new game where Hyperic can make a play to win -- though it would need to add depth and value to its offerings. After all, looming in the background is the distinct possibility that some day Amazon and Google will rollout their own monitoring services.</p>
]]></content:encoded>
</item>
<item>
<title><![CDATA[Подсказки по использованию App Engine]]></title>
<link>http://techworkru.wordpress.com/?p=278</link>
<pubDate>Wed, 20 Aug 2008 09:05:31 +0000</pubDate>
<dc:creator>techworkru</dc:creator>
<guid>http://techworkru.wordpress.com/?p=278</guid>
<description><![CDATA[Как из приложения получить его идентификатор и версию?]]></description>
<content:encoded><![CDATA[<h3>Как из приложения получить его идентификатор и версию?</h3>
<p>Используйте функцию <em>os.getcwd()</em> или переменную окружения <em>os.environ['PATH_TRANSLATED']</em></p>
<pre style="color:#000000;"><span style="color:#808030;">&#62;</span><span style="color:#808030;">&#62;</span><span style="color:#808030;">&#62;</span> os<span style="color:#808030;">.</span>getcwd<span style="color:#808030;">(</span><span style="color:#808030;">)</span>
<span style="color:#0000e6;">'/base/data/home/apps/shell/1.21'</span>
<span style="color:#808030;">&#62;</span><span style="color:#808030;">&#62;</span><span style="color:#808030;">&#62;</span> os<span style="color:#808030;">.</span>getcwd<span style="color:#808030;">(</span><span style="color:#808030;">)</span><span style="color:#808030;">.</span>split<span style="color:#808030;">(</span><span style="color:#0000e6;">'/'</span><span style="color:#808030;">)</span><span style="color:#808030;">[</span><span style="color:#808030;">-</span><span style="color:#008c00;">2</span><span style="color:#808030;">]</span>
<span style="color:#0000e6;">'shell'</span>
<span style="color:#808030;">&#62;</span><span style="color:#808030;">&#62;</span><span style="color:#808030;">&#62;</span> os<span style="color:#808030;">.</span>getcwd<span style="color:#808030;">(</span><span style="color:#808030;">)</span><span style="color:#808030;">.</span>split<span style="color:#808030;">(</span><span style="color:#0000e6;">'/'</span><span style="color:#808030;">)</span><span style="color:#808030;">[</span><span style="color:#808030;">-</span><span style="color:#008c00;">1</span><span style="color:#808030;">]</span>
<span style="color:#0000e6;">'1.21'</span>

<span style="color:#808030;">&#62;</span><span style="color:#808030;">&#62;</span><span style="color:#808030;">&#62;</span> os<span style="color:#808030;">.</span>environ<span style="color:#808030;">[</span><span style="color:#0000e6;">'PATH_TRANSLATED'</span><span style="color:#808030;">]</span>
<span style="color:#0000e6;">'/base/data/home/apps/shell/1.21/shell.py'</span>
<span style="color:#808030;">&#62;</span><span style="color:#808030;">&#62;</span><span style="color:#808030;">&#62;</span> os<span style="color:#808030;">.</span>environ<span style="color:#808030;">[</span><span style="color:#0000e6;">'PATH_TRANSLATED'</span><span style="color:#808030;">]</span><span style="color:#808030;">.</span>split<span style="color:#808030;">(</span><span style="color:#0000e6;">'/'</span><span style="color:#808030;">)</span><span style="color:#808030;">[</span><span style="color:#808030;">-</span><span style="color:#008c00;">3</span><span style="color:#808030;">]</span>
<span style="color:#0000e6;">'shell'</span></pre>
<h3>Как определить текущий хост?</h3>
<p>Есть один очень интересный файл, который уникален на каждом сервере:</p>
<pre style="color:#000000;"><span style="color:#808030;">&#62;</span><span style="color:#808030;">&#62;</span><span style="color:#808030;">&#62;</span> <span style="color:#e34adc;">open</span><span style="color:#808030;">(</span><span style="color:#0000e6;">'/base/python_dist/search.config'</span><span style="color:#808030;">)</span><span style="color:#808030;">.</span>read<span style="color:#808030;">(</span><span style="color:#808030;">)</span>
'datapath <span style="color:#808030;">.</span>\nsorttempdir <span style="color:#808030;">.</span>\ndisk <span style="color:#808030;">/</span>export<span style="color:#808030;">/</span>hdc3<span style="color:#808030;">/</span>borgletdata<span style="color:#808030;">/</span>dirs<span style="color:#808030;">/</span>prod<span style="color:#808030;">-</span>appengine<span style="color:#808030;">.</span>\
mpm_python_dist_v12<span style="color:#808030;">.</span>apphosting<span style="color:#808030;">.</span><span style="color:#008c00;">77627982</span><span style="color:#808030;">/</span>bigfiledata<span style="color:#808030;">/</span><span style="color:#008c00;">466024</span>'

<span style="color:#808030;">&#62;</span><span style="color:#808030;">&#62;</span><span style="color:#808030;">&#62;</span> <span style="color:#e34adc;">open</span><span style="color:#808030;">(</span><span style="color:#0000e6;">'/base/python_dist/search.config'</span><span style="color:#808030;">)</span><span style="color:#808030;">.</span>read<span style="color:#808030;">(</span><span style="color:#808030;">)</span>
'datapath <span style="color:#808030;">.</span>\nsorttempdir <span style="color:#808030;">.</span>\ndisk <span style="color:#808030;">/</span>export<span style="color:#808030;">/</span>hdc3<span style="color:#808030;">/</span>borgletdata<span style="color:#808030;">/</span>dirs<span style="color:#808030;">/</span>prod<span style="color:#808030;">-</span>appengine<span style="color:#808030;">.</span>\
mpm_python_dist_v12<span style="color:#808030;">.</span>apphosting<span style="color:#808030;">.</span><span style="color:#008c00;">77627739</span><span style="color:#808030;">/</span>bigfiledata<span style="color:#808030;">/</span><span style="color:#008c00;">465336</span>'</pre>
<p>Вы можете идентифицировать машину, на которой работает данный процесс, проанализировав хэш содержимого из этого файла. К примеру так:</p>
<pre style="color:#000000;"><span style="font-weight:bold;color:#800000;">def</span> get_server_id<span style="color:#808030;">(</span><span style="color:#808030;">)</span><span style="color:#808030;">:</span>
    <span style="font-weight:bold;color:#800000;">try</span><span style="color:#808030;">:</span>
        fd   <span style="color:#808030;">=</span> <span style="color:#e34adc;">open</span><span style="color:#808030;">(</span><span style="color:#0000e6;">'/base/python_dist/search.config'</span><span style="color:#808030;">)</span>
        data <span style="color:#808030;">=</span> fd<span style="color:#808030;">.</span>read<span style="color:#808030;">(</span><span style="color:#808030;">)</span>
        fd<span style="color:#808030;">.</span>close<span style="color:#808030;">(</span><span style="color:#808030;">)</span>
    <span style="font-weight:bold;color:#800000;">except</span> <span style="color:#e34adc;">IOError</span><span style="color:#808030;">:</span>
        <span style="font-weight:bold;color:#800000;">return</span> <span style="color:#0000e6;">'unknown'</span>

    <span style="font-weight:bold;color:#800000;">return</span> <span style="color:#0000e6;">'%s'</span> <span style="color:#808030;">%</span> data<span style="color:#808030;">.</span><span style="color:#e34adc;">__hash__</span><span style="color:#808030;">(</span><span style="color:#808030;">)</span></pre>
<p>Google не сообщает, на скольких серверах работает ваше приложение (и скорее всего это будет зависеть от генерируемого вашим сайтом трафика). Для определения того, сколько машин используется для функционирования приложения можно воспользоваться следующей методикой: включить <em>server_id</em> в содержимое страницы сайта. Затем достаточно провести несколько загрузок, чтобы понять сколько уникальных идентификаторов сервера будет выдано.</p>
<pre style="color:#000000;">$ <span style="font-weight:bold;color:#800000;">for</span> i <span style="font-weight:bold;color:#800000;">in</span> <span style="background:#ffffe8 none repeat scroll 0 0;color:#000000;">`seq </span><span style="background:#ffffe8 none repeat scroll 0 0;color:#008c00;">20</span><span style="background:#ffffe8 none repeat scroll 0 0;color:#000000;">`</span><span style="color:#800080;">;</span> <span style="font-weight:bold;color:#800000;">do</span>
    curl <span style="color:#44aadd;">-s</span> http<span style="color:#808030;">:</span><span style="color:#40015a;">/</span><span style="color:#40015a;">/cometchat</span><span style="font-weight:bold;color:#800000;">.</span>appspot<span style="font-weight:bold;color:#800000;">.</span>com<span style="color:#e34adc;">&#124;</span><span style="color:#0f69ff;">\</span>
    grep server_id<span style="color:#800080;">;</span> <span style="color:#0f69ff;">\</span>
  <span style="font-weight:bold;color:#800000;">done</span>    <span style="color:#e34adc;">&#124;</span>sort -n<span style="color:#e34adc;">&#124;</span>uniq <span style="color:#44aadd;">-c</span>

     <span style="color:#008c00;">20</span> server_id<span style="color:#808030;">:</span> <span style="color:#0000e6;">'7341146770217830363'</span></pre>
<p>В примере видно, что приложение работает только на одном сервере.</p>
<h3>Как можно идентифицировать текущий процесс?</h3>
<p>И следующий вопрос: сколько процессов моего приложения запущено на одном сервере? Можно проанализировать с помощью глобальной переменной:</p>
<pre style="color:#000000;">the_process_global <span style="color:#808030;">=</span> <span style="color:#0000e6;">"something"</span>

<span style="font-weight:bold;color:#800000;">def</span> get_process_id<span style="color:#808030;">(</span><span style="color:#808030;">)</span><span style="color:#808030;">:</span>
    <span style="font-weight:bold;color:#800000;">return</span> <span style="color:#0000e6;">'%s'</span> <span style="color:#808030;">%</span> <span style="color:#e34adc;">id</span><span style="color:#808030;">(</span>the_process_global<span style="color:#808030;">)</span></pre>
<p>Теперь мне известно, что приложение задействует два процесса:</p>
<pre style="color:#000000;">$ <span style="font-weight:bold;color:#800000;">for</span> i <span style="font-weight:bold;color:#800000;">in</span> <span style="background:#ffffe8 none repeat scroll 0 0;color:#000000;">`seq </span><span style="background:#ffffe8 none repeat scroll 0 0;color:#008c00;">20</span><span style="background:#ffffe8 none repeat scroll 0 0;color:#000000;">`</span><span style="color:#800080;">;</span> <span style="font-weight:bold;color:#800000;">do</span>
    curl <span style="color:#44aadd;">-s</span> http<span style="color:#808030;">:</span><span style="color:#40015a;">/</span><span style="color:#40015a;">/cometchat</span><span style="font-weight:bold;color:#800000;">.</span>appspot<span style="font-weight:bold;color:#800000;">.</span>com<span style="color:#e34adc;">&#124;</span><span style="color:#0f69ff;">\</span>
    grep _id<span style="color:#800080;">;</span>
  <span style="font-weight:bold;color:#800000;">done</span>    <span style="color:#e34adc;">&#124;</span>sort -n<span style="color:#e34adc;">&#124;</span>uniq <span style="color:#44aadd;">-c</span>

    <span style="color:#008c00;">13</span> process_id<span style="color:#808030;">:</span> <span style="color:#0000e6;">'12457625149327067176'</span>
     <span style="color:#008c00;">7</span> process_id<span style="color:#808030;">:</span> <span style="color:#0000e6;">'3996238433791648184'</span></pre>
<h3>Работаем ли мы в среде разработки или на сервере?</h3>
<p>Я использую такой шаблон:</p>
<pre style="color:#000000;"><span style="font-weight:bold;color:#800000;">if</span> os<span style="color:#808030;">.</span>environ<span style="color:#808030;">.</span>get<span style="color:#808030;">(</span><span style="color:#0000e6;">'SERVER_SOFTWARE'</span><span style="color:#808030;">,</span><span style="color:#0000e6;">''</span><span style="color:#808030;">)</span><span style="color:#808030;">.</span>startswith<span style="color:#808030;">(</span><span style="color:#0000e6;">'Devel'</span><span style="color:#808030;">)</span><span style="color:#808030;">:</span>
    HOST<span style="color:#808030;">=</span><span style="color:#0000e6;">'local'</span>
<span style="font-weight:bold;color:#800000;">elif</span> os<span style="color:#808030;">.</span>environ<span style="color:#808030;">.</span>get<span style="color:#808030;">(</span><span style="color:#0000e6;">'SERVER_SOFTWARE'</span><span style="color:#808030;">,</span><span style="color:#0000e6;">''</span><span style="color:#808030;">)</span><span style="color:#808030;">.</span>startswith<span style="color:#808030;">(</span><span style="color:#0000e6;">'Goog'</span><span style="color:#808030;">)</span><span style="color:#808030;">:</span>
    HOST<span style="color:#808030;">=</span><span style="color:#0000e6;">'google'</span>
<span style="font-weight:bold;color:#800000;">else</span><span style="color:#808030;">:</span>
    <span style="color:#696969;"># logging.error('Неизвестный сервер?')</span>
    HOST<span style="color:#808030;">=</span><span style="color:#0000e6;">'unknown'</span></pre>
<h3>Cookies?</h3>
<p>Google в своем интерфейсе определило объекты <a href="http://googleappengine.ru/docs/webapp/requestclass.html" target="_blank">request</a> и <a href="http://googleappengine.ru/docs/webapp/responseclass.html" target="_blank">response</a> как наследники соответствующих классов из библиотеки <a href="http://pythonpaste.org/webob/reference.html" target="_blank">WebOb</a>. Таким образом мы можем из объекта <em>request</em> получить содержимое cookie запроса:</p>
<pre style="color:#000000;">username <span style="color:#808030;">=</span> self<span style="color:#808030;">.</span>request<span style="color:#808030;">.</span>cookies<span style="color:#808030;">.</span>get<span style="color:#808030;">(</span><span style="color:#0000e6;">'username'</span><span style="color:#808030;">,</span> <span style="color:#0000e6;">''</span><span style="color:#808030;">)</span></pre>
<p>К сожалению, вы не сможете напрямую использовать метод <em>response.set_cookie </em>из библиотеки WebOb. Но это можно всегда сделать вручную:</p>
<pre style="color:#000000;">self<span style="color:#808030;">.</span>response<span style="color:#808030;">.</span>headers<span style="color:#808030;">.</span>add_header<span style="color:#808030;">(</span>
        <span style="color:#0000e6;">'Set-Cookie'</span><span style="color:#808030;">,</span>
        <span style="color:#0000e6;">'username=%s; expires=Fri, 31-Dec-2020 23:59:59 GMT'</span> \
          <span style="color:#808030;">%</span> username<span style="color:#808030;">.</span>encode<span style="color:#808030;">(</span><span style="color:#808030;">)</span><span style="color:#808030;">)</span></pre>
<p>Дополнительные методы работы с cookie есть <a href="http://groups.google.com/group/google-appengine/browse_thread/thread/6120f16c2db05f7e/e8262c5cb4d6685c?lnk=gst" target="_blank">в группе обсуждения</a> на английском языке.</p>
<h3>Отладка работы с данными хранилища</h3>
<p>Я создал <a href="http://ai.pjwstk.edu.pl/%7Emajek/dump/debug.py" target="_blank">простой отладчик для работы с хранилищем</a>. Он добавляет отладочную информацию в конец каждой создаваемой страницы. Для его использования требуется, чтобы классы обработчиков наследовались от <em>debug.DebugMiddleware</em> вместо <em>webapp.RequestHandler</em>.</p>
<p>К примеру:</p>
<pre style="color:#000000;"><span style="font-weight:bold;color:#800000;">class</span> List<span style="color:#808030;">(</span>debug<span style="color:#808030;">.</span>DebugMiddleware<span style="color:#808030;">)</span><span style="color:#808030;">:</span>
    <span style="font-weight:bold;color:#800000;">def</span> get<span style="color:#808030;">(</span>self<span style="color:#808030;">)</span><span style="color:#808030;">:</span>
        <span style="color:#808030;">.</span><span style="color:#808030;">.</span><span style="color:#808030;">.</span> blabla <span style="color:#808030;">.</span><span style="color:#808030;">.</span><span style="color:#808030;">.</span></pre>
<p>Отдалочная информация будет выглядеть так:</p>
<pre style="color:#000000;"><span style="color:#808030;">*</span><span style="color:#808030;">*</span><span style="color:#808030;">*</span><span style="color:#808030;">*</span> Request took<span style="color:#808030;">:</span>   830ms<span style="color:#808030;">/</span>170ms <span style="color:#808030;">(</span>real time<span style="color:#808030;">/</span>cpu time<span style="color:#808030;">)</span>
<span style="color:#808030;">*</span><span style="color:#808030;">*</span><span style="color:#808030;">*</span><span style="color:#808030;">*</span> GQLs<span style="color:#808030;">,</span> datastore accessed <span style="color:#008c00;">1</span> times<span style="color:#808030;">.</span>
98ms GQL app<span style="color:#808030;">:</span> <span style="color:#800000;">"</span><span style="color:#0000e6;">:self</span><span style="color:#800000;">"</span>
            kind<span style="color:#808030;">:</span> <span style="color:#800000;">"</span><span style="color:#0000e6;">Image</span><span style="color:#800000;">"</span>
            Order <span style="color:#808030;">{</span>
            property<span style="color:#808030;">:</span> <span style="color:#800000;">"</span><span style="color:#0000e6;">modified</span><span style="color:#800000;">"</span>
            direction<span style="color:#808030;">:</span> <span style="color:#008c00;">2</span>
            <span style="color:#808030;">}</span>
            args<span style="color:#808030;">:</span> <span style="color:#808030;">(</span><span style="color:#008c00;">50</span><span style="color:#808030;">,</span><span style="color:#808030;">)</span> <span style="color:#808030;">{</span><span style="color:#808030;">}</span></pre>
<p>GQL запрос, который сгенерировал ее:</p>
<pre style="color:#000000;">ims <span style="color:#808030;">=</span> Image<span style="color:#808030;">.</span>all<span style="color:#808030;">(</span><span style="color:#808030;">)</span><span style="color:#808030;">.</span>order<span style="color:#808030;">(</span><span style="color:#0000e6;">"-modified"</span><span style="color:#808030;">)</span><span style="color:#808030;">.</span>fetch<span style="color:#808030;">(</span><span style="color:#008c00;">50</span><span style="color:#808030;">)</span></pre>
<p>Другой пример отладочной информации:</p>
<pre style="color:#000000;"><span style="color:#808030;">*</span><span style="color:#808030;">*</span><span style="color:#808030;">*</span><span style="color:#808030;">*</span> Request took<span style="color:#808030;">:</span>   150ms<span style="color:#808030;">/</span>130ms <span style="color:#808030;">(</span>real time<span style="color:#808030;">/</span>cpu time<span style="color:#808030;">)</span>
<span style="color:#808030;">*</span><span style="color:#808030;">*</span><span style="color:#808030;">*</span><span style="color:#808030;">*</span> GQLs<span style="color:#808030;">,</span> datastore accessed xx times<span style="color:#808030;">.</span>
  219ms PUT <span style="color:#808030;">(</span><span style="color:#808030;">{</span><span style="color:#800000;">'</span><span style="color:#0000e6;">full</span><span style="color:#800000;">'</span><span style="color:#808030;">:</span><span style="color:#808030;">.</span><span style="color:#808030;">.</span><span style="color:#808030;">.</span>
  178ms PUT <span style="color:#808030;">(</span><span style="color:#808030;">{</span><span style="color:#800000;">'</span><span style="color:#0000e6;">full</span><span style="color:#800000;">'</span><span style="color:#808030;">:</span><span style="color:#808030;">.</span><span style="color:#808030;">.</span><span style="color:#808030;">.</span>
    6ms GET <span style="color:#808030;">(</span><span style="color:#808030;">[</span>datastore<span style="color:#808030;">_</span>types<span style="color:#008c00;">.</span>Key<span style="color:#008c00;">.</span>from<span style="color:#808030;">_</span>path<span style="color:#808030;">(</span><span style="color:#800000;">'</span><span style="color:#0000e6;">Image</span><span style="color:#800000;">'</span><span style="color:#808030;">,</span> 350L<span style="color:#808030;">,</span> <span style="color:#808030;">_</span>app<span style="color:#808030;">=</span>u<span style="color:#0000e6;">'</span>srv'<span style="color:#808030;">)</span><span style="color:#808030;">]</span><span style="color:#808030;">,</span><span style="color:#808030;">)</span> <span style="color:#808030;">{</span><span style="color:#808030;">}</span>
    2ms GET <span style="color:#808030;">(</span><span style="color:#808030;">[</span>datastore<span style="color:#808030;">_</span>types<span style="color:#008c00;">.</span>Key<span style="color:#008c00;">.</span>from<span style="color:#808030;">_</span>path<span style="color:#808030;">(</span><span style="color:#800000;">'</span><span style="color:#0000e6;">Image</span><span style="color:#800000;">'</span><span style="color:#808030;">,</span> 349L<span style="color:#808030;">,</span> <span style="color:#808030;">_</span>app<span style="color:#808030;">=</span>u<span style="color:#0000e6;">'</span>srv'<span style="color:#808030;">)</span><span style="color:#808030;">]</span><span style="color:#808030;">,</span><span style="color:#808030;">)</span> <span style="color:#808030;">{</span><span style="color:#808030;">}</span>
    2ms GET <span style="color:#808030;">(</span><span style="color:#808030;">[</span>datastore<span style="color:#808030;">_</span>types<span style="color:#008c00;">.</span>Key<span style="color:#008c00;">.</span>from<span style="color:#808030;">_</span>path<span style="color:#808030;">(</span><span style="color:#800000;">'</span><span style="color:#0000e6;">Image</span><span style="color:#800000;">'</span><span style="color:#808030;">,</span> 348L<span style="color:#808030;">,</span> <span style="color:#808030;">_</span>app<span style="color:#808030;">=</span>u<span style="color:#0000e6;">'</span>srv'<span style="color:#808030;">)</span><span style="color:#808030;">]</span><span style="color:#808030;">,</span><span style="color:#808030;">)</span> <span style="color:#808030;">{</span><span style="color:#808030;">}</span></pre>
<p>Этот отладчик очень удобно использовать в Django.</p>
<h3>Динамическая загрузка изображений на сервер</h3>
<p>Вот код, который я использую:</p>
<pre style="color:#000000;"><span style="color:#a65700;">&#60;</span><span style="font-weight:bold;color:#800000;">form</span><span style="color:#274796;"> </span><span style="color:#074726;">action</span><span style="color:#808030;">=</span><span style="color:#0000e6;">"."</span><span style="color:#274796;"> </span><span style="color:#074726;">method</span><span style="color:#808030;">=</span><span style="color:#0000e6;">"post"</span><span style="color:#274796;"> </span><span style="color:#074726;">enctype</span><span style="color:#808030;">=</span><span style="color:#0000e6;">"multipart/form-data"</span><span style="color:#a65700;">&#62;</span>
    <span style="color:#a65700;">&#60;</span><span style="font-weight:bold;color:#800000;">label</span><span style="color:#a65700;">&#62;</span>File: <span style="color:#a65700;">&#60;/</span><span style="font-weight:bold;color:#800000;">label</span><span style="color:#a65700;">&#62;</span><span style="color:#a65700;">&#60;</span><span style="font-weight:bold;color:#800000;">input</span><span style="color:#274796;"> </span><span style="color:#074726;">name</span><span style="color:#808030;">=</span><span style="color:#0000e6;">"file"</span><span style="color:#274796;"> </span><span style="color:#074726;">type</span><span style="color:#808030;">=</span><span style="color:#0000e6;">"file"</span><span style="color:#a65700;">&#62;</span><span style="color:#a65700;">&#60;</span><span style="font-weight:bold;color:#800000;">br</span><span style="color:#274796;"> </span><span style="color:#a65700;">/&#62;</span>
    <span style="color:#a65700;">&#60;</span><span style="font-weight:bold;color:#800000;">input</span><span style="color:#274796;"> </span><span style="color:#074726;">type</span><span style="color:#808030;">=</span><span style="color:#0000e6;">"submit"</span><span style="color:#a65700;">&#62;</span>
<span style="color:#a65700;">&#60;/</span><span style="font-weight:bold;color:#800000;">form</span><span style="color:#a65700;">&#62;</span></pre>
<p>На стороне сервера:</p>
<pre style="color:#000000;"><span style="font-weight:bold;color:#800000;">class</span> Image<span style="color:#808030;">(</span>db<span style="color:#808030;">.</span>Model<span style="color:#808030;">)</span><span style="color:#808030;">:</span>
    name        <span style="color:#808030;">=</span> db<span style="color:#808030;">.</span>StringProperty<span style="color:#808030;">(</span><span style="color:#808030;">)</span>
    content     <span style="color:#808030;">=</span> db<span style="color:#808030;">.</span>BlobProperty<span style="color:#808030;">(</span><span style="color:#808030;">)</span>

<span style="font-weight:bold;color:#800000;">class</span> UploadImage<span style="color:#808030;">(</span>webapp<span style="color:#808030;">.</span>RequestHandler<span style="color:#808030;">)</span><span style="color:#808030;">:</span>
    <span style="font-weight:bold;color:#800000;">def</span> post<span style="color:#808030;">(</span>self<span style="color:#808030;">)</span><span style="color:#808030;">:</span>
        <span style="font-weight:bold;color:#800000;">if</span> <span style="color:#0000e6;">'file'</span> <span style="font-weight:bold;color:#800000;">not</span> <span style="font-weight:bold;color:#800000;">in</span> self<span style="color:#808030;">.</span>request<span style="color:#808030;">.</span>POST<span style="color:#808030;">:</span>
            self<span style="color:#808030;">.</span>error<span style="color:#808030;">(</span><span style="color:#008c00;">400</span><span style="color:#808030;">)</span>
            self<span style="color:#808030;">.</span>response<span style="color:#808030;">.</span>out<span style="color:#808030;">.</span>write<span style="color:#808030;">(</span><span style="color:#0000e6;">"Файл не указан!"</span><span style="color:#808030;">)</span>
            <span style="font-weight:bold;color:#800000;">return</span>

        <span style="font-weight:bold;color:#800000;">if</span> <span style="color:#808030;">(</span>self<span style="color:#808030;">.</span>request<span style="color:#808030;">.</span>POST<span style="color:#808030;">.</span>get<span style="color:#808030;">(</span><span style="color:#0000e6;">'file'</span><span style="color:#808030;">,</span> <span style="color:#e34adc;">None</span><span style="color:#808030;">)</span> <span style="font-weight:bold;color:#800000;">is</span> <span style="color:#e34adc;">None</span> <span style="font-weight:bold;color:#800000;">or</span>
           <span style="font-weight:bold;color:#800000;">not</span> self<span style="color:#808030;">.</span>request<span style="color:#808030;">.</span>POST<span style="color:#808030;">.</span>get<span style="color:#808030;">(</span><span style="color:#0000e6;">'file'</span><span style="color:#808030;">,</span> <span style="color:#e34adc;">None</span><span style="color:#808030;">)</span><span style="color:#808030;">.</span>filename<span style="color:#808030;">)</span><span style="color:#808030;">:</span>
            self<span style="color:#808030;">.</span>error<span style="color:#808030;">(</span><span style="color:#008c00;">400</span><span style="color:#808030;">)</span>
            self<span style="color:#808030;">.</span>response<span style="color:#808030;">.</span>out<span style="color:#808030;">.</span>write<span style="color:#808030;">(</span><span style="color:#0000e6;">"Файл не указан!"</span><span style="color:#808030;">)</span>
            <span style="font-weight:bold;color:#800000;">return</span>

        file_data <span style="color:#808030;">=</span> self<span style="color:#808030;">.</span>request<span style="color:#808030;">.</span>POST<span style="color:#808030;">.</span>get<span style="color:#808030;">(</span><span style="color:#0000e6;">'file'</span><span style="color:#808030;">)</span><span style="color:#808030;">.</span><span style="color:#e34adc;">file</span><span style="color:#808030;">.</span>read<span style="color:#808030;">(</span><span style="color:#808030;">)</span>
        file_name <span style="color:#808030;">=</span> self<span style="color:#808030;">.</span>request<span style="color:#808030;">.</span>POST<span style="color:#808030;">.</span>get<span style="color:#808030;">(</span><span style="color:#0000e6;">'file'</span><span style="color:#808030;">)</span><span style="color:#808030;">.</span>filename

        im <span style="color:#808030;">=</span> Image<span style="color:#808030;">(</span><span style="color:#808030;">)</span>
        im<span style="color:#808030;">.</span>name    <span style="color:#808030;">=</span> file_name
        im<span style="color:#808030;">.</span>content <span style="color:#808030;">=</span> file_data
        im<span style="color:#808030;">.</span>save<span style="color:#808030;">(</span><span style="color:#808030;">)</span>
        self<span style="color:#808030;">.</span>response<span style="color:#808030;">.</span>out<span style="color:#808030;">.</span>write<span style="color:#808030;">(</span><span style="color:#0000e6;">"Изображение %r сохранено."</span> <span style="color:#808030;">%</span> im<span style="color:#808030;">.</span>name<span style="color:#808030;">)</span></pre>
<h3>Как определить размер и тип изображения</h3>
<p>Существует реализации функции <a href="http://ai.pjwstk.edu.pl/%7Emajek/dump/getimageinfo.py" target="_blank">getImageInfo</a>, которая позволяет определять размер изображения без использования внешних библиотек. Использование очень простое:</p>
<pre style="color:#000000;">content_type<span style="color:#808030;">,</span> width<span style="color:#808030;">,</span> height <span style="color:#808030;">=</span> getImageInfo<span style="color:#808030;">(</span>im<span style="color:#808030;">.</span>content<span style="color:#808030;">)</span></pre>
<h3>Динамическая работа с изображениями</h3>
<p>По этому поводу существует отдельная <a href="http://googleappengine.ru/articles/images.html" target="_blank">статья</a> в официальной документации. Ниже приведен мой упрощенный код, который я использую:</p>
<pre style="color:#000000;"><span style="font-weight:bold;color:#800000;">class</span> ServeImage<span style="color:#808030;">(</span>webapp<span style="color:#808030;">.</span>RequestHandler<span style="color:#808030;">)</span><span style="color:#808030;">:</span>
    <span style="font-weight:bold;color:#800000;">def</span> get<span style="color:#808030;">(</span>self<span style="color:#808030;">,</span> key<span style="color:#808030;">)</span><span style="color:#808030;">:</span>
        im <span style="color:#808030;">=</span> db<span style="color:#808030;">.</span>get<span style="color:#808030;">(</span>db<span style="color:#808030;">.</span>Key<span style="color:#808030;">(</span>key<span style="color:#808030;">)</span><span style="color:#808030;">)</span>
        <span style="font-weight:bold;color:#800000;">if</span> <span style="font-weight:bold;color:#800000;">not</span> im<span style="color:#808030;">:</span>
            self<span style="color:#808030;">.</span>error<span style="color:#808030;">(</span><span style="color:#008c00;">404</span><span style="color:#808030;">)</span>
            <span style="font-weight:bold;color:#800000;">return</span>

        content_type<span style="color:#808030;">,</span> width<span style="color:#808030;">,</span> height <span style="color:#808030;">=</span> getImageInfo<span style="color:#808030;">(</span>im<span style="color:#808030;">.</span>content<span style="color:#808030;">)</span>
        self<span style="color:#808030;">.</span>response<span style="color:#808030;">.</span>headers<span style="color:#808030;">.</span>add_header<span style="color:#808030;">(</span><span style="color:#0000e6;">"Expires"</span><span style="color:#808030;">,</span> <span style="color:#0000e6;">"Thu, 01 Dec 2014 16:00:00 GMT"</span><span style="color:#808030;">)</span>
        self<span style="color:#808030;">.</span>response<span style="color:#808030;">.</span>headers<span style="color:#808030;">[</span><span style="color:#0000e6;">"Content-Type"</span><span style="color:#808030;">]</span> <span style="color:#808030;">=</span> content_type
        self<span style="color:#808030;">.</span>response<span style="color:#808030;">.</span>out<span style="color:#808030;">.</span>write<span style="color:#808030;">(</span>im<span style="color:#808030;">.</span>content<span style="color:#808030;">)</span></pre>
]]></content:encoded>
</item>
<item>
<title><![CDATA[Полнотекстовый поиск в Google App Engine]]></title>
<link>http://techworkru.wordpress.com/?p=276</link>
<pubDate>Tue, 19 Aug 2008 05:57:12 +0000</pubDate>
<dc:creator>techworkru</dc:creator>
<guid>http://techworkru.wordpress.com/?p=276</guid>
<description><![CDATA[Несмотря на то, что App Engine уже умеет выполнять полнотек]]></description>
<content:encoded><![CDATA[<p>Несмотря на то, что App Engine уже умеет выполнять полнотекстовый поиск, эта возможность еще не документирована, так как пока находится в активной разработке.</p>
<p>Когда платформа Google App Engine была впервые представлена широким массам, многие недоумевали, почему в Datastore API не реализован полнотекстовый поиск. Очень странно - бизнес Google основан по поиске, однако эти новое передовое решение его не поддерживает!</p>
<p>На сегодняшний момент интерфейс Google App Engine имеет примитивную реализацию полнотекстового поиска данных в хранилище, скрытую в недрах модуля google.appengine.ext.search. (По которому практически нет никакого описания, кроме самих исходных кодов). Вы можете использовать этот модуль, создавая модели наследники от класса <strong>search.SearchableModel</strong>, вместо обычного <strong>db.Model</strong>.</p>
<p>К примеру так:</p>
<pre>from google.appengine.ext import db
from google.appengine.ext import search

class Article(search.SearchableModel):
  title = db.TextProperty()
  publishDate = db.DateTimeProperty(auto_now_add=True)
  text = db.TextProperty()

# НЕ индексируется
article = Article(text ='''This is the totally secret article text
which talks about sausages and cheese in the middle of itself.''')
article.title = "Fine cuisine"
article.save() 

# индексируется
article = Article()
article.title = "What I feed my dogs"
article.text = '''This is the totally secret article text
which talks about sausages and cheese in the middle of itself.'''
article.save() 

print "Результаты"

query = Article.all().search("sausages cheese dogs").order("-publishDate")
for a in query:
  print "%s &#124; %s" % (a.title, a.publishDate)

print "Конец вывода"</pre>
<h3>Ограничения</h3>
<p>Алгоритм поиска тривиально выполняет нахождение заданных слов, он не проводит точное сравнение фразы, поиск подстроки, булевых операторов, не выполняет учет морфологии языка и не содержит прочих возможностей, присущих продвинутому полнотекстовому поиску.</p>
<p><strong>Латентность</strong></p>
<p><strong></strong> Философия работы хранилища заключается в том, что на сегодняшний момент дисковое пространство очень дешево, и соответственно выполнять обработку данных следует при сохранении объекта. Таким же образом работают модели SearchableModel, при вызове метода save() они выполняют построение индексов. Это означает, что модели типа SearchableModels будут работать всегда медленее стандартных моделей. Имейте всегда это ввиду.</p>
<p><strong>Индекс индекса</strong></p>
<p>Как вы знаете, пакет разработки Google App Engine SDK создает описание для всех необходимых индексов в файле index.yaml, для которых возникает потребность при разработке приложения. Однако, если вы не имеете возможности провести тесты приложения, покрывающие целиком все его запросы, тогда необходимо добавить описания соответствующих индексов в файл index.yaml вручную. В этих случаях необходимо учесть, что полнотекстовый индекс содержится в свойстве с именем __searchable_text_index. К примеру, индекс можно определить как:</p>
<pre><code>  - kind: Article
    properties:
    - name: __searchable_text_index
    - name: publishDate
      direction: desc
</code></pre>
<p>Это все о работе полнотекстового поиска в App Engine. Пока он не поражает обилием своих возможностей, однако этого хватит для большинства применений.</p>
]]></content:encoded>
</item>
<item>
<title><![CDATA[Epic Quota fail.]]></title>
<link>http://vipulsolanki.wordpress.com/?p=51</link>
<pubDate>Mon, 18 Aug 2008 09:57:58 +0000</pubDate>
<dc:creator>vipulsolanki</dc:creator>
<guid>http://vipulsolanki.wordpress.com/?p=51</guid>
<description><![CDATA[Google AppEngine Quota fail in their own AppGallery application.

]]></description>
<content:encoded><![CDATA[<p style="text-align:center;">Google AppEngine Quota fail in their own AppGallery application.</p>
<p style="text-align:center;"><a href="http://vipulsolanki.wordpress.com/files/2008/08/appengine.jpg"><img class="alignnone size-medium wp-image-50" src="http://vipulsolanki.wordpress.com/files/2008/08/appengine.jpg?w=300" alt="" width="300" height="151" /></a></p>
]]></content:encoded>
</item>
<item>
<title><![CDATA[Web2py - первый взгляд]]></title>
<link>http://techworkru.wordpress.com/?p=255</link>
<pubDate>Mon, 18 Aug 2008 09:11:58 +0000</pubDate>
<dc:creator>techworkru</dc:creator>
<guid>http://techworkru.wordpress.com/?p=255</guid>
<description><![CDATA[Web2Py (ранее называвшийся Gluon) - это новое слово в фреймво]]></description>
<content:encoded><![CDATA[<p><strong>Web2Py</strong> (ранее называвшийся Gluon) - это новое слово в фреймворках для Python. Он очень похож на Django, но позволяет создавать и разрабатывать приложения прямо в онлайне. Он делает за вас большую часть работы и теперь появилась возможность запустить его на платформе Google App Engine.</p>
<p>На сайте Vimeo <a href="http://www.vimeo.com/moogaloop.swf?clip_id=932708&#38;server=www.vimeo.com&#38;show_title=1&#38;show_byline=1&#38;show_portrait=0&#38;color=&#38;fullscreen=0" target="_blank">выложен пример</a>, показывающий начало разработки с Web2Py:</p>
<p><a href="http://techworkru.files.wordpress.com/2008/08/web2py.png"><img class="aligncenter size-full wp-image-257" src="http://techworkru.wordpress.com/files/2008/08/web2py.png" alt="" width="431" height="271" /></a></p>
]]></content:encoded>
</item>
<item>
<title><![CDATA[Загрузка файлов через форму Django Forms]]></title>
<link>http://techworkru.wordpress.com/?p=253</link>
<pubDate>Mon, 18 Aug 2008 08:37:12 +0000</pubDate>
<dc:creator>techworkru</dc:creator>
<guid>http://techworkru.wordpress.com/?p=253</guid>
<description><![CDATA[Поле FileField из библиотеки Django Forms можно привязать к свой]]></description>
<content:encoded><![CDATA[<p>Поле FileField из библиотеки Django Forms можно привязать к свойству типа BlobProperty объекта хранилища. Ниже описано как это делается:</p>
<p><strong>Модель:</strong></p>
<pre>from appengine_django.models import BaseModel
from google.appengine.ext import db

class AlbumItem(BaseModel):
  creationDate = db.DateTimeProperty(auto_now_add=True)
  image = db.BlobProperty(required=True)
  note = db.StringProperty()</pre>
<p><strong>Отображение формы:</strong></p>
<pre>class AlbumItemForm(ModelForm):
 image = FileField()
 note = CharField(widget=Textarea)

 class Meta:
  model = AlbumItem
  exclude = ("creationDate")

def albumentry(request):
 if request.POST:
  form = AlbumItemForm(request.POST, request.FILES)
  if form.is_valid():
   albumEntry = form.save()
   return HttpResponseRedirect('/showalbumentry/%s' % albumEntry.key())
  else:
   return render_to_response('albumentry.html', locals())
 else:
  form = AlbumItemForm()
  return render_to_response('albumentry.html', locals())</pre>
<p>Содержимое указанного в коде файла шаблона <em>albumentry.html</em> тривиально, мы не будем полностью описывать его. Главное поместите в код формы шаблонную переменную <strong>{{form}}</strong> и не забудьте выставить атрибут <strong>enctype="multipart/form-data"</strong> в теге &#60;form&#62;.</p>
<p>Все работает автоматически - содержимое файла с изображением, загружаемое на сервер, будет помещено в свойство <em>image</em>. Используемый код работает на версиях  App Engine SDK 1.1, Django Helper r30 и Django 0.97.</p>
]]></content:encoded>
</item>
<item>
<title><![CDATA[Использование reCAPTCHA в Google App Engine]]></title>
<link>http://techworkru.wordpress.com/?p=251</link>
<pubDate>Sun, 17 Aug 2008 11:00:53 +0000</pubDate>
<dc:creator>techworkru</dc:creator>
<guid>http://techworkru.wordpress.com/?p=251</guid>
<description><![CDATA[Борьба со спамом приобретает промышленные масштабы, и ]]></description>
<content:encoded><![CDATA[<p>Борьба со спамом приобретает промышленные масштабы, и одним из способов помешать искусственному интеллекту присылать нашему приложению "рекламные подарки", является установка на сайт <a href="http://ru.wikipedia.org/wiki/CAPTCHA" target="_blank">CAPTCHA</a>.</p>
<p>Одним из популярных разновидностей такого сервиса является <a href="http://recaptcha.net/">reCAPTCHA</a>, созданная в университете Карнеги Меллон, которая не только дает наилучшие результаты из себе подобных, но и помогает человечеству оцифровывать книги.</p>
<h3>С чего начать?</h3>
<p>Сначала необходимо получить <a href="http://recaptcha.net/api/getkey" target="_blank">ключ сервиса</a> на сайте recaptcha.net.<br />
После регистрации вы сможете создать неограниченное количество сервисных ключей для вашего домена.<br />
<strong>Примечание:</strong> Если вы хотите, чтобы reCAPTCHA корректно работала в отладочной среде, то необходимо также получить ключ для localhost.<br />
После генерации пары ключей, вы увидите следующую страницу:</p>
<p><a href="http://bp2.blogger.com/_lBJLUOoqxdY/SBhXGtPCKWI/AAAAAAAAAOE/m9wzHNAB51A/s1600-h/recaptcha_yourdomain.PNG"><img style="display:block;text-align:center;cursor:pointer;margin:0 auto 10px;" src="http://bp2.blogger.com/_lBJLUOoqxdY/SBhXGtPCKWI/AAAAAAAAAOE/m9wzHNAB51A/s400/recaptcha_yourdomain.PNG" border="0" alt="" /></a>Сохраните этот публичный и приватный ключ - они понадобятся вам позже. Если вдруг они будут случайно потеряны, то сможете вернуться на сайт recaptcha.net и снова их получить. Ни в коем случае не сообщайте свой приватный ключ.</p>
<p>После регистрации в сервисе, вам потребуется простой класс на языке <a href="http://bp1.blogger.com/_lBJLUOoqxdY/SBhqddPCKbI/AAAAAAAAAOs/F_Lvw7wbT_4/s1600-h/helloworld_package_structure.png"><img style="float:right;cursor:pointer;margin:0 0 10px 10px;" src="http://bp1.blogger.com/_lBJLUOoqxdY/SBhqddPCKbI/AAAAAAAAAOs/F_Lvw7wbT_4/s320/helloworld_package_structure.png" border="0" alt="" /></a></p>
<p>Python, <a href="http://pypi.python.org/pypi/recaptcha-client" target="_blank">адаптированный для App Engine.</a> Скачать его можно <a href="http://dev.feth.com/Python/Google%20App%20Engine/recaptcha/captcha.source" target="_blank">здесь</a>. После загрузки переименуйте его в captcha.py и скопируйте в каталог приложения (я предпочитаю размещать его в подкаталоге recaptcha/client/ - не забудьте добавить к каждому подкаталогу пустой файл __init__.py).</p>
<p>Для демонстрации работы мы дополним стандартный пример из Руководства для начинающих Google App Engine.</p>
<p>Откройте файл helloworld.py и добавьте строчки импорта модулей captcha и environ:</p>
<pre><span style="color:#3366ff;">from </span>os <span style="color:#3366ff;">import </span>environ
<span style="color:#3366ff;">from </span>recaptcha.client <span style="color:#3366ff;">import </span>captcha</pre>
<p>Затем перейдите к контроллеру <span style="font-family:courier new;">MainPage</span> и добавьте следующее:</p>
<pre>chtml = captcha.displayhtml(
  public_key = <span style="color:#33cc00;">"ВАШ-ПУБЛИЧНЫЙ-КЛЮЧ"</span>,
  use_ssl = <span style="color:#3366ff;">False</span>,
  error = <span style="color:#3366ff;">None</span>)

template_values = {
<span style="color:#000000;">   ...</span>
<span style="color:#ff0000;">   'captchahtml': chtml</span>
}</pre>
<p>Соответственно замените выражение <span style="font-family:courier new;">ВАШ-ПУБЛИЧНЫЙ-КЛЮЧ</span> своим ключом, иначе получите ошибку:<br />
<span style="font-weight:bold;font-style:italic;">"Invalid public key. Make sure you copy and pasted it correctly."</span></p>
<p>Теперь перейдите к файлу с шаблоном и добавьте тэг <span style="font-family:courier new;">captchahtml</span> внутри формы:</p>
<pre>&#60;form&#62;
  ...
<span style="color:#ff0000;">    {{ captchahtml }}</span>
&#60;/form&#62;</pre>
<p>После этого можно открыть свой браузер и увидеть reCAPTCHA в отдельном iframe:</p>
<p><a href="http://bp3.blogger.com/_lBJLUOoqxdY/SBhhD9PCKYI/AAAAAAAAAOU/78Lp0rAsBjk/s1600-h/recaptcha_example_box.PNG"><img style="display:block;text-align:center;cursor:pointer;margin:0 auto 10px;" src="http://bp3.blogger.com/_lBJLUOoqxdY/SBhhD9PCKYI/AAAAAAAAAOU/78Lp0rAsBjk/s320/recaptcha_example_box.PNG" border="0" alt="" /></a></p>
<p>Теперь после того, как получены отправленные через форму данные, нам необходимо выполнить проверку того, что в катпчу введен правильный код. Мы проведем изменение метода <span style="font-family:courier new;">post</span> класса <span style="font-family:courier new;">Guestbook</span> и добавим код:</p>
<pre><span style="color:#3366ff;">def </span>post(<span style="font-style:italic;">self</span>):
  challenge = <span style="font-style:italic;color:#000000;">self.</span>request.get(<span style="color:#33cc00;">'recaptcha_challenge_field'</span>)
  response  = <span style="font-style:italic;color:#000000;">self</span>.request.get(<span style="color:#33cc00;">'recaptcha_response_field'</span>)
  remoteip  = environ[<span style="color:#33cc00;">'REMOTE_ADDR'</span>]

  cResponse = captcha.submit(
                 challenge,
                 response,
                 <span style="color:#33cc00;">"ВАШ-ПРИВАТНЫЙ-КЛЮЧ"</span>,
                 remoteip)

<span style="color:#3366ff;">    if </span>cResponse.is_valid:
<span style="color:#999999;">        # код введен верный</span>
<span style="color:#999999;">        # продолжаем работу приложения
<span style="color:#000000;">    <span style="color:#3366ff;">else</span>:
  </span></span>    <span style="color:#999999;"><span style="color:#000000;">error = cResponse.error_code
</span></span>    <span style="color:#999999;"><span style="color:#000000;">...
</span></span></pre>
<p>Аналогично, замените выражение <span style="font-family:courier new;">ВАШ-ПРИВАТНЫЙ-КЛЮЧ</span> ранее полученным приватным ключом.</p>
<p>Информация, введенная пользователем в поле reCAPTCHA будет отправлена на проверку вместе с его IP адресом. Мы получим ответ от сервера reCAPTCHA в формате объекта <span style="font-family:courier new;">RecaptchaResponse</span>.</p>
<p>Объект <span style="font-family:courier new;">RecaptchaResponse</span> имеет два атрибута:</p>
<ul>
<li> <span style="font-family:courier new;">is_valid</span>, установленный в True, если тест был пройден удачно (в противном случае False)</li>
<li><span style="font-family:courier new;">error_code</span> , содержащий <a href="http://recaptcha.net/apidocs/captcha/" target="_blank">код ошибки API</a>, если произошла какая-то проблема.</li>
</ul>
<p><strong>Примечание</strong>: В Руководстве для начинающих Google App Engine, информация введенная в формы, передавалась контроллеру Guestbook. В обычном случае мы будем передавать ее в контроллер MainPage, и обрабатывать код ошибки из объекта RecaptchaResponse (если он будет) методом <span style="font-family:courier new;">displayhtml</span> класса captcha:</p>
<pre>chtml = captcha.displayhtml(
  public_key = <span style="color:#33cc00;">"ВАШ-ПУБЛИЧНЫЙ-КЛЮЧ"</span>,
  use_ssl = <span style="color:#3366ff;">False</span>,
  <span style="color:#ff0000;">error = </span><span style="color:#ff0000;">cResponse.error_code</span>)</pre>
<p>Это позволит вывести читаемое сообщение об ошибке пользователю:</p>
<p><a href="http://bp1.blogger.com/_lBJLUOoqxdY/SBhmGdPCKZI/AAAAAAAAAOc/JTuR2GDmQVg/s1600-h/recaptcha_example_box_wrong.PNG"><img style="display:block;text-align:center;cursor:pointer;margin:0 auto 10px;" src="http://bp1.blogger.com/_lBJLUOoqxdY/SBhmGdPCKZI/AAAAAAAAAOc/JTuR2GDmQVg/s320/recaptcha_example_box_wrong.PNG" border="0" alt="" /></a></p>
<p>и дать ему возможность еще раз ввести ответ на CAPTCHA без необходимости повторного ввода других данных в поля формы.</p>
<p><strong>Имейте ввиду:</strong> Для каждой отправленной пользователем CAPTCHA, выполняется удаленный запрос к серверу reCAPTCHA. Этот запрос является <span style="font-weight:bold;">синхронным</span>, и таким образом посетитель будет ждать некоторое время, пока сервер обработает этот запрос. В том случае, если сервер reCAPTCHA в этот момент не был доступен, будет возвращен код ответа <span style="font-family:courier new;">recaptcha-not-reachable</span>.</p>
]]></content:encoded>
</item>
<item>
<title><![CDATA[Установка расширения Subversion в Eclipse]]></title>
<link>http://techworkru.wordpress.com/?p=231</link>
<pubDate>Sat, 16 Aug 2008 09:02:41 +0000</pubDate>
<dc:creator>techworkru</dc:creator>
<guid>http://techworkru.wordpress.com/?p=231</guid>
<description><![CDATA[К сожалению, в стандартной поставке среды Eclipse содержи]]></description>
<content:encoded><![CDATA[<p>К сожалению, в стандартной поставке среды Eclipse содержится только система управления версиями CVS. Мы рассмотрим процесс установки более новой системы управления Subversion.<br />Установку можно начать из меню <strong>Help -&#62; Software Updates -&#62; Find and Install...</strong>:</p>
<p><a href="http://techworkru.files.wordpress.com/2008/08/eclipse-svn1.png"><img class="aligncenter size-medium wp-image-232" src="http://techworkru.wordpress.com/files/2008/08/eclipse-svn1.png?w=300" alt="" width="300" height="210" /></a></p>
<p>Выбираем <strong>Search new features to install </strong>и нажимаем на кнопку <strong>Next &#62;:</strong></p>
<p><a href="http://techworkru.files.wordpress.com/2008/08/eclipse-svn2.png"><img class="aligncenter size-medium wp-image-233" src="http://techworkru.wordpress.com/files/2008/08/eclipse-svn2.png?w=259" alt="" width="259" height="300" /></a></p>
<p>В появившемся окне нажимаем кнопку <strong>New remote site:</strong></p>
<p><a href="http://techworkru.files.wordpress.com/2008/08/eclipse-svn3.png"><img class="aligncenter size-medium wp-image-234" src="http://techworkru.wordpress.com/files/2008/08/eclipse-svn3.png?w=259" alt="" width="259" height="300" /></a></p>
<p>Далее заполняем поля Name названием расширения <strong>Subclipse</strong> и указываем его адрес в поле URL: <a href="http://subclipse.tigris.org/update_1.4.x">http://subclipse.tigris.org/update_1.4.x</a>. Убеждаемся, что в главном окне установлена галочка на вновь добавленном сайте Subclipse:</p>
<p><a href="http://techworkru.files.wordpress.com/2008/08/eclipse-svn5.png"><img class="aligncenter size-medium wp-image-235" src="http://techworkru.wordpress.com/files/2008/08/eclipse-svn5.png?w=259" alt="" width="259" height="300" /></a></p>
<p>Нажимаем кнопку <strong>Finish</strong>. В следующем окне выбираем опции <strong>SVNKit Adapter</strong> и <strong>Subclipse</strong>. Щелкаем по кнопке <strong>Next &#62;</strong>:</p>
<p><a href="http://techworkru.files.wordpress.com/2008/08/eclipse-svn6.png"><img class="aligncenter size-medium wp-image-236" src="http://techworkru.wordpress.com/files/2008/08/eclipse-svn6.png?w=263" alt="" width="263" height="300" /></a></p>
<p>Далее принимаем условия лицензионного соглашения:</p>
<p><a href="http://techworkru.files.wordpress.com/2008/08/eclipse-svn7.png"><img class="aligncenter size-medium wp-image-237" src="http://techworkru.wordpress.com/files/2008/08/eclipse-svn7.png?w=255" alt="" width="255" height="300" /></a></p>
<p>На следующем экране проверяем выбранные опции установки и жмем по кнопке <strong>Finish</strong>:</p>
<p><a href="http://techworkru.files.wordpress.com/2008/08/eclipse-svn8.png"><img class="aligncenter size-medium wp-image-238" src="http://techworkru.wordpress.com/files/2008/08/eclipse-svn8.png?w=263" alt="" width="263" height="300" /></a></p>
<p>Ну и напоследок щелкаем по кнопке <strong>Install All</strong>:</p>
<p><a href="http://techworkru.files.wordpress.com/2008/08/eclipse-svn9.png"><img class="aligncenter size-medium wp-image-239" src="http://techworkru.wordpress.com/files/2008/08/eclipse-svn9.png?w=300" alt="" width="300" height="250" /></a></p>
<p>После проведения установки, Eclipse сообщит нам, что необходимо перезапустить среду для применения изменений:</p>
<p><a href="http://techworkru.files.wordpress.com/2008/08/eclipse-svn10.png"><img class="aligncenter size-medium wp-image-240" src="http://techworkru.wordpress.com/files/2008/08/eclipse-svn10.png?w=300" alt="" width="300" height="89" /></a></p>
<p>В завершении м