限制Jersey中的路径媒体类型映射

时间:2015-02-06 15:42:21

标签: java jersey mapping jersey-2.0 media-type

我为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,那么该扩展程序就会被删除。

有没有办法告诉泽西岛跳过这个资源的过滤?

编辑:在peeskillet回答之后,我的假设得到了确认。我已提交了改进请求:https://java.net/jira/browse/JERSEY-2780

1 个答案:

答案 0 :(得分:4)

首先,这绝不是一个错误。这是预期的行为。媒体类型映射的目的与使用文件无关,而是与设置标头可能不可用的情况(例如在浏览器中)的替代形式的内容协商。

虽然不在官方规范中,但此功能是规范最终版本之前草稿的一部分。大多数实现决定以某种方式包含它。泽西碰巧让你配置它。所以可以看到here in the spec in 3.7.1 Request Preprocessing

  
      
  1. 设置

         
        
    • M = {config.getMediaTypeMappings().keySet()}
    •   
    • L = {config.getLanguageMappings().keySet()}
    •   
    • m = null
    •   
    • l = null
    •   
    • 其中config是ApplicationConfig的应用程序提供的子类的实例。
    •   
  2.   
  3. 对于从右到左扫描的最终路径段中的每个分机(.字符后跟一个或多个字母数字字符)e

         
        
    • (a)从e
    • 中删除前导'。'字符   
    • (b)如果mnulleM的成员,则从有效请求URI中删除相应的扩展名并设置m = e
    •   
    • (c)如果lnulleL的成员,则从有效请求URI中删除相应的扩展名并设置l = e }。否则转到第4步
    •   
  4.   
  5. 如果m不为null,则将Accept标头的值设置为config.getExtensionMappings().get(m)

  6.   

3(b)基本上是说应该从请求的URI中删除扩展名,并且4表示应该有一些扩展映射可以将json(扩展名)映射到application/json并将其设置为Accept标头。您可以从不同的测试中看到这种行为

@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
  结果: file; Accept: application/xml

如果我们注释掉该配置属性,您将看到尚未设置Accept标头。

  

curl -v http://localhost:8080/api/mapping/files/file.xml -X POST
  结果: file.xml; Accept: */**

那就是说......

配置ServerProperties.MEDIA_TYPE_MAPPINGS时,org.glassfish.jersey.server.filter.UriConnegFilter是用于此功能的过滤器。您可以在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]));

所以没有办法配置这个(至少就我从查看源代码而言),所以我们必须扩展该类,覆盖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
  结果: file.xml; Accept: application/xml

     

curl -v http://localhost:8080/api/mapping/files/file.json -X POST
  结果: file.json; Accept: application/json