Loading...

November 3, 2009

Add File Upload Support to Groovlets in Google App Engine

Gaelyk adds Groovy and Groovlets to Google App Engine. It is a very simple framework with a lot of the Google App Engine services already injected into the Groovlets for easy access. If we want to upload files to our Google App Engine application we must handle this in our Groovlets. In Grooovy's JIRA there is a proposed patch to use Apache Commons FileUpload. We will use this as inspiration to add file upload support to a Groovlet running in Google App Engine.

First we must download Apache Commons FileUpload and the dependent Apache Common IO libraries. We add these to the war/WEB-INF/lib directory of our Gaelyk application. Next we create a simple Groovlet (upload.groovy) that creates an HTML page with a form. The form has a several input fields of different types among fields of the type file to upload files. We must make sure the enctype attribute of the form is set to multipart/form-data.

html.html {
    head {
        title 'Sample to Upload Files to Google App Engine'
        style '''
            label { display: block; padding: 0.2em; }
        '''
    }
    body {
        h1 'Sample to Upload Files to Google App Engine'
        form action: 'action.groovy', method: 'post', enctype: 'multipart/form-data', {
            label 'Username: ', {
                input type: 'text', name: 'username'
            }
            label 'Likes: ', {
                select name: 'language', {
                    option 'Groovy'
                    option 'Java'
                    option 'Scala'
                }
            }
            label 'Photo: ', {
                input type: 'file', name: 'photo'
            }
            label 'Resume: ', {
                input type: 'file', name: 'resume'
            }
            input type: 'submit', name: 'submit', value: 'Send to server'
        }
    }
}

The Groovlet, action.groovy, contains the code to handle the uploaded files:

import org.apache.commons.io.IOUtils
import org.apache.commons.fileupload.util.Streams
import org.apache.commons.fileupload.servlet.ServletFileUpload

uploads = [:]  // Store result from multipart content.
if (ServletFileUpload.isMultipartContent(request)) {
    def uploader = new ServletFileUpload()  // Cannot use diskbased fileupload in Google App Engine!
    def items = uploader.getItemIterator(request)
    while (items.hasNext()) {
        def item = items.next()
        def stream = item.openStream() 
        try {
            if (item.formField) {  // 'Normal' form field.
                params[item.fieldName] = Streams.asString(stream)
            } else {
                // Assign upload file contents to uploads map. 
                // The value is also a map with the keys name, contentType and data.
                uploads[item.fieldName] = [
                    name: item.name,
                    contentType: item.contentType,
                    data: IOUtils.toByteArray(stream)
                ]
            }
        } finally {
            IOUtils.closeQuietly stream
        }
    }
}

if (params.submit) {
    handle()
}

def handle() {
    // Build response HTML.
    html.html {
        head {
            title 'Result of Upload'
        }
        body {
            h1 'Result of Upload'
            h2 'Parameters'
            ul {
                params.each {
                    li "Received parameter '$it.key' with value '$it.value'"
                }
            }
            h2 'Uploads'
            ul {
                uploads.each {
                    li "Received parameter '$it.key' with name '$it.value.name', content type '$it.value.contentType' and data size '$it.value.data.length'"
                }
            }
        }
    }
}

The following screenshots show the upload page and the result page after we have filled in values for the form fields and selected two files: