【问题标题】:Jersey/JAX-RS : Return Content-Length in response header instead of chunked transfer encodingJersey/JAX-RS:在响应标头中返回 Content-Length 而不是分块传输编码
【发布时间】:2012-07-25 22:03:40
【问题描述】:

我正在使用 Jersey 来创建 RESTful API 资源,并使用 ResponseBuilder 来生成响应。

RESTful 资源的示例代码:

public class infoResource{
  @GET
  @Path("service/{id}")
  @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
  public Response getCompany(@PathParam("id")String id) {
      //company is just a POJO.
      Company company = getCompany(id);
      return Response.status(200).entity(company).build();  
  }
}

在响应中,它在响应标头中返回分块传输编码。在“泽西世界”中,让它在响应标头中返回 Content-Length 标头而不是 Transfer-Encoding: chunked 标头的正确方法是什么?

【问题讨论】:

    标签: java rest jaxb jersey jax-rs


    【解决方案1】:

    选择Content-LengthTransfer-Encoding 只是这些容器的选择。这实际上是缓冲区大小的问题。

    一种可能的解决方案是提供一个SevletFilter,它缓冲所有编组的字节并设置Content-Length 标头值。

    this page

    @WebFilter
    public class BufferFilter implements Filter {
    
        @Override
        public void init(FilterConfig filterConfig) {
        }
    
        @Override
        public void doFilter(ServletRequest request,
                             ServletResponse response,
                             FilterChain chain)
            throws IOException, ServletException {
    
            final ByteArrayOutputStream buffer =
                new ByteArrayOutputStream();
    
            // prepare a new ServletResponseWrapper
            // which returns the buffer as its getOutputStream();
    
            chain.doFilter(...)
    
            // now you know how exactly big is your response.
    
            final byte[] responseBytes = buffer.toByteArray();
            response.setContentLength(responseBytes.length);
            response.getOutputStream().write(responseBytes);
            response.flush();
        }
    
        @Override
        public void destroy() {
        }
    }
    

    【讨论】:

    • 最好缓冲到文件中(以避免内存不足 -> 如果您需要返回大响应)。但我明白你的意思了。
    • here's a full working example filter 对于这个解决方案,仍然使用ByteArrayOutputStream,但很容易进入并尝试......仅供参考......
    【解决方案2】:

    在扩展 ResourceConfig 的类中,您可以设置缓冲区大小。超过此大小的响应将被分块,下面将有 Content-Length。

    public class ApplicationConfig extends ResourceConfig {
    
      public ApplicationConfig() {
        //your initialization
        property(CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER, 2000000); 
      }
    }
    

    【讨论】:

      【解决方案3】:

      例如,如果您的输入流是从本地文件系统中读取的,只需 添加:

      response.header( "Content-Length", file.length() );
      

      查看完整代码以获得更清晰的解释:

      @Path("/files")
      public class FileDownloadService {
      
          private static final String TXT_FILE = "C:\\your file";    
          @GET
          @Path("/txt")
          @Produces(MediaType.APPLICATION_OCTET_STREAM)
          public Response getTextFile() throws IOException {
              File file = new File(TXT_FILE);
              FileInputStream inStream = new FileInputStream(file);
              ResponseBuilder response = Response.ok((Object) inStream);
              response.header("Content-Disposition", "attachment; filename=\"filename\"");
              response.header( "Content-Length", file.length() );
              return response.build();
          }  
      }
      

      客户端是一个 Apache HttpClient 代码。

      【讨论】:

      • 我认为这个答案是错误的。我使用了 FileInputStream 并设置了 Content-Length 标头,但球衣仍然使用 Transfer-Encoding: chunked。 Jersey 会将 InputStreams 缓冲到一定大小,如果整个流适合缓冲区,它将不使用分块编码。您可能在测试 Content-Length 时遇到了这个问题,这让它看起来很有效。
      【解决方案4】:

      StackOverflow 上一个非常相似的问题的答案可以be found here

      我已将其复制到此处以确保它不会被转换为评论:

      一个很好的示例过滤器,可以从项目中独立使用,是来自 github 上的 Carrot2 项目的 this ContentLengthFilter.java

      请注意,使用带有字节流的响应包装器来解决问题,因此这也确保Transfer-Encoding: Chunked 不会被过滤器链中的其他过滤器/代码设置,并在以下情况下覆盖您的 Content-Length 标头设置好了。您可以通过使用更大的文件进行测试来验证这一点,因为它们通常会在响应中分块。

      我也要把文件的内容复制到这里,以确保它不会变成一个损坏的链接:

      /*
       * Carrot2 project.
       *
       * Copyright (C) 2002-2010, Dawid Weiss, Stanisław Osiński.
       * All rights reserved.
       *
       * Refer to the full license file "carrot2.LICENSE"
       * in the root folder of the repository checkout or at:
       * http://www.carrot2.org/carrot2.LICENSE
       */
      
      package org.carrot2.webapp;
      
      import java.io.ByteArrayOutputStream;
      import java.io.IOException;
      import java.io.OutputStreamWriter;
      import java.io.PrintWriter;
      
      import javax.servlet.Filter;
      import javax.servlet.FilterChain;
      import javax.servlet.FilterConfig;
      import javax.servlet.ServletException;
      import javax.servlet.ServletOutputStream;
      import javax.servlet.ServletRequest;
      import javax.servlet.ServletResponse;
      import javax.servlet.http.HttpServletResponse;
      import javax.servlet.http.HttpServletResponseWrapper;
      
      /**
       * Buffer the output from filters below and set accurate <code>Content-Length</code>
       * header. This header is required by flash, among others, to display progress
       * information.
       */
      public class ContentLengthFilter implements Filter
      {
          private final static class BufferingOutputStream extends ServletOutputStream
          {
              private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
      
              @Override
              public void write(int b) throws IOException
              {
                  baos.write(b);
              }
      
              @Override
              public void write(byte [] b) throws IOException
              {
                  baos.write(b);
              }
      
              @Override
              public void write(byte [] b, int off, int len) throws IOException
              {
                  baos.write(b, off, len);
              }
          }
      
          private final static class BufferingHttpServletResponse extends
              HttpServletResponseWrapper
          {
              private enum StreamType
              {
                  OUTPUT_STREAM,
                  WRITER
              }
      
              private final HttpServletResponse httpResponse;
      
              private StreamType acquired;
              private PrintWriter writer;
              private ServletOutputStream outputStream;
              private boolean buffering;
      
              public BufferingHttpServletResponse(HttpServletResponse response)
              {
                  super(response);
                  httpResponse = response;
              }
      
              @Override
              public ServletOutputStream getOutputStream() throws IOException
              {
                  if (acquired == StreamType.WRITER)
                      throw new IllegalStateException("Character stream already acquired.");
      
                  if (outputStream != null)
                      return outputStream;
      
                  if (hasContentLength())
                  {
                      outputStream = super.getOutputStream();
                  }
                  else
                  {
                      outputStream = new BufferingOutputStream();
                      buffering = true;
                  }
      
                  acquired = StreamType.OUTPUT_STREAM;
                  return outputStream;
              }
      
              @Override
              public PrintWriter getWriter() throws IOException
              {
                  if (acquired == StreamType.OUTPUT_STREAM)
                      throw new IllegalStateException("Binary stream already acquired.");
      
                  if (writer != null)
                      return writer;
      
                  if (hasContentLength())
                  {
                      writer = super.getWriter();
                  }
                  else
                  {
                      writer = new PrintWriter(new OutputStreamWriter(
                          getOutputStream(), getCharacterEncoding()), false);
                  }
      
                  acquired = StreamType.WRITER;
                  return writer;
              }
      
              /**
               * Returns <code>true</code> if the user set <code>Content-Length</code>
               * explicitly.
               */
              private boolean hasContentLength()
              {
                  return super.containsHeader("Content-Length");
              }
      
              /**
               * Push out the buffered data.
               */
              public void pushBuffer() throws IOException
              {
                  if (!buffering)
                      throw new IllegalStateException("Not buffering.");
      
                  BufferingOutputStream bufferedStream = 
                      (BufferingOutputStream) outputStream;
      
                  byte [] buffer = bufferedStream.baos.toByteArray();
                  httpResponse.setContentLength(buffer.length);
                  httpResponse.getOutputStream().write(buffer);
              }
          }
      
          public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
              throws IOException, ServletException
          {
              final HttpServletResponse response = (HttpServletResponse) resp;
              final BufferingHttpServletResponse wrapped = 
                  new BufferingHttpServletResponse(response);
      
              chain.doFilter(req, wrapped);
      
              if (wrapped.buffering)
              {
                  wrapped.pushBuffer();
              }
          }
      
          public void destroy()
          {
              // Empty
          }
      
          public void init(FilterConfig config) throws ServletException
          {
              // Empty
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2011-01-26
        • 1970-01-01
        • 2011-02-16
        • 2018-06-26
        • 1970-01-01
        • 2014-05-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多