Как заставить URIBuilder.path(...) кодировать такие параметры, как "%AD"? Этот метод не всегда правильно кодирует параметры в процентах

Как заставить URIBuilder.path(...) кодировать параметры, такие как "%AD"?

Методы path, replacePath а также segment из URIBuilder не всегда правильно кодировать параметры в процентах.

Если параметр содержит символ "%", за которым следуют два символа, которые вместе образуют символ в кодировке URL, "%" не кодируется как "%25".

Например

URI uri = UriBuilder.fromUri("https://dummy.com").queryParam("param", "%AD");
String test = uri.build().toString();

"test" - это " https://dummy.com/?param=%AD"
Но это должен быть " https://dummy.com/?param=%25AD" (с символом "%", закодированным как "%25")

Метод UriBuilderImpl.queryParam(...) ведет себя так, когда два символа, следующие за "%", являются шестнадцатеричными. То есть метод "com.sun.jersey.api.uri.UriComponent.isHexCharacter(char)" возвращает true для символов, следующих за "%".

Я думаю, что поведение UriBuilderImpl является правильным, потому что я предполагаю, что он пытается не кодировать параметры, которые уже закодированы. Но в моем сценарии я никогда не буду пытаться создавать URL с параметрами, которые уже закодированы.

Что я должен делать?

Мое веб-приложение использует Джерси, и во многих местах я создаю URI с помощью класса UriBuilder или вызываю метод getBaseUriBuilder из UriInfo объекты.

Я могу заменить "%" на "%25", каждый раз, когда я вызываю методы queryParam, replaceQueryParam или же segment, Но я ищу менее громоздкое решение.

Как я могу заставить Джерси вернуть мою собственную реализацию UriBuilder?

Я думал о создании класса, который расширяет UriBuilderImpl, который переопределяет эти методы и выполняет эту замену перед вызовом super.queryParam(...) или что угодно.

Есть ли способ заставить Джерси возвращать мой собственный UriBuilder вместо UriBuilderImpl при вызове UriBuilder.fromURL(...), UriInfo.getBaseUriBuilder(...) и т. Д.?

Глядя на метод RuntimeDelegateЯ думал о расширении RuntimeDelegateImpl, Моя реализация переопределит метод createUriBuilder(...)который вернул бы мой собственный UriBuilder, вместо UriBuilderImpl, Затем я бы добавил файл META-INF/services/javax.ws.rs.ext.RuntimeDelegate и в нем, полное имя класса моего RuntimeDelegateImpl,

Проблема в том, что jersey-bundle.jar уже содержит META-INF/services/javax.ws.rs.ext.RuntimeDelegate это указывает на com.sun.jersey.server.impl.provider.RuntimeDelegateImplТаким образом, контейнер загружает этот файл вместо моего javax.ws.rs.ext.RuntimeDelegate, Поэтому он не загружает мой RuntimeDelegateреализация.

Можно ли обеспечить мою собственную реализацию RuntimeDelegate?

Должен ли я выбрать другой подход?

3 ответа

UriBuilder

Это возможно с помощью UriComponent из Джерси или URLEncoder напрямую из Java:

UriBuilder.fromUri("https://dummy.com")
        .queryParam("param",
                UriComponent.encode("%AD",
                    UriComponent.Type.QUERY_PARAM_SPACE_ENCODED))
        .build();

Какой результат в:

https://dummy.com/?param=%25AD

Или же:

UriBuilder.fromUri("https://dummy.com")
        .queryParam("param", URLEncoder.encode("%AD", "UTF-8"))
        .build()

Приведет к:

https://dummy.com/?param=%25AD

Для более сложных примеров (например, кодирование JSON в параметре запроса) такой подход также возможен. Давайте предположим, что у вас есть JSON как {"Entity":{"foo":"foo","bar":"bar"}}, При кодировании с использованием UriComponent результат для параметра запроса будет выглядеть так:

https://dummy.com/?param=%7B%22Entity%22:%7B%22foo%22:%22foo%22,%22bar%22:%22bar%22%7D%7D

JSON, как это может быть даже введено через @QueryParam в поле ресурса / параметр метода (см. JSON в разделе " Параметры запроса" или "Как вставить пользовательские типы Java через аннотации параметров JAX-RS").


Какую версию Джерси вы используете? В тегах вы упоминаете Джерси 2, но в RuntimeDelegate раздел вы используете Джерси 1 материал.

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

Следующие:

  1. UriBuilder.fromUri("http://localhost:8080").queryParam("name", "{value}").build("%20");
  2. UriBuilder.fromUri("http://localhost:8080").queryParam("name", "{value}").buildFromEncoded("%20");
  3. UriBuilder.fromUri("http://localhost:8080").replaceQuery("name={value}).build("%20");
  4. UriBuilder.fromUri("http://localhost:8080").replaceQuery("name={value}).buildFromEncoded("%20");

Будет выводить:

  1. http://localhost:8080?name=%2520
  2. http://localhost:8080?name=%20
  3. http://localhost:8080?name=%2520
  4. http://localhost:8080?name=%20

через http://comments.gmane.org/gmane.comp.java.jsr311.user/71

Кроме того, на основе документации Class UriBuilder в следующем примере показано, как получить то, что вам нужно.

Шаблоны URI разрешены в большинстве компонентов URI, но их значение ограничено конкретным компонентом. Например

UriBuilder.fromPath("{arg1}").build("foo#bar");

приведет к кодированию '#' так, что результирующий URI будет "foo% 23bar". Для создания URI "foo # bar" используйте

UriBuilder.fromPath("{arg1}").fragment("{arg2}").build("foo", "bar")

вместо. Имена и разделители шаблонов URI никогда не кодируются, но их значения кодируются при создании URI. Регулярные выражения параметров шаблона игнорируются при построении URI, т.е. проверка не выполняется.

При запуске можно переписать поведение по умолчанию в джерси, например, с помощью статического помощника, который вызывает RuntimeDelegate.setInstance(yourRuntimeDelegateImpl),

Поэтому, если вы хотите иметь UriBuilder, который кодирует проценты, даже если они выглядят как часть уже закодированной последовательности, это будет выглядеть так:

[...]
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.ext.RuntimeDelegate;

import com.sun.jersey.api.uri.UriBuilderImpl;
import com.sun.ws.rs.ext.RuntimeDelegateImpl;
// or for jersey2:
// import org.glassfish.jersey.uri.internal.JerseyUriBuilder;
// import org.glassfish.jersey.internal.RuntimeDelegateImpl;

public class SomeBaseClass {

    [...]

    // this is the lengthier custom implementation of UriBuilder
    // replace this with your own according to your needs
    public static class AlwaysPercentEncodingUriBuilder extends UriBuilderImpl {

        @Override
        public UriBuilder queryParam(String name, Object... values) {
            Object[] encValues = new Object[values.length];
            for (int i=0; i<values.length; i++) {
                String value = values[i].toString(); // TODO: better null check here, like in base class
                encValues[i] = percentEncode(value);
            }
            return super.queryParam(name, encValues);
        }

        private String percentEncode(String value) {
            StringBuilder sb = null;
            for (int i=0;  i < value.length(); i++) {
                char c = value.charAt(i);
                // if this condition is is true, the base class will not encode the percent
                if (c == '%' 
                    && i + 2 < value.length()
                    && isHexCharacter(value.charAt(i + 1)) 
                    && isHexCharacter(value.charAt(i + 2))) {
                    if (sb == null) {
                        sb = new StringBuilder(value.substring(0, i));
                    }
                    sb.append("%25");
                } else {
                    if (sb != null) sb.append(c);
                }
            }
            return (sb != null) ? sb.toString() : value;
        }

        // in jersey2 one can call public UriComponent.isHexCharacter
        // but in jersey1 we need to provide this on our own
        private static boolean isHexCharacter(char c) {
            return ('0' <= c && c <= '9')
                || ('A' <=c && c <= 'F')
                || ('a' <=c && c <= 'f');
        }
    }

    // here starts the code to hook up the implementation
    public static class AlwaysPercentEncodingRuntimeDelegateImpl extends RuntimeDelegateImpl {
        @Override
        public UriBuilder createUriBuilder() {
            return new AlwaysPercentEncodingUriBuilder();
        }
    }

    static {
        RuntimeDelegate myDelegate = new AlwaysPercentEncodingRuntimeDelegateImpl();
        RuntimeDelegate.setInstance(myDelegate);
    }

}

Предостережение: Конечно, это не очень настраивается, и если вы сделаете это в некотором библиотечном коде, который может быть повторно использован другими, это может вызвать некоторое раздражение.

Например, у меня была та же проблема, что и у OP, когда я писал клиент покоя в плагине Confluence, и вместо этого я получил решение "вручную кодировать каждый параметр", так как плагины загружаются через OSGi и, таким образом, просто не могут дотронуться до RuntimeDelegateImpl (получение java.lang.ClassNotFoundException: com.sun.ws.rs.ext.RuntimeDelegateImpl во время выполнения вместо).

(И для справки, в jersey2 это выглядит очень похоже; особенно код для подключения пользовательского RuntimeDelegateImpl такой же.)

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