Chapter 2. Ring and the Ring Server

In the last chapter, we generated a new web application using the Luminus template. However, before we get too deep into the development of our app and playing with all the toys, it's important for us to get a high-level understanding of two technologies that will support everything we build and do, and that's Ring and the Ring Server.

Understanding Ring in Clojure

 

"Ring is a Clojure web applications library inspired by Python's WSGI and Ruby's Rack. By abstracting the details of HTTP into a simple, unified API, Ring allows web applications to be constructed of modular components that can be shared among a variety of applications, web servers, and web frameworks."

 
 --- James Reeves

James Reeves is also known as Weavejester; he is the creator and maintainer of Ring and about a billion other Clojure-based technologies (https://github.com/ring-clojure/ring/blob/master/README.md).

In simple terms, Ring handles all the nitty gritty HTTP implementation details, such as HTTP request/response, parameters, cookies, and so on. It abstracts the underlying implementations away from our code, allowing us to focus on writing our application instead of low-level HTTP crud. This abstraction, coupled with the fact that Ring is built on top of the HTTP Servlet specification, enables us to package our application and host it in a variety of servlet containers, such as GlassFish (https://glassfish.java.net), Tomcat (http://tomcat.apache.org), and Jetty (http://eclipse.org/jetty).

We can even run our application as a standalone, which is actually the easiest and most popular way of running a web application written in Clojure and using Ring. This is made possible thanks to the embedded Jetty server, which if we wanted could also be swapped out for http-kit (http://http-kit.org), a highly efficient HTTP client/server for Clojure.

At a high level, Ring is composed of 5 components: request maps, response maps, handlers, middleware, and adapters.

Request maps

Ring represents HTTP requests as simple Clojure maps, whose keys are drawn from the Java Servlet API and the official documentation RFC2616 – Hypertext Transfer Protocol - HTTP/1.1 (http://www.w3.org/Protocols/rfc2616/rfc2616.html). Practically speaking, the request map contains the following keys:

  • :server-port: This is the port on which the request is being handled.
  • :server-name: This is the resolved name or IP address of the server handling the request.
  • :remote-addr: This is the IP address of the client, which is making the request.
  • :uri: This is the part of the address after the domain name. For example, for the address http://ryans.io/some/beautiful/uri, the request map's :uri would be /some/beautiful/uri.
  • :query-string: This is the HTTP query string of the request, if one exists. For example, for the address http://ryans.io/some/beautiful/uri?color=blue&favPrime=7, the request map's :query-string would be color=blue&favPrime=7.
  • :scheme: This is the protocol used to make the request as a keyword; :http for HTTP request, and :https for Secure HTTP.
  • :request-method: This is the HTTP method used to make the request as a keyword, so it will be one of :get, :post, :put, :delete, :head, or :options keys.
  • :headers: This is a map of the header names (as lowercased string) to header values (also as string). Here's a sample code:
    {:headers {"content-type" "text/html"
      "content-length" "500"
      "pragma" "no-cache"}}
  • :body: This is a string of any contents in the request body itself (such as the contents of an HTTP POST request).

The request maps, however, are not restricted to this information, and often contain additional keys. Middleware, as we'll see later, can mutate the request map by adding additional keys.

Response maps

Similar to request maps, Ring represents an HTTP response as a simple Clojure map. The response map contains only three keys:

  • :status: This is the HTTP status code of the response as an integer, such as 200 or 403. A full list of HTTP status codes is made available as part of the RFC2616, and can be viewed at http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html. I urge you to take a read—it's surprisingly interesting!
  • :headers: Similar to the one of request map, :headers contains a map of header names (string) to header values.
  • :body: This is the body of the response. This can be one of the following four types, and the behavior will change for each:
    • When a String, the body is sent directly to the client as is
    • When an ISeq, each element of the sequence is sent to the client as a String
    • When a File, the contents of the file will be sent to the client
    • When an InputStream, the contents of the stream are sent to the client, after which the stream is closed

An example of a simple Hello World! response map can look like this:

{:status 200
  :headers {"Content-Type" "text/html"}
  :body "<html><body><h1>Hello, World!</h1></body></html>"}

Handlers

A handler is a Clojure function that accepts a request map and returns a response map, that is, process the incoming request and return a response. Here's an example code:

(defn hello-world
  "Simply produces a Hello World Response Map for some mythical,
    useless application."
  [request]
  {:status 200
    :headers {"Content-Type" "text/html"}
    :body "<html><body><h1>Hello, World!</h1></body></html>"})

Handlers are the core of our application. Typically our URLs will map one-to-one with a handler.

Let's create our own handler, and remap one of our routes to use it instead of the default Luminus generated one. Don't fret if this doesn't make complete sense to you right now—it will within a couple of chapters. Perform the following steps (assume that all paths are from the root of the Luminus-generated hipstr application):

  1. Open the src/hipstr/routes/home.clj file.
  2. Above the call to defroutes, add the following handler:
    (defn foo-response []
      {:status 200
        :headers {"Content-Type" "text/html"}
        :body "<html><body><h1>Hello World!</h1></body>
      </html>"})
  3. Modify the /about route (the last line in the file) to use the foo-response handler instead of the home-page handler:
    (GET "/about" [] (foo-response))
  4. Open your browser and navigate to http://localhost:3000/about. You'll now see a simple Hello World! instead of the default Luminus About page:
    Handlers

    Note

    Remember to keep your server running while reading this book, as we'll be doing boatloads of examples. Boatloads! You can start the development server by executing the following command from the root of your hipstr source directory:

    # lein ring server
    

Congrats! You just wrote your first handler! Pretty simple, right? Don't worry if this didn't make a whole lot of sense, we'll go into more detail in Chapter 4, URL Routing and Template Rendering.

Middleware

Middleware, as described in Chapter 1, Getting Started With Luminus, are functions that sit between the adapter and the handler. A middleware function accepts a handler and returns a new handler function. Middleware functions have complete access to the request map and/or response map. As such, middleware functions can perform some type of action on the map (such as adding a key, or logging the map to file) before passing it on to the handler.

As an example, let's write a middleware function that adds a completely meaningless yet excitable key to the request map, :go-bowling?, and then consume this key in the handler we just created:

  1. Open the src/hisptr/middleware.clj file.
  2. Right after the namespace declaration, add the following function, which takes a handler, and returns a new handler function (which in turn adds a new key to the request map and calls the next handler in the chain):
    (defn go-bowling? [handler]
      (fn [request]
        (let [request (assoc request :go-bowling? "YES! NOW!")]
          (handler request))))
  3. In the development-middleware definition, add our new go-bowling middleware:
    (def development-middleware
      [go-bowling?
        wrap-error-page
        wrap-exceptions])
  4. Back in the src/hipstr/routes/home.clj, adjust the :body value in our foo-response handler to emit the :go-bowling? key on the request map. Don't forget to adjust the handler's parameters to accept the request map:
    (defn foo-response [request]
      {:status 200
        :headers {"Content-Type" "text/html"}
        :body (str "<html><body><dt>Go bowling?</dt>""<dd>"(:go-bowling? request)"</dd></body></html>")})
  5. Lastly, change the /about route to make use of the request map:
    (GET "/about" request (foo-response request))
  6. Finally, refresh your page at http://localhost:3000/about and see our middleware in action!

You just became a middleware wizard! If you'd like more information about middleware and how it's natively used in Ring, check out https://github.com/ring-clojure/ring/wiki/Concepts#middleware.

Adapters

Adapters are the glue between the underlying HTTP and our handlers. The Ring library comes with a Jetty adapter ([ring/ring-jetty-adapter "1.3.0"]), which sits between a Jetty servlet container and the rest of the application stack. At a high level, an adapter will convert the incoming HTTP request into a request map, pass the map off to a handler, and then convert the returned response map into the appropriate servlet HTTP response to send back to the client.

Adapters

Knowing how to write adapters is beyond the scope of this book, and is something you do not need to know in order to build a web application using Clojure.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.95.245