Question

I am using Jersey (ver 1.9.1) to implement RESTful web service for png images. I'm using Apache HttpClient (ver. 4x) at client side. The code on client side calls HttpGet to download image. On successful download, it saves the InputStream from HttpEntity to the disk. Now the problem is resulting file and the file on the server is different. The output image file produced by client code is not Render-able.

@GET
@Path("/public/profile/{userId}")
@Produces({ "image/png" })
public Response getImage(@PathParam(value = "userId") String userId) {
    Response res = null;
    // ImageManagement.gerProfilePicture(userId) returns me profile picture
    // of the provided userId in PathParam
    File imageFile = ImageManagement.getProfilePicture(userId);
    if (imageFile == null) {
        res = Response.status(Status.NOT_FOUND).build();
    } else {
        res = Response
                .ok(imageFile, "image/png")
                .header("Content-Disposition",
                        "attachment; filename=Img" + userId + ".png")
                .build();
    }
    return res;
}

My client code below invokes above resource method

private File downloadProfilePicture(String userId) throws IOException{
    // URIHelper is a utility class, this give me uri for image resource
    URI imageUri = URIHelper.buildURIForProfile(userId);

    HttpGet httpGet = new HttpGet(imageUri);
    HttpResponse httpResponse = httpClient.execute(httpGet);
    int statusCode = httpResponse.getStatusLine().getStatusCode();

    File imageFile = null;
    if (statusCode == HttpURLConnection.HTTP_OK) {
        HttpEntity httpEntity = httpResponse.getEntity();
        Header[] headers = httpResponse.getHeaders("Content-Disposition");
        imageFile = new File(OUTPUT_DIR, headers[0].getElements()[0]
                .getParameterByName("filename").getValue());
        FileOutputStream foutStream = new FileOutputStream(imageFile);
        httpEntity.writeTo(foutStream);
        foutStream.close();
    }
    return imageFile;
}

Now problem is the file exists on the server and file downloaded are different.

Below is the dump of the file exists on the server.

Dump of the file on server

Below is the dump of the downloaded file.

Dump of the file on client

You can see, some bytes are being changed. Is Jersey server api modifying the data in stream from file? What is going wrong?

Update:

If I hit the same url from browser, it downloads the file but downloaded file is not viewable. So the issue seems associated with server.

Was it helpful?

Solution 3

I figured out that it was my fault. I was modifying the response data (by changing it's encoding) in a code of the Filter. This filter is used to set the content length header and processes 'eTag'. The idea is borrowed from here: http://www.infoq.com/articles/etags

@Override
public void doFilter(ServletRequest request, ServletResponse response,
        FilterChain chain) throws IOException, ServletException {

    HttpServletRequest servletRequest = (HttpServletRequest) request;
    HttpServletResponse servletResponse = (HttpServletResponse) response;

    HttpResponseCatcher wrapper = new HttpResponseCatcher(
            (HttpServletResponse) response);

    chain.doFilter(request, wrapper);

    final byte[] responseBytes = wrapper.getByteArray();

    String digest = getMd5Digest(responseBytes);

    String etag = '"' + digest + '"';
    // always store the ETag in the header
    servletResponse.setHeader("ETag", etag);

    String previousEtag = servletRequest.getHeader("If-None-Match");
    // compare previous token with current one
    if (previousEtag != null && previousEtag.equals(etag)) {
        servletResponse.sendError(HttpServletResponse.SC_NOT_MODIFIED);
        // use the same date we sent when we created the ETag the first time
        // through
        servletResponse.setHeader("Last-Modified",
                servletRequest.getHeader("If-Modified-Since"));
    } else {
        // first time through - set last modified time to now
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.MILLISECOND, 0);
        Date lastModified = cal.getTime();
        servletResponse.setDateHeader("Last-Modified",
                lastModified.getTime());

        servletResponse.setContentLength(responseBytes.length);
        ServletOutputStream sos = servletResponse.getOutputStream();
        sos.write(responseBytes);
        sos.flush();
        sos.close();
    }
}

I have a HttpResponseCacher class which extends HttpServletResponseWrapper.

public class HttpResponseCatcher extends HttpServletResponseWrapper {

    private ByteArrayOutputStream buffer;

    public HttpResponseCatcher(HttpServletResponse res) {
        super(res);
        this.buffer = new ByteArrayOutputStream();
    }

    //There is some more code in the class, but that is not relevant to the problem...
    public byte[] getByteArray() {
        //The problem is here... this.buffer.toString().getBytes() changes to encoding of the data      
        return this.buffer.toString().getBytes();
    }
}

I changed the code in byte[] getByteArray() from return this.buffer.toString().getBytes(); to return this.buffer.toByteArray(); and this fixed the problem.

OTHER TIPS

I would try returning an input stream instead of a File object. I think that the media type may be getting messed with, or the default file handling is messing with the output. So using maybe:

Response.ok(new FileInputStream(imageFile), "image/png") .header("Content-Disposition","attachment; filename=Img" + userId + ".png") .build();

Take a different approach with the server. Either as documented in the Jersey manual or like this:

@GET
@Path("/public/profile/{userId}")
@Produces("image/png")
public Response getFullImage(...) {

    Path path = Paths.get("path/to/file");
    byte[] imageData = Files.readAllBytes(path);

    // uncomment line below to send non-streamed
    // return Response.ok(imageData).build();

    // uncomment line below to send streamed
    // return Response.ok(new ByteArrayInputStream(imageData)).build();
}

Sidenote: I don't think it's a good idea to return image data in a REST service. It ties up your server's memory and I/O bandwidth.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top