Странная проблема с кодировкой URL
У меня странная проблема с urlencoding знаком плюс +
в качестве параметра запроса для запроса к API. Документация API гласит:
Дата должна быть в формате W3C, например, "2016-10-24T13:33:23+02:00".
Пока все хорошо, поэтому я использую этот код (минимизированный) для генерации URL, используя Spring UriComponentBuilder:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssX");
ZonedDateTime dateTime = ZonedDateTime.now().minusDays(1);
String formated = dateTime.format(formatter);
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(baseUrl);
uriComponentsBuilder.queryParam("update", formated);
uriComponentsBuilder.build();
String url = uriComponentsBuilder.toUriString();
Незакодированный запрос будет выглядеть так:
https://example.com?update=2017-01-05T12:40:44+01
Закодированная строка приводит к:
https://example.com?update=2017-01-05T12:40:44%2B01
которая (ИМХО) является правильно закодированной строкой запроса. Увидеть %2B
заменяя +
в +01
в конце строки запроса.
Однако теперь, когда я отправляю запрос к API, используя закодированный URL, я получаю сообщение об ошибке, в котором говорится, что запрос не может быть обработан.
Если, однако, я заменяю %2B
с +
перед отправкой запроса работает:
url.replaceAll("%2B", "+");
Насколько я понимаю, +
знак является заменой whitespace
, Таким образом, URL, который сервер действительно видит после декодирования, должен быть
https://example.com?update=2017-01-05T12:40:44 01
Я прав с этим предположением?
Могу ли я что-нибудь сделать, кроме как связаться с владельцем API, чтобы заставить его работать с использованием правильно закодированного запроса, кроме странных нестандартных замен строк?
ОБНОВИТЬ:
Согласно спецификации RFC 3986 (раздел 3.4), +
войти в параметр запроса не нужно кодировать.
3.4. запрос
Компонент запроса содержит неиерархические данные, которые наряду с данными в компоненте пути (раздел 3.3) служат для идентификации
ресурс в рамках схемы URI и полномочий по именованию
(если есть). Компонент запроса обозначен первым вопросом
знак ("?") и оканчивается символом цифры ("#")
или к концу URI.Бернерс-Ли и др. Отслеживание стандартов [Страница 23] Общий синтаксис RFC 3986 URI
Январь 2005query = *( pchar / "/" / "?" )
Косая черта ("/") и знак вопроса ("?") Могут представлять данные в компоненте запроса. Помните, что некоторые старые ошибочные реализации могут некорректно обрабатывать такие данные, когда они используются в качестве базового URI для относительных ссылок (раздел 5.1), по-видимому
потому что они не могут отличить данные запроса от данных пути, когда
ищу иерархические разделители. Однако в качестве компонентов запроса
часто используются для переноса идентифицирующей информации в форме
пары "ключ = значение" и одно часто используемое значение является ссылкой на
другой URI, иногда для удобства использования лучше избегать
кодирование этих символов.
Согласно этому ответу на stackru, Spring UriComponentBuilder использует эту спецификацию, но, по-видимому, на самом деле это не так. Таким образом, новый вопрос будет, как заставить UriComponentBuilder следовать спецификациям?
2 ответа
Так что кажется, что Spring UriComponentBuilder кодирует весь URL, устанавливая флаг кодирования в false
в build()
метод не имеет никакого эффекта, потому что toUriString()
метод всегда кодирует URL, как он вызывает encode()
явно после build()
:
/**
* Build a URI String. This is a shortcut method which combines calls
* to {@link #build()}, then {@link UriComponents#encode()} and finally
* {@link UriComponents#toUriString()}.
* @since 4.1
* @see UriComponents#toUriString()
*/
public String toUriString() {
return build(false).encode().toUriString();
}
Решением для меня (на данный момент) является кодирование того, что действительно нужно кодировать вручную. Другим решением может быть (может потребоваться и кодировка) получение URI и дальнейшая работа с ним.
String url = uriComponentsBuilder.build().toUri().toString(); // returns the unencoded url as a string
Вы можете использовать builder.build().toUriString()
Это сработало для меня
благодаря
Кодирование 2017-01-05T12:40:44+01
дает тебе 2017-01-05T12%3A40%3A44%2B01
вместо 2017-01-05T12:40:44%2B01
как вы предложили.
Может быть, поэтому сервер не может обработать ваш запрос, это наполовину закодированная дата.
В org/springframework/web/util/HierarchicalUriComponents.java
QUERY_PARAM {
@Override
public boolean isAllowed(int c) {
if ('=' == c || '+' == c || '&' == c) {
return false;
}
else {
return isPchar(c) || '/' == c || '?' == c;
}
}
}
символ "+" не допускается, поэтому он будет закодирован