如何强制URIBuilder.path(...)编码像"%AD"这样的参数?此方法并不总是正确地使用百分比编码参数

Mon*_*rlo 17 jax-rs jersey jersey-1.0

如何强制URIBuilder.path(...)编码参数如"%AD"

的方法path,replacePathsegmentURIBuilder并不总是编码参数与百分比,正确.

当参数包含字符"%"后跟两个字符一起形成URL编码字符时,"%"不会编码为"%25".

例如

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

"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应用程序使用新泽西州,在许多地方我建立一个使用类UriBuilder的URI或调用该方法getBaseUriBuilderUriInfo对象.

每次调用方法时queryParam,我都可以用"%25"替换"%" ,replaceQueryParam或者segment.但我正在寻找一个不那么麻烦的解决方案.

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

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

在调用UriBuilder.fromURL(...)UriInfo.getBaseUriBuilder(...)等时,有没有办法让Jersey返回我自己的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,因此容器加载该文件而不是my javax.ws.rs.ext.RuntimeDelegate.因此,它不会加载我的RuntimeDelegate实现.

有可能提供我自己的实现RuntimeDelegate吗?

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

Mic*_*dos 35

UriBuilder

这可以借助来自Jersey 的UriComponent或直接来自Java的URLEncoder来实现:

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

结果如下:

https://dummy.com/?param=%25AD
Run Code Online (Sandbox Code Playgroud)

要么:

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

将导致:

https://dummy.com/?param=%25AD
Run Code Online (Sandbox Code Playgroud)

对于更复杂的示例(即在查询参数中编码JSON),这种方法也是可能的.我们假设你有一个类似的JSON {"Entity":{"foo":"foo","bar":"bar"}}.使用UriComponent查询结果进行编码时,param会如下所示:

https://dummy.com/?param=%7B%22Entity%22:%7B%22foo%22:%22foo%22,%22bar%22:%22bar%22%7D%7D
Run Code Online (Sandbox Code Playgroud)

像这样的JSON甚至可以通过@QueryParam资源字段/方法参数注入(参见查询参数中的JSON或如何通过JAX-RS参数注释注入自定义Java类型).


你使用哪个泽西版?在标签中你提到泽西2,但在RuntimeDelegate你正在使用泽西1的东西.


JSu*_*uar 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");
Run Code Online (Sandbox Code Playgroud)

将导致'#'的编码,使得结果URI为"foo%23bar".要创建URI"foo #bar"使用

UriBuilder.fromPath("{arg1}").fragment("{arg2}").build("foo", "bar")
Run Code Online (Sandbox Code Playgroud)

代替.URI模板名称和分隔符从不编码,但在构建URI时会对其值进行编码.构建URI时会忽略模板参数正则表达式,即不执行验证.


Cle*_*aar 5

可以在启动时手动覆盖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);
    }

}
Run Code Online (Sandbox Code Playgroud)

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

例如,当我在Confluence插件中编写一个休息客户端时,我遇到了与OP相同的问题,并最终得到了"手动编码每个参数"的解决方案,因为插件是通过OSGi加载的,因此根本无法触及RuntimeDelegateImpl(java.lang.ClassNotFoundException: com.sun.ws.rs.ext.RuntimeDelegateImpl改为运行时).

(仅仅为了记录,在jersey2中看起来非常相似;特别是挂钩自定义RuntimeDelegateImpl的代码是相同的.)