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