Wednesday, October 21, 2009

Groovy Goodness: Groovlets as Lightweight Servlets

Groovlets are Groovy scripts that are executed by a servlet. With Groovlets a user can request a Groovy script that is executed on the server and the results are displayed in a web browser. We only have to define a servlet in the web.xml of a Java web application, place the Groovy libraries in the web application's lib folder and we can execute Groovy scripts.

The Groovy script, or we can call it a Groovlet, has a couple of implicit variables we can use. For example reqeust, response to access the HttpServletRequest and HttpServletResponse objects. We have access to the session with the session variable. And if we want to output data we can use out, sout and html.

Let's create a Groovy script serverinfo.groovy:

def method = request.method

if (!session) {
    session = request.getSession(true)

if (!session.groovlet) {
    session.groovlet = 'Groovlets rock!'

html.html {
    head {
        title 'Groovlet info'
    body {
        h1 'General info'
        ul {
            li "Method: ${method}"
            li "RequestURI: ${request.requestURI}"
            li "session.groovlet: ${session.groovlet}"
            li "application.version: ${context.version}"
        h1 'Headers'
        ul {
            headers.each {
                li "${it.key} = ${it.value}"

A simple script to start Jetty so we can run our Groovlet:

import org.mortbay.jetty.Server
import org.mortbay.jetty.servlet.*
import groovy.servlet.*

@Grab(group='org.mortbay.jetty', module='jetty-embedded', version='6.1.14')
def startJetty() {
    def jetty = new Server(9090)
    def context = new Context(jetty, '/', Context.SESSIONS)  // Allow sessions.
    context.resourceBase = '.'  // Look in current dir for Groovy scripts.
    context.addServlet(GroovyServlet, '*.groovy')  // All files ending with .groovy will be served.
    context.setAttribute('version', '1.0')  // Set an context attribute.

println "Starting Jetty, press Ctrl+C to stop."

When we run the script to start Jetty and visit http://localhost:9090/serverinfo.groovy with our browser we get the following output:


Andrey Paramonov said...

Did you mean version='6.1.14'?

mrhaki said...

@Andrey Paramonov: Thank you for spotting the 'typo'. It must be 6.1.14 indeed.

Anonymous said...

When I ran this in Eclipse (from SpringSource), I got NoClassDefFoundError on javax.servlet.http.Servlet. Doesn't grape actually download the jetty (which includes servlet api)?

mrhaki said...

@Anonymous: All dependencies should be downloaded by Grape/Ivy, including the Servlet API. I will test this tonight in Eclipse as I only have an internet connection via the browser... I will let you know.

Anonymous said...

Thanks mrhaki. I really enjoy your blog as I learn quite a bit from it. When I ran this inside eclipse, I checked to be sure it downloaded all the jar files under my home home directory and I saw all the dependencies downloaded. So I am not sure what happened.

mrhaki said...

@Anonymous: I wasn't able to run the sample in Eclipse / SpringSource Tool Suite either, but I discovered the reason. The Groovy plugin in Eclipse is installed in the plugins/org.codehaus.groovy_* directory. When we look in the lib directory we notice the absence of servlet-api-2.4.jar, which is available in the lib directory of a normal Groovy installation. So if we have Groovy installed on our computer we have to look for the lib directory and the file servlet-api-2.4.jar and copy it to the lib directory of the Eclipse Groovy plugin. At least this example can be run from Eclipse / SpringSource Tool Suite now.

Anonymous said...

How does one bind groovlet session, request, etc. to object scope?

Yes, at script level one can access, for example, session.user, but when trying to do the same in object scope, none of the essential groovlet attributes are available.

Class Scoped {
Scoped() {
if(session?.user) // do something

I know one can pass in the script context (i.e. pass "this" to object at script level), but I'm looking for a thread safe way to work with these attribs.

Bind looks promising, as does Threadlocal for thread safety (the latter looking quite difficult to do say, session.role = 'admin', as TL setter method overwrites existing stored value).

That there is nothing on the net re: binding groovlet script context to object scope means everyone is using Grails?

mrhaki said...

@Anonymous: I am not quite sure what you mean. I think it is good to keep request/session/servletContext scoped variables in the groovlet. The groovlet knows about these scopes and variables and can pass them on to other objects. Actually with Grails you also don't have access to the session scope in all objects. The controllers can use session.user, but for example a service can not access the session scope variables. You can use Groovy's Meta Object Protocol (MOP) to add certain properties to your Scoped class and use those to assign values.
And otherwise you can pass the value to the Scoped class, which makes the Scoped class decoupled from the session (and thus web releated stuff).

Post a Comment