Search code examples
javaimagejerseyapache-httpclient-4.xcontent-encoding

Image file produced by the server is corrupt/incorrect


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.


Solution

  • 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.