【问题标题】:Image file produced by the server is corrupt/incorrect服务器生成的图像文件损坏/不正确
【发布时间】:2012-12-31 16:47:35
【问题描述】:

我正在使用 Jersey(版本 1.9.1)为 png 图像实现 RESTful Web 服务。我在客户端使用 Apache HttpClient(版本 4x)。客户端的代码调用 HttpGet 来下载图像。成功下载后,它将 InputStream 从 HttpEntity 保存到磁盘。现在问题是生成的文件和服务器上的文件不同。客户端代码生成的输出图像文件不可渲染。

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

下面我的客户端代码调用了上面的资源方法

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

现在的问题是服务器上存在文件和下载的文件不同。

以下是服务器上存在的文件转储。

下面是下载文件的转储。

你可以看到,一些字节被改变了。 Jersey 服务器 api 是否从文件中修改流中的数据?出了什么问题?

更新:

如果我从浏览器点击相同的 url,它会下载文件但下载的文件不可见。所以这个问题似乎与服务器有关。

【问题讨论】:

  • 服务器提供的文件是正确的,例如,您可以在浏览器中显示它吗?
  • 是的。它是png格式的。
  • 马塞尔问的是,如果你用浏览器请求相同的 URL,你能看到图像吗?如果可以,问题不在于服务器。顺便说一句,在客户端上,您不需要所有代码。只需阅读一个网址
  • 两个文件大小相同。我注意到一些字节正在改变。这对我来说似乎是一些编码问题。但不知道发生了什么。
  • Marcel,对不起,我误解了你的问题。如果我从浏览器点击 url,浏览器会下载文件。但是下载的文件是不可见的。所以我相信,问题出在服务器上。

标签: java image jersey apache-httpclient-4.x content-encoding


【解决方案1】:

我会尝试返回输入流而不是 File 对象。我认为媒体类型可能被弄乱了,或者默认文件处理弄乱了输出。所以使用也许:

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

【讨论】:

  • 突发新闻:我发现这是我的错。我正在修改过滤器中的响应以设置其内容长度。虽然我不确定,如果在过滤器中设置 ContentLength 是个好主意。我会在我的回答中澄清发生了什么。
【解决方案2】:

对服务器采取不同的方法。可以是documented in the Jersey manual,也可以是这样:

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

旁注:我认为在 REST 服务中返回图像数据不是一个好主意。它占用了服务器的内存和 I/O 带宽。

【讨论】:

  • 两者都不起作用。服务器正在破坏流。顺便说一句,我不相信将整个图像数据加载到内存中。我做错了什么或错过了什么?容器可以改变流吗?
  • 不知道。您可以尝试按照文档将文件直接流式传输到客户端吗?这里stackoverflow.com/questions/1442893/file-download-servlet(getFullImage() 将返回 void)。加载到内存中的 byte[] 是正确的,即没有改变任何字节?
  • 我尝试过实现简单的 HttpServlet。此 servlet 成功生成图像,我可以在浏览器上查看。
  • 这是一个开始...尝试将 Servlet 中的代码放入 getFullImage
  • @BenPage current documentation 不再包含该示例,但我更新了指向 Internet 存档中的版本的链接。
【解决方案3】:

我发现这是我的错。我正在修改过滤器代码中的响应数据(通过更改其编码)。此过滤器用于设置内容长度标头并处理“eTag”。这个想法是从这里借来的: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();
    }
}

我有一个扩展 HttpServletResponseWrapper 的 HttpResponseCacher 类。

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

我将byte[] getByteArray() 中的代码从return this.buffer.toString().getBytes(); 更改为return this.buffer.toByteArray();,这解决了问题。

【讨论】:

  • 我不确定在过滤器中设置标题是否是个好主意。对我来说,这将大部分非业务逻辑隔离在资源类之外。我会很感激更具体的答案,它可能适合我在代码中所做的所有方面。
  • 不,不是。 etag 处理对我来说是一个可以接受的例外,因为它确实是一个横切关注点,如果它确实可以概括的话。但是,设置内容长度应该留给请求处理框架。如果在某些情况下框架不能正确处理这个问题,你应该在你设置响应的地方(即在方法中)做。
猜你喜欢
  • 2023-04-08
  • 2014-04-26
  • 2012-08-03
  • 2014-04-24
  • 2013-09-05
  • 2021-08-21
  • 1970-01-01
  • 2013-08-29
  • 2017-08-23
相关资源
最近更新 更多