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

时间:2014-01-29 13:02:55

标签: jersey jax-rs 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();

“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或调用getBaseUriBuilder个对象的UriInfo方法。

每次调用方法queryParamreplaceQueryParamsegment时,我都可以将“%”替换为“%25”。但我正在寻找一种不那么麻烦的解决方案。

如何让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的{​​{1}},因此容器会加载该文件而不是com.sun.jersey.server.impl.provider.RuntimeDelegateImpl。因此,它不会加载我的javax.ws.rs.ext.RuntimeDelegate实现。

是否可以提供我自己的RuntimeDelegate实现?

我应采取不同的方法吗?

3 个答案:

答案 0 :(得分:34)

UriBuilder

这可以借助泽西岛的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"}}这样的JSON。使用UriComponent进行编码时,查询参数的结果如下所示:

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

这样的JSON甚至可以通过@QueryParam注入资源字段/方法参数(参见JSON in Query Params or How to Inject Custom Java Types via JAX-RS Parameter Annotations)。


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

答案 1 :(得分:6)

查看以下示例是否有帮助。下面链接的主题广泛讨论了可用的功能及其不同的输出。

  

以下内容:

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

    将输出:

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

通过http://comments.gmane.org/gmane.comp.java.jsr311.user/71

此外,based on the Class UriBuilder documentation,以下示例显示了如何获取您所追求的内容。

  

URI的大部分组件都允许使用URI模板,但是它们的值   仅限于特定组件。 E.g。

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

将导致'#'的编码,使得结果URI为   “富%23bar”。要创建URI“foo #bar”,请使用

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

代替。 URI模板名称和分隔符从不编码,但它们的编码   构建URI时会对值进行编码。模板参数定期   构建URI时忽略表达式,即不进行验证   进行。

答案 2 :(得分:5)

可以在启动时手动覆盖平针织物中的默认行为,例如使用调用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插件中编写一个休息客户端时,我遇到与OP相同的问题,并最终使用&#34;手动编码每个参数&#34;相反,因为插件是通过OSGi加载的,因此根本无法触及RuntimeDelegateImpl(在运行时获取java.lang.ClassNotFoundException: com.sun.ws.rs.ext.RuntimeDelegateImpl)。

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