Grails(特定于iOS):返回video(mp4)文件会导致Broken Pipeexception(已针对此响应调用getOutputStream())
我试图从我的Grails控制器返回一个mp4文件,以便它可以在浏览器中播放。 以下是我所拥有的最简单的版本:
def file = new File(<path to mp4 file>) response.outputStream << file.newInputStream()
奇怪的是,这是从桌面(我的MacBook上的Chrome)击中它,适用于Android手机,但不适用于iPad Air。
iOS请求中的一个标题与“0-1”的“范围”不同,但看起来可能不会导致问题(通过在我的笔记本电脑上添加该请求进行testing)。
例外说:
ERROR errors.GrailsExceptionResolver - SocketException occurred when processing request: [GET]
并进一步说下去
getOutputStream() has already been called for this response.
我发现了许多类似的错误,但他们谈论webRequest.setRenderView(false),刷新和closures输出stream,以及许多其他选项。 我试过所有这些,但似乎没有任何工作。
真正让我感到满意的是,除了iOS之外,它都可以工作。
任何想法将不胜感激。 提前致谢!
更新1
根据Graeme的回答,Chrome的accept头是:
accept -> text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
而且iOS产生多个请求,它们具有以下接受头文件:
accept -> text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 accept -> */*
第二个接受头, */*
是exception期间发生的事情。
我也为Grails创build了一个JIRA问题: http : //jira.grails.org/browse/GRAILS-11325
可能与发送的Accept头相关,因为Grails根据Accept头来进行一些parsing。 如果您可以在JIRA中发布一个示例,并重复这些步骤将有所帮助。
这原来是一个iOS的具体问题。 范围头文件是需要实现的,如果你试图返回整个文件内容的范围请求的响应,iOS 不会提出额外的请求。
以下是我使用的代码:
try { def rangeValue = request.getHeader("range") log.debug("rangeValue: ${rangeValue}") if (rangeValue != null) { // Get start and end string, substring(6) removes "bytes=" def (start, end) = rangeValue.substring(6).split("-") def startInt = start.toLong() def endInt = end.toLong() def fileSize = file.length() response.reset() response.setStatus(206) response.setHeader("Accept-Ranges", "bytes") // WARNING: Do not sent Content-length, as it appears to prevent videos from working in iOS response.setHeader("Content-range", "bytes ${start}-${end}/"+Long.toString(fileSize)) response.setContentType("video/quicktime") def bytes = new byte[endInt-startInt+1] def inputStream = file.newInputStream() // Skip to the point in the inputStream that the range is requesting inputStream.skip(startInt) // Read a chunk of the input stream into the bytes array inputStream.read(bytes, 0, bytes.length) response.outputStream << bytes } else { response.outputStream << file.newInputStream() } } catch (ClientAbortException e) { log.error("User aborted download") }
有几个重要的注意事项:
- 如果在响应中返回了
Content-length
头,则iOS将不播放video。 看起来像这可能是相关的内容被gzipped – https://stackoverflow.com/a/2359184/2601060 - 当使用
inputStream.read()
函数时,它将始终从stream的开始处开始读取,因此请确保将文件skip()
到文件的适当位置(startInt) - 响应可以被
reset()
以确保已经写入的任何东西不被包括(这可能不是必需的,但是防止自动的grails动作提供默认行为)