Fast and Scalable Clojure Ring Web Applications with Comsat

DZone 's Guide to

Fast and Scalable Clojure Ring Web Applications with Comsat

· Java Zone ·
Free Resource

As many clojurists know already, Clojure’s minimalistic conciseness and lucidity can bring productivity and maintenance benefits to daily development tasks, but also aesthetic bliss and even joy to our jobs; thanks to Pulsar you can also enjoy fibers’ high performance and advanced concurrency abstractions on traditional Clojure applications. Comsat 0.3.0 has been recently released and it brings the power of fibers to Ring web development as well: let’s have a quick overview of what’s possible.

Ring, shortly

While dissecting the Servlet API we have already had a refreshing look at Ring from a modeling and API design perspective. Ring has long become the foundation of choice for most Clojure web applications and frameworks.

At the lowest level a Ring web application is simply a handler, that is a function transforming a map representing an HTTP request into a map representing an HTTP response.

Since the need to perform pre- and post-processing of requests is a very common one, Ring suggests using middlewares, that is higher-order functions transforming a handler into a new handler with additional logic. Ring provides some off-the-shelf middlewares already, for example to serve files and classpath resources or to enrich the request map with multi-part information. Of course middlewares can be chained in a specific order through ordinary functional composition, because what we get by applying a middleware to a handler is another handler, to which then several more middlewares can be applied.

Finally Ring provides a Jetty-based HTTP adapter for handlers. There are several other adapters besides Ring’s, but typically any of them wil be a function receiving a handler as its main input and an additional options map (e.g. for listening interfaces and ports, thread pool sizes etc.) that will start an endless HTTP serving loop.

The basics: dropping-in Comsat’s fiber-blocking adapter

The Comsat Ring fiber-blocking adapter is based on Jetty 9 and it relies on Servlet Async support (available since Servlet 3.0). For each request, it will spawn a new fiber and return immediately so that the expensive server threads are freed as soon as possible; the fiber will execute the handler and commit the response later on, when processing and response-building are complete.

Comsat Ring fibers are spawned via Pulsar, so the handler and all middlewares applied around it need to be suspendable: this can be done easily through either Pulsar’s sfn / defsfn macros or the suspendable! function. As a courtesy, the adapter will do it for you on the final handler it is being passed.

Let’s port the following simple “Hello World” Ring web application to the fiber-blocking adapter:

(ns myapp
  (:use ring.adapter.jetty))

(defn- hello-world [request]
  (Thread/sleep 100)
  {:status  200
   :headers {"Content-Type" "text/plain"}
   :body    "Hello World"})

(defn run [] (run-jetty hello-world {:port 8080}))`

The final Leiningen project file project.clj will be something like the following, where we’re including co.paralleluniverse/comsat-ring-jetty9 as a dependency instead of ring/ring-jetty-adapter and we’re configuring Quasar’s instrumentation agent to run:

(defproject myapp "0.1.0-SNAPSHOT"
            :description "Comsat Ring Hello World example."
            :min-lein-version "2.4.3"

            [[org.clojure/clojure "1.6.0"]

             [co.paralleluniverse/comsat-ring-jetty9 "0.3.0"]]

            :main myapp.core/run

            :java-agents [[co.paralleluniverse/quasar-core "0.6.2"]])

First of all, change your use/require clauses slightly in order to use the fiber-blocking adapter and declare the handler as suspendable; then, change your thread-blocking sleep into a fiber-blocking one, just to make sure that the handler is actually running inside a fiber:

(ns myapp.core
  (:use co.paralleluniverse.fiber.ring.jetty9)
  (:require [co.paralleluniverse.pulsar.core :refer [sfn defsfn suspendable!]])
  (:import (co.paralleluniverse.fibers Fiber)))

(defsfn hello-world [request]
  (Fiber/sleep 1000)
  {:status  200
   :headers {"Content-Type" "text/plain"}
   :body    "Hello World"})

(defn run [] (run-jetty hello-world {:port 8080}))

It is as simple as that: your existing application’s handler is now running inside efficient fibers rather than consuming expensive threads.lein run will serve our Hello World on port 8080.

Applying middlewares

Let’s step up slightly: what if we want to apply middlewares? It is very easy: we’re going to use the usual threading macro -> with the additional care of making suspendable the result of each middleware application, so that run-jetty will run the enriched handler rather than the basic one. Here’s an example:

(ns myapp.core
  (:use co.paralleluniverse.fiber.ring.jetty9
  (:require [co.paralleluniverse.pulsar.core :refer [sfn defsfn suspendable!]])
  (:import (co.paralleluniverse.fibers Fiber)))

(defn- fiber-sleep-middleware [h] #(do (Fiber/sleep 1000) ((suspendable! h) %)))

(defsfn hello-world [request]
  {:status  200
   :headers {"Content-Type" "text/plain"}
   :body "Hello World"})

(defn run [] (run-jetty
               (-> hello-world
                   (wrap-file "public")
               {:port 8080}))

There are actually a few more changes than strictly needed:

  • The Fiber/sleep call is now part of a middleware that will perform it before invoking the handler, so we can apply it around any of them. In addition it will make the handler suspendable if it’s not already.
  • run-jetty wraps the handler with our fiber-sleep-middleware, then with a Ring middleware that will try serving static files from a public directory in our project (and that will delegate to the inner handler if it can’t find them), then again with fiber-sleep-middleware.

Let’s now drop any file into our public directory, such as some testPage.html:

<!DOCTYPE html>
<html lang="en">
    <meta charset="utf-8"/>
    <h1>Static test page!</h1>

When we browse to http://localhost:8080 we get our nice fiber-blocking, dynamically-generated “Hello World” text after a couple of seconds, as all the middleware layers are traversed in this case; if we hit instead http://localhost:8080/testPage.html, then the file-serving middleware will take over and we’ll get our “Static test page!” title after one second, as the inner handlers won’t be called anymore.

Using Ring-preserving routing frameworks

Some micro-frameworks provide convenience facilities such as routing while still reusing entirely Ring concepts. For example Moustacheprovides just one macro, app, to be used as a routing pre-processor with existing Ring handlers.

This Leinigen template provides a starting point based on Comsat-Ring and Moustache on the server side, while the browser side usesClojureScriptcore.async and Om.

Let’s look at the main server-side module:

(ns ring-sample.core
    (:use co.paralleluniverse.fiber.ring.jetty9)
      [co.paralleluniverse.pulsar.core :refer [sfn defsfn suspendable!]]
      [ring.middleware.json :as midjson]
      [ring.middleware.resource :as midres]
      [net.cgrand.moustache :as moustache]
      [ring.util.response :as ringres])
    (:import (co.paralleluniverse.fibers Fiber)))

(def ^:private app-routes
    [] (sfn [_] (Fiber/sleep 100) (ringres/resource-response "index.html" {:root "public"}))
    ["widgets"] (sfn [_] (Fiber/sleep 100) (ringres/response [{:name "Widget 1"} {:name "Widget 2"}]))))

(def app
  (-> app-routes
      (midres/wrap-resource "/public")

(defn run [] (run-jetty app {:port 8080}))

The moustache/app macro builds a new Ring handler that will route te request to other functions based on its URL, in this case differentiating between the root and “widgets” paths. We simply define anonymous single-route handlers as suspendable and then ensure that the ones built by Moustache and enriched by middlewares are suspendable too.

Integrating other Clojure web frameworks

Other server-side Clojure frameworks often end up building Ring-compatible handlers but provide the developer with abstractions that are different from Ring’s, such as Compojure’s routes.

Using these frameworks in fiber-blocking mode may require some more work, as they can stack additional calls on top of the user-provided logic, all of which need to be made suspendable too. This can be as easy as adding a few suspendable! statements but in other cases some more tweaking is needed as these calls can be inaccessible (e.g. anonymous functions). Compojure itself is a very popular choice but at present it is not supported out-of-the-box.

Pulsar’s automatic suspendables in the works

We have just realized that explicitly declaring suspendables can be as easy as adding a few statements or it can a bit trickier, especially when dealing with third-party libraries.

Luckily automatic instrumentation is in the works as part of Pulsar: this basically means that the only needed change will be replacing the call to your previous Ring adapter with a call to Comsat’s and your web application will then fly on high-performance fibers rather than running on threads. It will no longer be necessary to declare your functions (or protocols) as suspendable – any Clojure code will work seemlessly with fibers. If this sounds intriguing, stay tuned.

Enjoying both bliss and top-notch performance

To me, Parallel Universe’s stack together with Clojure is a developer’s dream coming true. Beauty and expression power finally don’t fight anymore with performance and scalability; on the contrary Quasar, Pulsar and Comsat bring unprecedented efficiency and best-of-breed scalability abstractions to the JVM at large and specifically to Clojure.

I hope you enjoyed having a quick taste of what’s possible; soon we’ll be publishing deeper explorations and more tutorials. In the meanwhile enjoy a highly maintainable, efficient, scalable and, last but not least, joyful Clojure coding experience with the Parallel Universe stack.

clojure, java

Published at DZone with permission of Fabio Tudone , DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}