【问题标题】:How to serve already gzipped content in JAX-RS?如何在 JAX-RS 中提供已压缩的内容?
【发布时间】:2015-04-15 14:56:01
【问题描述】:

我正在使用 Resteasy 开发一个小型 JAX-RS 应用程序。我希望应用程序为 Javascript 和 CSS 文件等提供一些静态内容,并且我想利用打包在 webjars.org 的 jar 中的资源的 gzip 压缩版本。因此,我需要处理 Accept-Encoding 标头并检查 .gz 是否存在(或不存在)。

到目前为止,我所拥有的是:

@Path("res/{path:.*}")
@GET
public Response webjars(@PathParam("path") String path, @HeaderParam("Accept-Encoding") String acceptEncoding) {

    // Guesses MIME type from the path extension elsewhere.
    String mime = mimes.getContentType(path);

    if (acceptEncoding.contains("gzip")) {
        InputStream is = getClass().getResourceAsStream("/META-INF/resources/webjars/" + path + ".gz");
        if (is != null)
            return Response.ok().type(mime).encoding("gzip").entity(is).build();
    }

    InputStream is = getClass().getResourceAsStream("/META-INF/resources/webjars/" + path);
    if (is != null)
        return Response.ok().type(mime).entity(is).build();

    return Response.status(Status.NOT_FOUND).build();
}

但它不起作用。提供的内容完全被破坏了。到目前为止,我发现了一个再次压缩流的组件:org.jboss.resteasy.plugins.interceptors.encoding.GZIPEncodingInterceptor,因为我手动填充了Content-Encoding 标头(使用ResponseBuilder.encoding 方法)。

这对我来说似乎是一个错误,因为显然无法共享已压缩的流。但是,这是否可以使用 JAX-RS 实现?这是 Resteasy 错误吗?

我可以想出多种方法在 Resteasy 外部实现同样的事情,比如映射 webjars.org servlet(我不在 Servlet API 3.0 环境中,所以我没有 META-INF/resources/ 自动类路径映射) .尽管如此,我的问题仍然占上风。它适用于其他几种情况。

更新:

为了记录,我已经填写了问题RESTEASY-1170

【问题讨论】:

  • It's not a bug; it's documented。我正在考虑避免禁用整个功能(我不完全确定如何),您可以简单地实现自己的拦截器并命名绑定它。问题在于 Content-Encoding 标头。您的自定义拦截器只需添加标题。这是我唯一能想到的。除非您想完全禁用该功能。为此,您需要做一些研究:-)
  • 是的,我知道注释。 GZIP 注释不应该足以有意启用压缩吗?如果是这样,为什么要检查 Content-Encoding 标头?
  • 我没有提到任何关于 @GZIP 注释的内容。我要说的是,如果您不希望它由当前拦截器处理,请不要设置标头,创建一个将名称绑定的拦截器,使用您自己的注释,并将优先级设置为比你想避免的低一个,然后在你的拦截器中设置标题。看起来很多(实际上不是),但这是我现在唯一能想到的。
  • 我会将该解决方案添加到我提到的“实现相同目标的方法”列表中。对我来说仍然是一个错误。
  • 限制!=错误。您可以随时提交和发布

标签: java jax-rs gzip resteasy content-encoding


【解决方案1】:

这是我上述评论的示例实现。

我的意思是,如果您不希望它被当前拦截器处理,请不要设置标题,创建一个将名称绑定的拦截器,使用您自己的注释,然后设置优先级低于您要避免的优先级,然后在您的拦截器中设置标题...

@AlreadyGzipped

@NameBinding
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AlreadyGzipped {}

WriterInterceptor。注意@PriorityGZIPEncodingInterceptor 使用 Priorities.ENTITY_CODER

@Provider
@AlreadyGzipped
@Priority(Priorities.ENTITY_CODER + 1000)
public class AlreadyGzippedWriterInterceptor implements WriterInterceptor {
    @Context HttpHeaders headers;

    @Override
    public void aroundWriteTo(WriterInterceptorContext wic) throws IOException, 
                                                      WebApplicationException {
        String header = headers.getHeaderString("Accept-Encoding");
        if (null != header && header.equalsIgnoreCase("gzip")) {
            wic.getHeaders().putSingle("Content-Encoding", "gzip");
        }
        wic.proceed();
    }  
}

测试资源

@Path("resource")
public class AlreadyGzippedResoure {

    @GET
    @AlreadyGzipped
    @Produces(MediaType.APPLICATION_OCTET_STREAM)
    public Response getAlreadGzipped() throws Exception {
        InputStream is = getClass().getResourceAsStream("/stackoverflow.png.gz");
        return Response.ok(is).build();
    }
}

测试

public class Main {
    public static void main(String[] args) throws Exception {
        Client client = ClientBuilder.newClient();
        String url = "http://localhost:8080/api/resource";

        Response response = client.target(url).request().acceptEncoding("gzip").get();
        Image image = ImageIO.read(response.readEntity(InputStream.class));
        JOptionPane.showMessageDialog(null,new JLabel(new ImageIcon(image)));
    }
}

结果

【讨论】:

  • 详细解答!我应该补充一点,记得注册拦截器,否则它不会被调用。如何注册它取决于您的应用程序的设置方式,请参见此处的示例 - stackoverflow.com/questions/19785001/…
  • 感谢您的回答。我注意到许多客户端(例如 Chrome)列出了几种可能的编码(例如“gzip、deflate、br”),因此它可能应该是包含而不是等号。
猜你喜欢
  • 2012-01-28
  • 2010-10-31
  • 1970-01-01
  • 2014-05-31
  • 1970-01-01
  • 1970-01-01
  • 2012-07-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多