Work: MindMap services

Work Comments Off

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

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

Share

Work: REST Batching, part III

Work Comments Off

продолжение см часть 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
Share

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 “Плохие” решения

Share

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 “Идеальное” решение

Share

Work: Cacti & e-mail alerts

Work Comments Off

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

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

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

Share

Work: Парадигмы программирования

Work Comments Off

Замечательная статья о парадигмах программирования ссылка

Share

Work: PHP skills

Work Comments Off

you-are-good-php-programmer.gif

смеха ради прошёл тест.  Полгода наверно не брал в руки шашек, а серьёзно так и никогда не брал. Если бы в гугль подсматривал наверно ещё бы круче сдал

Share

Life: IT bubble 1.0

Life, Work Comments Off

Замечательный текст. Про рунет времен первого интернет бума. Очень познавательно.
http://www.ashmanov.com/pap/bubble/

Настоящие имена компаний изменены, но при желании узнаются. Сохраню пожалуй себе на случай если пропадёт статья.
Жизнь внутри пузыря

Share

Work: Double-Check Locking

Work Comments Off

Открытие дня – Double-Check Locking работает в .NET в отличие от Java.

Share

Work: C# and MS

Java, Work Comments Off

Предал светлые идела Java, работаю над проектом на C#. Прошёл месяц, так что все начальные впечатления должны были сгладится – и вот каков сухой остаток.

Чувствую себя маленькой шестеренкой в большом часовом организме Microsoft. Я уже и забыл как это. Никогда не испытывал никаких неприязненных чувств к MS, всегда считал да и продолжаю считать holy war-ные дела большими глупостями. Но разница с Java миром сильно режет глаз.

.Net это мир чистогана в империи MS. Шаг влево, шаг вправо, прыжок на месте карается тщетным чтением MSDN. Все что нужно среднестатистическому программисту уже сделано в core .NET, если это не сделано – значит это продаётся вот тут и тут за $1000+, если это не продаётся, значит это ересь. :-)

Забавно видеть как такое заботливо прокопанное русло неотвратно приводит ко всё большему и большему vendor-binding. Это как карусель, на которой весело ехать, но тяжело соскочить.

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

P.S. А сам язык C# – хорош да.

Share
Entries RSS Comments RSS Log in Admin