a simple netty HTTP server in clojure

Recently I’ve been toying with various clojure wrappers around java web servers. My goal is to write a small evented server that can queue up HTTP requests and then kick off some long-running processes.

So far I’ve tried aleph, compjure/ring/jetty and saturnine.

The compojure stack is by far the cleanest, but it’s geared more
towards synchronous request/response cycles. I’d like to use
something like to EventMachine and saturnine seems the closest to
that goal. However, saturnine‘s current HTTP implementation is
lacking.

Now before I could contribute to saturnine I first needed to understand netty. If you’re trying to learn netty, I recommend you first read the users guide and then jump straight to the API docs on ChannelPipeline.

Next, I needed to write a basic HTTP server using netty. This post on StackOverflow and this sample code on netty’s website helped me get a basic HTTP server up an running.

Below is a a nieve translation of the netty sample code into clojure. Note that this is not listed here as an example of sexy clojure code, but rather a starting point for someone looking to get dirty with the netty libraries in clojure.

    (ns xcombinator.netty.server
      (:gen-class)
      (:use clojure.contrib.import-static)
      (:import
         [java.net InetSocketAddress]
         [java.util.concurrent Executors]
         [org.jboss.netty.bootstrap ServerBootstrap]
         [org.jboss.netty.channel Channels ChannelPipelineFactory
           SimpleChannelHandler SimpleChannelUpstreamHandler]
         [org.jboss.netty.channel.socket.nio NioServerSocketChannelFactory]
         [org.jboss.netty.buffer ChannelBuffers]
         [org.jboss.netty.handler.codec.http HttpRequestDecoder 
           HttpResponseEncoder DefaultHttpResponse]
    ))

    (import-static org.jboss.netty.handler.codec.http.HttpVersion HTTP_1_1)
    (import-static org.jboss.netty.handler.codec.http.HttpResponseStatus OK)
    (import-static org.jboss.netty.handler.codec.http.HttpHeaders$Names CONTENT_TYPE)

    (declare make-handler)

    (defrecord Server [#^ServerBootstrap bootstrap channel])

    (defn start
      "Start a Netty server. Returns the pipeline."
      [port handler]
      (let [channel-factory (NioServerSocketChannelFactory.
                              (Executors/newCachedThreadPool)
                              (Executors/newCachedThreadPool))
            bootstrap (ServerBootstrap. channel-factory)
            pipeline (.getPipeline bootstrap)]
        (.addLast pipeline "decoder" (new HttpRequestDecoder))
        (.addLast pipeline "encoder" (new HttpResponseEncoder))
        (.addLast pipeline "handler" (make-handler))
        (.setOption bootstrap "child.tcpNoDelay", true)
        (.setOption bootstrap "child.keepAlive", true)
        (new Server bootstrap (.bind bootstrap (InetSocketAddress. port))))) 

    (defn stop-server
      {:doc "Stops a Server instance"
       :arglists '([server])}
      [{bootstrap :bootstrap channel :channel}]
      (do (.unbind channel)
          (.releaseExternalResources bootstrap)))

    (defn http-response
      [status]
      (doto (DefaultHttpResponse. HTTP_1_1 status)
        (.setHeader CONTENT_TYPE "text/plain; charset=UTF-8")
        (.setContent (ChannelBuffers/copiedBuffer 
                       (str "Success: " status) "UTF-8"))))

    (defn make-handler
      "Returns a Netty handler."
      []
      (proxy [SimpleChannelUpstreamHandler] []
        (messageReceived [ctx e]
          (let [c (.getChannel e)
                cb (.getMessage e)
                ]
            (println "HTTP request from" c)
            (.write c (http-response OK))
            (-> e .getChannel .close)))

        (exceptionCaught
          [ctx e]
          (let [throwable (.getCause e)]
            (println "@exceptionCaught" throwable))
          (-> e .getChannel .close))))

    (comment 
      (def *server* (start 3335 make-handler))
      (stop-server *server*)
    )

Here’s a project.clj that will load up the right dependencies:

    (defproject server "0.0.1"
      :description "Simple Netty HTTP server"
      :repositories [["JBoss" "http://repository.jboss.org/maven2"]]
      :dependencies
        [[org.clojure/clojure "1.2.0-beta1"]
        [org.clojure/clojure-contrib "1.2.0-beta1"]
        [org.jboss.netty/netty "3.2.0.BETA1"]
        [log4j/log4j           "1.2.14"]]
      :dev-dependencies [[autodoc              "0.7.0"]
                         [lein-clojars         "0.5.0-SNAPSHOT"]
                         [lein-run "1.0.0-SNAPSHOT"]
                         [swank-clojure "1.2.1"]]

      :namespaces [xcombinator.netty.server])

Then lein swank and evaluate the (def *server*... line in your
REPL. You’ll have a simple netty HTTP server running on port 3335.

Share:
  • del.icio.us
  • Reddit
  • Technorati
  • Twitter
  • Facebook
  • Google Bookmarks
  • HackerNews
  • PDF
  • RSS
This entry was posted in programming. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.