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