lehs is a hyper-text transfer protocol (http) web server implemented in clojure. Rather than being a full-feature, scalable web service, it is meant to provide a light-weight and easy to use API for embedding a web server (and, consequently, a web interface) into your application.
Starting lehs is very easy. You only need to call one function: start-server
; Start lehs, listening for connections on port 8080 (http) and 9999 (https)
(lehs.core/start-server 8080 9999)
Lehs will run indefinitely, handling user-agent requests and sending responses.
To stop the server, use the function stop-server
:
; stop the running lehs server
(stop-server)
You probably dont want to have your entire application hang on the web server, though, so start it as a future:
; Start lehs as a background task
(def lehs-result (future (lehs.core/start-server 8080 9999)))
; When it's finished, it will terminate with the symbol 'clean-exit:
(if (= 'clean-exit @lehs-result) (println "Lehs terminated correctly")
(println "An error occurred"))
Lehs stores all of the resources it handles in a referenced map structure. The resource itself, internally, is simply a function that returns a string, a byte array or a response map structure (see appendix). As arguments, the resource function must accept two maps: the first is the request map that the server received from the user agent, the second is the response (so far) that lehs has put together that will be sent back.
For example, to create a simple web page containing the text "Hello world!":
(defn hello-page [req res] "<html><body>Hello world!</html></body")
Then, to add the resource:
(lehs.resource/add-resource "/hello.htm" hello-page)
Lehs provides a macro that makes it really easy to generate a new resource: defresource
. The macro will generate your function header (deconstructing the request map for you) and automatically add the resource to lehs' resource map.
; Create and add our Hello world page, this time using defresource:
(defresource "/hello.htm" "<html><body>Hello world!</html></body>")
The request map is deconstructed into the following variables for your resource function to use:
Argument | Description |
---|---|
method | The request method (get, post, head etc...) |
path | The path to the resource from request line's URI |
query | The query part of the uri, which is actually a map of key-value pairs. The query ?a=1&b=2 is parsed into {:a 1, :b 2} |
fragment | The fragment part of the uri |
headers | Any headers that the user agent sent with the request. This is also a map, e.g. {:Accept "text/html"} |
message | The decoded message body. In the case of a form message body sent with a post request (application/x-www-form-urlencoded), it is decoded into a map. E.g. {:Name "Joe" :Occupation "Programmer" :Age "28"} |
So, if you receive a request for the URI "/foo/greet.html?name=Joe", your resource function can use the values in the query to tailor the response:
(defresource "/foo/greet.html"
(html5 [:html [:body [:h1 "Greetings!"] [:p (if (nil? (query :name)) "Hello!")
(str "Hello, " (query :name) "!")]]]))
Resulting in some HTML like the following:
<html>
<body>
<h1>Greetings!</h1>
<p>Hello, Joe!</p>
</body>
</html>
To create resources from the contents of a directory (recursively), use the function `defresource-dir. If the contents of the directory ./data are the files
- ./data/foo.htm
- ./data/img/foo.png
- ./data/css/foo.css
The result of the following would be to add a resource for each, as "/data/foo.htm", "/data/img/foo.png" and /data/css/foo.css, respectively. Each resource is created as a function the ignores the two arugments it is passed and returns a byte array of the file's contents.
; add contents of data directory to resources
(defresource-dir ".\\data\\")
You may also return a map structure. The defresource macro provides
your resource function with the variable res
. This variable
contains the preliminary response that lehs will send out.
Returning a map structure gives you fine-tuned control over the response that
lehs sends back to the user agent. Using the variable res
as a
base, you can associate into the map structure additional response
headers, the response code, etc. For example, the following snippet sets
the location to include the fragment 'foo' and sets the response code
to 201. It also returns in the message part a small web-page:
(defresource "/d"
(assoc-in-many res [[[:headers :Location] "/d#foo"]
[[:res-ln :code] 201]
[[:message] "<html><body><p id=\"foo\">Foo</p></body></html>"]]))
Keep in mind that you don't need to calculate the message length or do any encoding; lehs does that for you automatically (except for properly escaping any HTML text you dont want being rendered).
See appendix for full response map format and a description of the function assoc-in-many
.
{:req-ln {:method :get|:post|:head|:delete
:uri {:path "val",
:query {:k1 "val", :k2 "val"}
:fragment "val"}
:version "val"}
:headers {:k1 "val", :k2 "val"}
:messsage {:foo "bar", :boo "far"}}
{:res-ln {:version "string" :code int :reason-phrase "string"}
:headers {:k1 "val",
:k2 "val"}
:message byte-array | "string"}
assoc-in-many
Takes as arguments a map and a vector of map-paths to associate to new values. For example:
; define a simple map
(def m {:a 1 :b {:c 2}})
; set some paths using assoc-in-many
(assoc-in-many m [[[:b :d] 4] [[:a] 9]])
; result is {:a 9 :b {:c 2 :d 4}}