Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 99 lines (81 sloc) 4.229 kB
f503bf3 Inital commit
Predrag Knezevic authored
1 class ForceResponseDownloadGrailsPlugin {
2 // the plugin version
4d1eb77 set version to 0.1.5
Predrag Knezevic authored
3 def version = "0.1.5"
f503bf3 Inital commit
Predrag Knezevic authored
4 // the version or versions of Grails the plugin is designed for
50536b5 publishing using release plugin
Predrag Knezevic authored
5 def grailsVersion = "1.1 > *"
f503bf3 Inital commit
Predrag Knezevic authored
6 // resources that are excluded from plugin packaging
7 def pluginExcludes = [
8 "grails-app/views/error.gsp",
9 "web-app/**",
10 "test/**"
11 ]
12
13 def author = "Predrag Knezevic"
14 def authorEmail = "pedjak@gmail.com"
15 def title = "Force Downloading Controller's Response in Browser"
16
50536b5 publishing using release plugin
Predrag Knezevic authored
17 def license = "APACHE"
18
569c426 use release plugin now
Predrag Knezevic authored
19 def scm = [url: "http://github.com/pedjak/grails-force-response-download"]
50536b5 publishing using release plugin
Predrag Knezevic authored
20
f503bf3 Inital commit
Predrag Knezevic authored
21 def description = '''\\
22 The plugin forces browser to open a dialog for downloading content produced within controller's action.
23 Although the theory says that this is easily controlled by specifying Content-Disposition HTTP header, the practice shows
24 that there are special situations (ofcourse) with IE that has to be handled properly.
25
26 Controllers are extended with forceDownload method that takes as parameters a Map specifying download options,
27 and a object containing content:
28
3d7c2a8 @manuelvio Corrected forceDownload call example
manuelvio authored
29 forceDownload(filename:"file", contentType:"application/octet-stream", contentLength: 123, content)
f503bf3 Inital commit
Predrag Knezevic authored
30
3d7c2a8 @manuelvio Corrected forceDownload call example
manuelvio authored
31 * filename specifies the name that will be presented in browser download dialog, if omitted the default value is 'file'
f503bf3 Inital commit
Predrag Knezevic authored
32 * contentType is MIME content type that will be sent to browser for the given content, if omitted the default value is application/octet-stream
33 * contentLength is optional, but recommended to have - browsers will be able to show proper progress while downloading.
34 If not explicitely specified, it can be read from content object, if it implements size() method
35 * content - optional. If omitted then controller's code must write to response stream or render response manually using standard Grails approaches
50536b5 publishing using release plugin
Predrag Knezevic authored
36
37 The source code is available at http://github.com/pedjak/grails-force-response-download
f503bf3 Inital commit
Predrag Knezevic authored
38 '''
39
40 // URL to the plugin's documentation
41 def documentation = "http://grails.org/plugin/force-file-download"
42
43 def observe = ['controllers']
44
45 def doWithDynamicMethods = { ctx ->
46 configureControllers(application)
47 }
48
49 private def configureControllers(application) {
50 def service = application.mainContext.userAgentIdentService
51 application.controllerClasses*.clazz.each { cls ->
52 cls.metaClass.forceDownload = { Map map = [filename:"file", contentType:"application/octet-stream" ], content = null ->
53 def response = delegate.response
54 def isIE = service.isMsie()
55
56 if (delegate.request.isSecure()) {
57 response.addHeader("Pragma", "no-cache")
58 response.addHeader("Expires", "-1")
59 response.addHeader("Cache-Control", "no-cache")
60 } else {
61 response.addHeader("Cache-Control", "private")
62 response.addHeader("Pragma", "public")
63 }
64 response.addHeader("Content-Disposition", "attachment; filename=\"${map.filename}\"");
65 if (isIE) {
66 response.addHeader("Connection", "close");
67 }
68 response.contentType = map.contentType
69 def length = map.contentLength ?: content != null ? (content.metaClass.respondsTo(content, "size") ? content.size() : null) : null
70
71 if (length != null) {
72 response.addHeader("Content-Length", "${length}");
73 }
74 def os = response.outputStream
75 if (content != null) {
76 os << content
77 os.close()
78 }
79 os
80 }
81 }
82 }
83
84 def onChange = { event ->
85 if (event.source && application.isControllerClass(event.source)) {
86 def context = event.ctx
87 if (!context) {
88 if (log.isDebugEnabled()) {
89 log.debug("Application context not found. Can't reload")
90 }
91
92 return
93 }
94 configureControllers(application)
95 }
96 }
97
98 }
Something went wrong with that request. Please try again.