Survey Results

A few weeks ago, we put out a short survey to gather information about how the community is actually using Immutant. We'd like to share our take on the results.

But first things first: THANK YOU! Time is currency in open-source, and we appreciate the 62 of you who spent a little of it feeding back your Immutant experience to us.

Given the small sample size and the non-rigorous approach we took to developing the questions, we can't really draw any statistically significant conclusions, but we can possibly get a general feel of the community.

Noteworthy items include:

  • It appears we need to do a bit better job of communicating the improvements of 2.x over 1.x. Of the minority of folks who evaluated Immutant and decided to use something else, most of them either hadn't actually used it or had only used 1.x. So we mayhap have some opportunity there.

  • There is a good bit of interest in clustering and a fairly high number of deployments to internal data centers. Those two are likely related. Immutant 2 is far more "cloud friendly" than its predecessor since it contains no app server, only libraries, so we may see more publicly-hosted deployments in the future.

  • Almost as many 2.x deployments in production as 1.x, even though 2.x is still in beta. We're going to chalk this up to the fact that 1) 2.x has been pretty stable over the course of its VERY long beta cycle, and b) 2.x is simpler than 1.x.

  • Of the folks that have used 2.x, almost as many have used it in WildFly as have embedded, which surprised us. We expected a larger percentage to be embedded users. It may be that some of the WildFly users came from 1.x, and appreciate the value of the container and clustering.

Take a gander at the the full results of the survey if you are so inclined.

Final Finally

2.0.0.Final is imminent! We're pretty excited about that! :)

As always, if you have any questions, comments, or issues with regards to Immutant, let us know.

Survey, etc

Just a few quick notes on recent Immutant happenings...

Survey

Yesterday we published a short survey to help us gauge how folks have been using Immutant. Please take a few moments to complete it if you haven't already.

Luminus

The Luminus web toolkit now includes an Immutant profile in its Leiningen project template, so you can now do this:

$ lein new luminus yourapp +immutant
$ cd yourapp
$ lein run -dev

That -dev option is triggering the use of immutant.web/run-dmc instead of immutant.web/run so it should plop you in your browser with code-reloading enabled. You can pass most of the other run options on the command line as well, e.g.

$ lein run port 3000

Beta2 bugs

In our last release, 2.0.0-beta2, we updated our dependency on the excellent potemkin library to version 0.3.11. Unfortunately, that exposed a bug whenever clojure.core/find was used on our Ring request map. Fortunately, it was already fixed in potemkin's HEAD, and Zach was kind enough to release 0.3.12. We've bumped up to that in our incrementals and hence our next release.

We've also fixed a thing or two to improve async support when running inside WildFly.

Plans

We're still hoping to release 2.0.0-Final within a month or so. Now would be a great time to kick the tires on beta2 or the latest incremental to ensure it's solid when we do!

Untangling the Web in The Deuce

Our org.immutant/web library has changed quite a bit in Immutant 2, both its API and its foundation: the Undertow web server. Among other things, this gives us much better performance (~35% more throughput than v1.1.1) and built-in support for websockets.

We've given a lot of thought to the API, specifically argument names, types, order, and return values. We're reasonably happy with what we have at this point, but still very much open to suggestions for improvements.

The Web API

The API for immutant.web is small, just two functions and a convenient macro:

  • run - runs your handler in a specific environment, responding to web requests matching a given host, port, and path. The handler may be either a Ring function, Servlet instance, or Undertow HttpHandler.
  • run-dmc - runs your handler in Development Mode (the 'C' is silent)
  • stop - stops your handler[s]

The following code fragments were tested against 2.x.incremental.96. You should read through the getting started post and require the immutant.web namespace at a REPL to follow along:

(require '[immutant.web :refer :all])

Common Usage

First, we'll need a Ring handler. Yours is probably fancier, but this one will do:

(defn app [request]
  {:status 200
   :body "Hello world!"})

To make the app available at http://localhost:8080/, do this:

(run app)

Which, if we make the default values explicit, is equivalent to this:

(run app {:host "localhost" :port 8080 :path "/"})

Or, since run takes options as either an explicit map or keyword arguments (kwargs), this:

(run app :host "localhost" :port 8080 :path "/")

The options passed to run determine the URL used to invoke your handler: http://{host}:{port}{path}

To replace your app handler with another, just call run again with the same options, and it'll replace the old handler with the new:

(run (fn [_] {:status 200 :body "hi!"}))

To stop the handler, do this:

(stop)

Which is equivalent to this:

(stop {:host "localhost" :port 8080 :path "/"})

Or, if you prefer kwargs, this:

(stop :host "localhost" :port 8080 :path "/")

Alternatively, you can save run's return value and pass it to stop to stop your handler.

(def server (run app {:port 4242 :path "/hello"}))
...
(stop server)

That's pretty much all there is to it.

You don't even really need to stop your handlers if you're content to just let the JVM exit, but it can be handy at a REPL.

Advanced Usage

The run function returns a map that includes the options passed to it, so you can thread run calls together, useful when your application runs multiple handlers. For example,

(def everything (-> (run hello)
                  (assoc :path "/howdy")
                  (->> (run howdy))
                  (merge {:path "/" :port 8081})
                  (->> (run ola))))

The above actually creates two Undertow web server instances: one serving requests for the hello and howdy handlers on port 8080, and one serving ola responses on port 8081.

You can stop all three apps (and shutdown the two web servers) like so:

(stop everything)

Alternatively, you could stop only the ola app like so:

(stop {:path "/" :port 8081})

You could even omit :path since "/" is the default. And because ola was the only app running on the web server listening on port 8081, it will be shutdown automatically.

Handler Types

Though the handlers you run will typically be Ring functions, you can also pass any valid implementation of javax.servlet.Servlet or io.undertow.server.HttpHandler. For an example of the former, here's a very simple Pedestal service running on Immutant:

(ns testing.hello.service
  (:require [io.pedestal.service.http :as http]
            [io.pedestal.service.http.route.definition :refer [defroutes]]
            [ring.util.response :refer [response]]
            [immutant.web :refer [run]]))

(defn home-page [request] (response "Hello World!"))
(defroutes routes [[["/" {:get home-page}]]])
(def service {::http/routes routes})

(defn start [options]
  (run (::http/servlet (http/create-servlet service)) options))

Development Mode

The run-dmc macro resulted from a desire to provide a no-fuss way to enjoy all the benefits of REPL-based development. Before calling run, run-dmc will first ensure that your Ring handler is var-quoted and wrapped in the reload and stacktrace middleware from the ring-devel library (which must be included among your [:profiles :dev :dependencies] in project.clj). It'll then open your app in a browser.

Both run and run-dmc accept the same options. You can even mix them within a single threaded call.

The Websocket API

Also included in the org.immutant/web library is the immutant.websocket namespace, which includes a Channel protocol and the create-handler function. It accepts a map of callback functions, invoked asynchronously during the lifecycle of a websocket. The valid websocket event keywords and their corresponding callback signatures are as follows:

  :on-message (fn [channel message])
  :on-open    (fn [channel])
  :on-close   (fn [channel {:keys [code reason]}])
  :on-error   (fn [channel throwable])
  :fallback   (fn [request] (response ...))

To create your websocket endpoint, pass the result from create-handler to immutant.web/run. Here's an example that asynchronously returns the upper-cased equivalent of whatever message it receives:

(ns whatever
  (:require [immutant.web :as web]
            [immutant.websocket :as ws]
            [clojure.string :refer [upper-case]]))

(defn create-websocket []
  (web/run
    (ws/create-handler {:on-message (fn [c m] (ws/send! c (upper-case m)))})
    {:path "/websocket"}))

Another function, immutant.websocket/create-servlet, can be used to create a JSR 356 Endpoint. The channel passed to the callbacks is an instance of javax.websocket.Session, extended to the immutant.websocket.Channel protocol.

Try it out!

We'd love to hear some feedback on this stuff. Find us on our community page and join the fun!