Каков наилучший способ создания HTTP-запроса, когда требуются сложные параметры?

У меня есть несколько веб-сервисов, которые я пишу, и я стараюсь быть максимально RESTful. Я размещаю эти веб-службы с помощью HTTPHandler, работающего внутри IIS/ASP.NET/SharePoint.

Большинство моих сервисов ожидают HTTP GET. У меня есть два из них, которые просто возвращают некоторые данные (например, запрос) и будут идемпотентными, но параметры могут быть несколько сложными. Оба из них могут включать символы в параметры службы, которые не разрешены, по крайней мере, для части URL-адреса PATH.

Используя IIS, ASP.NET и SharePoint, я обнаружил, что следующие символы в пути URL даже не попадают в мой HttpHandler, даже если закодирован Url (запрос разрывается, и у меня нет простого управления этим):

  • % (% 25)
  • & (% 26)
  • * (% 2a, но URL не кодировался)
  • + (% 2b)
  • : (% 3а)
  • <(% 3c)
  • (% 3e)

Следующие символы попали в мой HttpHandler, но UriTemplate не смог обработать их должным образом, даже если закодирован Url:

  • (% 23)

  • , (%2e, но Url не кодировался; UriTemplate удалил символ ".", Если это последний символ перед /)
  • ? (% 3f)
  • / (% 2f - UriTemplate завершается ошибкой по очевидным причинам, даже если UrlEncoded)
  • \ (% 5с)

Итак, я был несколько тщательным, но мне нужно проверить эти символы в коде URL в строке запроса. Похоже, что это будет работать по большей части там.

В одном из моих сервисов специальные символы, которые являются параметром, являются семантически частью запроса / фильтра (фактически, терминами поиска для поискового сервиса), но в другом они на самом деле не являются частью запроса / фильтра, поэтому в идеале они являются частью путь, а не строка запроса.

У меня вопрос, какой вариант лучше? Вот некоторые из них, которые я знаю:

  1. Используйте HTTP GET и строку запроса. Все, что может использовать специальные символы, должно быть в строке запроса и закодировано в URL. Это то место, где я склоняюсь, но меня беспокоят очень длинные строки запросов (IE имеет ограничение 2083)

  2. Используйте HTTP GET и base64-кодировку внутри пути. Используйте Модифицированную Base64 для URL для любых параметров, которые могут использовать специальные символы, и сохраните их как часть пути, если это необходимо. Я попробовал это, и это работает, но это отчасти уродливо. По-прежнему беспокоит чрезвычайно длинные строки запроса.

  3. Используйте HTTP POST и тело сообщения. Все, что может использовать специальные символы, должно быть в теле запроса. Похоже, достойное решение, но посты, как понимают, не являются идемпотентными и (я думал), как правило, предназначены для изменений (тогда как здесь никаких изменений не происходит).

  4. Используйте HTTP GET и тело сообщения. Все, что может использовать специальные символы, должно быть в теле запроса. Это кажется плохой идеей в соответствии с SO: HTTP GET с телом запроса и Роем ​​Филдингом.

  5. Используйте комбинацию #3 и #1 или #2 выше в зависимости от размера запроса.

  6. Другой???

Обратите внимание, что в некоторых случаях я могу изменить ситуацию, чтобы предотвратить использование специальных символов (и я могу это сделать), но я не смогу сделать это во всех случаях.


Что касается длины URI, RFC2616 Sec3.2.1 говорит следующее:

Протокол HTTP не устанавливает никаких априорных ограничений на длину URI. Серверы ДОЛЖНЫ иметь возможность обрабатывать URI любого ресурса, который они обслуживают, и ДОЛЖНЫ иметь возможность обрабатывать URI неограниченной длины, если они предоставляют формы на основе GET, которые могут генерировать такие URI. Сервер ДОЛЖЕН вернуть статус 414 (Request-URI Too Long), если URI длиннее, чем может обработать сервер (см. Раздел 10.4.15).

  Note: Servers ought to be cautious about depending on URI lengths
  above 255 bytes, because some older client or proxy
  implementations might not properly support these lengths.

Кроме того, максимальная длина URL-адреса составляет 2 083 символа в Internet Explorer.

Связанный: Как передать сложные запросы в REST?

11 ответов

Решение

Я рекомендую вам прочитать спецификацию HTTP 1.1, особенно разделы 3.2 Унифицированные идентификаторы ресурсов и 9.1.1 Безопасные методы. Они, надеюсь, ответят на ваш вопрос.


Вот дополнительная информация:

Там нет идеального способа сделать это.

Правильный способ HTTP/REST - использовать GET и указывать все параметры в URL в качестве аргументов запроса. Вы определили две практические проблемы с этим подходом

  1. Программное обеспечение вашего сервера неправильно передает вам некоторые символы, даже если URL закодирован. На самом деле, это меня удивляет, и вам следует внимательнее присмотреться к тому, что происходит, и вы даже не можете получить процент через URL. Предоставляет ли ваша структура необработанный доступ к PATH_INFO или другим необработанным символам? Это может дать вам обходной путь.
  2. Ваши строки запроса могут быть слишком длинными. Вы упоминаете ограничение в 2083 байта в MSIE. Это может или не может быть практической проблемой для вас, в зависимости от того, является ли MSIE клиентом вашего API. (Т. Е. Через Javascript, выполняющий вызовы JSON API). Но, по моему опыту, очень длинные URL-адреса будут таинственно ломаться в нескольких местах; прокси-серверы кешируются по пути, даже с межсетевым экраном с сохранением состояния. Если у вас есть абсолютный контроль над клиентами и сетевым путем, вы, вероятно, можете жить с опасностями длинных URL. Если это публичный API, забудьте об этом.

Надеюсь, вы можете сделать простой GET работать в вашей среде. Вы можете даже подумать о рефакторинге своего API, чтобы уменьшить размер данных запроса.

Но что, если вы не можете заставить GET работать? Вы предлагаете несколько альтернатив. Я бы немедленно уволил двух из них. Не помещайте содержимое в тело запроса GET; если вы попробуете это, слишком много программного обеспечения сломается, и в любом случае оно нарушает тот самый REST-дух, который вы пытаетесь уловить. И я бы не стал использовать кодировку base64. Это может помочь вам обойти проблему 1, ваш сервер не обрабатывает некоторые символы в URL-адресах правильно. Но при неправильном применении это фактически сделает ваши URL длиннее, а не короче, что усугубит проблему 2. Даже если вы правильно сделаете base64 и включите некоторое сжатие, это не сделает URL-адреса значительно короче, а сделает клиента намного сложнее.

Ваше наиболее практичное решение, вероятно, вариант 3, HTTP POST. Это не RESTful; Вы должны использовать GET для запросов только для чтения. И вы потеряете некоторые преимущества подхода REST с кэшированием GET и тому подобного. С другой стороны, он будет работать правильно и просто с большим разнообразием интернет-инфраструктуры и программных библиотек. Затем вы можете передать столько данных, сколько хотите, в теле POST с помощью кодирования multipart/form-data, JSON или XML. (Я построил две основные общедоступные веб-службы с использованием SOAP, который представляет собой просто XML для POST. Это некрасиво и не RESTful, но работает надежно.)

REST - отличная дизайнерская парадигма. Это руководство. Если оно не подходит вашему приложению, не думайте, что вам нужно его придерживаться. HTTP не подходит для передачи больших объемов данных на сервер с помощью GET. Если вам нужны гигантские параметры запроса, сделайте что-нибудь еще.

Если запрос слишком велик для ввода в URI, превратите его в ресурс (например, сохраненный поиск). Я работал над успокаивающим API для системы бронирования отелей; В поисковом запросе было слишком много параметров (предпочтений, списка номеров и т. д.), поэтому я превратил его в ресурс, который я ВЫСТАВЛЯЮ на сервере. Затем сервер отвечает с URI, однозначно идентифицирующим поиск, в теле которого размещен запрос + его результаты:

POST http://hotels.xyz/searches
body <search><query>...</query></search>

отклик

201 Created - Location: http://hotels.xyz/searches/someID
Body <search><query>...</query><results>...</results></search>

Используйте пользовательские заголовки HTTP с HTTP GET, если ничего не получится. Заголовки HTTP могут быть установлены почти всеми клиентами.

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

Я бы нацелился на HTTP POST. Он красиво маркируется, когда попадает в PHP (или что бы вы ни использовали) и не имеет ограничений по размеру, которые есть у других.

Я бы определенно начал с того, с чего вы начали: сокращение URL. Я бы попытался сократить имена параметров (? A =XXX;b=YYY;c=zzz); Перекодировать весь запрос в Base64; GZip Base64; Хаффман кодирует GZip; ... все, что нужно. Как только я понял, что сокращение не будет работать во всех случаях (у вас есть какая-то динамическая система создания фильтров, которую можно добавлять на неопределенное время, или без), вы должны признать, что, возможно, пытаетесь сделать все в пределах одного запроса может не работать...

Я НЕ собираюсь предлагать вам генерировать несколько GET с разделенными параметрами и пытаться отслеживать запросы таким образом...

Единственный "надежный" метод, который я МОГУ предложить, - это сохранить / установить запрашиваемую строку запроса в одном запросе (POST) и вернуть ей фиксированный размер идентификатора (или guid), который идентифицирует расположение параметра запроса в вашем хранилище данных (filterID), затем выполните фактический запрос GET, используя токен filterID вместо значения строки запроса полного фильтра. Это позволит все виды аккуратных вещей, таких как кеширование ответов на основе filterID, так что вы можете (теоретически) повторно использовать те же фильтры позже (вместо повторного ввода их вручную, просто сохраните "метку" вместе с телом фильтра и выберите из последние 5 фильтров по меткам) или, по крайней мере, сохраните их вместе с вашими данными, чтобы каждый раз, когда вы обновляете страницу, она не пересылала весь запрос фильтра.

Вы должны поместить параметры в строку запроса, используя HTTP-запрос GET. Ограничения в некоторых старых веб-браузерах не являются проблемой, потому что только люди, просматривающие API в веб-браузере, скорее всего, являются разработчиками (или, по крайней мере, техническими).

Помните, что клиентские приложения не должны манипулировать URL-адресами, предоставленными им вашим API. URL-адреса представляют собой непрозрачные идентификаторы для клиентов, используемые только для направления их туда, где можно найти конкретные ресурсы.

Если это невозможно по какой-либо причине, я бы использовал POST-запрос с параметрами, закодированными в теле. Это не будет полностью RESTful, но при условии, что ваши ресурсы спроектированы правильно, влияние на клиентский код должно быть минимальным.

Если вы генерируете эти длинные URL-адреса на сервере, вы можете использовать сжатие для информации о пути.

Так что если у вас есть что-то вроде /? Param1=bla-bla¶m2=bla-bla, вы просто сжимаете эти параметры и делаете URL похожим на /? Query=ASsadcnfAFFASFscnsdlc

Когда вы получаете такой запрос, вы просто распаковываете его и анализируете строку параметра.

Рой Филдинг, вероятно, одобрил бы использование POST в этой ситуации, но вы должны спросить его.

Как правило, большинство приложений, в которых используются предоставленные пользователем данные, передаваемые на сервер, небезопасны. Единственным исключением является случай, когда информация представлена ​​в форме обобщенных параметров запроса, для которых существует компромисс между GET и POST, который обычно включает в себя размер содержимого параметра. GET желателен только для тех случаев, когда параметры могут быть выражены как значимый URI.

Подумайте о поддержке:
- GET запросы с короткой строкой запроса
- POST-запросы с длинной строкой запроса в теле и X-HTTP-Method-Override: GET ( https://en.wikipedia.org/wiki/List_of_HTTP_header_fields)

Остерегайтесь не смешивать "POST /orders", который является массовым созданием новых заказов, и "POST /orders" с "X-HTTP-Method-Override: GET", который является поиском заказа.

base64 должен это сделать. в противном случае используйте знак%, который является стандартным.

Другие вопросы по тегам