<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0"><channel><description>
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));

try {
var pageTracker = _gat._getTracker("UA-10316527-4");
pageTracker._trackPageview();
} catch(err) {}</description><title>Кушаем у мистера Джо</title><generator>Tumblr (3.0; @mrjoes)</generator><link>http://mrjoes.tumblr.com/</link><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/EatAtMrJoes" /><feedburner:info uri="eatatmrjoes" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://tumblr.superfeedr.com/" /><item><title>Realtime Web №3 - TornadIO2</title><description>&lt;p&gt;Написал мини-статью для Хабра о новой TornadIO2 с поддержкой самых новых с свежих версий socket.io.&lt;/p&gt;
&lt;p&gt;Смотреть &lt;a href="http://habrahabr.ru/blogs/python/133350/" title="Habrahabr Link"&gt;тут&lt;/a&gt;.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/EatAtMrJoes/~4/c2vGe6FJQ5Y" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/EatAtMrJoes/~3/c2vGe6FJQ5Y/13273802122</link><guid isPermaLink="false">http://mrjoes.tumblr.com/post/13273802122</guid><pubDate>Thu, 24 Nov 2011 18:28:51 -0500</pubDate><category>tornado</category><category>python</category><category>programming</category><category>socket.io</category><category>tornadio</category><feedburner:origLink>http://mrjoes.tumblr.com/post/13273802122</feedburner:origLink></item><item><title>Почему питон кушает много памяти</title><description>&lt;p&gt;Короткая заметка о том, почему python любит память. Я, возможно, покажусь Капитаном Очевидностью, но все же.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;Вот возьмем такой рафинированый пример:&lt;/p&gt;
&lt;pre&gt;class A(object):
    def __init__(self, i):
        self.j = i

x = [A(i) for i in xrange(1000000)]

input()&lt;/pre&gt;
&lt;p&gt;Запустим его на 32 битной системе (с 32 битным питоном) и увидим что он кушает, примерно, 190 МБ памяти.&lt;/p&gt;
&lt;p&gt;Как-то много для массива из миллиона указателей на объекты размером в 4 байта.&lt;/p&gt;
&lt;p&gt;Теперь берем 64 битную систему и проверяем сколько скушает данный пример. У &lt;a href="http://piranha.org.ua/"&gt;piranha&lt;/a&gt; получилось 384 МБ - в примерно два раза больше чем у меня.&lt;/p&gt;
&lt;p&gt;Упс.&lt;/p&gt;
&lt;p&gt;А теперь попытаемся разобраться чем же это все может быть вызвано.&lt;/p&gt;
&lt;p&gt;Мне было совсем лениво заниматься тестами памяти, тем более что кто-то это уже сделал - &lt;a href="http://www.valuedlessons.com/2008/10/blog-post.html"&gt;&lt;a href="http://www.valuedlessons.com/2008/10/blog-post.html"&gt;http://www.valuedlessons.com/2008/10/blog-post.html&lt;/a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Посмотрим на табличку. new-style class = 336, int = 24. (336+24)*1000000 = 360,000,000 или ~351 МБ. Из количества используемой памяти можно сделать вывод, что тесты проводились на 64-битной системе. Остальное можно списать на стандартные либы, системные либы в процессе питона и так далее.&lt;/p&gt;
&lt;p&gt;Откуда столько памяти на один int? Вспоминаем что питон работает с объектами. Имена переменных это просто ссылки на объекты. Кроме того, сборщик мусора работает на подсчете ссылок, так что каждый объект должен хранить количество ссылок на себя.&lt;/p&gt;
&lt;p&gt;Вот и получается - поскольку все на указателях, то при переходе на архитектуру с вдвое большим размером оных, использование памяти в питоне увеличивается почти в два раза. Да и без перехода получается ерунда - 24 байта на значение в 4 байта это тоже overkill.&lt;/p&gt;
&lt;p&gt;Так что, если у вас памяти не очень много (VPS, etc) - однозначно использовать 32 битный питон. Если памяти много (больше 3 ГБ), то тогда прийдется использовать 64 битный, так как &lt;a href="http://ru.wikipedia.org/wiki/PAE"&gt;PAE&lt;/a&gt; еще и притормаживает.&lt;/p&gt;
&lt;p&gt;Такие вот пироги с котятами.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/EatAtMrJoes/~4/hWg-55aoHfY" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/EatAtMrJoes/~3/hWg-55aoHfY/3400635288</link><guid isPermaLink="false">http://mrjoes.tumblr.com/post/3400635288</guid><pubDate>Sun, 20 Feb 2011 05:44:00 -0500</pubDate><category>python</category><category>memory</category><category>sigh</category><category>cruel life</category><feedburner:origLink>http://mrjoes.tumblr.com/post/3400635288</feedburner:origLink></item><item><title>greenlet/gevent</title><description>&lt;p&gt;Тут будет немного сумбурных мыслей по поводу greenlet’ов в контексте gevent.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;&lt;strong&gt;Гринлеты это хак&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Немного пояснений о том как же работают гринлеты и к чему это все может привести.&lt;/p&gt;
&lt;p&gt;В питоне весь стек - unmanaged. Это означает, что используется “обычный” стек, который предоставляет операционная система вместе со своим менеджментом стековой памяти (дополнительное выделение страниц под стек если нужно еще и так далее). В этом самом unmanaged стеке хранятся всякие локальные переменные питоновского интерпретатора, локальные переменные сишных модулей и тому подобное. Такая вот солянка из всего сразу.&lt;/p&gt;
&lt;p&gt;Поскольку питон - интерпретируемый язык и если бы у него был управляемый стек (стек програмы на питоне был бы реализован сам интерпретатором), то greenlet’ы делаются элементарно - заводим два стека и все.&lt;/p&gt;
&lt;p&gt;А поскольку стек как раз не управляется питоном, то greenlet начинает шаманить с системным стеком. А именно:&lt;/p&gt;
&lt;p&gt;1. При переключении гринлетов (из А в Б) вычисляет размер использованого стека относительно вышестоящего стек-фрейма&lt;br/&gt;2. Копирует данную область из стека в кучу (делая malloc/realloc)&lt;br/&gt;3. Переписывает данные из кучи в стек для гринлета Б&lt;br/&gt;4. Патчит регистры процессора, что бы указатель стека был на правильном месте&lt;br/&gt;5. Освобождает память в куче для гринлета Б (free)&lt;br/&gt;6. Патчит PyThreadState (переменные recursion_depth, top_frame) что бы питон не сломался&lt;br/&gt;7. Возвращает управление в интерпретатор питона&lt;/p&gt;
&lt;p&gt;И того, на одно переключение гринлета происходит 2 memcpy, возможно один realloc и один free. Зачем free? Потому что при алгоритмах с рекурсией размер стека может быть большим. Например - 50 КБ. Соответственно если после переключения оставлять копию стека в куче - получим 2х кратное увеличение использования памяти.&lt;/p&gt;
&lt;p&gt;Какие общие проблемы у такого подхода:&lt;/p&gt;
&lt;p&gt;1. Проблемы со сборкой мусора&lt;/p&gt;
&lt;p&gt;Из официальных док: Greenlets do not participate in garbage collection; cycles involving data that is present in a greenlet’s frames will not be detected. Storing references to other greenlets cyclically may lead to leaks.&lt;/p&gt;
&lt;p&gt;Что означает - любые переменные которые были выгружены гринлетами в кучу из стека не будут принимать участия в сборке. А поскольку у питона обычный reference count, то и все объекты на которые они ссылаются тоже подчищены не будут. &lt;/p&gt;
&lt;p&gt;Так что прийдется очень внимательно писать код, а то потом прийдется ловить ошибки на продакшене.&lt;/p&gt;
&lt;p&gt;2. Вызов функции в питоне обойдется дешевле чем переключение гринлета. Возможно поэтому tornado в тестах был чуть-чуть, но быстрее чем gevent.&lt;/p&gt;
&lt;p&gt;3. Магия для интерпретатора&lt;/p&gt;
&lt;p&gt;PyThreadState это как бы внутренняя структура питона, которая используется для определения текущего состояния интерпретатора в текущем потоке. В ней хранится текущий уровень рекурсии, ссылка на текущий фрейм, текущее исключение (если есть) и т.д.&lt;/p&gt;
&lt;p&gt;Гринлеты занимаются магией и влезают в стек. Если произойдет исключение будет очень трудно посмотреть реальный стек-трейс.&lt;/p&gt;
&lt;p&gt;Если произойдет исключение в гринлете, оно передается на вышестоящий гринлет. Если произошло еще одно исключение в вышестоящем гринлете, то исходное исключение потеряется. А все потому, что питон работает категориями потоков (PyThreadState на поток), а у нас кучка виртуальных потоков в одном реальном. К слову, из одного гринлета можно читать исключение которое произошло в другом гринлете.&lt;/p&gt;
&lt;p&gt;Реальный размер используемого системного стека может быть значительно больше чем значение PyThreadState.recursion_depth&lt;span&gt; - &lt;/span&gt;при слоеном пироге из фреймов разных гринлетов. Чем грозит - не знаю, памяти сейчас много, но может себя вести странно на “нестандартных” (не х86) архитектурах.&lt;/p&gt;
&lt;p&gt;К слову, текущая интеграция для x64 нарушает конвенции вызовов функций (ABI) - не сохраняет все нужные регистры при переключении гринлетов. Так что я бы не советовал их использовать на х64 - можно словить кучу ошибок.&lt;/p&gt;
&lt;p&gt;Вот тут еще есть немного: &lt;a href="http://code.google.com/p/coev/wiki/GreenletProblems"&gt;&lt;a href="http://code.google.com/p/coev/wiki/GreenletProblems"&gt;http://code.google.com/p/coev/wiki/GreenletProblems&lt;/a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ну а теперь о хорошем.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Мне нравится концепция асинхронного программирования без callback’ов. Код становится сильно проще и красивее.&lt;/p&gt;
&lt;p&gt;gevent, в целом - симпатичный. Ну да, нужно патчить все что можно, что бы библиотека питона стала асинхронной. Некрасиво, но других вариантов нет.&lt;/p&gt;
&lt;p&gt;Например взять ту же SQLAlchemy - ее заставили работать асинхронно под gevent. Ага, monkey patching, но какие есть альтернативы, учитывая что это самая вменяемая ORM и она, к сожалению, не умеет работать асинхронно из коробки?&lt;/p&gt;
&lt;p&gt;Что плохо - основа на которой написан gevent меня пугает, поскольку мне страшно ставить ее на высоконагруженный продакшн. Слишком много магии - очень плохо. Хотя вроде spotify что-то там писал на gevent, может библиотека уже production ready.&lt;/p&gt;
&lt;p&gt;Ну а какие есть альтернативы?&lt;/p&gt;
&lt;p&gt;Торнадо, да. Но появляется требование работы с базой данных и привет. Без баз данных - магии нет, код проще, но callback’и.&lt;/p&gt;
&lt;p&gt;Такие вот дела.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/EatAtMrJoes/~4/5aZubcyYJSU" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/EatAtMrJoes/~3/5aZubcyYJSU/3197071123</link><guid isPermaLink="false">http://mrjoes.tumblr.com/post/3197071123</guid><pubDate>Wed, 09 Feb 2011 05:45:00 -0500</pubDate><category>python</category><category>async</category><category>web</category><category>gevent</category><feedburner:origLink>http://mrjoes.tumblr.com/post/3197071123</feedburner:origLink></item><item><title>Realtime Web №2 - TornadIO</title><description>&lt;p&gt;В продолжение предыдущего &lt;a href="http://mrjoes.tumblr.com/post/2599984527/realtimeweb"&gt;поста&lt;/a&gt; про Web реального времени: &lt;a href="http://socket.io/"&gt;socket.io&lt;/a&gt; oказался хорошим, а вот существующая серверная поддержка для питона под названием &lt;a href="https://github.com/SocketTornadIO/SocketTornad.IO"&gt;SocketTornad.IO&lt;/a&gt; - не очень. Кроме кучки существующих багов, похоже что проект умер. Да и внутри там еще та каша.&lt;/p&gt;
&lt;p&gt;И в результате - приветствуем TornadIO. TornadIO - &lt;a href="http://ru.wikipedia.org/wiki/Comet_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)"&gt;Comet&lt;/a&gt; библиотека для организации постоянной связи между браузером и сервером для быстрой передачи сообщений туда и обратно.&lt;/p&gt;
&lt;p&gt;По сути, переписал заново SocketTornad.IO, исправил ошибки, добавил всяких вкусностей и так далее. Вот сегодня его добавили на главную страницу socket.io, чему я рад.&lt;/p&gt;
&lt;p&gt;Под катом - немного информации что это такое и с чем это едят.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;Socket.io - это клиентская библиотка, которая предоставляет единый интерфейс для связи с comet сервером используя один из поддерживаемых протоколов.&lt;/p&gt;
&lt;p&gt;Протоколы поддерживаются такие:&lt;/p&gt;
&lt;p&gt;1. Websocket&lt;br/&gt;2. Flashsocket &lt;br/&gt;3. XHR multipart&lt;br/&gt;4. XHR polling &lt;br/&gt;5. JSONp&lt;br/&gt;6. HTMLFile (IE only)&lt;/p&gt;
&lt;p&gt;socket.io достаточно умный что бы выбрать наиболее эффективный протокол из поддерживающихся на клиенте.  Если протокол по каким-то причинам не сработал (Flashsocket не смог достучаться, так как прокси или еще чего), то берет следующий протокол. В списке выше, протоколы указаны в порядке их эффективности.&lt;/p&gt;
&lt;p&gt;Немного о каждом протоколе:&lt;/p&gt;
&lt;p&gt;1. Websocket&lt;/p&gt;
&lt;p&gt;- Обычные websocket’ы из коробки.&lt;br/&gt;- Открывается одно соединение на прием и отправку данных. &lt;br/&gt;- Proxy поддерживаются&lt;/p&gt;
&lt;p&gt;2. Flashsocket&lt;/p&gt;
&lt;p&gt;- Эмуляция websocket’ов через специальный флешовый скрипт. Используется &lt;a href="https://github.com/gimite/web-socket-js"&gt;&lt;a href="https://github.com/gimite/web-socket-js"&gt;https://github.com/gimite/web-socket-js&lt;/a&gt;&lt;br/&gt;&lt;/a&gt;- Одно соединение на прием и отправку данных&lt;br/&gt;- Proxy не поддерживается&lt;/p&gt;
&lt;p&gt;К слову - web-socket-js еще та петрушка, которую можно очень хорошо соптимизировать, чем обещают заняться разработчики socket.io.&lt;/p&gt;
&lt;p&gt;3. XHR multipart&lt;/p&gt;
&lt;p&gt;- Один долгоиграющий GET запрос на прием данных с сервера&lt;br/&gt;- По POST запросу на отправку данных на сервер с клиента&lt;br/&gt;- Proxy поддерживается&lt;/p&gt;
&lt;p&gt;4. XHR polling&lt;/p&gt;
&lt;p&gt;- Куча GET запросов, которые висят до 20 секунд если нет данных, для чтения данных с сервера&lt;br/&gt;- По POST запросу на отправку данных на сервер с клиента &lt;/p&gt;
&lt;p&gt;5. JSONp&lt;/p&gt;
&lt;p&gt;- Аналогично XHR polling, но данные приходят с сервера как JS скрипт.&lt;/p&gt;
&lt;p&gt;6. HTMLFile&lt;/p&gt;
&lt;p&gt;- Хитрая штука, которая нечто среднее между XHR multipart и XHR polling и только для IE&lt;br/&gt;- Одно соединение для приема данных&lt;br/&gt;- По POST запросу на отправку данных&lt;/p&gt;
&lt;p&gt;Теперь о TornadIO.&lt;/p&gt;
&lt;p&gt;TornadIO - это библиотека для поддержки транспортов socket.io поверх торнадо. По сути, мимикрирует внешнее API websocket’ов (их поддержка в торнадо появилась достаточно давно), при этом поддерживает все транспортные протоколы socket.io.&lt;/p&gt;
&lt;p&gt;По сути, пишем код 1 раз и пускай socket.io + TornadIO заботятся о транспортном уровне.&lt;/p&gt;
&lt;p&gt;Как это выглядит с точки зрения кода. Напишем простой realtime чат.&lt;/p&gt;
&lt;p&gt;Вот это наша серверная часть (инициализация опущена, только сама логика чата):&lt;/p&gt;
&lt;pre class="brush: python"&gt;class ChatConnection(tornadio.SocketConnection):
    participants = set()

    def on_open(self, *args, **kwargs):
        # Register incoming connection
        self.participants.add(self)
        self.send("Welcome!")

    def on_message(self, message):
        # Broadcast incoming message
        for p in self.participants:
            p.send(message)

    def on_close(self):
        # Remove client
        self.participants.remove(self)

        # Broadcast that client left
        for p in self.participants:
            p.send("A user has left.")
&lt;/pre&gt;
&lt;p&gt;Каждый клиент - это отдельный экземпляр класса ChatConnection. Появился новый клиент - мы его добавили в set, ушел - убрали. Прислал сообщение - разослали всем в чате.&lt;/p&gt;
&lt;p&gt;Клиентская часть выглядит так: &lt;a href="https://github.com/MrJoes/tornadio/blob/master/examples/chatroom/index.html"&gt;&lt;a href="https://github.com/MrJoes/tornadio/blob/master/examples/chatroom/index.html"&gt;https://github.com/MrJoes/tornadio/blob/master/examples/chatroom/index.html&lt;/a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Все достаточно просто.&lt;/p&gt;
&lt;p&gt;Взять библиотеку &lt;a href="https://github.com/MrJoes/tornadio"&gt;можно тут&lt;/a&gt; или на &lt;a href="http://pypi.python.org/pypi/TornadIO/"&gt;pypi&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Примеры можно посмотреть тут: &lt;a href="https://github.com/MrJoes/tornadio/tree/master/examples"&gt;&lt;a href="https://github.com/MrJoes/tornadio/tree/master/examples"&gt;https://github.com/MrJoes/tornadio/tree/master/examples&lt;/a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Надеюсь еще кому-то пригодится.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/EatAtMrJoes/~4/lDLkbggsAi8" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/EatAtMrJoes/~3/lDLkbggsAi8/2963182191</link><guid isPermaLink="false">http://mrjoes.tumblr.com/post/2963182191</guid><pubDate>Thu, 27 Jan 2011 15:48:00 -0500</pubDate><category>python</category><category>realtime</category><category>tornado</category><category>tornadio</category><category>socket.io</category><category>web</category><feedburner:origLink>http://mrjoes.tumblr.com/post/2963182191</feedburner:origLink></item><item><title>Realtime Web</title><description>&lt;p&gt;Решил разобраться что и как у нас с realtime в современном Вебе.&lt;!-- more --&gt;&lt;/p&gt;
&lt;p&gt;Для примера возьмем какую-нибудь онлайн игру, для которой важны (в порядке важности):&lt;br/&gt;1. Streaming данных с сервера в реальном времени (с минимальной задержкой)&lt;br/&gt;2. Время отклика на действия игрока&lt;br/&gt;3. Возможность работы через прокси&lt;/p&gt;
&lt;p&gt;Пункт 3й желателен, но не обязателен.&lt;/p&gt;
&lt;p&gt;Из средств будем использовать HTML, js, python, при этом не очень хочется использовать twisted. Желательно хотя бы tornado.&lt;/p&gt;
&lt;p&gt;Какие есть варианты:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Long running AJAX&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Что такое: делаем запрос на сервер, который ждет ответа неограниченно долго. После получения ответа делаем запрос еще раз. И так до бесконечности.&lt;/p&gt;
&lt;p&gt;Существует в нескольких вариациях: с использованием XHR, с использованием скрытого фрейма и так далее.&lt;/p&gt;
&lt;p&gt;Проблемы:&lt;br/&gt;- Бьет по производительности, т.к. мы сами разрешили клиентам долбить наш веб сервер&lt;br/&gt;- Время отклика сильно зависит от нагруженности веб морды &lt;br/&gt;- Синхронные фреймворки использовать нельзя, а юзать асинхронные не сильно удобно. Вариант решения - nginx который будет распределять запросы по маске между 2 бекендами, один на той же Сварге, а второй на торнадо.&lt;br/&gt;- Нельзя напрямую обращаться к серверу на другом порту/тазике из за ограничений AJAX на тему cross domain scripting.&lt;/p&gt;
&lt;p&gt;Достоинства:&lt;br/&gt;- Работает везде&lt;br/&gt;- Поддержка проксей&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Cometd, он же Bayeux Protocol&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;По сути, попытка “стандартизации” long polling request’ов. Может работать как в обычном long polling режиме, так и с извратом - открывается 2 соединения, где одно соединение используется для отсылки данных к серверу, а другое - для отсылки данных от сервера.&lt;/p&gt;
&lt;p&gt;Описывать буду именно режим с двумя соединениями.&lt;/p&gt;
&lt;p&gt;Проблемы:&lt;br/&gt;- Для питона поддерживается только Cometd, который написан поверх Twisted&lt;br/&gt;- Twisted :-)&lt;br/&gt;- Протокол относительно сложный, да и синхронизировать 2 соединения в одно логическое не очень удобно&lt;/p&gt;
&lt;p&gt;Достоинства:&lt;br/&gt;- Время отклика сильно лучше&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. WebSockets&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Попытка добавить сокеты к клиентскому JS. Открываем соединение с помощью HTTP протокола, сервер видит что это именно WebSocket и дальше его как-то обрабатывает.&lt;/p&gt;
&lt;p&gt;Проблемы:&lt;br/&gt;- Почти никто из браузеров не поддерживает&lt;br/&gt;- Те кто поддерживают - отключили из за найденной дыры в протоколе&lt;br/&gt;- Финальных спеков нет, 76 ревизий настораживают.&lt;br/&gt;- Всякие там кеширующие прокси не умеют и не понимают (nginx, например). Есть HAProxy, но делать цепочку Client -&gt; HAProxy -&gt; nginx -&gt; wsgi не очень хочется.&lt;/p&gt;
&lt;p&gt;Достоинства:&lt;br/&gt;- Сокеты в JS, ура!&lt;/p&gt;
&lt;p&gt;Кстати, я так и не понял зачем было городить это все. Со времен появления SSL был такой HTTP метод как CONNECT. Его отлично понимали proxy, его легко можно прикрутить к веб серверу. Если нужно различать куда стучимся - используем custom header, делов то. Даже если будет срабатывать same origin policy для JS, то на всех существующих проксях (nginx например) легко можно будет заворачивать трафик куда надо по хидеру.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. socketjs &amp; co&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Идея тоже весьма простая - делаем мелкий флешовый скрипт, который средствами флеша имитирует работу обычного tcp сокета. Работает везде где есть флеш.&lt;/p&gt;
&lt;p&gt;Достоинства:&lt;br/&gt;- Легко использовать&lt;br/&gt;- Чистый tcp&lt;br/&gt;- Быстрый&lt;br/&gt;- Работает почти везде (найти юзера у которого нет флеша нынче тяжело)&lt;/p&gt;
&lt;p&gt;Недостатки:&lt;br/&gt;- Завязка на флеш&lt;br/&gt;- Надо писать свой сетевой протокол, т.к. ничего готового нет.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. js-amqp&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Есть такая штука как AMQP (Advanved Messaging Queue Protocol) который позволяет создавать очереди сообщений. К сожалению, протокол overdesigned, т.е. его использовать не очень удобно, но production-ready решения есть, например RabbitMQ, ActiveMQ и т.д.&lt;/p&gt;
&lt;p&gt;По сути, браузер коннектится к AMQP серверу и получает/отправляет собщения. Все круто, но использовать AMQP для передачи сообщений между клиентом и сервером это как палить из пушки по воробьям.&lt;/p&gt;
&lt;p&gt;Недостатки:&lt;br/&gt;- Сложный и неэффективный протокол (первоначальный автор спецификаций AMQP от него отказался и решил делать ZeroMQ)&lt;br/&gt;- Большая половина функциональности использоваться не будет&lt;br/&gt;- Интеграция js-amqp представляет собой флешовый скрипт с неким JS API.&lt;/p&gt;
&lt;p&gt;Достоинства:&lt;br/&gt;- Не заморачиваемся со своим сетевым протоколом&lt;br/&gt;- Быстрее long polling ajax, медленнее сырых сокетов&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;6. Использовать Mongrel2 как фронтенд&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Mongrel2 это такая штука, которая ловит входящие запросы (HTTP, TCP, etc) и конвертирует их в сообщения ZeroMQ. Подписчики на эти сообщения их обрабатывают и отправляют ответ обратно к Mongrel2, который уже отправляет их на клиент.&lt;/p&gt;
&lt;p&gt;Достоинства:&lt;br/&gt;- Получаем ZeroMQ из коробки (т.е. заодно и хорошую расширяемость на будущее)&lt;/p&gt;
&lt;p&gt;Недостатки:&lt;br/&gt;- Не решает вопроса с транспортом от браузера к серверу&lt;br/&gt;- Слишком новая технология&lt;br/&gt;- Нет никаких тестов&lt;br/&gt;- Никто не писал об опыте использования&lt;/p&gt;
&lt;p&gt;Это как бы все что я смог накопать.&lt;/p&gt;
&lt;p&gt;Какие еще есть идеи:&lt;/p&gt;
&lt;p&gt;- ZeroMQ&lt;/p&gt;
&lt;p&gt;Отличная штука - сокеты на стероидах, с гарантированной доставкой в случае потери tcp соединения и так далее. Есть интеграции почти со всеми языками, включая node.js.&lt;/p&gt;
&lt;p&gt;Но. Интеграция всегда завязана на Сишную либу, которая уже и занимается протоколом. Т.е. даже интеграция на Java тянет за собой сишную либу.&lt;/p&gt;
&lt;p&gt;Нативных клиентов (например как для redis) нет вообще.&lt;/p&gt;
&lt;p&gt;Подумываю написать клиент на жаваскрипте поверх socketjs, благо протокол там ну очень простой. Но это когда-нить потом.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Итог&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Я вот даже не знаю на чем остановиться. Использовать Cometd не очень хочется, там страшный Twisted. Делать все на AMQP - overkill. Писать свой wire протокол поверх WebSockets/socketjs - не хочется, но чую прийдется.&lt;/p&gt;
&lt;p&gt;P.S. Я нашел его! Смотреть тут:  &lt;a href="https://github.com/SocketTornadIO/SocketTornad.IO"&gt;&lt;a href="https://github.com/SocketTornadIO/SocketTornad.IO"&gt;https://github.com/SocketTornadIO/SocketTornad.IO&lt;/a&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/EatAtMrJoes/~4/-wkQNcOUVR4" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/EatAtMrJoes/~3/-wkQNcOUVR4/2599984527</link><guid isPermaLink="false">http://mrjoes.tumblr.com/post/2599984527</guid><pubDate>Tue, 04 Jan 2011 16:56:00 -0500</pubDate><category>development</category><category>web</category><category>realtime</category><feedburner:origLink>http://mrjoes.tumblr.com/post/2599984527</feedburner:origLink></item><item><title>Секреты рендеринга Starcraft 2: Ролики</title><description>&lt;a href="http://users.livejournal.com/__vortex__/3640.html"&gt;Секреты рендеринга Starcraft 2: Ролики&lt;/a&gt;&lt;img src="http://feeds.feedburner.com/~r/EatAtMrJoes/~4/DqZCqdYyXPY" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/EatAtMrJoes/~3/DqZCqdYyXPY/1541474113</link><guid isPermaLink="false">http://mrjoes.tumblr.com/post/1541474113</guid><pubDate>Thu, 11 Nov 2010 03:40:23 -0500</pubDate><feedburner:origLink>http://mrjoes.tumblr.com/post/1541474113</feedburner:origLink></item><item><title>Секреты рендеринга Starcraft 2</title><description>&lt;a href="http://users.livejournal.com/__vortex__/3391.html"&gt;Секреты рендеринга Starcraft 2&lt;/a&gt;&lt;img src="http://feeds.feedburner.com/~r/EatAtMrJoes/~4/hmuAbDbyPcU" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/EatAtMrJoes/~3/hmuAbDbyPcU/1469212753</link><guid isPermaLink="false">http://mrjoes.tumblr.com/post/1469212753</guid><pubDate>Wed, 03 Nov 2010 04:13:30 -0400</pubDate><category>gamedev</category><category>starcraft</category><category>rendering</category><feedburner:origLink>http://mrjoes.tumblr.com/post/1469212753</feedburner:origLink></item><item><title>Производительность .NET</title><description>&lt;p&gt;Сейчас занимаюсь прототипированием MMO сервера на C#. Как будет что-то готовое, будет серия постов на тему его архитектуры.&lt;/p&gt;
&lt;p&gt;А сейчас хочу рассказать насколько быстрое создание объектов в шарпе через new().&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;В свое время кодируя на С/С++ всегда считал (и продолжаю считать), что лишние создания объектов это зло. Точнее - и операция медленная и вообще плохо. Потому, ради производительности, делались такие штуки, как всякие там кеширующие фабрики: если объект уничтожается, он не прибивается, а добавляется в очередь фабрики. Если нам понадобился новый экземпляр такого объекта - мы смотрим чего там у нас закешировано и если ничего - то создаем новый.&lt;/p&gt;
&lt;p&gt;Понятное дело, что был контроль длины очереди и тому подобное.&lt;/p&gt;
&lt;p&gt;И решил я, значит, сделать похожий велосипед для шарпа - у меня создается куча мелких объектов в рантайме.&lt;/p&gt;
&lt;p&gt;Написал и ужаснулся: мой вариант с очередью в 6 (!) раз медленнее обычного new(). При том, что мой был однопоточный, а new() работает и в многопоточном окружении. Если что, за основу брался код отсюда: &lt;a href="http://www.codeproject.com/KB/recipes/ObjectPooling.aspx"&gt;&lt;a href="http://www.codeproject.com/KB/recipes/ObjectPooling.aspx"&gt;http://www.codeproject.com/KB/recipes/ObjectPooling.aspx&lt;/a&gt;&lt;/a&gt; который я сильно почистил и оптимизировал.&lt;/p&gt;
&lt;p&gt;Если что, объекты были действительно мелкие - по 12 байт каждый.&lt;/p&gt;
&lt;p&gt;Вторая часть истории - у C# есть такой класс BitConverter. У него есть кучка статических методов, которые позволяют конвертировать базовые типы данных (int, etc) в/из массив байт. Проблема состоит в том, что прототип функций конвертации в массив примерно такой:&lt;/p&gt;
&lt;pre class="brush: csharp"&gt;byte[] ToShort(int value)
&lt;/pre&gt;
&lt;p&gt;Внутри оно делает new byte[4] и сериализирует в него value.&lt;/p&gt;
&lt;p&gt;Явная проблема с производительностью? Да, я тоже подумал. И написал свой очередной велосипед.&lt;/p&gt;
&lt;p&gt;И что вы думаете? Вот мои результаты в ms, для int, 10000000 итераций:&lt;/p&gt;
&lt;p&gt;MyFromArray: 121.0107&lt;br/&gt;BitConverterFromArray: 65.0044&lt;br/&gt;MyToArray: 115.01&lt;br/&gt;BitConverterToArray: 194.0111&lt;/p&gt;
&lt;p&gt;Грубо говоря, мои функции вида:&lt;/p&gt;
&lt;pre class="brush: csharp"&gt;short ReadShort(byte[] buf, int offset);
&lt;/pre&gt;
&lt;p&gt;оказались примерно в 2 раза медленней аналогов из BitConverter из за дополнительных проверок “а не надо ли менять byte order” и из за того, что мои методы не статические.&lt;/p&gt;
&lt;p&gt;А функции записи вида:&lt;/p&gt;
&lt;pre class="brush: csharp"&gt;void Write(short value, byte[] buf, int offset);
&lt;/pre&gt;
&lt;p&gt;оказались всего лишь на ~80% быстрее чем вариант в BitConverter с созданием нового объекта… Я ожидал разницы где-то в порядок, на самом деле.&lt;/p&gt;
&lt;p&gt;Вот что получается - запись в массив 4х байт одной операцией (*(int*)&amp;buf = value) всего лишь на 80% быстрее чем создание массива из 4х байт в куче и записью туда этих самых 4х байт той же операцией.&lt;/p&gt;
&lt;p&gt;Так что будем считать что new() в .NET почти бесплатен.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/EatAtMrJoes/~4/-Py34rZko_w" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/EatAtMrJoes/~3/-Py34rZko_w/1425456683</link><guid isPermaLink="false">http://mrjoes.tumblr.com/post/1425456683</guid><pubDate>Thu, 28 Oct 2010 15:59:00 -0400</pubDate><category>programming</category><category>performance</category><category>.net</category><feedburner:origLink>http://mrjoes.tumblr.com/post/1425456683</feedburner:origLink></item><item><title>Небольшой реверс инжиниринг Minecraft</title><description>&lt;p&gt;В связи с массовым помешательством на &lt;a title="Minecraft" href="http://www.minecraft.net/"&gt;Minecraft&lt;/a&gt;, включая мое собственное, решил оптимизировать себе процесс рудокопательства.&lt;/p&gt;
&lt;p&gt;Дальше немного о том, как Minecraft написан, как функционирует и соображения почему-же оно смогло так хорошо продаться.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;Если кто не в курсе, Minecraft это такая себе игра, которая представляет собой &lt;a title="DF" href="http://www.bay12games.com/dwarves/"&gt;Dwarf Fortress&lt;/a&gt; от лица рядового гнома. Можно крафтить вещи, копать ямы и отбиваться от монстров. Мир бесконечен - создается на лету. Физические ограничения - 65 тыс. квадратных километров.&lt;/p&gt;
&lt;p&gt;Minecraft написан на Java с использованием &lt;a href="http://lwjgl.org/"&gt;&lt;a href="http://lwjgl.org/"&gt;http://lwjgl.org/&lt;/a&gt;&lt;/a&gt; - некой платформо-зависимой прослойки, которая представляет собой что-то типа SDL, но для Java. Игра может работать как апплет в браузере или как standalone приложение. Разница только в точке входа.&lt;/p&gt;
&lt;p&gt;Код игры обфусцирован, что бы усложнить жизнь читерам и им сочувствующим. Но это не сильно помогает - даже в мультиплеере сервер весьма мало чего контролирует. Т.е. игроки летают, прыгают, как хотят и тому подобное. Кроме того, есть известные баги, например в мультиплеере можно создавать дубликаты вещей.&lt;/p&gt;
&lt;p&gt;Кода мало. Ну, реально, там несколько десятков классов, простой рендер, простая сетевая подсистема. Это в клиенте. В сервере итого меньше.&lt;/p&gt;
&lt;p&gt;Взять тот же рендер - там все на кубиках построено. Земля рендерится через VBO, куда накидываются видимые грани кубиков. Никакого occlusion culling нет, да и не нужен он - там все квадратиками и в одном VBO до 15 тысяч полигонов. Никаких шейдеров, все на FFP.&lt;/p&gt;
&lt;p&gt;Освещение сделано руками - у игры простой трейсер освещенности и она просто меняет цвета вершин в полигонах, что бы сделать их ярче или темнее. Получилось весьма симпатично - препятствия в освещении учитываются и общий вид вписывается в концепцию картинки игры. Минус такого подхода состоит в том, что он пересоздает VBO каждый раз, как меняется освещение. Поскольку в игре динамических (двигающихся) источников нет, то и перегенераций мало - когда факел на стену вешается.&lt;/p&gt;
&lt;p&gt;Собственно я решил сделать себе радар с минералами для мультиплеера. Вариантов несколько:&lt;/p&gt;
&lt;p&gt;1. Сделать injection своего Java класса и работать с игровыми данными&lt;br/&gt;2. Сделать прослойку между opengl.dll и игрой и выключать z-test для нужной мне геометрии&lt;/p&gt;
&lt;p&gt;С первым вариантом мне не хотелось возиться - копать обфусцированный Java код то еще удовольствие.&lt;/p&gt;
&lt;p&gt;Второй вариант был сильно проще, но нужные мне минералы видны, только если они граничат с пустой клеткой. Что меня вполне устраивало - мир бесконечен, так что минералов мне хватит.&lt;/p&gt;
&lt;p&gt;Ну заодно зафиксировал цвета в VBO, чем получил равномерное освещение везде.&lt;/p&gt;
&lt;p&gt;Как бы пруф:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://media.tumblr.com/tumblr_l9v57zsfLX1qa1n1c.jpg"/&gt;&lt;/p&gt;
&lt;p&gt;Ну и напоследок…&lt;/p&gt;
&lt;p&gt;Посмотрев внутренности игры, однозначно прихожу к выводу, что идея решает. Главное совсем уж не подкачать с реализацией. Человек не сильно заморачивался с картинкой, кодом и просто взял интересную идею и начал обвешивать ее фичами. Я не могу сказать, что Minecraft хорошо написан, не могу сказать что Notch (собственно автор) мега-супер-программер. Просто человеку пришла в голову хорошая идея и он, как умел, ее реализовал. Я совершенно не могу сравнить количество труда затраченного на Minecraft с любой другой более крупной игрой. Народ тратит уйму денег, уйму усилий и совершенно не добивается того, что смог сделать один программист с хорошей идеей.&lt;/p&gt;
&lt;p&gt;Так что congratulations Notch.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/EatAtMrJoes/~4/dAe_jS4ZStA" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/EatAtMrJoes/~3/dAe_jS4ZStA/1254167079</link><guid isPermaLink="false">http://mrjoes.tumblr.com/post/1254167079</guid><pubDate>Wed, 06 Oct 2010 01:26:00 -0400</pubDate><category>games</category><category>minecraft</category><category>cheats</category><feedburner:origLink>http://mrjoes.tumblr.com/post/1254167079</feedburner:origLink></item><item><title>О том, как подкрадывается песец...</title><description>&lt;p&gt;Есть у нас приложение, которое мы пишем на основе DotNetNuke (DNN). Причина почему выбрали DNN - настоял заказчик. Мол, DNN самый “взрослый”, “стабильный”, “корпоративный” и тому подобное.&lt;/p&gt;
&lt;p&gt;Вот, на днях у нас упал продакшен. Хорошо так упал.&lt;/p&gt;
&lt;p&gt;Начал исследовать причину, поковырялся - оказалось что падает вся ферма из веб серверов (их 3 штуки), если запускается операция создания сайта. DNN поддерживает несколько сайтов работающих в одной инсталляции DNN.&lt;/p&gt;
&lt;p&gt;После исследования, получилась весьма интересная картина, которая касается внутренностей ASP.NET, а так же архитектурных решений DNN.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;В ASP.NET есть фича, которая занимается тем, что отслеживает изменения файлов в проекте ASP.NET. Если оно находит файл который изменился в определенных директориях - пересобирает приложение и перезапускает его.&lt;/p&gt;
&lt;p&gt;Кроме того, в ASP.NET есть кеш. Кеш это key/value storage, который локален для процесса ASP.NET. Операции стандартные - добавить, получить, удалить и т.д. Есть expiration settings.&lt;/p&gt;
&lt;p&gt;Есть стандартные рекомендации по deployment’у DNN - выкладываем файлы в расшаренной директории (SMB share), все физические сервера работают с проектом с этой расшареной директории, т.к. изменения файлов тоже надо синхронизировать и все сервера должны об этом знать.&lt;/p&gt;
&lt;p&gt;Так вот, есть веб ферма с 3 физическими серверами. Работает с одной папкой на шаре. У всех серверов - свой локальный кеш, который как-бы надо синхронизировать.&lt;/p&gt;
&lt;p&gt;Вместо использования централизированного кеша (memcached/redis/etc), разработчики DNN делают велосипед - синхронизационный провайдер. Это такая штука, которая каким-то образом дает другим серверам в ферме знать, что такой вот ключ уже не действителен и его надо удалить.&lt;/p&gt;
&lt;p&gt;В бесплатной версии ДНН таких провайдеров есть два: один через базу данных, другой через файлы.&lt;/p&gt;
&lt;p&gt;Первый фигачит в таблицу все удаленные ключи (таблица жутко разрастается). Сервера время от времени синхронизируются с таблицей (читают все что есть) и удаляют такие ключи локально. Время от времени таблица подчищается. Способ тормозной, сервер БД ложится на лопатки и вообще все плохо.&lt;/p&gt;
&lt;p&gt;Второй работает еще веселее. На каждый ключ создается файл. Все файлы в одной директории - файлов получается over 100500. Поскольку все сервера работают с шары, файлы доступны для всех. Сервера мониторят изменения таких файлов и если меняется дата создания или файл удаляется - удаляют соответствующий ключ у себя.&lt;/p&gt;
&lt;p&gt;И тут подкрался песец… &lt;/p&gt;
&lt;p&gt;Создание сайта (стандартная операция) работает с обычным API DNN а-ля “создать страницу”, “положить модуль на страницу” и т.д. Каждая из таких операций инвалидирует кеш по нужным им ключам (“удалить ключ Page_1234”, “Создать ключ Module_1234” и т.д). И таких операций _очень_ много. Около 40 тысяч для полноценного портала.&lt;/p&gt;
&lt;p&gt;В результате, происходит 40 тысяч добавлений/удалений файлов с шары за очень короткий промежуток времени. ASP.NET на каждом из серверов обрабатывает file change notifications, что жрет процессор (их много, пускай даже они и ерундовые), потом еще происходит удаление ключей, а в результате - полный армагеддон на всех серверах.&lt;/p&gt;
&lt;p&gt;Клиенты отписались в саппорт DNN - они ответили, что у них в платной версии есть специальный синхронизационный провайдер, который дает супер производительность в ферме. Купили. Ничего не изменилось - сервера все равно падают.&lt;/p&gt;
&lt;p&gt;Оказалось что новый провайдер работает через HTTP: каждый web сервер говорит соседям что у них удалился ключ. В результате, при создании портала, эти 40 тысяч запросов идут на все соседние сервера в течении небольшого промежутка времени и они успешно захлебываются. Что в лоб, что по лбу.&lt;/p&gt;
&lt;p&gt;В результате, как quick fix, пока выключили апдейты кеша при создании сайтов и руками удаляем ключики после таких операций. Теперь думаем как фиксить правильно - то ли переехать на какой-нить memcached, то ли писать свой синхронизатор на сокетах.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/EatAtMrJoes/~4/YkIcFq7E3PY" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/EatAtMrJoes/~3/YkIcFq7E3PY/749770967</link><guid isPermaLink="false">http://mrjoes.tumblr.com/post/749770967</guid><pubDate>Tue, 29 Jun 2010 10:23:00 -0400</pubDate><feedburner:origLink>http://mrjoes.tumblr.com/post/749770967</feedburner:origLink></item><item><title>О собеседованиях</title><description>&lt;p&gt;У нас иногда появляются вакансии. И мне приходится проводить интервью.&lt;/p&gt;
&lt;p&gt;В принципе, мы требования к кандидатам не завышаем, в требованиях пишем стандартную ASP.NET солянку: .NET, ASP.NET, SQL, JavaScript. Набирали и во время кризиса, но кандидатов было много, а вакансий мало.&lt;/p&gt;
&lt;p&gt;Ну и значит, приходили эти кандидаты на собеседования и пытались его с блеском (или не очень) провести.&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;Как-то так получалось, что люди делились на две категории: толковые и не очень.&lt;/p&gt;
&lt;p&gt;Толковые отвечали на вопросы (если знали ответ). Если не знали - пытались догадаться или сделать выводы из наводящих вопросов. В целом - было приятно общаться. Если человек не подходил, то не по техническим вопросам.&lt;/p&gt;
&lt;p&gt;А вот другие… Скажем вот один из стандартных вопросиков.&lt;/p&gt;
&lt;p&gt;Что напишет следующий код:&lt;/p&gt;
&lt;pre class="brush: csharp"&gt;public class Test
{
    private static void Main(string[] args)
    {
        try
        {
            int[] a = null;
            int i = a[m1(true)];
        }
        catch (Exception e)
        {
            Console.WriteLine(e.GetType());
        }
    }

    public static int m1(bool b)
    {
        if (b)
            throw new Exception("Some Exception");
        return 0;
    }
}
&lt;/pre&gt;
&lt;p&gt;Варианты ответов:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;a) Some Exception&lt;br/&gt;b) System.NullReferenceException&lt;br/&gt;c) System.Exception&lt;br/&gt;d) Throws a runtime exception&lt;br/&gt;e) None of the above&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Как ни странно, на такой, в принципе простой задаче, которая просто показывает умеет ли человек читать код резалось почти 2/3 соискателей.&lt;/p&gt;
&lt;p&gt;Варианты ответов, из тех что запомнились:&lt;/p&gt;
&lt;p&gt;- Будет System.NullReferenceException, потому что компилятор знает что там null&lt;br/&gt;- Не знаю&lt;br/&gt;- Эксепшена не будет&lt;/p&gt;
&lt;p&gt;Мне вот интересно - это нам так везло или это такая ситуация с C#/ASP.NET вообще?&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/EatAtMrJoes/~4/W2jyvLiHuqM" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/EatAtMrJoes/~3/W2jyvLiHuqM/616267534</link><guid isPermaLink="false">http://mrjoes.tumblr.com/post/616267534</guid><pubDate>Thu, 20 May 2010 10:04:00 -0400</pubDate><category>programming</category><category>wtf</category><category>asp.net</category><feedburner:origLink>http://mrjoes.tumblr.com/post/616267534</feedburner:origLink></item><item><title>Private, protected, internal, etc...</title><description>&lt;p&gt;Вот, на работе, столкнулись с простой задачей: кусочек стороннего кода надо запускать асинхронно. Кусочек может работать минут 10 и запускать его синхронно (в результате web запроса) как-то совсем неправильно. На всякий случай уточню, что исходники этого кусочка есть.&lt;/p&gt;
&lt;p&gt;Вроде бы - чего тут сложного? А оказалось…&lt;/p&gt;
&lt;p&gt;1. Кусок зависел от инициализированного окружения ASP.NET. Использовал аж одну функцию из всего API ASP.NET - меппинг виртуальных URL в физический путь. Без инициализации ASP.NET, меппинг кидал исключение.&lt;br/&gt;2. Кусок нельзя было выдрать минимальными усилями (и дописать/переписать), так как он завязан на функционал, который помечен как internal (в шарпе internal означает что объект/метод доступен только из своей сборки и снаружи не виден), так что выдирание кусочка привело бы не только к размножению идентичного кода, а так же к выдиранию всех зависимостей&lt;br/&gt;3. Инициализацию ASP.NET нельзя сфабриковать - все методы, которые инициализируют ASP.NET являются или internal или private.&lt;br/&gt;4. У ASP.NET есть официальное API инициализации, которое “поднимает” новый application domain (весьма тяжелая операция) и с помощью remoting устанавливает связь с произвольным классом внутри этого application domain.&lt;br/&gt;5. Кроме самой инициализации ASP.NET, кусочку потребовалась инициализация через его собственный фильтр запросов, который проверяет не был ли его фреймворк инициализирован и инициализирует, если надо. Что бы вызвать фильтр, надо повозиться с созданием HttpApplication, который хочет полноценной инициализации, типа той, которая приходит из ISAPI фильтра ASP.NET (а-ля http module в apache).&lt;/p&gt;
&lt;p&gt;В сухом остатке:&lt;br/&gt;1. Обдурить маленький кусок кода без полной инициализации ASP.NET оказалось нельзя&lt;br/&gt;2. Инициализация ASP.NET очень тяжелая, так как ведет к полной компиляции ASP.NET приложения. Кроме того все приложение грузится в память, в нашем случае это примерно 100 мегов в памяти, несмотря на то, что оно совсем не надо.&lt;br/&gt;3. Необходимо следить что бы application domain не умер во время работы. Если умер - прибить и создать заново (что накладно)&lt;/p&gt;
&lt;p&gt;К чему я это все веду:&lt;br/&gt;1. В питоне я бы просто вызвал функцию инициализации фреймворка напрямую. Работоспособность при переезде на новую версию фреймворка контролировал бы с помощью юнит-теста.&lt;br/&gt;2. Ограничение доступа на уровне “ты сюда не ходи, ты сюда ходи” в таких ситуациях очень сильно мешает.&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/EatAtMrJoes/~4/Za9SUEjfIAo" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/EatAtMrJoes/~3/Za9SUEjfIAo/609423953</link><guid isPermaLink="false">http://mrjoes.tumblr.com/post/609423953</guid><pubDate>Tue, 18 May 2010 03:22:25 -0400</pubDate><category>Architecture</category><category>asp.net</category><category>programming</category><feedburner:origLink>http://mrjoes.tumblr.com/post/609423953</feedburner:origLink></item><item><title>ASP.NET и "спецсимволы" в URL</title><description>&lt;p&gt;Вообще, если будет настроение, надо бы написать пост, почему же ASP.NET (который не MVC) плох. В целом, как технология.&lt;/p&gt;
&lt;p&gt;Ну а пока просто расскажу как непонятная хрень осложняет жизнь.&lt;/p&gt;
&lt;p&gt;На днях у нас нашелся странный баг: если в URL, в пути, содержится символ ‘&amp;’ то ничего не работает и возвращается 400 Bad Request.&lt;/p&gt;
&lt;p&gt;Т.е. вот такая вот ссылка не работает: &lt;a href="http://foo.com/candy/m&amp;ms/"&gt;http://foo.com/candy/m&amp;ms/&lt;/a&gt; , хотя ‘&amp;’ не является запрещенным символом.&lt;/p&gt;
&lt;p&gt;Оказывается - это веселое legacy, которое окончательно так и не исправили. Предполагалось, что если убрать вот такие вот символы из запросов, то приложение станет сильно безопаснее.&lt;/p&gt;
&lt;p&gt;Чтобы убрать эти проверки, придется менять реестр в двух местах. Что нам не подходит, так как нас на production сервера не пустят, плюс они боятся менять параметры, которые могут повлиять на безопасность. Пришлось переписывать логику генерации ссылок, что бы такие вот параметры шли через query strings, что немного усложнило саму логику и, к сожалению, заняло определенное время на борьбу с ветряной мельницей.&lt;/p&gt;
&lt;p&gt;Предсказывая возможный вопрос, параметр этот может принимать значения из [a-zA-Z&amp;: ], так что раньше необходимости деления параметров по категориям не было.&lt;/p&gt;
&lt;p&gt;Ах да, самое главное забыл - мы получаем 400 Bad Request, даже если используются URL Rewrite, т.е. разобранная ссылка уже не содержит амперсанд в пути.&lt;/p&gt;
&lt;p&gt;Т.е. если исходная ссылка была: &lt;a href="http://foo.com/candy/m&amp;ms/"&gt;http://foo.com/candy/m&amp;ms/&lt;/a&gt; , разобраная: &lt;a href="http://foo.com/Default.aspx?candy=m&amp;ms"&gt;http://foo.com/Default.aspx?candy=m&amp;ms&lt;/a&gt; , то ошибка все равно останется.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/EatAtMrJoes/~4/mHfKcTIWyuY" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/EatAtMrJoes/~3/mHfKcTIWyuY/530526820</link><guid isPermaLink="false">http://mrjoes.tumblr.com/post/530526820</guid><pubDate>Sun, 18 Apr 2010 08:28:00 -0400</pubDate><category>programming</category><category>codewtf</category><category>asp.net</category><feedburner:origLink>http://mrjoes.tumblr.com/post/530526820</feedburner:origLink></item><item><title>О перегрузке объектов лишними сущностями</title><description>&lt;p&gt;Очень часто я сталкиваюсь с подходом в программировании, который я  мог бы описать как “все включено”.&lt;/p&gt;
&lt;p&gt;Рассмотрим простой пример: некий разработчик игр, который пишет на  С++, решил что STL это тормоз и вообще ужасная вещь, надо писать свой  велосипед. Он сделал свои контейнеры, свои строки, все свое. Но рассказ  будет не о NIH синдроме, а о том, как эти классы были спроектированы.&lt;/p&gt;
&lt;p&gt;Возьмем его класс строки, как пример. Он умел все. Кроме базовых  операций по работе со строками (сравнений и т.д.), строка умела читать  себя из файла, писать себя в файл, конвертироваться во все базовые типы  С++ и тому подобное. Все это помещалось в одном исходной файле размером  этак под 160+ килобайт.&lt;/p&gt;
&lt;p&gt;Это была присказка.&lt;/p&gt;
&lt;p&gt;Так вот, на днях, один из разработчиков на нашем проекте (ASP.NET) решил  сделать extension для списка строк, который бы умел сохранять и грузить  его из CSV файла. В C# extension это что-то типа синтаксического сахара  над существующим классом, который представляет собой функцию которую  можно вызвать через объект данного класса. Для примера выше это было бы  что-то типа такого:&lt;/p&gt;
&lt;pre class="brush: csharp"&gt;List csv = List.LoadCSV("hello.csv");
csv.SaveCSV("hello2.csv");
&lt;/pre&gt;
&lt;p&gt;Загрязнять объекты методами, которые не являются их  неотъемлемой частью - это зло. Такой код потом тяжело поддерживать,  классы раздуваются, теряется “идеологическая стройность” существующего  кода, а так же теряется расширяемость - логика поведения таких вот  утилитарных функций становится зашита в класс.&lt;/p&gt;
&lt;p&gt;Думаете эти примеры придуманы и в реальном мире не встречаются?&lt;/p&gt;
&lt;p&gt;Вот взять &lt;a&gt;web2py&lt;/a&gt;, зачем делать экспорт и  импорт данных в CSV частью объекта базы данных? Разве такой код сложнее  написать:&lt;/p&gt;
&lt;pre class="brush: python"&gt;export_to_csv(db, open('somefile.csv',  'wb'))&lt;/pre&gt;
&lt;p&gt;чем такой:&lt;/p&gt;
&lt;pre class="brush: python"&gt;db.export_to_csv(open('somefile.csv',  'wb'))&lt;/pre&gt;
&lt;p&gt;Но во втором случае, идет жесткая привязка к типу объекта (база  данных) и теряется reuse кода - duck typing в питоне никто не отменял.&lt;/p&gt;
&lt;p&gt;Другой пример из того же web2py: сохранение результатов запроса как  HTML! Это вообще нонсенс, включать в ORM логику представления:&lt;/p&gt;
&lt;pre class="brush: python"&gt;rows = db(db.person.id &gt; 0).select()
print rows.xml()
&lt;/pre&gt;
&lt;p&gt;Или вот взять Руби (за пример спасибо Александру Соловьеву):&lt;/p&gt;
&lt;pre class="brush: ruby"&gt;piranha@gto ~&gt; irb
&gt;&gt; String.methods.length
=&gt; 98
&gt;&gt; Fixnum.methods.length
=&gt; 97
&gt;&gt; require 'active_support'
=&gt; true
&gt;&gt; String.methods.length
=&gt; 190
&gt;&gt; Fixnum.methods.length
=&gt; 188
&lt;/pre&gt;
&lt;p&gt;Зачем?&lt;/p&gt;
&lt;p&gt;Может кто-то знает почему такое делают и почему это может быть,  даже теоретически, хорошо?&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/EatAtMrJoes/~4/byrwy1apEBg" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/EatAtMrJoes/~3/byrwy1apEBg/517954479</link><guid isPermaLink="false">http://mrjoes.tumblr.com/post/517954479</guid><pubDate>Tue, 13 Apr 2010 06:00:00 -0400</pubDate><feedburner:origLink>http://mrjoes.tumblr.com/post/517954479</feedburner:origLink></item><item><title>Svarga - еще один web фреймворк</title><description>&lt;p&gt;Примерно год назад решил сделать небольшой сайтик для себя. Думал на чем же делать (первоначально хотел на ASP.NET, но оный уже на работе набил оскомину), так что решил писать на питоне с использованием Django. Предупреждаю сразу, пост сугубо технический и предполагает значение Django в какой-либо степени.&lt;/p&gt;
&lt;p&gt;Django, конечно, классная штука, но через пару недель разработки столкнулся с тем, что я не могу сделать множество вещей или “просто” или “эффективно”.&lt;/p&gt;
&lt;p&gt;Пример таких вещей:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Странная система настроек доступа в contrib.auth - права доступа прописываются на уровне моделей&lt;/li&gt;
&lt;li&gt;Убогие возможности по разграничению доступа к различным частям админки&lt;/li&gt;
&lt;li&gt;Админка ориентирована на управление данными (читай - моделями) и не представляет собой некий фреймворк для построения админок&lt;/li&gt;
&lt;li&gt;Тормоза в contrib.auth при использовании профилей (надо патчить модель пользователя)&lt;/li&gt;
&lt;li&gt;Убогие возможности ORM’ки (те же агрегации появились уже после того, как я решил делать сайт) и т.д.&lt;/li&gt;
&lt;li&gt;Тормоза в шаблонах, особенно при наследовании&lt;/li&gt;
&lt;li&gt;Ужасные forms&lt;/li&gt;
&lt;li&gt;Смесь логики представления с моделями данных (см. Meta в моделях)&lt;/li&gt;
&lt;li&gt;И так далее.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;В общем, посидели мы с &lt;a title="Пиранья" target="_self" href="http://www.piranha.org.ua/"&gt;Пираньей&lt;/a&gt; и решили что надо что-то делать. Текущие фреймворки а-ля Pylons как-то не нравились тем, что они представляют собой сборную солянку, которую все равно надо собирать. А такого, чтобы взял и запустил проект с минимальными телодвижениями - не нашли.&lt;/p&gt;
&lt;p&gt;В результате, было принято решение делать фреймворк который внешне похож на Django, но без определенных его недостатков. За основу взяли:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Werkzeug как основу WSGI приложения&lt;/li&gt;
&lt;li&gt;Jinja2 как шаблонизатор&lt;/li&gt;
&lt;li&gt;SQLAlchemy как ORM по умолчанию (если что - ее можно заменить)&lt;/li&gt;
&lt;li&gt;WTForms для управления формами&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;a href="http://bitbucket.org/piranha/svarga/wiki/Home"&gt;Svarga&lt;/a&gt; представляет собой относительно тонкую прослойку между этими библиотеками. Кроме того, реализует некоторые вещи из django.contrib, например django.contrib.auth.&lt;/p&gt;
&lt;p&gt;Разработка ведется в фоновом режиме, так что фреймворк скорее хобби, чем основная работа. Первые строчки кода попали в репозиторий примерно год назад.&lt;/p&gt;
&lt;p&gt;Теперь немного о том, как это все выглядит.&lt;/p&gt;
&lt;p&gt;Некоторые архитектурные решения могут показаться спорными, но я попробую объяснить чем они вызваны и какие они могут иметь последствия.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Environment&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;В Svarga введено понятие окружения. Окружение это “глобальный” объект, который содержит всякие разные настройки, необходимые для обслуживания запроса от клиента. Каждый запрос получает свое личное окружение.&lt;/p&gt;
&lt;p&gt;Глобальность окружения условная - окружение это что-то типа threadlocals объекта с поддержкой greenlet’ов, и состоит в том, что оно доступно отовсюду.&lt;/p&gt;
&lt;p&gt;Делается это как-то так:&lt;/p&gt;
&lt;pre class="brush: python"&gt;from svarga import env&lt;/pre&gt;
&lt;p&gt;Конечно, таким подходом теряется функциональная чистота кода и, теоретически, производительность. Производительность теряется в том, что обращение к атрибутам окружения идет через промежуточный метод.&lt;/p&gt;
&lt;p&gt;Кроме того, в тех приложениях что я видел в Django, почти всегда кто-то пишет свой велосипед для доступа к request через threadlocals. В Svarga вы можете хранить переменные в окружении.&lt;/p&gt;
&lt;p&gt;Что хранится в environment? Куча всего, например:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;env.request - текущий запрос, если есть&lt;/li&gt;
&lt;li&gt;env.sqla - сессия алхимии, если включена&lt;/li&gt;
&lt;li&gt;env.jinja - контекст Jinja2&lt;/li&gt;
&lt;li&gt;env.user - если подключен contrib.auth&lt;/li&gt;
&lt;li&gt;и т.д.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Следует заметить, что окружение доступно из шаблонов:&lt;/p&gt;
&lt;pre class="brush: python"&gt;{{ env.user.last_name }}, {{ env.user.first_name }}&lt;/pre&gt;
&lt;p&gt;Что у нас получилось с таким подходом? Примерно следующее:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Окружение доступно отовсюду, что само по себе большой плюс&lt;/li&gt;
&lt;li&gt;Решение вопроса с загрязнением объекта запроса. Лично я считаю, что кормить дополнительные поля в request в Django это неправильно&lt;/li&gt;
&lt;li&gt;Унификация хранения различных данных, которые необходимы для работы программы&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Какие последствия использования окружения? Самое главное - во view не передается request, так как он и так доступен через окружение. Вот именно тут теряются функциональные принципы: функция которая, возможно, работает с запросом, не принимает его как параметр. Однако, благодаря Werkzeug этот самый запрос не всегда и нужен, так как следующий код это вполне себе полноценный view:&lt;/p&gt;
&lt;pre class="brush: python"&gt;@as_html('hello.html')
def hello(a,b,c,d):
    return dict(m=a*b*c*d)
&lt;/pre&gt;
&lt;p&gt;Дело в том, что Werkzeug умеет проверять, конвертировать и передавать параметры во view. Правило для примера выше выглядеть как-то так:&lt;/p&gt;
&lt;pre class="brush: python"&gt;Rule('/mul/&lt;int:a&gt;/&lt;int:b&gt;/&lt;int:c&gt;/&lt;int:d&gt;/', view='test.views.hello', endpoint='test.hello')
&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Routing&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Routing у нас от Werkzeug. Отличается он от Django тем, что вместо сырых regexp в правилах, используется более-менее читабельный синтаксис.&lt;/p&gt;
&lt;p&gt;Кроме того, он поддерживает фильтры, которые проверяют и конвертируют значения. В примере выше, “&lt;int:a&gt;” проверяет что на этом месте будет число и если это число, конвертирует значение в тип int и передает его во view.&lt;/p&gt;
&lt;p&gt;Фильтров разных много, и если надо - можно писать свои.&lt;/p&gt;
&lt;p&gt;Кроме того, Svarga реализует несколько полезных штук, например так называемые &lt;a href="http://hg.piranha.org.ua/svarga/docs/shortcuts.bundle.html"&gt;bundle&lt;/a&gt;. Bundle (хм, наверное правильно будет его перевести как упаковка) это класс, который содержит view как методы, и является чем-то типа Include() правила.&lt;/p&gt;
&lt;p&gt;Т.е. такая вот штука это и есть bundle:&lt;/p&gt;
&lt;pre class="brush: python"&gt;class MyBundle(Bundle):
     @expose('/')
     @as_html('entry.html')
     def entry(self):
         return dict(a=10)
&lt;/pre&gt;
&lt;p&gt;И потом его можно использовать так:&lt;/p&gt;
&lt;pre class="brush: python"&gt;url_map = Map(
    MyBundle('b1', '/1/'),
    MyBundle('b2', '/2/'),
    MyBundle('b3', '/3/'),
)
&lt;/pre&gt;
&lt;p&gt;Обратившись по адресу /1/ получим вызов в первом объекте, /2/ - во втором и так далее.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Обработка запросов&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Мы поддерживаем только WSGI интерфейс. На текущий момент, для отладочного режима используется Werkzeug. Но, к сожалению, он медленнее чем CherryPy, так что для production лучше его не использовать. Кроме того, у нас в contrib есть поддержка tornado, который работает быстрее первых двух.&lt;/p&gt;
&lt;p&gt;Если кто не знает что такое Werkzeug - советую почитать его документацию. Самое удобное и полезное что в нем есть, это онлайновый отладчик. Упало приложение? Не вопрос, можно на месте посмотреть stack trace, а заодно и получить доступ к консоли и посмотреть какие были переменные, потрейсить и т.д. По сути, это полноценный pdb, но через браузер.&lt;/p&gt;
&lt;p&gt;Как и в джанго, у нас есть request middleware, причем подход ничем не отличается - можно получить управление до выполнения запроса, можно после, можно при исключении и т.д.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Приложения&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Система приложений работает полностью аналогично оной в Django. Т.е. функционал всего проекта разбивается на отдельные части, которые можно подключать. Основные отличия:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Возможность инициализации приложения при старте процесса&lt;/li&gt;
&lt;li&gt;Поддержка локальных директорий с шаблонами&lt;/li&gt;
&lt;li&gt;Поддержка локальных директорий со статическими файлами. Причем есть команда генерации конфигурации для apache/nginx&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Про первое хотелось бы рассказать немного больше. Каждое приложение может представить такую вот функцию в своем __init__.py:&lt;/p&gt;
&lt;pre class="brush: python"&gt;def init(settings, env_class):
    settings.add_template_path('blog', 'templates')
    settings.add_static_dir('blog', 'static')
&lt;/pre&gt;
&lt;p&gt;Эта функция вызовется один раз и позволит выполнить некий код инициализации приложения.&lt;/p&gt;
&lt;p&gt;Параметров в такую функцию два - объект настроек конкретного приложения и класс окружения. Первое используется для различных настроек, например добавления статической директории, а второе - для добавления каких либо сущностей в класс окружения, а так же доступа к общим настройкам.&lt;/p&gt;
&lt;p&gt;Чем это удобно?&lt;/p&gt;
&lt;ol&gt;&lt;li&gt;Возможность конфигурировать приложения так, как ему надо&lt;/li&gt;
&lt;li&gt;Общий подход для всех приложений - никто не будет делать какие-то там синглтоны и флажки, что приложение уже инициализировано&lt;/li&gt;
&lt;li&gt;Возможность расширить класс-окружения, вместо постоянного добавления свойств уже во время жизни приложения. Например, в Django, request.auth добавляется в request middleware, на каждый запрос. У нас - один раз, при инициализации contrib.auth. Удобно и быстро.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;&lt;strong&gt;Модели&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Изначально, мы поддерживали только SQLAlchemy, но потом решили что можно поддержать GAE, а может и вообще nosql базы данных. В результате чего, код был немного отрефакторен. Сейчас имеется следующее:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Возможность добавления бекендов для разных библиотек&lt;/li&gt;
&lt;li&gt;Возможность использования нескольких разных бекендов параллельно&lt;/li&gt;
&lt;li&gt;Определен некий общий знаменатель для декларативного синтаксиса описания моделей. Если модель описать с помощью этого синтаксиса, бекенд БД конвертирует такую модель в понятное его библиотеки представление. Дает возможность писать один код для разных баз данных, например общий код contrib.auth отлично работает и под SQLAlchemy и под GAE.&lt;/li&gt;
&lt;li&gt;Общий формат метаданных, который создает конкретный бекенд. Т.е. можно работать с описанием модели через общий интерфейс. Сейчас ModelForm и ModelFormSet используют метаданные для создания форм.&lt;/li&gt;
&lt;li&gt;Определен общий интерфейс ModelManager, по аналогии с Django.&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;По умолчанию, используется &lt;a href="http://www.sqlalchemy.org/"&gt;SQLAlchemy&lt;/a&gt;. Основа моделей - sqlalchemy.ext.declarative, с кучкой всяких полезностей и красивостей.&lt;/p&gt;
&lt;p&gt;Несколько примеров красивостей:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Автоматическая генерация primary key с названием id, если его нет&lt;/li&gt;
&lt;li&gt;Автоматическая генерация имени таблицы, если ее нет&lt;/li&gt;
&lt;li&gt;Всякие хелперы для создания ManyToMany связей без ручного создания промежуточной таблицы&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;В целом, работа с моделями не сильно отличается от работы с моделями в Django, за исключением использования алхимии и ее синтаксиса работы с запросами. К слову, алхимия в этом плане наголову выше чем Django. Мне даже для очень сложных запросов не пришлось писать сырой SQL.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Шаблоны&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Для шаблонизации мы используем &lt;a href="http://jinja.pocoo.org/2/"&gt;Jinja 2&lt;/a&gt;. Сама по себе Jinja крута:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Очень-очень быстрая (шаблоны компилируются в код питона)&lt;/li&gt;
&lt;li&gt;Поддерживает sandboxing, особенно для случаев, когда шаблоны могут храниться в базе&lt;/li&gt;
&lt;li&gt;Умеет сильно больше чем шаблоны Django. Писать свои теги на каждый чих больше не надо&lt;/li&gt;
&lt;li&gt;Синтаксис от Django&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Единственная вещь, которую ставят в недостаток Jinja - возможность писать логику в шаблонах. Тут я категорически не согласен. Следует разделять логику приложения и логику представления. Генерация меню по определенным правилам - это точно логика представления, но в Django придется писать своей тег, если там не просто цикл по массиву. В Jinja это решается встроенными средствами.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Команды&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Писать свои команды в джанго было сущим наказанием. Для добавления команды приходилось писать страницу кода. Данная секция является больше рекламой &lt;a href="http://hg.piranha.org.ua/opster/docs/"&gt;Opster&lt;/a&gt;‘a (тоже от Пираньи), который занимается менеджментом команд и генерацией документации для них.&lt;/p&gt;
&lt;p&gt;Вот пример вполне полноценной команды:&lt;/p&gt;
&lt;pre class="brush: python"&gt;@command(name='lock-users', usage='-l LOCK users')
def lock_users(users, lock=('l', True, 'true or false')):
    'Lock or unlock users'
    q = User.objects.filter(User.name.contains(name))
    q = q.update(dict(locked=lock))
    print 'Done'
&lt;/pre&gt;
&lt;p&gt;Кстати, благодаря алхимии, в коде выше, sql injection не произойдет. Запрос будет вида: UPDATE users SET locked=? WHERE users.name LIKE ? и все литералы и параметры будут переданы отдельно.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Админка&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Django не была бы такой без своей знаменитой админки. Тут мы абсолютно согласны с большинством. Но… Лично я считаю что админка в Django сделана плохо - она нерасширяемая. Точнее, количество телодвижений, необходимых для изменения ее функционала, слишком велико даже для простых вещей.&lt;/p&gt;
&lt;p&gt;В результате, было принято решение пойти по другому пути. Админка в Svarga это некий микро-фреймворк для создания административного интерфейса. Она легко расширяется - можно легко добавить нестандартные вьюшки или изменить поведение дефолтных. По сути, админка основана на bundle, которые просто монтируются под /admin/.&lt;/p&gt;
&lt;p&gt;Пример “полноценной” админки модели:&lt;/p&gt;
&lt;pre class="brush: python"&gt;class SampleAdmin(ModelAdmin):
    class Meta:
        menu = ('Sample Admin', 'Test')
        model = Sample
&lt;/pre&gt;
&lt;p&gt;Определяем ее в приложении в admin.py и получаем админку модели Sample. Если очень хочется, можно свои вьюшки добавить, если унаследовать класс от AdminView и реализовать свои view методы.&lt;/p&gt;
&lt;p&gt;К слову, рабочей и стабильной админки еще нет, она все еще в стадии разработки.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Приложеньица&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Фреймворк постоянно развивается. Сейчас в contrib обитает следующее:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;admin - микро-фреймворк для построения админки. Все еще в разработке, но посмотреть уже можно.&lt;/li&gt;
&lt;li&gt;appengine - базовая поддержка Google AppEngine. К сожалению не развивается и не умеет метаданных (ModelForm не взлетит), но работает. Patches are welcome, as usual.&lt;/li&gt;
&lt;li&gt;auth - аналог django.contrib.auth. Умеет много всего, например возможность переопределения модели User, более чистое API - данные отделены от логики, поддержка нескольких backend’ов и т.д.&lt;/li&gt;
&lt;li&gt;cache - аналог django.contrib.cache&lt;/li&gt;
&lt;li&gt;cherrypy - HTTP сервер с использованием CherryPy&lt;/li&gt;
&lt;li&gt;dberrorlog - простенький logger всяких эксепшенов в базу&lt;/li&gt;
&lt;li&gt;jslib - всякие полезности и удобности при работе с JavaScript&lt;/li&gt;
&lt;li&gt;migrate - обертка над SQLAlchemy-migrate, с удобностями&lt;/li&gt;
&lt;li&gt;sessions - аналог django.contrib.session&lt;/li&gt;
&lt;li&gt;tableview - позволяет строить HTML таблички, который умеет сортировку, paging из набора исходных данных&lt;/li&gt;
&lt;li&gt;tornado - HTTP сервер с использованием Tornado&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;Что дальше?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Следует учесть, что на текущий момент, это хобби. И поскольку это хобби, проектом занимаемся когда есть свободное время.&lt;/p&gt;
&lt;p&gt;У проекта серьезно хромает документация - ее почти нет и очень сильно не хватает unit test’ов.&lt;/p&gt;
&lt;p&gt;На текущий момент, занимаемся:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Чисткой и украшательством API&lt;/li&gt;
&lt;li&gt;Доделываем админку&lt;/li&gt;
&lt;li&gt;Доделываем миграции (улучшение интеграции с sqlalchemy-migrate)&lt;/li&gt;
&lt;li&gt;Поддержкой интернационализации&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Напоследок хочется сказать - patches and contributions are welcome :-)&lt;/p&gt;
&lt;p&gt;P.S. Забыл самое главное, проект живет тут: &lt;a href="http://bitbucket.org/piranha/svarga/wiki/Home"&gt;http://bitbucket.org/piranha/svarga/wiki/Home&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/EatAtMrJoes/~4/NQHFGYsiNEg" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/EatAtMrJoes/~3/NQHFGYsiNEg/508537910</link><guid isPermaLink="false">http://mrjoes.tumblr.com/post/508537910</guid><pubDate>Fri, 09 Apr 2010 12:36:00 -0400</pubDate><category>jinja2</category><category>programming</category><category>python</category><category>sqlalchemy</category><category>svarga</category><category>web</category><category>werkzeug</category><category>django</category><feedburner:origLink>http://mrjoes.tumblr.com/post/508537910</feedburner:origLink></item><item><title>Немного о странных архитектурных решениях</title><description>&lt;p&gt;Сейчас у меня основная работа связана с разработкой софтинки на ASP.NET. Во время разработки оной, наткнулся на весьма неприятную штуку, которая теоретически должна была облегчить жизнь всяким программистам, а на самом деле является весьма спорной и даже вредной.&lt;/p&gt;
&lt;p&gt;Описание задачи: в .NET framework есть клиент SMTP. Письма он умеет посылать синхронно и асинхронно. Во втором случае можно подкинуть callback, который вызовется со статусом операции. Нужно посылать письма асинхронно, причем хотелось минимальными усилиями. Ну чего нам стоит вызвать SendAsync() вместо Send()? А оказалось, что не все так просто.&lt;/p&gt;
&lt;p&gt;Начиная с ASP.NET 2.0 (сейчас на дворе 3.5), разработчики ввели поддержку асинхронных операций в базовый функционал. Но при этом сделали достаточно странное предположение - программе &lt;em&gt;всегда&lt;/em&gt; нужен будет результат асинхронной операции &lt;em&gt;до&lt;/em&gt; отдачи ответа клиенту. В результате, они сделали такой вот велосипед - программист может запустить несколько асинхронных задач, используя стандартные средства .NET, а страница будет ждать, пока они не завершатся. Кроме того, они запретили (кидается исключение) использование асинхронных задач, если для страницы не поставить флажок Async=”True”.&lt;/p&gt;
&lt;p&gt;В целом, подход достаточно странный - у нас бизнес логика лежит в отдельных assembly, и посылка писем оттуда же. Получается, что для отсылки писем асинхронно, через стандартное API, мне прийдется менять уровень представления (хотя бы флажок Async=”true” повесить на все нужные страницы). Что как бы эээ.&lt;/p&gt;
&lt;p&gt;Вот такие вот дела.&lt;/p&gt;
&lt;p&gt;P.S. Если использовать асинхронную посылку писем вне контекста запроса ASP.NET, то все работает нормально.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/EatAtMrJoes/~4/YvV6-3KSDMA" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/EatAtMrJoes/~3/YvV6-3KSDMA/507975339</link><guid isPermaLink="false">http://mrjoes.tumblr.com/post/507975339</guid><pubDate>Fri, 09 Apr 2010 06:42:00 -0400</pubDate><category>asp.net</category><category>programming</category><category>codewtf</category><feedburner:origLink>http://mrjoes.tumblr.com/post/507975339</feedburner:origLink></item><item><title>Как взломали защиту Assassins Creed 2</title><description>&lt;p&gt;Расскажу интересную историю о том, как взломали DRM Assassins Creed 2, а заодно и другие игры, которые ее используют.&lt;/p&gt;
&lt;p&gt;Началось все с того, что в какой-то момент Ubisoft решила, что самостоятельно написанная защита сильно лучше существующих решений на рынке. Это частично так, так как для большинства защит уже существуют полуавтоматические распаковщики и игрушки появляются на варезниках чуть ли не сразу. Но и написание своей защиты тоже дело нетривиальное и очень легко проколоться на мелочах, что и случилось.&lt;/p&gt;
&lt;p&gt;Вкратце как работает защита, на примере Assassins Creed 2:&lt;/p&gt;
&lt;p&gt;1. Защита требует постоянного подключения к серверу защиты&lt;br/&gt;2. Защита интегрирована в игру&lt;br/&gt;3. Защита передает некие запросы на сервер защиты, тот отдает назад некие данные, которые необходимы для игры. Без этих данных игра падает.&lt;/p&gt;
&lt;p&gt;Все замечательно, кроме того что offline игра требует online подключения.&lt;/p&gt;
&lt;p&gt;Теперь о том, как ломали:&lt;/p&gt;
&lt;p&gt;1. Было выяснено, что общение с сервером идет по HTTPS&lt;br/&gt;2. Сертификат SSL оказался самоподписанным (!)&lt;br/&gt;3. Кто-то написал MITM сервер, который выдавал игре свой сертификат и логировал ответы и запросы в файл.&lt;br/&gt;4. После того, как кто-то прошел всю игру и вытащил все возможные ответы и вопросы, раздал файл всем желающим.&lt;/p&gt;
&lt;p&gt;Мораль такова: если бы они задумались о поднятии простенькой цепочки из двух сертификатов - сертификат был бы подписан ключем центра сертификации и публичный ключ ЦС был бы вшит в игру, так просто игра бы не сдалась. Во всяком случае без модификации кода или замены публичного ключа на чужой.&lt;/p&gt;
&lt;p&gt;Второй вывод - протокол должен использовать хоть какую-то случайность в ответе/запросе, так как это бы сильно усложнило взлом защиты, потому что пришлось бы разбираться в принципах формирования запросов, собрать необходимое количество статистической информации об ответах и так далее.&lt;/p&gt;&lt;img src="http://feeds.feedburner.com/~r/EatAtMrJoes/~4/cX2Pd8Y5gQA" height="1" width="1"/&gt;</description><link>http://feedproxy.google.com/~r/EatAtMrJoes/~3/cX2Pd8Y5gQA/507890727</link><guid isPermaLink="false">http://mrjoes.tumblr.com/post/507890727</guid><pubDate>Fri, 09 Apr 2010 05:30:00 -0400</pubDate><category>asassins creed</category><category>ubisoft</category><category>drm</category><category>security</category><category>games</category><feedburner:origLink>http://mrjoes.tumblr.com/post/507890727</feedburner:origLink></item></channel></rss>

