Странная проблема с кодировкой 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
Январь 2005

  query       = *( 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;
            }
        }
    }

символ "+" не допускается, поэтому он будет закодирован

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