4

I sometimes want to return a large (several MB) binary object as a response from a JAX-RS resource method. I know the size of the object, and I want the Content-Length header to be set on the response, and I don't want chunked transfer encoding to be used.

In Jersey 1.x, I solved this with a custom MessageBodyWriter:

public class Blob {
  public InputStream stream;
  public long length;    
}

@Provider
public class BlobWriter extends MessageBodyWriter<Blob> {

  public boolean isWriteable(Class<?> type, Type genericType,
                               Annotation[] annotations, MediaType mediaType) {
    return Blob.class.isAssignableFrom(type);
  }

  public long getSize(T t, Class<?> type, Type genericType, Annotation[] annotations,
                        MediaType mediaType) {
    return t.length;
  }

  public void writeTo(T t, Class<?> type, Type genericType, Annotation[] annotations,
                      MediaType mediaType,
                      MultivaluedMap<String, Object> httpHeaders,
                      OutputStream entityStream)
            throws java.io.IOException {
    org.glassfish.jersey.message.internal.ReaderWriter.writeTo(t.stream, entityStream);
  }
}

But this stopped working when I upgraded to Jersey 2.x, since JAX-RS/Jersey 2 does not care about MessageBodyWriter.getSize() anymore. How can I accomplish this with Jersey 2?

Mikael Ståldal
  • 374
  • 1
  • 3
  • 11
  • An answer to a very similar question on StackOverflow can [be found here](https://stackoverflow.com/a/46121697/26510) – Brad Parks Sep 08 '17 at 18:32

2 Answers2

4

It seems to be possible to set the Content-Length header from MessageBodyWriter.writeTo() using the supplied httpHeaders:

  public void writeTo(T t, Class<?> type, Type genericType, Annotation[] annotations,
                      MediaType mediaType,
                      MultivaluedMap<String, Object> httpHeaders,
                      OutputStream entityStream)
            throws java.io.IOException {
    httpHeaders.addFirst(HttpHeaders.CONTENT_LENGTH, t.length.toString);
    org.glassfish.jersey.message.internal.ReaderWriter.writeTo(t.stream, entityStream);
  }
Mikael Ståldal
  • 374
  • 1
  • 3
  • 11
  • This worked for me. It's super obscure that setting the Content-Length header stops JAX-RS from implicitly buffering your response but I can't argue with results. – Poke Dec 20 '18 at 20:10
3

From the JAX-RS 2.0 ApiDoc:

As of JAX-RS 2.0, the method has been deprecated and the value returned by the method is ignored by a JAX-RS runtime. All MessageBodyWriter implementations are advised to return -1 from the method. Responsibility to compute the actual Content-Length header value has been delegated to JAX-RS runtime.

The JAX-RS implementation will also decide if a Content-Length Header or Transfer-Encoding: chunked will be sent.

As you already know the Content-Length you can set the a temporary header in the ResourceClass and set the real one in a ContainerResponseFilter:

@Path("/foo")
public class SomeResource {

    @GET
    public Blob test() {
        return Response.ok(...).header("X-Content-Length", blob.length()).build();
    }

}

@Provider
public class HeaderFilter implements ContainerResponseFilter {

    @Override
    public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
        String contentLength = responseContext.getHeaderString("X-Content-Length");
        if (contentLength != null) {
            responseContext.getHeaders().remove("Transfer-Encoding");
            responseContext.getHeaders().remove("X-Content-Length");
            responseContext.getHeaders().putSingle("Content-Length", contentLength);
        }
    }

}
lefloh
  • 10,653
  • 3
  • 28
  • 50
  • I know what the JAX-RS 2.0 spec says, and that is my problem. I do not want Jersey to decide for me, I want to force Content-Length always. – Mikael Ståldal Jul 04 '14 at 12:48
  • Adding a ServletFilter with extra buffering is not an option since the response can be very large (several MB), and buffering that is a waste of memory. – Mikael Ståldal Jul 04 '14 at 12:50
  • If you know the size you should be able to store it somewhere and access this information in the ServletFilter. For instance set a `X-Content-Length`Header in the resource-class. – lefloh Jul 04 '14 at 13:15
  • Yes, that would probably work. However, it seems like an overly complicated solution. And it also gives you an unnecessary dependency to the Servlet API. There have to be a simpler, JAX-RS only, solution. – Mikael Ståldal Jul 06 '14 at 09:17
  • Is this `ContainerResponseFilter` the only working solution? It looks ridiculous though. – Michael-O Jan 23 '15 at 20:53
  • AFAIK yes. The decision is that the runtime and not the application should compute the actual Content-Length header so I don't think that there is a "non-workaround-solution". – lefloh Jan 23 '15 at 22:03
  • @lefloh, it seems to work. The header is written to the response. Thanks! – Michael-O Jan 26 '15 at 08:54