0

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.

Pawan
  • 1,183
  • 16
  • 29
  • The file served from the server is correct i.e. can you display it in a browser for example? – Marcel Stör Dec 31 '12 at 16:55
  • Yes. It is in png format. – Pawan Dec 31 '12 at 16:57
  • What Marcel asked is, if you request the same URL with a browser, can you see the image? If you can, the problem is not with the server. BTW, on the client you don't need all that code. Just read a URL – Raffaele Dec 31 '12 at 17:22
  • Both files are of same size. I noticed that some bytes are being changed. This looks like some encoding issue to me. But no idea whats happening. – Pawan Dec 31 '12 at 17:23
  • Marcel, Sorry, I've misinterpreted your question. If I hit url from browser, browser downloads the file. But the downloaded file is not viewable. So I believe, the problem is with server. – Pawan Dec 31 '12 at 17:35
  • Why aren't you using the jersey client stuff? it's easier. (no I don't work there. ;) – Zagrev Jan 01 '13 at 08:58
  • @Zagrev, the problem is with Jersey server api which is corrupting the stream. – Pawan Jan 01 '13 at 09:10

3 Answers3

1

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.

Marcel Stör
  • 22,695
  • 19
  • 92
  • 198
  • Neither one is working. Server is corrupting the stream. BTW, I'm not convinced with loading entire image data in memory. Anything I've done wrong or missed anything? Can container alter the stream? – Pawan Dec 31 '12 at 20:01
  • 1
    No idea. Can you try to stream the file directly to the client as documented e.g. here http://stackoverflow.com/questions/1442893/file-download-servlet (getFullImage() would return void). The byte[] loaded into memory is correct i.e. no bytes altered? – Marcel Stör Dec 31 '12 at 20:41
  • I've tried implementing simple HttpServlet. This servlet successfully produces image, which I can view on browser. – Pawan Jan 01 '13 at 08:19
  • That's a start...try putting the code from the Servlet in `getFullImage`. – Marcel Stör Jan 01 '13 at 09:48
  • 1
    @BenPage the [current documentation](https://jersey.java.net/documentation/latest/jaxrs-resources.html) doesn't contain the example anymore but I updated the link now pointing to a version in the Internet archive. – Marcel Stör Aug 30 '13 at 19:43
1

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

Zagrev
  • 2,000
  • 11
  • 8
  • Breaking news: I figured out that it was **my fault**. I am modifying response in a filter in order to set it's content length. Though I am not sure, if it is good idea to set ContentLength in a filter. I'll clarify what was happening in my answer. – Pawan Jan 01 '13 at 10:30
0

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.

Pawan
  • 1,183
  • 16
  • 29
  • I am not sure whether it is good idea to set headers in Filters. For me, this isolates most of the non-business logic outside of the resource classes. I'll appreciate more concrete answer which may fit for all aspects I am doing in my code. – Pawan Jan 01 '13 at 12:12
  • 1
    No, it's not. `etag` processing is an acceptable exception for me as it really is a cross-cutting concern IF and only if it can be generalized, indeed. However, setting the content length should be left to the request processing framework. If there are cases where the framework doesn't handle this properly you should do it right where you set the response (i.e. in the method). – Marcel Stör Jan 01 '13 at 12:42