泽西/ JAX-RS:响应头的代替块传输编码返回的Content-Length(Jersey/JAX

2019-07-30 14:59发布

我使用泽西创建RESTful API资源, ResponseBuilder产生响应。

对于REST的资源的示例代码:

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标头的响应头?

Answer 1:

选择Content-LengthTransfer-Encoding仅仅是那些容器的选择。 这真是缓冲区大小的问题。

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

请参阅此页 。

@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() {
    }
}


Answer 2:

在你的类扩展ResourceConfig可以设置缓冲区的大小。 这个规模以上的回答将被分块,下面将有内容长度。

public class ApplicationConfig extends ResourceConfig {

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


Answer 3:

例如,如果你的InputStream为从本地文件系统读取,只需添加:

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的代码。



Answer 4:

An answer to a very similar question on StackOverflow can be found here

I've copied it here to make sure it's not converted to a comment:

A great sample filter for doing this, that can be used standalone from the project, is this ContentLengthFilter.java from the Carrot2 project on github.

Note that uses a response wrapper with a byte stream to solve the issue, so this also ensures that Transfer-Encoding: Chunked doesn't get set by some other Filter/code in the filter chain, and override your Content-Length header when it's set. You can verify that by testing this with larger files, as they'd normally be chunked in the response.

I'm going to copy the contents of the file here as well, to ensure it doesn't become a broken link:

/*
 * 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
    }
}


文章来源: Jersey/JAX-RS : Return Content-Length in response header instead of chunked transfer encoding