限制 Jersey 中的路径媒体类型映射
Limit path media type mappings in Jersey
我已经为我的 Jersey 应用程序配置 MEDIA_TYPE_MAPPINGS
。不幸的是,这会导致我的应用程序中的通用上传服务出现一些问题。
@PUT
@Path("files/{filename}")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Response uploadFile(
@PathParam("filename") @NotNull @Size(max = 240) String filename, DataSource dataSource)
如果有人上传 .../files/file.xml
扩展名被删除。
有没有办法告诉 Jersey 跳过对该资源的过滤?
编辑:经过peeskillet的回答,我的假设得到了证实。我已提交改进请求:https://java.net/jira/browse/JERSEY-2780
首先,这绝不是一个错误。这是预期的行为。媒体类型映射的目的与处理文件无关,而是一种内容协商的替代形式,用于设置 headers 可能不可用的情况,例如在浏览器中。
虽然不在官方规范中,但此功能是规范最终发布之前的草案的一部分。大多数实现决定以一种或另一种方式包含它。 Jersey 恰好让你配置它。所以可以看到here in the spec in 3.7.1 Request Preprocessing
Set
M
= {config.getMediaTypeMappings().keySet()}
L
= {config.getLanguageMappings().keySet()}
m
= null
l
= null
- Where config is an instance of the application-supplied subclass of ApplicationConfig.
For each extension (a .
character followed by one or more alphanumeric characters) e
in the final path segment scanning from right to left:
- (a) Remove the leading ‘.’ character from
e
- (b) If
m
is null
and e
is a member of M
then remove the corresponding extension from the effective request URI and set m = e
.
- (c) Else if
l
is null
and e
is a member of L
then remove the corresponding extension from the effective request URI and set l = e
. Else go to step 4
If m is not null then set the value of the Accept header to config.getExtensionMappings().get(m)
3(b) 基本上是说应该从请求的 URI 中删除扩展名,而 4 是说应该有一些扩展名映射可以将 json
(扩展名)映射到 application/json
并将其设置为 Accept
header。你可以从不同的测试中看到,这种行为
@POST
@Path("/files/{file}")
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response doTest(@PathParam("file") String fileName, @Context HttpHeaders headers) {
String accept = headers.getHeaderString(HttpHeaders.ACCEPT);
return Response.ok(fileName + "; Accept: " + accept).build();
}
...
Map<String, MediaType> map = new HashMap<>();
map.put("xml", MediaType.APPLICATION_XML_TYPE);
resourceCnfig.property(ServerProperties.MEDIA_TYPE_MAPPINGS, map);
curl -v http://localhost:8080/api/mapping/files/file.xml -X POST
Result: file; Accept: application/xml
如果我们注释掉那个配置 属性,你会看到 Accept
header 还没有设置。
curl -v http://localhost:8080/api/mapping/files/file.xml -X POST
Result: file.xml; Accept: */**
话虽这么说...
当您配置 ServerProperties.MEDIA_TYPE_MAPPINGS
、org.glassfish.jersey.server.filter.UriConnegFilter
is the filter used for this feature. You can see in the source code in line 204 and 211 时,过滤器正在剥离扩展
path = new StringBuilder(path).delete(index, index + suffix.length() + 1).toString();
...
rc.setRequestUri(uriInfo.getRequestUriBuilder().replacePath(path).build(new Object[0]));
所以没有办法配置这个(至少我从查看源代码可以看出),所以我们必须扩展 class,覆盖 filter
方法和至少取出实际进行替换的最后一行,然后注册过滤器。这是我为使其正常工作所做的工作。我只是简单地复制并粘贴了过滤器中的代码,并注释掉了替换扩展名的那一行
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.annotation.Priority;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.core.Configuration;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.PathSegment;
import javax.ws.rs.core.UriInfo;
import org.glassfish.jersey.server.filter.UriConnegFilter;
@PreMatching
@Priority(3000)
public class MyUriConnegFilter extends UriConnegFilter {
public MyUriConnegFilter(@Context Configuration config) {
super(config);
}
public MyUriConnegFilter(Map<String, MediaType> mediaTypeMappings,
Map<String, String> languageMappings) {
super(mediaTypeMappings, languageMappings);
}
@Override
public void filter(ContainerRequestContext rc)
throws IOException {
UriInfo uriInfo = rc.getUriInfo();
String path = uriInfo.getRequestUri().getRawPath();
if (path.indexOf('.') == -1) {
return;
}
List<PathSegment> l = uriInfo.getPathSegments(false);
if (l.isEmpty()) {
return;
}
PathSegment segment = null;
for (int i = l.size() - 1; i >= 0; i--) {
segment = (PathSegment) l.get(i);
if (segment.getPath().length() > 0) {
break;
}
}
if (segment == null) {
return;
}
int length = path.length();
String[] suffixes = segment.getPath().split("\.");
for (int i = suffixes.length - 1; i >= 1; i--) {
String suffix = suffixes[i];
if (suffix.length() != 0) {
MediaType accept = (MediaType) this.mediaTypeMappings.get(suffix);
if (accept != null) {
rc.getHeaders().putSingle("Accept", accept.toString());
int index = path.lastIndexOf('.' + suffix);
path = new StringBuilder(path).delete(index, index + suffix.length() + 1).toString();
suffixes[i] = "";
break;
}
}
}
for (int i = suffixes.length - 1; i >= 1; i--) {
String suffix = suffixes[i];
if (suffix.length() != 0) {
String acceptLanguage = (String) this.languageMappings.get(suffix);
if (acceptLanguage != null) {
rc.getHeaders().putSingle("Accept-Language", acceptLanguage);
int index = path.lastIndexOf('.' + suffix);
path = new StringBuilder(path).delete(index, index + suffix.length() + 1).toString();
suffixes[i] = "";
break;
}
}
}
if (length != path.length()) {
//rc.setRequestUri(uriInfo.getRequestUriBuilder().replacePath(path).build(new Object[0]));
}
}
}
然后配置它
Map<String, MediaType> map = new HashMap<>();
map.put("xml", MediaType.APPLICATION_XML_TYPE);
map.put("json", MediaType.APPLICATION_JSON_TYPE);
resourceConfig.register(new MyUriConnegFilter(map, null));
curl -v http://localhost:8080/api/mapping/files/file.xml -X POST
Result: file.xml; Accept: application/xml
curl -v http://localhost:8080/api/mapping/files/file.json -X POST
Result: file.json; Accept: application/json
我已经为我的 Jersey 应用程序配置 MEDIA_TYPE_MAPPINGS
。不幸的是,这会导致我的应用程序中的通用上传服务出现一些问题。
@PUT
@Path("files/{filename}")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Response uploadFile(
@PathParam("filename") @NotNull @Size(max = 240) String filename, DataSource dataSource)
如果有人上传 .../files/file.xml
扩展名被删除。
有没有办法告诉 Jersey 跳过对该资源的过滤?
编辑:经过peeskillet的回答,我的假设得到了证实。我已提交改进请求:https://java.net/jira/browse/JERSEY-2780
首先,这绝不是一个错误。这是预期的行为。媒体类型映射的目的与处理文件无关,而是一种内容协商的替代形式,用于设置 headers 可能不可用的情况,例如在浏览器中。
虽然不在官方规范中,但此功能是规范最终发布之前的草案的一部分。大多数实现决定以一种或另一种方式包含它。 Jersey 恰好让你配置它。所以可以看到here in the spec in 3.7.1 Request Preprocessing
Set
M
={config.getMediaTypeMappings().keySet()}
L
={config.getLanguageMappings().keySet()}
m
=null
l
=null
- Where config is an instance of the application-supplied subclass of ApplicationConfig.
For each extension (a
.
character followed by one or more alphanumeric characters)e
in the final path segment scanning from right to left:
- (a) Remove the leading ‘.’ character from
e
- (b) If
m
isnull
ande
is a member ofM
then remove the corresponding extension from the effective request URI and setm = e
.- (c) Else if
l
isnull
ande
is a member ofL
then remove the corresponding extension from the effective request URI and setl = e
. Else go to step 4If m is not null then set the value of the Accept header to
config.getExtensionMappings().get(m)
3(b) 基本上是说应该从请求的 URI 中删除扩展名,而 4 是说应该有一些扩展名映射可以将 json
(扩展名)映射到 application/json
并将其设置为 Accept
header。你可以从不同的测试中看到,这种行为
@POST
@Path("/files/{file}")
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response doTest(@PathParam("file") String fileName, @Context HttpHeaders headers) {
String accept = headers.getHeaderString(HttpHeaders.ACCEPT);
return Response.ok(fileName + "; Accept: " + accept).build();
}
...
Map<String, MediaType> map = new HashMap<>();
map.put("xml", MediaType.APPLICATION_XML_TYPE);
resourceCnfig.property(ServerProperties.MEDIA_TYPE_MAPPINGS, map);
curl -v http://localhost:8080/api/mapping/files/file.xml -X POST
Result:file; Accept: application/xml
如果我们注释掉那个配置 属性,你会看到 Accept
header 还没有设置。
curl -v http://localhost:8080/api/mapping/files/file.xml -X POST
Result:file.xml; Accept: */**
话虽这么说...
当您配置 ServerProperties.MEDIA_TYPE_MAPPINGS
、org.glassfish.jersey.server.filter.UriConnegFilter
is the filter used for this feature. You can see in the source code in line 204 and 211 时,过滤器正在剥离扩展
path = new StringBuilder(path).delete(index, index + suffix.length() + 1).toString();
...
rc.setRequestUri(uriInfo.getRequestUriBuilder().replacePath(path).build(new Object[0]));
所以没有办法配置这个(至少我从查看源代码可以看出),所以我们必须扩展 class,覆盖 filter
方法和至少取出实际进行替换的最后一行,然后注册过滤器。这是我为使其正常工作所做的工作。我只是简单地复制并粘贴了过滤器中的代码,并注释掉了替换扩展名的那一行
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.annotation.Priority;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.core.Configuration;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.PathSegment;
import javax.ws.rs.core.UriInfo;
import org.glassfish.jersey.server.filter.UriConnegFilter;
@PreMatching
@Priority(3000)
public class MyUriConnegFilter extends UriConnegFilter {
public MyUriConnegFilter(@Context Configuration config) {
super(config);
}
public MyUriConnegFilter(Map<String, MediaType> mediaTypeMappings,
Map<String, String> languageMappings) {
super(mediaTypeMappings, languageMappings);
}
@Override
public void filter(ContainerRequestContext rc)
throws IOException {
UriInfo uriInfo = rc.getUriInfo();
String path = uriInfo.getRequestUri().getRawPath();
if (path.indexOf('.') == -1) {
return;
}
List<PathSegment> l = uriInfo.getPathSegments(false);
if (l.isEmpty()) {
return;
}
PathSegment segment = null;
for (int i = l.size() - 1; i >= 0; i--) {
segment = (PathSegment) l.get(i);
if (segment.getPath().length() > 0) {
break;
}
}
if (segment == null) {
return;
}
int length = path.length();
String[] suffixes = segment.getPath().split("\.");
for (int i = suffixes.length - 1; i >= 1; i--) {
String suffix = suffixes[i];
if (suffix.length() != 0) {
MediaType accept = (MediaType) this.mediaTypeMappings.get(suffix);
if (accept != null) {
rc.getHeaders().putSingle("Accept", accept.toString());
int index = path.lastIndexOf('.' + suffix);
path = new StringBuilder(path).delete(index, index + suffix.length() + 1).toString();
suffixes[i] = "";
break;
}
}
}
for (int i = suffixes.length - 1; i >= 1; i--) {
String suffix = suffixes[i];
if (suffix.length() != 0) {
String acceptLanguage = (String) this.languageMappings.get(suffix);
if (acceptLanguage != null) {
rc.getHeaders().putSingle("Accept-Language", acceptLanguage);
int index = path.lastIndexOf('.' + suffix);
path = new StringBuilder(path).delete(index, index + suffix.length() + 1).toString();
suffixes[i] = "";
break;
}
}
}
if (length != path.length()) {
//rc.setRequestUri(uriInfo.getRequestUriBuilder().replacePath(path).build(new Object[0]));
}
}
}
然后配置它
Map<String, MediaType> map = new HashMap<>();
map.put("xml", MediaType.APPLICATION_XML_TYPE);
map.put("json", MediaType.APPLICATION_JSON_TYPE);
resourceConfig.register(new MyUriConnegFilter(map, null));
curl -v http://localhost:8080/api/mapping/files/file.xml -X POST
Result:file.xml; Accept: application/xml
curl -v http://localhost:8080/api/mapping/files/file.json -X POST
Result:file.json; Accept: application/json