Clojure Web Development Essentials
上QQ阅读APP看书,第一时间看更新

What is the Ring Server?

The first thing to know is that Ring and the Ring Server are not, I repeat, are not the same thing. Whereas Ring provides a suite of libraries which abstract the underlying implementation details, the Ring Server library provides the ability to start an actual web server to serve a Ring handler.

What is the Ring Server?

Whenever we use lein ring server from the command line, we start the Ring Server (there are other ways, but we'll get to those later in this chapter, and in Chapter 11, Environment Configuration and Deployment). At startup, the Ring Server will execute any registered application initialization hooks, and then start an embedded Jetty server, which serves our application handler. Incoming requests are then processed by Ring, as described in the previous section, until we shut down our app. On shutdown, Ring stops the embedded Jetty server and executes any registered shutdown hooks.

We can see this in the interaction between our hipstr.handler and hipstr.repl namespaces. Let's examine the hipstr.handler namespace, and then we'll see how the hipstr.repl namespace uses it.

hipstr.handler

The hipstr.handler namespace is the bootstrap to our application. In this, we define an initialization hook, a shutdown hook, a couple of application routes, and the application handler.

Initialization hooks

The hipstr.handler/init defines the initialization hook to be invoke immediately before starting the embedded Jetty server. Typically, we add any kind of application runtime configuration and invoke any other components required for the duration of the application. For example, our application configures various Timbre logging appenders and initiates the session manager.

(defn init
  "init will be called once when app is deployed as a servlet on an app server such as Tomcat
  put any initialization code here"
  []
  ;…snipped for brevity…
)

This initialization hook is configured for use either in the project configuration or through the REPL (discussed later).

Shutdown hooks

The hipstr.handler/destroy defines the shutdown hook to invoke immediately before exiting. The application shutdown hook is a function that performs anything our application needs to do before it permanently exits, such as stopping the session manager, or emitting a log statement stating the application is shutting down:

(defn destroy
  "destroy will be called when your application
  shuts down, put any clean up code here"
  []
  ;…snipped for brevity…)

The shutdown hook is configured for use either in the project configuration, or through the REPL (again, which we'll discuss shortly).

App routes

Routes are what tie a request to a specific Ring handler, that is, which URL should execute which chunk of code. The Compojure library provides us the tools to tie these two things together. We've already played with these earlier in the chapter, when we fooled around with the /About route to execute the foo-response handler.

The hipstr.handler/app-routes defines two special defroutes: route/resources and route/not-found.

(defroutes app-routes
  (route/resources "/")
  (route/not-found "Not Found"))

The (route/resources "/") route defines the URL from which our static resources will be served (found in the hipstr/resources/public directory). For example, the URL to our hipstr/resources/public/css/screen.css file would simply be /css/screen.css. If we changed our resources route to (route/resources "/artifacts/"), then the URL to the same screen.css file would be /artifacts/css/screen.css.

The (route/not-found "Not Found") defines what should be emitted for a 404 Not Found HTTP response. The luminus template simply defaults to Not Found (in practice, you'll likely want to have a handler that renders something a little more pretty). Ring will take the "Not Found" parameter and insert it into the <body> element of an HTML document on our behalf.

Tip

True story: Ring puts anything that isn't a Clojure map inside the <body> element, whereas a map will be treated as a Ring response.

The application handler

The real meat of the hipstr.handler namespace is the application handler. This is where we package together the various routes and middleware, define how we want our sessions to behave, any access rules to protected routes (for example, authenticated-only pages), and which formats should be serialized/deserialized to /from EDN in the requests/responses. Let's define the application handler:

(def app (app-handler
  ;; add your application routes here
  [home-routes app-routes]
  ;; add custom middleware here
  :middleware (load-middleware)
  ;; timeout sessions after 30 minutes
  :session-options {:timeout (* 60 30)
  :timeout-response (redirect "/")}
  ;; add access rules here
  :access-rules []
  ;; serialize/deserialize the following data formats
  ;; available formats:
  ;; :json :json-kw :yaml :yaml-kw :edn :yaml-in-html
  :formats [:json-kw :edn]))

The app handler packages our entire application, which will receive the request maps from Ring, and return response maps. As we build on our hipstr example, we will be modifying this function to include our own routes, access rules, and so on.

hipstr.repl

The hipstr.handler namespace defines what and how our application works, whereas the hipstr.repl namespace actually consumes it and makes it all run through the Clojure REPL. The hipstr.repl namespace is considerably more simple than the hipstr.handler namespace; it merely consists of an atom (to store a Jetty server instance returned from the Ring Server library), a start-server function, a stop-server function, and a get-handler function.

Start-server

The hipstr.repl/start-server function attempts to start the Ring Server on a given port, defaulting to port 3000. It also forwards the application handler (returned from get-handler) along with any runtime options, to the underlying Jetty server (a full list of which is defined at https://github.com/weavejester/ring-server#usage). Here is the code for starting the Ring Server:

;… Snipped for brevity
(defonce server (atom nil))
;…

(defn start-server
  "used for starting the server in development mode from REPL"
  [& [port]]
  (let [port (if port (Integer/parseInt port) 3000)]
    (reset! server
      (serve (get-handler)
        {:port port
        :init init                                ;#1
        :auto-reload? true
        :destroy destroy                          ;#2
        :join? false}))                           ;#3
        (println (str "You can view the site at http://localhost:" port))))

The :init and :destroy keys at #1 and #2 configure the initialization and shutdown hooks, respectively. The :join? option at #3 will determine if the thread will wait until the underlying Jetty instance stops. When set to false, the Ring Server will return the actual Jetty instance, which we keep a reference to in the server atom. When running the server through the REPL, it's best to keep this option set to false, thereby allowing us to stop the server without having to kill our REPL session.

Stop-server

The stop-server function simply stops the retained Jetty instance, and then destroys it.

(defn stop-server []
  (.stop @server)
  (reset! server nil))

The server is now magically stopped.

Get-handler

The get-handler function returns our hipstr.handler/app handler, exposes the static resources directory, and wraps the handler with one last bit of middleware, which adds a couple more headers to the response. The added middleware also returns a 304 Not Modified response if it detects the document being served hasn't been modified since the previous request:

(defn get-handler []
  ;; #'app expands to (var app) so that when we reload our code,
  ;; the server is forced to re-resolve the symbol in the var
  ;; rather than having its own copy. When the root binding
  ;; changes, the server picks it up without having to restart.
  (-> #'app
    ; Makes static assets in
    ; $PROJECT_DIR/resources/public/ available.
    (wrap-file "resources")
    ; Content-Type, Content-Length, and Last Modified headers
    ; for files in body
    (wrap-file-info)))