продолжение см часть 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
продолжение см часть 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 “Плохие” решения
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 “Идеальное” решение
В художественной галерее AGO выставлены картины замечательного канадского художника Cornelius Krieghoff. Впрочем, канадским его назвать можно с большой натяжкой. Родился в Германии, жил в Нью-Йорке. Записался а армию США и дезертировал четыре года спустя. Удрал в Канаду, жил и писал в Монреале, Торонто, Квебеке, Париже. Затем, на старости лет, почему-то переехал в Чикаго.
Но любим мы его не только за это. Любим мы его за живописание быта тех дней. Художник не разменивался по мелочам и рисовал только про пьянки и про индейцев. Строго говоря он писал только четыре типа картин:
1. Перевернутые сани
2. Бухие французы
3. Охотники с добычей
4. Индейцы на привале
Krieghoff, конечно, был настоящим мастером, вариаций
этих четырёх тем он наплодил изрядно.
Но тема саней присутствует почти на каждом полотне, и в подавляющем большинстве вариаций из них кубарем летят французы в характерных красных шапках.
Иногда, видимо для усиления экспрессии, тема пьянки получает дальнейшее развитие. Вот на этом полотне под названием “На утро после пьянки” изображены гонцы за бухлом в перевёрнутых санях а также ещё с десятка два людей, продолжающих веселиться.
Каждый человечек отлично прописан во всех деталях, включая емкости с алкоголем.
А вот, извольте, французы напились и не заплатили за платную дорогу. Мальчик кричит им вслед непечатные слова, а инвалид смотритель даже порывается догнать.
Есть и другие варианты, например – французы напились и перевернули чужие сани в глубокий снег. Или просто ехали и перевернулись. Тут, очевидно, художник тешит свою любовь и к перевернутым саням и бухим современникам.
Интересно, картины Krieghoff были настолько популярны, что раскупались как горячие пирожки, а в 19-том веке даже был черный рынок поддельных картин. Быть может это объясняет однообразность сюжетов. Официальная критика объясняет тематику нарушения закона – буйным духом художника.
Как бы там ни было, картины очень добрые, какой-то радостью от них веет, легкостью жизни, что ли? Краски очень яркие, закат неизменно алеет тонкой полоской, небо голубеет среди редких облаков, снежные горы и еловые леса, лошади выдыхающие пар, гавкающие собаки и обязательно веселящиеся человеки.
Одна эта экспозиция стоит похода в AGO.
У меня на столе стоит вот такой вот телефон.
На экране пишет пропущенные звонки, время, есть там даже какое-то меню. Я ради любопытства пару раз туда лазил, но заблудившись терял интерес. В общем – ничего особенного, стоит и стоит, иногда я даже по нему звоню, или мне спамеры звонят.
Так вот сегодня, этот телефон меня уведомил, что он тут себе скачал новую прошивку и собирается перегрузиться. Спрашивал, не против ли я это сделать прямо сейчас, или может на потом отложить…
Это просто финиш, наверно настольные офисные телефоны это последние устройства перед холодильниками от которых я жду такого.
Случайно наткнулся на статью про телеуправляемые танкетки времен второй мировой, и вспомнил где я их видел – в игре “Company of Heroes”! Я ещё очень хорошо помню, как удивился этому чуду инженерной мысли – маленькое, с гусеницами, едет быстро, кто это и откуда?, и пока я сидел и удивлялся, танкетка доехала до моих войск и рванула. Вот примерно так оно тогда выглядело
Оказалось, это вовсе не полёт фантазии программистов, а вовсе даже пример из жизни. Они были и бензиновые и электрические, а управлялись по проводу, который разматывался-сматывался на катушку на корме. Вот кинохроника тех дней.

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

