Jersey/JAX-RS : Return Content-Length in response

2019-04-22 22:53发布

I'm using Jersey to create RESTful API resources, and ResponseBuilder to generate the response.

Example code for the RESTful resource:

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();  
  }
}

In the response, it's returning chunked transfer encoding in the response headers. What is the proper way in the "Jersey world" to have it return the Content-Length header instead of the Transfer-Encoding: chunked header in the response headers?

4条回答
老娘就宠你
2楼-- · 2019-04-22 23:35

Selecting Content-Length or Transfer-Encoding is just those Containers choice. It's really a matter of buffer size.

One possible solution is providing a SevletFilter which buffers all those marshalled bytes and sets Content-Length header value.

See 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() {
    }
}
查看更多
够拽才男人
3楼-- · 2019-04-22 23:40

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
    }
}
查看更多
Rolldiameter
4楼-- · 2019-04-22 23:49

In you class that extends ResourceConfig you can set the buffer size. Responses above this size will be chunked, below will have Content-Length.

public class ApplicationConfig extends ResourceConfig {

  public ApplicationConfig() {
    //your initialization
    property(CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER, 2000000); 
  }
}
查看更多
一纸荒年 Trace。
5楼-- · 2019-04-22 23:51

For example, if your inputstream is read from a local file system, just add:

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

Check the full code for a clearer explanation:

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

The client side is a Apache HttpClient code.

查看更多
登录 后发表回答