Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I set Content-Length when returning large objects in Jersey JAX-RS server

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?

like image 924
Mikael Ståldal Avatar asked Mar 20 '23 03:03

Mikael Ståldal


2 Answers

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);
  }
like image 165
Mikael Ståldal Avatar answered Apr 06 '23 18:04

Mikael Ståldal


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

}
like image 24
lefloh Avatar answered Apr 06 '23 18:04

lefloh