文件下载时出现问题。

huangapple go评论71阅读模式
英文:

Issues while File download

问题

我正在尝试从Angular UI下载文件,即使在后端代码中出现异常,我仍然会收到200 OK作为响应。

以下是我拥有的代码:

public ResponseEntity<Object> downloadDocument(@PathVariable("docId") Long docId,
        HttpServletResponse response) {

    OutputStream outputStream = null;

    try {
        outputStream = response.getOutputStream();
        docService.downloadDocument(docId, outputStream);
        return ResponseEntity.ok().contentType(MediaType.parseMediaType(MediaType.APPLICATION_OCTET_STREAM_VALUE))
                .body("Success");
    } catch (Exception e) {
        return ResponseEntity.badRequest().body(e.getMessage());
    } finally {
        if (Objects.nonNull(outputStream)) {
            IOUtils.closeQuietly(outputStream);
        }
    }
}

你能帮我看看这里有什么问题吗?

英文:

I'm trying to download a file from Angular UI, even after I got exception in backed code still I'm getting 200 ok as the response.

Here is the code I have :

public ResponseEntity&lt;Object&gt; downloadDocument(@PathVariable(&quot;docId&quot;) Long docId,
			HttpServletResponse response) { 

OutPutStream outputStream = null;

try {
outputStream = response.getOutputStream();
docService.downloadDocument(docId,outputStream);
return ResponseEntity.ok().contentType(MediaType.parseMediaType(MediaType.APPLICATION_OCTET_STREAM_VALUE))
					.body(&quot;Success&quot;);
} catch(Exception e) {
return ResponseEntity.badRequest().body(e.getMessage());
} finally {
if (Objects.nonNull(outputStream)) {
				IOUtils.closeQuietly(outputStream);
			}
}

Can you please help me out what's wring here.

答案1

得分: 1

当你打开输出流时,头部信息被发送。期间。

从那时起就没有回溯了。你不能先打开输出流,然后后来说:哦,等等!不!算了!坏请求!

下面是它的工作方式 - 你选择一边并坚持下去。你可以:

  1. 自己处理;使用 response 和那里可用的方法来设置响应;你可以设置头部、返回代码和消息,还可以获取响应主体的输出流,并以这种方式发送数据。如果你这样做,你就不能 ALSO 返回一个 ResponseEntity!
  2. 不要 添加一个 HttpServletResponse 参数,而是返回一个 ResponseEntity 对象。

你两者都在做,这是不允许的。

坦白说,我感到吃惊;Spring 有点有问题,应该在这里抛出异常,因为它根本无法提供你在这里要求的东西。

注意:通常情况下,异常的类型比消息更具信息性(许多异常甚至没有消息)。

把它全部组合起来:

public ResponseEntity&lt;?&gt; downloadDocument(@PathVariable(&quot;docId&quot;) Long docId) { 

  try {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    docService.downloadDocument(docId, baos);
    return ResponseEntity.ok()
      .contentType(MediaType.parseMediaType(MediaType.APPLICATION_OCTET_STREAM_VALUE))
      .body(baos.toByteArray());
  } catch(Exception e) {
    return ResponseEntity.badRequest().body(e.getMessage());
  }
}

'toString' 默认打印自己的类型,如果没有消息,而如果有消息,则打印自己的类型加上消息,所以你会得到例如:

NullPointerException

NullPointerException: 参数 foo

这就是你想要的(与前一种情况不同,前一种情况是字面空白字符串,而后一种情况只是 '参数 foo',这也不是特别有见地的)。

一般来说,消息通常不打算在没有异常类型上下文的情况下有意义。

注意:这将在将下载文档的整个内容缓存在服务器的内存中之前发送。如果文档很大,这是一个不好的主意,但是,如果你想要 '流式传输' 它,HTTP 协议本质上存在一个相当严重的问题:一旦开始发送,你已经发送了 '错误状态'(即已经发送了 200 OK),所以如果文档下载过程在中途抛出异常,你不能返回并发送错误消息。你需要一种线协议,可以分块发送主体并具有一个 '类型' 代码,以便接收方可以扫描这些代码并知道 '这个类型表示有更多的数据在流动',以及 '这个类型表示发生了错误,现在我可以读取错误描述'。所有这些都会变得非常复杂。通过缓存它,你可以避免这种混乱。但如果文档非常大,你将不得不处理上面的情况,没有快速解决方法。

英文:

When you open the outputstream, the headers are sent. period.

There's no backtracking from that point on. You can't first open the outputstream and then later go: Oh, wait! No! nevermind! bad request!

Here's how it works - you pick a side and stick to it. You can either:

  1. Handle it yourself; use response and the methods available there to set up your response; you can set headers, the return code and message, and you can obtain an outputstream for the response body, and send data that way. If you do this, you can't ALSO return a ResponseEntity!
  2. Do NOT even add an HttpServletResponse parameter, and instead return a ResponseEntity object.

You're doing both, which is not allowed.

I'm frankly surprised; spring is a bit broken and ought to be throwing exceptions here, as it cannot possibly serve up what you're asking it to do here.

NB: Note that the type of an exception is usually more informative than the message (many exceptions don't even have a message).

Putting it all together:

public ResponseEntity&lt;?&gt; downloadDocument(@PathVariable(&quot;docId&quot;) Long docId) { 

  try {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    docService.downloadDocument(docId, baos);
    return ResponseEntity.ok()
      .contentType(MediaType.parseMediaType(MediaType.APPLICATION_OCTET_STREAM_VALUE))
      .body(baos.toByteArray());
  } catch(Exception e) {
    return ResponseEntity.badRequest().body(e.getMessage());
  }
}

'toString' defaults to printing its own type if there is no message, and its own type plus the message if there is, so you get e.g.:

NullPointerException

or

NullPointerException: parameter foo

which is what you want (versus a literal blank string in the former case, and just 'parameter foo' in the latter, which isn't particularly insightful either).

messages are generally intended not to necessarily make sense without the context of the type of the exception.

NB: This will cache the entire content of the downloaded document into the memory of the server before sending it onwards. If the document is large, this is a bad idea, but, if you want to 'stream' it, you have a pretty serious problem inherent in the HTTP protocol: Once you start sending, you've already sent the 'error status' (i.e. you already sent 200 OK), so if the document download process throws an exception halfway through, you cannot go back and send an error message. You need some sort of wire protocol where you send the body in chunks and have a 'type' code you send, so that the recipient can scan for these and knows 'this type means there's more data flowing', and 'this type means an error occured and now I can read the error description'. That all gets quite complicated. By caching it, you avoid this mess. But if that document can be very large you're going to have to deal with the above, there are no quick fixes.

huangapple
  • 本文由 发表于 2020年8月4日 22:30:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/63249096.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定