'Embedding recent Jetty in Groovy using Grape' post illustration

Embedding recent Jetty in Groovy using Grape

avatar

Several days ago I have a need to launch Groovlets on a web server. I have decided to go with recent Jetty for this task, developed by Eclipse Foundation at these days. It turned out to be non trivial task, but at the end it all worked so I decided to share my experience with you.

The straightforward way to start recent stable Jetty version would be:

jetty_example_1.groovy
1
2
3
4
5
6
7
8
9
10
11
12
import org.eclipse.jetty.server.Server

@Grab('org.eclipse.jetty:jetty-server:8.1.8.v20121106')

def startJetty() {
    def jetty = new Server(9090)

    jetty.start()
}

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

After running this we will get the following result:

1
2
3
4
5
6
7
8
java.lang.RuntimeException:
Error grabbing Grapes -- [download failed: org.eclipse.jetty.orbit#javax.servlet;3.0.0.v201112011016!javax.servlet.orbit]
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
    ...
    at groovy.grape.GrapeIvy.getDependencies(GrapeIvy.groovy:411)
    ...

There is a well known problem with this Servlet 3.0 implementation by Eclipse. To overcome it, we might try to use well known solution, e.g. specify that packaging type of this artefact is jar and not orbit as specified in pom file. To do this we need to disable transitive dependecy loading of jetty-server and to download all of its dependencies manually:

jetty_example_2.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.servlet.*
import groovy.servlet.*

@Grapes([
    @Grab(group='org.eclipse.jetty.orbit', module='javax.servlet', version='3.0.0.v201112011016', ext='jar'),
    @Grab(group='org.eclipse.jetty', module='jetty-server', version='8.1.8.v20121106', transitive=false),
    @Grab(group='org.eclipse.jetty', module='jetty-continuation', version='8.1.8.v20121106'),
    @Grab(group='org.eclipse.jetty', module='jetty-http', version='8.1.8.v20121106'),
    @Grab(group='org.eclipse.jetty', module='jetty-servlet', version='8.1.8.v20121106', transitive=false),
    @Grab(group='org.eclipse.jetty', module='jetty-security', version='8.1.8.v20121106', transitive=false)
])

...

This solution will work if you run it from command line, but if you will run it from IntelliJ IDEA you might face the following issue:

1
2
3
4
5
java.lang.SecurityException:
class "javax.servlet.ServletRegistration$Dynamic"'s signer information does not match signer information of other classes in the same package
    at java.lang.ClassLoader.checkCerts(ClassLoader.java:806)
    ...
    at com.intellij.rt.execution.CommandLineWrapper.main(CommandLineWrapper.java:108)

One solution to defeat this problem is to use stock Servlet 3.0 implementation provided by Oracle (in this case the Grab list can be simplified, because we can use GrabExclude and transitive dependency resolution):

jetty_example_3.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.servlet.*
import groovy.servlet.*

@Grapes([
    @Grab('org.eclipse.jetty:jetty-server:8.1.8.v20121106'),
    @Grab('org.eclipse.jetty:jetty-servlet:8.1.8.v20121106'),
    @Grab('javax.servlet:javax.servlet-api:3.0.1'),
    @GrabExclude('org.eclipse.jetty.orbit:javax.servlet')
])

def startJetty() {
    def jetty = new Server(9090)

    def context = new ServletContextHandler(jetty, '/', ServletContextHandler.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.

    jetty.start()
}

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

This solution works fine in IntelliJ IDEA. You can create sample Groovlet and place it to the same folder as the script above to test this server:

example.groovy
1
2
3
4
5
6
7
8
9
10
println """
<html>
    <head>
        <title>Example Groovy Servlet</title>
    </head>
    <body>
Hello from Groovy, ${request.remoteHost}: ${new Date()}
    </body>
</html>
"""

Now if you open http://localhost:9090/example.groovy you should see the output of this Groovlet. You don't need to relauch the script when you edit the code of Groovlet, which is very nice.

To cut down the number of manual dependencies one can also use aggregate Jetty jars:

jetty_example_3.groovy
1
2
3
4
@Grapes([
    @Grab(group='javax.servlet', module='javax.servlet-api', version='3.0.1'),
    @Grab(group='org.eclipse.jetty.aggregate', module='jetty-all-server', version='8.1.8.v20121106', transitive=false)
])

If you're looking for a developer or considering starting a new project,
we are always ready to help!