Exposing Functionality Over HTTP with Groovy and Ultra-Lightweight HTTP Servers
I used Groovy for its high productivity, especially regarding JDBC – with GSQL I needed only two lines to get the data from a DB in a user-friendly format.
My ideal solution would make it possible to start the server with support for HTTPS and authorization and declare handlers for URLs programatically, in a single file (Groovy script), in just few lines of code. (Very similar to the Gretty solution below + the security stuff.)
Side Notes
Note on Grape
Grape, the Groovy packaging engine, makes it possible to download dependencies at runtime via @Grab annotations. If you run your groovy script f.ex. via /bin/groovy then it will just work because Groovy is distributed together with Ivy, which is required for Grape to work. (If using IntelliJ then add ivy.jar manually to the project’s classpath and then invoke intention action (Mac: Alt+Enter) on the @Grab annotation to download it and add it to the classpath.)
Note on HTTPS/SSL Configuration
To enable HTTPS, you will need to create a keystore with a key pair, which is well described in the documentation of Jetty (step 1a).
For the impatient:
- Run
keytool -keystore $HOME/.keystore -alias myGroovyServer -genkey -keyalg RSA
- When asked “What is your first and last name?”, provide the hostname where the service will be running, e.g. “localhost” or “myserver.example.com”
- Specify the same password for the keystore and the generated key (e.g. “myKeystorePsw”)
- When running the server, supply the (absolute) path to the generated file .keystore (in a server-specific way) and set the system property javax.net.ssl.keyStorePassword to the password
1. Simple HTTP Request and Response Solutions
Attempt 1: Gretty
Gretty is a Groovy wrapper for Netty, the asynchronous web server, and is written in Groovy++. (Intro article for Gretty.)
Pros: Well integrated with Groovy, simple to get started with, supports serving static resources and more, Netty is cool.
Cons: Undocumented, the project seems to be dormant, no clear way to add user authorization and HTTPS.
The code:
@GrabConfig(systemClassLoader=true) @GrabResolver(name='gretty', root='http://groovypp.artifactoryonline.com/groovypp/libs-releases-local') @Grapes([ @Grab('org.mbte.groovypp:gretty:0.4.279'), @Grab('mysql:mysql-connector-java:5.1.16')]) import org.mbte.gretty.httpserver.* import groovy.sql.Sql class Main { final def db = [url: 'jdbc:mysql://localhost:3306/user', user: 'dbUser', psw: 'dbPsw' ] def run() { startServer() } def getUser(def code) { println "Connecting to the DB to check '$code'..." def sql = Sql.newInstance( db.url, db.user, db.psw) return sql.firstRow("select * from users where code = $code") ?: "No such code found" } def startServer() { GrettyServer server = [] server.groovy = [ localAddress: new InetSocketAddress(6789), // no host => all defaultHandler: { response.redirect "/" }, "/:code": { get { def user = getUser(it.code) response.text = "The code '${it.code}' refers to $user\n" // => st. like: "The code 'abc' refers to [id:123, name:me@somewhere.no, code:abc]" } } ] server.start() println "Groovy server is ready to serve" } } new Main().run()
Jetty
Pros: Mature, powerful, often used in the embedded form, supports HTTPS and authorization (also programatically).
Pitfall: You cannot use org.eclipse.jetty:jetty-server because Grape.grab will fail to download the dependency org.eclipse.jetty.orbit:javax.servlet due to Ivy getting confused by packaging vs. extension. Use org.eclipse.jetty.aggregate:jetty-server instead (the Jetty aggregate packages merge multiple smaller JARs).
Example: Jetty with Security
(based on the articles about Embedding Jetty (including SSL) for programatic configuration and handling requests via a custom handler or servlet (very well written indeed) and How to Configure Security with Embedded Jetty for programatic configuration of authentication and authorization)
import groovy.sql.Sql import javax.servlet.* import javax.servlet.http.* import org.eclipse.jetty.server.* import org.eclipse.jetty.server.ssl.SslSelectChannelConnector import org.eclipse.jetty.servlet.* import org.eclipse.jetty.security.* import org.eclipse.jetty.util.security.* @GrabConfig(systemClassLoader = true) @Grapes([ @Grab('org.eclipse.jetty.aggregate:jetty-server:8.1.2.v20120308'), @Grab('org.eclipse.jetty.aggregate:jetty-servlet:8.1.2.v20120308'), @Grab(group='javax.servlet', module='javax.servlet-api', version='3.0.1'), @Grab('mysql:mysql-connector-java:5.1.16')]) class Main extends HttpServlet { final def db = [url: 'jdbc:mysql://localhost:3306/user', user: 'dbUser', psw: 'dbPsw' ] protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { final String code = request.pathInfo.substring(1); // skip leading '/' response.setContentType("text/plain"); try { def user = getUser(code) response.setStatus(HttpServletResponse.SC_OK); response.getWriter().println("Usage of the code '${code}': $user\n") } catch (Exception e) { response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR) response.getWriter().println("Connection to the database failed. This may be due to temporary " + "connection problems or due to misconfiguration. Try later.") } } def getUser(def code) { println "Connecting to the DB to check '$code'..." def sql = Sql.newInstance( db.url, db.user, db.psw) return sql.firstRow("select * from users where code = $code") ?: "No such code found" } public static startServer() { Server server = new Server(); server.setHandler(createServletHandlerWithAuthentication( "/", new Main(), createAuthenticationConstraint())) server.setConnectors((Connector[])[createSslConnector()]) server.start(); server.join(); } /** Wrap the servlet in the servlet handler and configure it to run at the given URL, setting its security handler. */ private static createServletHandlerWithAuthentication(String contextPath, Servlet servlet, SecurityHandler securityHandler) { final String pathSpec = "/*" ServletContextHandler servletHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS) servletHandler.setContextPath(contextPath) servletHandler.setSecurityHandler(securityHandler) servletHandler.addServlet(new ServletHolder(servlet), pathSpec) return servletHandler } /** Create HTTPS connector running at port 6789 and using key pair from the hard-coded keystore. */ private static Connector createSslConnector() { SslSelectChannelConnector ssl_connector = new SslSelectChannelConnector() ssl_connector.setPort(6789) def cf = ssl_connector.getSslContextFactory() cf.setKeyStore(System.getProperty("user.home") + "/.keystore") cf.setKeyStorePassword("myKeystorePsw") cf.setKeyManagerPassword("myKeystorePsw") return ssl_connector } /** Create a security handler requiring authentication with username/password. */ private static SecurityHandler createAuthenticationConstraint() { Constraint constraint = new Constraint(); constraint.setName(Constraint.__BASIC_AUTH); constraint.setRoles((String[])["user"]); constraint.setAuthenticate(true); ConstraintMapping cm = new ConstraintMapping(); cm.setConstraint(constraint); cm.setPathSpec("/*"); // auth. required for any URL def loginSrv = new HashLoginService() loginSrv.putUser("myLogin", new Password("myPassword"), (String[])["user"]) loginSrv.setName("My App Realm") SecurityHandler sh = new ConstraintSecurityHandler() sh.setLoginService(loginSrv) sh.setConstraintMappings((ConstraintMapping[])[cm]); return sh } } Main.startServer()
Additional resources:
- Post: Embedded Groovy executing Groovlets (Groovy scripts with access to request/response and support for generating HTML)
- Post: A Groovy+Jetty blog featuring support for command-line arguments, @Grab, and serving of static resources
- Post: Enabling HTTPS for an Embedded Jetty
Winstone
Winstone is a 200KB servlet container available via Maven, last release 2008. It seems to be focused on serving WARs.
Sun Java 6 HttpServer
Sun JRE 6+ contains a ligthweight, programmatically controled HTTP server, supporting also HTTPS. Example code.
2. REST-based Solutions
Jersey JAX-RS
Jersey, the reference implementation of JAX-RS (aka REST), can run on an embedded test server such as Grizzly, GlassFish, or Jetty.
Pros: The reference implementation of JAX-RS, i.e. standard.
Cons: Troubleshooting Jersey isn’t as easy as I’d like it to be. The documentation should be better (compare to Jetty’s), this is really a weak point (try to find anything about securing Jersey with an embedded Grizzly).
Example: Jersey with embedded Grizzly, without security
(If interested in security and authentication, check out the sample project https-clientserver-grizzly. It seems little to complex to me.)
import groovy.sql.Sql import javax.ws.rs.* import javax.ws.rs.core.* import com.sun.jersey.api.core.* import com.sun.jersey.api.container.grizzly2.GrizzlyServerFactory import org.glassfish.grizzly.http.server.HttpServer @GrabConfig(systemClassLoader = true) @GrabResolver(name = 'gretty', root = 'http://groovypp.artifactoryonline.com/groovypp/libs-releases-local') @Grapes([ @Grab('com.sun.jersey:jersey-server:1.12'), @Grab('com.sun.jersey:jersey-core:1.12'), @Grab(group='com.sun.jersey', module='jersey-grizzly2', version='1.12'), @Grab(group='javax.ws.rs', module='jsr311-api', version='1.1.1'), @Grab('mysql:mysql-connector-java:5.1.16')]) @Path("/{code}") class Main { final def db = [url: 'jdbc:mysql://localhost:3306/user', user: 'dbUser', psw: 'dbPsw' ] @GET @Produces("text/plain") public Response getUserByCode(@PathParam('code') String code) { try { def user = getUser(code) return Response.ok().entity("Usage of the code '${code}': $user\n".toString()).build(); } catch (Exception e) { Response.serverError().entity("Connection to the database failed. This may be due to temporary " + "connection problems or due to misconfiguration. Try later. Cause: $e".toString()).build(); } } def getUser(def code) { println "Connecting to the DB to check '$code'..." def sql = Sql.newInstance( db.url, db.user, db.psw) return sql.firstRow("select * from users where code = $code") ?: "No such code found" } public static startServer() { ResourceConfig resources = new ClassNamesResourceConfig(Main) def uri = UriBuilder.fromUri("http://localhost/").port(6789).build(); HttpServer httpServer = GrizzlyServerFactory.createHttpServer(uri, resources); println("Jersey app started with WADL available at ${uri}application.wadl") System.in.read(); httpServer.stop(); } } Main.startServer()
RESTEasy with an Embedded TJWS (Tiny Java Web Server and Servlet Container)
TJWS is trully miniature, 100KB footprint, runs also on Android, about 5 times smaller than the competitors LWS and Jetty.
From the RESTEasy documentation:
@Path("") public class MyResource { @GET public String get() { return "hello world"; } public static void main(String[] args) throws Exception { TJWSEmbeddedJaxrsServer tjws = new TJWSEmbeddedJaxrsServer(); tjws.setPort(8081); tjws.getRegistry().addPerRequestResource(MyResource.class); tjws.start(); } }
TJWS itself supports SSL, I’m not sure about the JBoss TJWS plugin for RESTEasy (which is the only version of tjws availabe in Maven). It can be also embedded but isn’t available via Maven and I don’t know if it supports mapping of requests to code (instead of WARs and JSPs).
Restlet with an Embedded Server
See the article Building RESTful Web Apps with Groovy and Restlet, Part 1: Up and Running (2008). As Restlet is available in Maven, we could just @Grab the dependencies.
What is even more interesting is the GroovyRestlet module that let you configure authorization and request handling programatically, with only few lines. (You can do this also in Java, with some more LoC.)
Doc for the release 2.1: How to implement authorization and HTTPS, the simplest possible REST server in ~ 6 lines of Java.
(Notice that Restlet comes with a simple HTTP server but can also use Jetty or Grizzly.)
Pros: RESt (though non-standard), good integration with Groovy (though it might be outdated)
Cons: As of 4/2012 Restlet is only in its private Maven repository though they’re going to be in Maven Central too, JAX-RS support isn’t fully implemented yet (Restlet 2.1-RC3). The documentation could be better (more comprehensive, more interlinked, more varied examples). To use HTTPS you must choose some other server than the internal one.
Example: Restlet + SimpleFramework Server + HTTPS and Authentication (w/o Groovy integration)
import groovy.sql.Sql import org.restlet.* import org.restlet.data.* import org.restlet.resource.* import org.restlet.security.* @GrabConfig(systemClassLoader = true) @GrabResolver(name = 'gretty', root = 'http://groovypp.artifactoryonline.com/groovypp/libs-releases-local') @GrabResolver(name = 'restlet', root = 'http://maven.restlet.org') @Grapes([ @Grab('org.restlet.jse:org.restlet:2.1-RC3'), @Grab('org.restlet.jse:org.restlet.ext.simple:2.1-RC3'), @Grab('mysql:mysql-connector-java:5.1.16')]) class Main extends ServerResource { final def db = [url: 'jdbc:mysql://localhost:3306/user', user: 'dbUser', psw: 'dbPsw' ] @Get public String getUser() { def code = getRequestAttributes().get("code") def user = getUser(code) return "Usage of the code '${code}': $user\n" } def getUser(def code) { println "Connecting to the DB to check '$code'..." def sql = Sql.newInstance( db.url, db.user, db.psw) return sql.firstRow("select * from users where code = $code") ?: "No such code found" } public static startServer() { Component component = new Component(); def userResourceFinder = component.getDefaultHost().createFinder(Main.class); component.getDefaultHost().attach("/{code}" , wrapResourceInAuthenticationCheck(component.getContext(), userResourceFinder)); configureHttpsServer(component, 6789) component.start() } /** * Add a Guard (a filter) that asks the user for username/password and checks it against a map. */ private static Restlet wrapResourceInAuthenticationCheck(Context context, Restlet resource) { MapVerifier verifier = new MapVerifier(); verifier.getLocalSecrets().put("myLogin", "myPassword".toCharArray()); ChallengeAuthenticator guard = new ChallengeAuthenticator(context.createChildContext(), ChallengeScheme.HTTP_BASIC, "My App"); guard.setVerifier(verifier); guard.setNext(resource); return guard; } /** * Create the server, instruct it to use a SslContextFactory, and configure the factory with * our keystore and password. I guess that which server to use is determined by Restlet based on which * package (*.ext.simple.*, *.ext.jetty.* etc.) is available. */ private static void configureHttpsServer(Component component, int port) { def secureServer = component.getServers().add(Protocol.HTTPS, port); // See http://www.restlet.org/documentation/2.1/jse/ext/org/restlet/ext/ssl/DefaultSslContextFactory.html // for params such as keystore path and password System.setProperty("javax.net.ssl.keyStorePassword", "myKeystorePsw") // used for keystorePassword & keyPassword def confg = secureServer.getContext().getParameters() confg.add("sslContextFactory", "org.restlet.ext.ssl.DefaultSslContextFactory") // Beware: keystorePath shall default to ${user.home}/.keystore but doesn't seem to do so => set it explicitly confg.add("keystorePath", "${System.getProperty('user.home')}/.keystore") } } Main.startServer()
Conclusion
I’d perhaps use either Jetty if REST isn’t needed and Jersey+Jetty otherwise (I’d certainly pick Jetty over Grizzly as the documentation is much better). Restlet might be interesting too provided that the Groovy integration works and if you don’t mind using a non-standard REST implementation.
Looking at the length of the code samples it might have been better to try Grails or st. similar after all
Reference: Exposing Functionality Over HTTP with Groovy and Ultra-Lightweight HTTP Servers from our JCG partner Jakub Holy at the The Holy Java blog.
Very useful and informative article