Work: Twitter goes from Ruby to Scala

Java, Work 2 Comments »

Что-то много вокруг разговоров о Scala, и с каждым днём всё больше и больше. Вот и Twitter туда же. Предали светлые идеалы Ruby.

“…One of the most important changes they introduced to improve performance in the last nine months is moving from a Ruby messaging middleware to a custom build JVM-based messaging middleware written in Scala.”

говорит Evan Weave

Work: MindMap services

Work No Comments »

Надо бы попробовать эти сервисы, как на презентации рекомендуют

А вот и оригинальная презентация

Work: REST Batching, part III

Work No Comments »

продолжение см часть II “Идеальное” решение

Плохое решение

Multipart/mixed MIME messages.

Так как HTTP протокол полон приятных сюрпризов, то есть ещё один способ втиснуть несколько request-ов в один, и соотвественно несколько respons-ов тоже, в один. Всё, что надо сделать - это указать “Content-Type: multipart/mixed”. А затем аккуратно перечислить все что надо. Разделительные символы прилагаются.
Вот пример

POST /batch-proxy HTTP/1.1
Host: example.org
Content-Type: multipart/mixed; boundary=batch

-batch
Batch-Operation: POST /my/resource1
Host: example.org
Content-Type: application/xml
<?xml version="1.0″?>
<entry xmlns="...">...</entry>

-batch
Batch-Operation: DELETE /my/resource2
Host: example.org
If-Match: "ABC123XYZ"

Выглядит замечательно, не так ли? Снимается проблема ~37% overhead в трафике, так как можно текст передавать как текст, а двоичные данные как двоичные данные. Правда на этом достоинства и кончаются. А проблемы всё теже самые, и не REST и security hole, и не прозрачность в общем, смотри выше по списку.

Кроме того, надо помнить, что MIME был создан для передачи 8-битного текста через 7-ми битный SMTP. Да в нём есть много интересного, но HTTP не является MIME совместимым протоколом. Есть тонкие различия вызванные в основном тем, что HTTP оптимизировался для передачи данных через двоичные соединения + обратная совместимость, а у MIME были совсем другие проблемы - вроде ограничения на максимальную длинну строки в e-mail. Всех желающих углубится в эти различия приглашаю ознакомится с секцией 19.4 RFC2616

На практике всё это означает, что клиенту и серверу нужно иметь качественный парсер MIME сообщений. Не просто продвинутый HTTP клиент, но и такой довольно экзотический парсер. По этому пути пошли ребята из Microsoft построив свою ADO.NET Data Services Framework а также в Google - batching для GData. Если вам такой путь приемлем - то для Java есть бесплатный mime4j, а для .NET есть комерческий Mime4Net.

Для тех, кто не хочет возится с MIME есть уж совсем плохое решение

Совсем плохое решение

XML/JSON mark-up
Берем и решаем задачу в лоб. Конвертируем всё в текст, для разметки используем XML или JSON.
Пример с JSON

POST /batch-proxy HTTP/1.1
Content-Type: application/json
Accept: application/json
X-HTTP-Method-Override: BATCH
[
 {
   "method" : "PUT",
   "url" :  "http://someserver.com/some/resource/url",
   "body" : "<request body goes here>",
   "If-Match" : "xxxxxxxxxxx"
 },
 {
   "method" : "GET",
   "url" : "http://someserver.com/some/resource/url2"
 },
]

Пример с XML

POST /batch-proxy HTTP/1.1
Content-Type: application/xml
Accept: application/xml
X-HTTP-Method-Override: BATCH
<?xml version='1.0'?>
 <batch xmlns:b='http://batch.someserver.com/schema'>
  <b:request verb='put' uri= http://someserver.com/some/resource/url'>
   <b:headers>
    <b:header name='Content-Type' value='text/xml; charset=UTF-8' />
    <b:header name='Content-Length' value='XXX' />
   </b:headers >
  <b:body>
    <![CDATA[ ... ]]>
  </b:body>
 </b:request>
 <b:request verb='get' uri='http://someserver.com/some/resource/url2'>
  <b:headers>
   <b:header name='Accept' value='text/xml' />
  </b:headers >
 </b:request>
</batch>

Очевидно, что XML намного более избыточен, но решение следует принимать на основании доступности того или другого парсера. Ответы сервера выглядят точно также.

Таким образом:

  • Каждая операция состоит из “конверта”, который содержит HTTP заголовки и тело запроса
    • “method” and “url” обозначают соотвественно HTTP verb и URL операции
    • “body”, обязательное только для POST и PUT, содержит данные которые были бы переданы в стандартом теле HTTP request/response
  • Произвольные HTTP заголовки тоже могут быть указаны как в request так и в response

А вот так например может выглядять response содержащий двоичные данные

200 OK
Content-Type: application/json; charset=UTF-8'
[
 {
  "code" : 200,
  "Content-type": "application/octet-stream",
  "Content-transfer-encoding": "base64",
  "body": "PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R=="
 }
 {
  "code" : 200,
  "Content-type": "application/octet-stream",
  "Content-transfer-encoding": "base64",
  "body": "PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R=="
 }
]

Что же плохого в этом решении? А все из списка приведенного в самом начале статьи. Всё что ни возьми - всё и плохо, и не REST. И тем не менее этот подход является самым простым для реализации. А простотой не стоит пренебрегать.

Есть несколько приёмов которые помогут предложить пристойные ответы на список недостатков. Можно

  • запретить запросы ко внешним серверам
  • запретить рекурсивные запросы к самому себе
  • ограничить количество запросов в пакете
  • ограничить общее время выполнения всего пакета и высылать на клиент HTTP error code 206 - Partial Content, в случае превышения
  • прекращать выполнение всего пакета после первой же ошибки
  • использовать протокол HTTP 1.0 при исполнении запросов в пакете
  • декларативно отказаться от атомарности операций
  • декларативно отказаться от оптимизации порядка или распаралеливания выполнения запросов
  • принимать только операции типа GET

Work: REST Batching, part II

Work 1 Comment »

продолжение см часть I Общие положения

«Идеальное» решение

HTTP 1.1 протокол изначально поддерживает приём-передачу нескольких request-response. Для этого нужны persisted connections, pipelining и chunking см ниже.

Клиент открывает соединение, пишет туда request-ы, читает respons-ы. Кроме очевидных требований о поддержке этих persisted connections, pipelining и chunking на сервере и клиенте, есть ещё одна неприятность -  протокол требует, что бы клиент выслал заново все свои запросы сделанные в рамках этого pipelined соединения если оно прервётся в середине сеанса. А для того, что бы эту перепосылку можно было сделать без опаски, все запросы должны быть idempotent, т.е. только GET, HEAD, OPTIONS, PUT и DELETE. Это решение действительно очень хорошее с точки зрения REST - URI уникально адресуют ресурсы, HTTP headers означают правильные вещи и обрабатываются правильным образом, все транзитные сервера видят HTTP метод и могут, что-то правильное по этому поводу предпринять.

Однако, это «идеальное» решение существует главным образом на бумаге. Persisted connections не поддерживают мобильные устройства. Не все HTTP клиенты умеют читать chunked responses и использовать pipelining, привет AJAX рещениям. Да что chunked responses - для многих библиотек послать PUT запрос - уже проблема. Финальным аккордом тут является отсутствие поддержки метода POST в HTTP pipelining.

Раз «идеальное» решение не подходит, вернёмся к идее тунелирования HTTP протокола внутри HTTP.

Что такое persisted connections, pipelining и chunking:

Persisted connections

По умолчанию все соеднинения в HTTP 1.1 постоянные. Сервер не закрывает соединение сразу после обработки запроса тем самым позволяя клиенту использовать это соединение опять и опять. Если клиент желает получить несколько ресурсов с одного и тогоже сервера получается большой выигрыш в производительности. Вместо того, что бы открывать несколько соединений, все запросы пройдут по этому единственному каналу. Как клиент так и сервер могут оборвать этот канал с помощью HTTP header «Connection: close». Интересно, что запросы-ответы не обязаны быть строго последовательными, другими словами, клиент не обязан ждать ответа на первый запрос, а может сразу делать следующий и следующий, это становится возможным благодаря

Pipelining

Клиент посылает серию запросов, а сервер возвращает ответы в том порядке в котором были получены запросы. Часто бывает, что содержимое ответа генерируется динамически и сервер не знает точную длину которую следовало бы поместить в HTTP header Content-Length. Это нормально, в HTTP 1.1 в отличие от HTTP 1.0 заголовок Content-Length не является обязательным.

Так как Content-Length не передаётся, то нужен какой-то механизм, который бы сообщил клиенту, где кончается один response и начинается следующий. HTTP 1.1 решает эту проблему с помощью

Chunking

В случае динамических ресурсов, когда response не содержит Content-Length, он содержит Transfer-Encoding: chunked. Само же тело содержит куски с указаннием длины индивидуального куска. Кусок нулевой длины отмечает конец respons-а. Тут, пользуясь случаем, передаю привет IE6, который виснет, если ему этот последний кусок не передать.

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

Что же делать? К сожалению существует только плохое решение и очень плохое.

продолжение см часть III “Плохие” решения

Work: REST Batching, part I

Work 2 Comments »

HTTP внутри HTTP это, как реторта с
личинками дельфинов - вещь в себе

Если вы разрабатываете REST API, то рано или поздно к вам придут ваши клиенты с просьбой сделать пакетную обработку. Сделать так, что бы сервер приимал произвольный набор запросов одним пакетом и отправлял назад все результаты тоже одновременно. Это касается не только GET запросов, а любых методов - PUT, POST, DELETE и т.д. Ответы тоже будут разные, и будут содержать различные типы данных - например текст в различных encoding-ах и charset-ах, или двоичные данные в произвольном формате. В общем случае, скажут они, было бы полезно уметь обрабатывать пакеты любых HTTP запросов, в том числе запросы к внешним серверам которые необходимо исполнить последовательно с запросами к серверам из локальной сети.

С первого взгляда это кажется замечательной идеей. Клиент делает единственный POST запрос, получает единственный ответ и здорово экономит на сетевых задержках. На самом деле, здесь столько подводных камней, что вы сможете пожалеть что вообще с этим связались. Но выбора обычно нет. Что же тут плохого? Посмотрим сначала на

Религиозные проблемы REST

  • Очевидно, что URI этого прокси одинаков для пакетов любых запросов - на вход поступауют произвольные запросы, на выходе мы имеет произвольные ресурсы. Значит этот URI перестаёт уникально идентифицировать ресурсы которые этот прокси обрабатывает
  • Требуемая операция содержится не в HTTP verb, а в теле запроса. И несмотря на то, что POST request вроде как является non-idempotent & non-safe - внутри может быть что угодно - как набор совершенно безобидных GET запросов, так и действительно non-idempotent POST запросы, а может быть и вовсе некоторая смесь из них. Только клиент и сервет знают, что же там такое внутри. HTTP метод перестал быть понятным для всех транзитных серверов, таких как HTTP прокси и кеши
  • В зависимости от набора запросов в пакете, сервер может создать новые ресурс(ы), обновить или удалить старые, или просто вернуть запрошенный ресурс. Некоторые из этих запросов могли завершиться с ошибкой, а другие были успешными. Передать эту информацию обратно клиенту в HTTP header нелегко, значит она будет втиснута в body ответа. Значит response тоже перестал быть прозрачным для всех транзитных серверов

Ну вот и всё, что бы из REST сделать SOAP больше ничего не нужно. На самом деле, стало даже хуже, чем если бы SOAP использовалось с самого начала - транзитные сервера теперь не знают у каких request/response HTTP headers значат то, что написанно в спецификации, а у каких уже ничего не значат.

Существуют также и

Прочие недостатки пакетирования запросов

Каким бы образом это решение не было бы сделано, результат будет являться тунелированием HTTP протокола внутри HTTP протокола. Задумайтесь на секунду, что это значит.

  • Клиенты должны понимать все тонкости HTTP, и не только на этапе создания пакета запросов, но также они должны иметь обработчики ошибок, повторов и т.п. Они должны уметь обрабатывать все фокусы HTTP протокола - chunked и gzipped ответы. Перенаправления и HTTP headers
  • Транзитные сервера перестанут кешировать, исчезнет поддержка ETag, If-Modified-Since и т.п. А умные load-balancer-ы перестанут load balance-ить
  • Сломается “conversation pattern” если таковой будет подразумеваться конечным сервером.
  • Огромная дыра в security, привет системным администраторам
  • ~37% overhead в трафике из-за преобразования двоичных данных в текст, а также неизвестный CPU overhead на сервере и клиенте что бы эти данные кодировать-декодировать
  • Поддержка атомарности выполнения всего набора запросов
  • Определение порядка выполнения запросов в наборе

На самом деле, если заглянуть в спецификацию протокола HTTP 1.1 то можно увидеть, что существует «Идеальное» решение


продолжение см часть II “Идеальное” решение

Work: Google visualization and chart API

Work 1 Comment »

http://chart.apis.google.com/chart?cht=p3&chd=s:Uf9a&chs=250x100&chl=January|February|March|April

В свете инструментации кода, мониторинга и сбора статистики не смог пройти мимо довольно интересных средств визуализации. Нельзя сказать, что такого раньше совсем не было, можно только сказать - что такого качество, даже за деньги, ещё поискать надо. Итак, о Google Chart API,  Google visualization API и о том, как сложно сделать простую вещь.

Read the rest of this entry »

Work: Заметки очень хитрого архитектора

Work 1 Comment »

Интересная статья об архитектуре одноразового почтового сервера Mailinator - The Architecture of Mailinator. Дизайн интересен сам по себе, написан живым языком а также служит отличной иллюстрацией того, насколько правильно сформулированное тех. задание улучшает качество продукта. Что такое качество см. здесь

Paul Tyma, автор, конечно немножко обманщик - он сам себе заказчик и волен менять условия как ему вздумается, но в остроте ума ему не отказать.

Paul Tyma два раза молодец - вместо того, что бы письма форвардить куда скажут - он только показывает их на своём сайте, крутит рекламу и в ус себе не дует.

На таких как Paul Tyma надо равняться!
Java guy, работал в Google, теперь трудится как CTO в компании которая делает известный обфускатор кода - Dotfuscator для .Net

Тут Paul в третий раз молодец - для Java разве продашь что-то? так, срам один, а на любителях Microsoft и заработать не грех.


Work: webrunner for NUnit tests

Work No Comments »

Хочется странного.

Есть набор юнит тестов (nunit) на C#, которые на самом деле не юнит, а вовсе даже acceptance/functional tests. Есть также желание их запускать через web. Нету только возможности. В стандартной поставке nunit есть GUI runner, есть консольный runner, а вот ASPX runner-a нету.

Покопавшись в интернете я нашёл некий runit 2.4.2, даже вместе с исходниками. Как и следовало ожидать, работать он отказался. Сначала жаловался на несоответствие assembly-ей, потом, после пересборки из исходников стал имитировать работу, рисовать web интерфейс, но тесты всё равно не запускает. Говорит - TestFixtureSetUp exception и всё тут. Пол дня воскресенья убил, уже наверно мог своё написать… Сдался, выкинул runit, а проблема всё равно осталась.

Можно конечно в CruiseControl.NET подцепить, как проект без исходников и артефактов, но как-то оно там не к месту вроде…

В общем рынок поразительно пуст - для functional tests предлагаются монстры вроде QEngine, QTP и прочего. На другом конце спектра питоновский twill. А по-середине - ничего нет. А делов-то! REST API протестировать, куда уж проще.

Справедливости ради, стоит заметить, что с Java было бы ничуть не легче. Я поначалу нашёл замечательный RestClient (с исходниками) Обрадовался ужас как, думал - вот оно торжество разума над Microsoft, даже поучаствовал в коллективной разработке и дописал command line interface туда. Но по зрелому размышлению, если отринуть красивое GUI, это ничуть не лучше nunit тестов, а даже и хуже:

  • программистам придётся писать на Java, в то время как основной проект на С#
  • web gui все равно нет
  • дебага нет, статической проверки ошибок нет, IDE нет
  • добавил туда поддержку JSON (30 минут) код теста с assert-ами разросся, начал добавлять поддержку XML посмотрел на размер тестов, ужаснулся

В общем так и докатился до nunit тестов.

Как народ функциональное тестирование REST API организует - ума не приложу, похоже никак, или на коленке пишет что-то своё.

Work: Cacti & e-mail alerts

Work No Comments »

Последняя преграда на пути к полному забвению AppManager-а - это email alerts. AppManager out of the box может их отсылать и вести состояние мониторов, а Cacti нет. К счастью решение есть. Вот тут нужно взять Plugin Architecture и установить, затем здесь взять plug-in  Thold.

Выглядеть оно обещает вот так

Народ на форумах вроде не жалуется. Надо как руки дойдут попробовать. К сожалению AppManager уже всё равно куплен.

Work: Load average

Work No Comments »

Очень странно себя ведёт Debian Linux на Amazon EC2
Никаких cron job нет, нагрузки тоже никакой нет, чего оно скачет? Загадка. И спросить не у кого. Прям как в том анекдоте, про “странный стук в подвале”

Вот 5-ти минутное среднее

а вот 30-ти минутное

Entries RSS Comments RSS Log in Admin