DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report
  1. DZone
  2. Coding
  3. Languages
  4. Groovy++ in action: Gretty/GridGain/REST/Websockets

Groovy++ in action: Gretty/GridGain/REST/Websockets

Alex Tkachman user avatar by
Alex Tkachman
·
May. 31, 11 · Interview
Like (0)
Save
Tweet
Share
24.04K Views

Join the DZone community and get the full member experience.

Join For Free

This article can be seen both as tutorial on Gretty web server and yet another demonstration how powerful and expressive can be Groovy++.

Gretty is very simple web framework for building asynchronious web servers and client. It is based on amazing Netty library and inherit from it very impressive performance. Gretty is written on Groovy++ and utilize both expressiveness of pure Groovy and fast-as-java performance of compiled code. If you are Java guy who heard a lot about Node.js but don't want to deal with javascript you must give a try to Gretty.

Gretty is very young  project and don't even have own website yet. You can find sources and examples at https://github.com/alextkachman/gretty

Let us start with examplr simplest possible web server, which does almost nothing except handling of some static content and redirecting every request, which can not be satisfied by static content, to some designated web page. Here is the Groovy++ code

@Typed package example

import org.mbte.gretty.httpserver.GrettyServer

new GrettyServer() [
// server local address
localAddress: new InetSocketAddress(8081),

// directory where to find static resources
static: "./static",

// default handler for requests which don't match any other pattern
default: {
redirect "/test.html"
}
].start ()

We use here Groovy++ "multi-property-set" operator to set many properties by one expression.

Groovy expert can be curious why is it better than named constructor parameters. The reason is that in real life instance of GrettyServer may come from Spring or Guice and different services or subsystems might contribute to what request the server is able to handle.

Of course, the same code can be written in Java. Well, probably it will be a little bit more verbose but Gretty can be used from Java, Scala or any other JVM language. Here is Java version of the code.

package example;

import org.mbte.gretty.httpserver.GrettyHttpHandler;
import org.mbte.gretty.httpserver.GrettyServer;

import java.net.InetSocketAddress;
import java.util.Map;

public class Step0_Java {
public static void main(String[] args) {
GrettyServer server = new GrettyServer();

// server local address
server.setLocalAddress(new InetSocketAddress(8081));

// directory where to find static resources
server.setStatic("./static");

// default handler for requests which don't match any other pattern
server.setDefault(new GrettyHttpHandler() {
public void handle(Map<String, String> pathArguments) {
redirect("/test.html");
}
});

server.start();
}
}

As good citizen of Groovy community Gretty provides special binding for pure Groovy. It looks almost the same as one for Groovy++ except some minor details (mostly because of language features, which is available in Groovy++ but not in Groovy Core)

Here is the same example using normal Groovy. The only difference (except that we don't have static compilation anymore) is that instead of "multi-property-set" operator we assign Map of settings to special property 'groovy'

package example

import org.mbte.gretty.httpserver.GrettyServer

def server = new GrettyServer()
server.groovy = [
// server local address
localAddress: new InetSocketAddress(8081),

// directory where to find static resources
static: "./static",

// default handler for requests which don't match any other pattern
default: {
redirect "/test.html"
}
]
server.start ()

Now let us do something less trivial. We want to achieve two goals now

  • to be able to test our server
  • to move part of the static content (some widely used javascript files) outside of our server

Both are pretty easy. Gretty reuses beautiful capability of Netty to run notonly over IP connections but also use in-memory communication. In test mode we just need to bind local address of our server not to IP socket but to in-memory one.

String [] args = binding.variables.args
def inTestMode = args?.length && args[0] == 'test'

new GrettyServer() [
// server local address
// if in test mode we use in-memory communication, otherwise IP
localAddress: !inTestMode ? new InetSocketAddress(9000) : new LocalAddress("test_server"),

// directory where to find static resources
static: "./static",

// default handler for request which don't match any rule
default: {
response.redirect "/webkvstore.html"
},

public: {
// redirect googlib path to google servers
get("/googlib/:path") {
def googleAjaxPath = 'http://ajax.googleapis.com/ajax/libs'

switch(it.path) {
case 'jquery.js':
redirect "${googleAjaxPath}/jquery/1.6.1/jquery.min.js"
break

case 'prototype.js':
redirect "${googleAjaxPath}/prototype/1.7.0.0/prototype.js"
break

default:
redirect "${googleAjaxPath}/${it.path}"
}
}
},
].start()

public section defines handlers to be used for matching requests. In our case any http GET request to url starting with '/googlib/' will be redirected to Google servers.

The handler itself is nothing special but instance of org.mbte.gretty.httpserver.GrettyHttpHandler which we saw already in imports of Java version of our code. Being so-called "single-abstract-method" GrettyHttpHandler can be almost always substitude by closure.

Now we are ready to write tests for our server. We will use client part of Gretty and in-memory communication.

if(inTestMode) {
try {
TestRunner.run(new TestSuite(BasicTest))
}
finally {
System.exit(0)
}
}

class BasicTest extends GroovyTestCase implements HttpRequestHelper {
void testRedirectToMainPage () {
doTest("/nosuchurl") { response ->
assert response.status == HttpResponseStatus.MOVED_PERMANENTLY
assert response.getHeaders(HttpHeaders.Names.LOCATION)[0] == "/webkvstore.html"
}
}

void testMainPage () {
doTest("/webkvstore.html") { response ->
assert response.contentText == new File("./static/webkvstore.html").text
}
}

void testRedirectToGoogle () {
doTest("/googlib/prototype.js") { response ->
assert response.status == HttpResponseStatus.MOVED_PERMANENTLY
assert response.getHeaders(HttpHeaders.Names.LOCATION)[0] == "http://ajax.googleapis.com/ajax/libs/prototype/1.7.0.0/prototype.js"

}
doTest("/googlib/jquery.js") { response ->
assert response.status == HttpResponseStatus.MOVED_PERMANENTLY
assert response.getHeaders(HttpHeaders.Names.LOCATION)[0] == "http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"
}
}
}

 Note use of HttpRequestHelper trait

It mixes helpful testing methods in to our test class

Note on testing methodology

We place our tests in the same Groovy script as server itself. While being very convinient of prototyping stage it becomes not good when your application grow, so you will probably use Guice or Spring or whatever approach you like to start your server in unit test mode. The point is that in-memory communication is still at your full disposal.

Now let us add some more useful functionality in to our server. We want clients to be able to store some temporary data on the server is map-like operation. The simplest solution is REST approach - http GET/PUT/DELETE operations with keys encoded in URL. For storage we will use beautiful distributed GridGain cache.

The full version of source code including test can be found at github repository Here we will show only important changes

First of all before starting our server we need to connect to GridGain

// start GridGain and obtain cache instance
def cache = GridFactory.start("spring-cache.xml").cache('replicated')

We also need to modify public block of our server and add handlers for our map operations

    public: {
// redirect googlib path to google servers
get("/googlib/:path") {
....................................................
}

get("/api/:key") {
if(it.key)
response.json = [result: cache.get(it.key)]
else
response.json = [result: cache.keySet()]
}

delete("/api/:key") {
response.json = [result: cache.remove(it.key)]
}

put("/api/:key") {
response.json = [result: cache.put(it.key, request.contentText)]
}
},

Notice how easy and convinient we create JSON responses.

Gretty utilizes Jackson library - probably the fastest JSON library for Java available today

As we said above test cases are available at github repository but there is one thing, which is important to know. Not every browser is capable to send AJAX requests other than GET & POST, so our story with PUT & DELETE operations can be useless. Fortunately there is common workaround recommended at least by both Microsoft and Google in their APIs - X-HTTP-Method-Override header.

Gretty supports this header both on client and server side as shown in following code snippet for test cases

        // request done as 'POST' method with additional header
// X-HTTP-Method-Override: DELETE
// we use methodOveride only in order to make sure that it handled correctly
doTest([uri: "/$api/1", methodOverride: HttpMethod.DELETE ]) { response ->
assert Map.fromJson(response.contentText).result == '932'
}

Interesting to note that we developed our server without opening browser even once (modulo of course checking design of web pages)

But there still something non-perfect in our server. It is synchronious! That means that while we do network round trip to GridGain the server thread is blocked waiting for result to arrive. That does not sound like right utilization of resources. Let us fix it.

Again whole source code can be found at github repository We will only provide important changes.

First of all we will use alternative and more flexible approach to define our new API. Instead of putting everything in to public section of our server we will define separate web context dedicated to our API. In fact public section is just shortcut for default web context.

Secondly, we will use GridGain asynchronious operations like getAsync etc. Asynchronious operations return almost immidiately and instead of operation's result return subclass of java.util.concurrent.Future called GridFuture. The huge benefit of GridFuture is the fact that it allows adding listeners for operation completion (OMG! why standard JDK future does not)

So we call asynchronious operation, register listener and free resources for handling of other requests (by returning from current handler). When listener invoked we are ready to send response back to the client.

Does not this code look elegant?

    public: {
..............................
},

webContexts: [
// alternative approach
// - use of web context instead of default context
// - use of asynchronous operations
"/asyncapi" : [
public: {
get("/:key") {
if(it.key) {
cache.getAsync(it.key) << { f ->
response.json = [result: f.get()]
}
}
else {
cache.getAllAsync() << { f ->
response.json = [result: f.get().keySet()]
}
}
}

delete("/:key") {
cache.removeAsync(it.key) << { f ->
response.json = [result: f.get()]
}
}

put("/:key") {
cache.putAsync(it.key, request.contentText) << { f ->
response.json = [result: f.get()]
}
}
}
]
]

 

Well, truly speaking, there is one small but important detail, which we did not explain yet. Obviously GridGain (designed for Java and Scala) does not provide Groovish leftShift API (<< operator). Even more important question is how does server know (except the fact that we responded nothing yet) that response is not completed and some continuation (our listener) plans to do it later.

To understand both issues we need to see how do we integrate GridGain in to our GrettyServer. Magically it is done by static compilation. In the code snippet below we define GrettyGridInClosure which subclass GridInClosure (base class for listener) from GridGain and mix it in with GrettyAsyncFunction trait from Gretty. GrettyAsyncFunction is capable to remember all necessary information of web request handled when it was created and than to reuse this knowledge when executed. At the same time leftShift method has parameter of type GrettyGridInClosure, which let compiler treat closure we provide as new instance of this class. Voila!

/**
* Utility class to make GridGain callback Gretty asynchronious
*/
abstract class GrettyGridInClosure<T> extends GridInClosure<T> implements GrettyAsyncFunction<T, Object> {
final void apply(T t) {
handlerAction(t)
}
}

/**
* Convenient << method for GridFuture
*/
static <V> void leftShift(GridFuture<V> self, GrettyGridInClosure<GridFuture<V>> listener) {
self.listenAsync listener
}

Very good - we have beautiful and covered with tests web server. The last feature we would like to add is to have similar API accessable via websockets. Yes, Gretty does support websockets.

The full code can be found in github repository which I recommend at least to look in order to see how websockets can be tested over in memory communication. Here we only provide snippet important for support of websockets. I hope the code is self-explaining (probably modulo "labeled closure" syntax used to define method onConnect)

   webContexts: [
// alternative approach
// - use of web context instead of default context
// - use of asynchronous operations
"/asyncapi" : [
public: {
......................................
websocket("/") { String msg ->
onConnect: {
send([msg: "welcome"].toJsonString())
}

def command = Map.fromJson(msg)
switch(command.method) {
case 'put':
cache.putAsync(command.key, command.value) << { f ->
send([result: f.get()].toJsonString())
}
break

case 'get':
if(command.key) {
cache.getAsync(command.key) << { f ->
sendJson([result: f.get()])
}
}
else {
cache.getAllAsync() << { f ->
sendJson([result: f.get().keySet()])
}
}
break

case 'delete':
cache.removeAsync(command.key) << { f ->
sendJson([result: f.get()])
}
break
}
}
}
]
]

Gretty is very young but already powerful project combining together brilliant Netty and capabilities of Groovy++. Maybe one day you will find it useful for your work.

Thank you for reading (hope it was interesting) and till next time.

Groovy (programming language) unit test Web Service Java (programming language)

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • The Path From APIs to Containers
  • REST vs. Messaging for Microservices
  • How to Submit a Post to DZone
  • HTTP vs Messaging for Microservices Communications

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: