开发者

Grails multiple requests when using chunked transfer encoding

I am downloading a file through Grails, and am recording the fact that this file has been downloaded by this user, with code like:

class MyController {

    private void recordTrackDownload() {
        def d = new Download(session.user, "/path/to/file")
        d.save()
    }

    def download = {
        def f = new File("/path/to/file")
        recordTrackDownload()
        response.contentType = "mime/type"
        response.outputStream << f.newInputStream()
        response.outputStream.flush()
    }
}

And I am seeing several records of the download appear for each click. I suspect that this has something to do with the several requests recorded by Firefox and the "Transfer-Encoding: chunked" header I am seeing in Live HTTP Headers. (This behavior is also apparent in Chromium)

I am also seeing an error logged in the terminal, which is probably be related, and may be a bug in Grails, or just a logged and ignored bug, but it seems to not stop the file from actually downloading:

2010-01-14 18:46:16,623 [http-8080-4] ERROR errors.GrailsExceptionResolver  - Executing action [download] of controller [com.tunited.music.DownloadTrackController]  caused exception: ClientAbortException:  java.net.SocketException: Connection reset
org.codehaus.groovy.grails.web.servlet.mvc.exceptions.ControllerExecutionException: Executing action [download] of controller [com.tunited.music.DownloadTrackController]  caused exception: ClientAbortException:  j
ava.net.SocketException: Connection reset
        at java.lang.Thread.run(Thread.java:636)
Caused by: org.codehaus.groovy.runtime.InvokerInvocationException: ClientAbortException:  java.net.SocketException: Connection reset
        ... 1 more
Caused by: ClientAbortException:  java.net.SocketException: Connection reset
        at com.tunited.music.DownloadTrackController$_closure2.doCall(DownloadTrackController.groovy:35)
        at com.tunited.music.DownloadTrackController$_closure2.doCall(DownloadTrackController.groovy)
        ... 1 more
Caused by: java.net.SocketException: Connection reset
        at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:113)
        at java.net.SocketOutputStream.write(SocketOutputStream.java:153)
        ... 3 more
2010-01-14 18:46:17,091 [http-8080-4] ERROR view.GroovyPageView  - Error processing GroovyPageView: getOutputStream() has already been called for this response
java.lang.IllegalStateException: getOutputStream() has already been called for this response
        at home_ed_dev_nomad_tunited_grails_app_views_error_gsp.run(home_ed_dev_nomad_tunited_grails_app_views_开发者_如何学Pythonerror_gsp:19)
        at java.lang.Thread.run(Thread.java:636)
2010-01-14 18:46:17,092 [http-8080-4] ERROR errors.GrailsExceptionResolver  - getOutputStream() has already been called for this response
java.lang.IllegalStateException: getOutputStream() has already been called for this response
        at java.lang.Thread.run(Thread.java:636)
2010-01-14 18:46:17,100 [http-8080-4] ERROR view.GroovyPageView  - Error processing GroovyPageView: getOutputStream() has already been called for this response
java.lang.IllegalStateException: getOutputStream() has already been called for this response
        at home_ed_dev_nomad_tunited_grails_app_views_error_gsp.run(home_ed_dev_nomad_tunited_grails_app_views_error_gsp:19)
        at java.lang.Thread.run(Thread.java:636)
2010-01-14 18:46:17,103 [http-8080-4] ERROR [/].[grails]  - Servlet.service() for servlet grails threw exception
java.lang.IllegalStateException: getOutputStream() has already been called for this response
        at org.apache.catalina.connector.Response.getWriter(Response.java:610)
        at org.apache.catalina.connector.ResponseFacade.getWriter(ResponseFacade.java:198)
        at org.codehaus.groovy.grails.web.sitemesh.GrailsPageResponseWrapper$5.activateDestination(GrailsPageResponseWrapper.java:141)
        at org.codehaus.groovy.grails.web.sitemesh.GrailsRoutablePrintWriter.getDestination(GrailsRoutablePrintWriter.java:41)
        at org.codehaus.groovy.grails.web.sitemesh.GrailsRoutablePrintWriter.flush(GrailsRoutablePrintWriter.java:159)
        at org.codehaus.groovy.grails.web.util.GrailsPrintWriter.flush(GrailsPrintWriter.java:98)
...
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
        at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:849)
        at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
        at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:454)
        at java.lang.Thread.run(Thread.java:636)
2010-01-14 18:46:17,106 [http-8080-4] ERROR [/].[default]  - Servlet.service() for servlet default threw exception
java.lang.IllegalStateException: getOutputStream() has already been called for this response
        at org.apache.catalina.connector.Response.getWriter(Response.java:610)
        at org.apache.catalina.connector.ResponseFacade.getWriter(ResponseFacade.java:198)
        at org.codehaus.groovy.grails.web.sitemesh.GrailsPageResponseWrapper$5.activateDestination(GrailsPageResponseWrapper.java:141)
        at org.codehaus.groovy.grails.web.sitemesh.GrailsRoutablePrintWriter.getDestination(GrailsRoutablePrintWriter.java:41)
        at org.codehaus.groovy.grails.web.sitemesh.GrailsRoutablePrintWriter.flush(GrailsRoutablePrintWriter.java:159)
        at org.codehaus.groovy.grails.web.util.GrailsPrintWriter.flush(GrailsPrintWriter.java:98)
...
        at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:849)
        at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
        at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:454)
        at java.lang.Thread.run(Thread.java:636)

I would ideally like my controller code to only be executed once, and the chunked file to be downloaded. I could do this by having several actions, the first of which records the download, and then redirects to the second which downloads the file. But putting another redirect in the way and having to secure the second url, using something like the flash scope, seems like a poor solution. It seems like there's just something I don't know about how the HTTP protocol or Grails works, and there's a simple way to fix this.

Any ideas ?


The really simple fix is not to chunk the encoding. If you set the content length there should be no chunking.

Something like:

response.contentType = "mime/type"
response.contentLength = f.length()


I'd guess that the error you have above is caused by the user cancelling the download. This would cause an error and as some of the data has been sent in the response the exception you see is from grails attempting to write an error message to the response.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜