如何强制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。
我该怎么办?
我的Web应用程序使用Jersey,在很多地方我使用类UriBuilder构建URI或调用getBaseUriBuilder
个对象的UriInfo
方法。
每次调用方法queryParam
,replaceQueryParam
或segment
时,我都可以将“%”替换为“%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
实现?
我应采取不同的方法吗?
答案 0 :(得分:34)
这可以借助泽西岛的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)
查看以下示例是否有帮助。下面链接的主题广泛讨论了可用的功能及其不同的输出。
以下内容:
UriBuilder.fromUri("http://localhost:8080").queryParam("name", "{value}").build("%20");
UriBuilder.fromUri("http://localhost:8080").queryParam("name", "{value}").buildFromEncoded("%20");
UriBuilder.fromUri("http://localhost:8080").replaceQuery("name={value}).build("%20");
- 醇>
UriBuilder.fromUri("http://localhost:8080").replaceQuery("name={value}).buildFromEncoded("%20");
将输出:
http://localhost:8080?name=%2520
http://localhost:8080?name=%20
http://localhost:8080?name=%2520
- 醇>
http://localhost:8080?name=%20
通过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的代码是相同的。)