Sunday, April 24, 2011

Jersey streaming binary data

In the previous post I showed how you can post binary data to a jersey REST api. You can also use Jersey to serve files, although its better done by apache or nginx but sometimes you might want to serve thumbnails stored in a database out of a service and put varnish in front of the REST api to cache the thumbnails. This is just a demonstration of using jersey to serve binary data in streaming fashion.

@Path("/download-service")
public class DownaloadService extends SecureRestService {
 private static final AppLogger logger  = AppLogger.getLogger(DownaloadService.class);

 @POST
 @Produces(MediaType.APPLICATION_OCTET_STREAM)
 public StreamingOutput getThumbnail(
   @FormParam("securityKey") final String securityKey,
   @FormParam("guid") final String guid) throws JSONException {
  return new StreamingOutput() {
   @Override
   public void write(OutputStream out) throws IOException {
    try {
     if (!isAuthorized(securityKey)) {
      response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
     } else {
      //Read thumbnail out of database and dovetail both streams(IOUtils.copy) to directly stream to the response without storing them in memory.
     }
    } catch (Throwable t) {
     logger.error(t);
     response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, t.getMessage());
    }
   }
  };
 }
}


To test this code you can again use Jersey client api and to verify against response status or data you can use the ClientResponse class as shown below

  Form form = new Form();
  form.add("securityKey", getSecureToken());
  form.add("guid", guid);

  ClientResponse response = webResource.path(
    "download-service").post(ClientResponse.class, form);
  Assert.assertNotEquals(HttpServletResponse.SC_NOT_AUTHORIZED, response.getStatus());

  ByteArrayOutputStream bos = new ByteArrayOutputStream();
  IOUtils.copy(response.getEntityInputStream(), bos);
  byte[] result = bos.toByteArray();
  Assert.assertArrayEquals(expected, result);

12 comments:

  1. You saved my day! Many many thanks! :)

    ReplyDelete
  2. This is a very useful bit, thank you:

    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    IOUtils.copy(response.getEntityInputStream(), bos);
    byte[] result = bos.toByteArray();

    ReplyDelete
  3. With "streaming" you are talking about HTTP completed upload/download to/from a server, right?! :)

    ReplyDelete
    Replies
    1. Yes complete download from a rest api. Upload is also similar.

      Delete
  4. I need to stream records as they are available. Hence i have


    import java.io.BufferedWriter;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.io.Writer;

    import javax.ws.rs.core.StreamingOutput;

    import org.apache.log4j.Logger;

    public class StreamingOutputImpl extends RetrieverResource implements StreamingOutput {
    private static final Logger LOGGER = Logger.getLogger(StreamingOutputImpl.class);
    private OutputStream os;

    public StreamingOutputImpl(final RequestParameters parameters, final String basePath) {
    super(parameters, basePath);
    }

    @Override
    public void write(final OutputStream os) throws IOException {
    this.os = os;
    read();
    }

    @Override
    public void writeRecord(final GenericRecord record) {
    try {
    Writer writer = new BufferedWriter(new OutputStreamWriter(os));
    writer.write(record.toString());
    writer.flush();
    } catch (final IOException e) {
    LOGGER.error("Error while write record " + record, e);
    }
    }
    }

    Assuming i have 100 records, and writeRecord() is invoked 100 times. I see the output in browser (with @GET and @POST) only after 100 th record and not after every record.

    Any suggestions?

    ReplyDelete
  5. How to implement the client in Jersey 2?

    ReplyDelete
  6. Just return the byte[] as entity works for me.

    ReplyDelete
    Replies
    1. for documents < 2GB yes, but for streaming data (a radio program that runs 24/7 for years for example) this would not be sufficient.

      Delete
  7. ByteArrayOutputStream bos = new ByteArrayOutputStream();
    IOUtils.copy(response.getEntityInputStream(), bos);
    byte[] result = bos.toByteArray();

    YOU SIMPLY SAVED ME!!! Thanks a lot!!!

    ReplyDelete