Work: REST Batching, part III

Work Add 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
Share

Comments are closed.

Entries RSS Comments RSS Log in Admin