21

如何强制URIBuilder.path(...)对参数进行编码,例如"%AD"

方法pathreplacePath并不总是正确地用百分比编码参数segmentURIBuilder

当参数包含字符“%”后跟两个字符共同构成 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。

我应该怎么办?

我的 Web 应用程序使用 Jersey,并且在许多地方我使用类 UriBuilder 构建 URI 或调用对象的方法getBaseUriBuilderUriInfo

每次调用方法时,我都可以将“%”替换为“%25” queryParamreplaceQueryParam或者segment。但我正在寻找一个不那么繁琐的解决方案。

如何让 Jersey 返回我自己的 UriBuilder 实现?

super.queryParam(...)我想创建一个扩展 UriBuilderImpl 的类,该类覆盖这些方法并在调用或其他任何操作之前执行此替换。

有什么方法可以让 Jersey 在调用UriBuilder.fromURL(...)UriInfo.getBaseUriBuilder(...) 等时返回我自己的 UriBuilder 而不是 UriBuilderImpl?

看着方法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

我应该采取不同的方法吗?

4

3 回答 3

38

UriBuilder

这可以在 Jersey 的UriComponent或Java的URLEncoder的帮助下实现:

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),这种方法也是可能的。假设您有一个类似{"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 或如何通过 JAX-RS 参数注释注入自定义 Java 类型)。


您使用哪个泽西版本?在您提到 Jersey 2 的标签中,但在RuntimeDelegate您使用 Jersey 1 的部分中。

于 2014-02-08T20:08:36.907 回答
6

看看下面的例子是否有帮助。下面链接的线程对可用功能及其不同输出进行了广泛的讨论。

以下:

  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 时忽略模板参数正则表达式,即不执行验证。

于 2014-02-06T03:32:46.253 回答
4

可以在启动时手动覆盖 jersey 中的默认行为,例如使用调用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);
    }

}

警告:当然,这种方式不是很可配置,如果你在一些可能被其他人重用的库代码中这样做,这可能会引起一些麻烦。

例如,在 Confluence 插件中编写 rest 客户端时,我遇到了与 OP 相同的问题,并以“手动编码每个参数”解决方案结束,因为插件是通过 OSGi 加载的,因此根本无法触及RuntimeDelegateImpljava.lang.ClassNotFoundException: com.sun.ws.rs.ext.RuntimeDelegateImpl改为在运行时获取)。

(只是为了记录,在 jersey2 中这看起来非常相似;尤其是挂钩自定义 RuntimeDelegateImpl 的代码是相同的。)

于 2015-04-23T09:49:21.587 回答