servlet/grails 如何正确地将 mp4 文件提供给 Safari?

How do servlet/grails correctly serve mp4 files to Safari?

在这个简单的代码中:

def index() {
    def file = new File('/tmp/big_buck_bunny_720p_50mb.mp4')
    println "file = ${file} , length = ${file.length()}"

    if (file.exists()) {
      webRequest.renderView = false;
      response.setContentType("video/mp4")
      response.setHeader("Content-disposition", "inline; filename=" + URLEncoder.encode(file.name, "UTF-8"));
      response.setHeader("Content-Length", String.valueOf(file.length()));

      InputStream is = new FileInputStream(file);

      response.outputStream << is
      response.outputStream.flush()
      response.outputStream.close()
      is.close()
    }
  } // index 

它可以正确地将 mp4 文件提供给 Firefox,但在 Safari(OS/X 上的 9.0.1)中,它无法播放,并且服务器报告:

file = /tmp/big_buck_bunny_720p_50mb.mp4 , length = 52464391
| Error 2015-11-23 10:37:13,339 [http-bio-8080-exec-3] ERROR errors.GrailsExceptionResolver  - SocketException occurred when processing request: [GET] /hello2/stream/index
Broken pipe. Stacktrace follows:
Message: Broken pipe
    Line | Method
->>  109 | socketWrite in java.net.SocketOutputStream
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|    153 | write       in     ''
|     17 | index . . . in hello2.StreamController$$EPUpAtg0
|    198 | doFilter    in grails.plugin.cache.web.filter.PageFragmentCachingFilter
|     63 | doFilter .  in grails.plugin.cache.web.filter.AbstractFilter
|   1142 | runWorker   in java.util.concurrent.ThreadPoolExecutor
|    617 | run . . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^    745 | run         in java.lang.Thread
| Error 2015-11-23 10:37:13,349 [http-bio-8080-exec-3] ERROR errors.GrailsExceptionResolver  - IllegalStateException occurred when processing request: [GET] /hello2/stream/index
getOutputStream() has already been called for this response. Stacktrace follows:
Message: Error processing GroovyPageView: getOutputStream() has already been called for this response
    Line | Method
->>  648 | doFilter  in /hello2/grails-app/views/error.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Caused by IllegalStateException: getOutputStream() has already been called for this response
->>  100 | flush     in java.io.FilterWriter
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|    198 | doFilter  in grails.plugin.cache.web.filter.PageFragmentCachingFilter
|     63 | doFilter  in grails.plugin.cache.web.filter.AbstractFilter
|   1142 | runWorker in java.util.concurrent.ThreadPoolExecutor
|    617 | run . . . in java.util.concurrent.ThreadPoolExecutor$Worker
^    745 | run       in java.lang.Thread
| Error 2015-11-23 10:37:13,354 [http-bio-8080-exec-3] ERROR [/hello2].[grails]  - Servlet.service() for servlet grails threw exception

为什么代码在 Firefox 中有效,但在 Safari 中抛出 Broken pipegetOutputStream() has already been called for this response 异常?

如何解决?谢谢。

环境:Grails 2.5.1

=====更新=====

我发现了这个问题:

Mp4 downloading instead of playing in Safari , and there is an URL http://techslides.com/demos/sample-videos/small.mp4

我的Safari可以正常播放视频

我尝试模拟header

$ curl -I http://techslides.com/demos/sample-videos/small.mp4
HTTP/1.1 200 OK
Server: nginx/1.4.1 (Ubuntu)
Date: Mon, 23 Nov 2015 04:25:36 GMT
Content-Type: video/mp4
Content-Length: 383631
Last-Modified: Sun, 16 Feb 2014 18:49:36 GMT
Connection: keep-alive
ETag: "53010840-5da8f"
Accept-Ranges: bytes

除了 ETag ,其他 header 被插入。 但是Safari还是不能播放,服务器报同样的异常。

对于任何对这个问题感兴趣(或陷入困境)的人,我已经找到了解决方案。这不是一个简单的解决方案。

解决方案在此URL:FileServlet supporting resume and caching and GZIP

它适用于 servlet 和 Grails。