The Clojure/Ring JVM request handler
Thanks to the JVM in the uWSGI server (updated to 1.9) plugin available from 1.9, Clojure web apps can be run on uWSGI.
The supported gateway standard is Ring, https://github.com/ring-clojure/ring . Its full specification is available here: https://github.com/ring-clojure/ring/blob/master/SPEC
A uWSGI build profile named “ring” is available for generating a monolithic build with both the JVM and Ring plugins.
From the uWSGI sources:
- UWSGI_PROFILE=ring make
The build system will try to detect your JDK installation based on various presets (for example on CentOS you can yum installjava-1.6.0-openjdk.x86_64-devel
or java-1.7.0-openjdk-devel.x86_64
or on Debian/Ubuntu openjdk-6-jdk
and so on…).
OSX/Xcode default paths are searched too.
After a successful build you will have the uwsgi binary and a uwsgi.jar file that you should copy in your CLASSPATH (or just rememberto set it in the uwsgi configuration every time).
See also
For more information on the JVM plugin check JVM in the uWSGI server (updated to 1.9)
Our first Ring app
A basic Clojure/Ring app could be the following (save it as myapp.clj):
- (ns myapp)
- (defn handler [req]
- {:status 200
- :headers { "Content-Type" "text/plain" , "Server" "uWSGI" }
- :body (str "<h1>The requested uri is " (get req :uri) "</h1>")
- }
- )
The code defines a new namespace called ‘myapp’, in which the ‘handler’ function is the Ring entry point (the function called at each web request)
We can now build a configuration serving that app on the HTTP router on port 9090 (call it config.ini):
- [uwsgi]
- http = :9090
- http-modifier1 = 8
- http-modifier2 = 1
- jvm-classpath = plugins/jvm/uwsgi.jar
- jvm-classpath = ../.lein/self-installs/leiningen-2.0.0-standalone.jar
- clojure-load = myapp.clj
- ring-app = myapp:handler
Run uWSGI:
- ./uwsgi config.ini
Now connect to port 9090 and you should see the app response.
As you can note we have manually added uwsgi.jar and the Leiningen standalone jar (it includes the whole Clojure distribution) to our classpath.
Obviously if you do not want to use Leiningen, just add the Clojure jar to your classpath.
The clojure-load
option loads a Clojure script in the JVM (very similar to what jvm-class
do with the basic jvm plugin).
The ring-app
option specify the class/namespace in which to search for the ring function entry point.
In our case the function is in the ‘myapp’ namespace and it is called ‘handler’ (you can understand that the syntax is namespace:function)
Pay attention to the modifier configuration. The JVM plugin registers itself as 8, while Ring registers itself as modifier 2 #1, yielding an effective configuration of “modifier1 8, modifier2 1”.
Using Leiningen
Leiningen is a great tool for managing Clojure projects. If you use Clojure, you are very probably a Leiningen user.
One of the great advantages of Leiningen is the easy generation of a single JAR distribution. That means you can deploy a whole appwith a single file.
Let’s create a new “helloworld” Ring application with the lein
command.
- lein new helloworld
Move it to the just created ‘helloworld’ directory and edit the project.clj file
- (defproject helloworld "0.1.0-SNAPSHOT"
- :description "FIXME: write description"
- :url "http://example.com/FIXME"
- :license {:name "Eclipse Public License"
- :url "http://www.eclipse.org/legal/epl-v10.html"}
- :dependencies [[org.Clojure/Clojure "1.4.0"]])
We want to add the ring-core
package to our dependencies (it contains a set of classes/modules to simplify the writing of ring apps) and obviously we need to change the description and URL:
- (defproject helloworld "0.1.0-SNAPSHOT"
- :description "My second uWSGI ring app"
- :url "https://uwsgi-docs.readthedocs.io/en/latest/Ring.html"
- :license {:name "Eclipse Public License"
- :url "http://www.eclipse.org/legal/epl-v10.html"}
- :dependencies [[org.Clojure/Clojure "1.4.0"] [ring/ring-core "1.2.0-beta1"]])
Now save it and run…
- lein repl
This will install all of the jars we need and move us to the Clojure console (just exit from it for now).
Now we want to write our Ring app, just edit the file src/helloworld/core.clj and place the following content in it:
- (ns helloworld.core
- (:use ring.util.response))
- (defn handler [request]
- (-> (response "Hello World")
- (content-type "text/plain")))
Then re-edit project.clj to instruct Leiningen on which namespaces to build:
- (defproject helloworld "0.1.0-SNAPSHOT"
- :description "FIXME: write description"
- :url "http://example.com/FIXME"
- :license {:name "Eclipse Public License"
- :url "http://www.eclipse.org/legal/epl-v10.html"}
- :aot [helloworld.core]
- :dependencies [[org.Clojure/Clojure "1.4.0"] [ring/ring-core "1.2.0-beta1"]])
As you can see we have added helloworld.core in the :aot
keyword.
Now let’s compile our code:
- lein compile
And build the full jar (the uberjar):
- lein uberjar
If all goes well you should see a message like this at the end of the procedure:
- Created /home/unbit/helloworld/target/helloworld-0.1.0-SNAPSHOT-standalone.jar
Take a note of the path so we can configure uWSGI to run our application.
- [uwsgi]
- http = :9090
- http-modifier1 = 8
- http-modifier2 = 1
- jvm-classpath = plugins/jvm/uwsgi.jar
- jvm-classpath = /home/unbit/helloworld/target/helloworld-0.1.0-SNAPSHOT-standalone.jar
- jvm-class = helloworld/core__init
- ring-app = helloworld.core:handler
This time we do not load Clojure code, but directly a JVM class.
Pay attention: when you specify a JVM class you have to use the ‘/’ form, not the usual dotted form.
The __init suffix is automatically added by the Clojure system when your app is compiled.
The ring-app
set the entry point to the helloworld.core namespace and the function ‘handler’.
We can access that namespace as we have loaded it with jvm-class
Concurrency
As all of the JVM plugin request handlers, multi-threading is the best way to achieve concurrency.
Threads in the JVM are really solid, do not be afraid to use them (even if you can spawn multiple processes too)
- [uwsgi]
- http = :9090
- http-modifier1 = 8
- http-modifier2 = 1
- jvm-classpath = plugins/jvm/uwsgi.jar
- jvm-classpath = /home/unbit/helloworld/target/helloworld-0.1.0-SNAPSHOT-standalone.jar
- jvm-class = helloworld/core__init
- ring-app = helloworld.core:handler
- master = true
- processes = 4
- threads = 8
This setup will spawn 4 uWSGI processes (workers) with 8 threads each (for a total of 32 threads).
Accessing the uWSGI api
Clojure can call native Java classes too, so it is able to access the uWSGI API exposed by the JVM plugin.
The following example shows how to call a function (written in python) via Clojure:
- (ns myapp
- (import uwsgi)
- )
- (defn handler [req]
- {:status 200
- :headers { "Content-Type" "text/html" , "Server" "uWSGI" }
- :body (str "<h1>The requested uri is " (get req :uri) "</h1>" "<h2>reverse is " (uwsgi/rpc (into-array ["" "reverse" (get req :uri)])) "</h2>" )
- }
- )
The “reverse” function has been registered from a Python module:
- from uwsgidecorators import *
- @rpc('reverse')
- def contrario(arg):
- return arg[::-1]
This is the used configuration:
- [uwsgi]
- http = :9090
- http-modifier1 = 8
- http-modifier2 = 1
- jvm-classpath = plugins/jvm/uwsgi.jar
- jvm-classpath = /usr/share/java/Clojure-1.4.jar
- Clojure-load = myapp.clj
- plugin = python
- import = pyrpc.py
- ring-app = myapp:handler
- master = true
Another useful feature is accessing the uwsgi cache. Remember that cache keys are string while values are bytes.
The uWSGI Ring implementation supports byte array in addition to string for the response. This is obviously a violation of the standardbut avoids you to re-encode bytes every time (but obviously you are free to do it if you like).
Notes and status
- A shortcut option allowing to load compiled code and specifying the ring app would be cool.
- As with the The JWSGI interface handler, all of the uWSGI performance features are automatically used (like when sending static files or buffering input)
- The plugin has been developed with the cooperation and ideas of Mingli Yuan. Thanks!