1
votes

I see that when a web browser connects to a HTTP server the following lines are sent:

GET / HTTP/1.1
Content-Length: 13

Hello, world!

I want to write a program that takes an InputStream, reads these lines and returns a Ring request map according to the ring specification.

Could you please suggest a Clojure library for this purpose? I took a quick look at web server source codes with ring support (for example http-kit) but with no success so far.

3
This is a very odd HTTP request, by the way. A GET with a body is apparently not illegal but servers are expected to ignore the body. - amalloy

3 Answers

3
votes

If you are looking for a library to transform textual HTTP input into a Ring request map, search no further than Ring, the premiere library for both dealing with HTTP and producing Ring request maps.

2
votes

This surely can be achieved with http-kit library. But there are 2 problems:

  1. The class org.httpkit.server.ClojureRing is private. You have to access its method via reflection.
  2. :remote-addr value is read from InetSocketAddress object assigned to request, or you need to to have X-Forwarded-For header in your request string.

Here is working demo:

(let [request "POST / HTTP/1.1\nContent-Length: 13\nX-Forwarded-For: 127.0.0.1\n\nHello, world!"
      bytes (java.nio.ByteBuffer/wrap (.getBytes request))
      decoder (org.httpkit.server.HttpDecoder. 
                8388608 4096 ProxyProtocolOption/DISABLED)
      method (.getMethod org.httpkit.server.ClojureRing 
                         "buildRequestMap"
                         (into-array [org.httpkit.server.HttpRequest]))]
  (.setAccessible method true)
  (.invoke method nil (object-array [(.decode decoder bytes)]))) 

=>
{:remote-addr "127.0.0.1",
 :headers {"content-length" "13", "x-forwarded-for" "127.0.0.1"},
 :async-channel nil,
 :server-port 80,
 :content-length 13,
 :websocket? false,
 :content-type nil,
 :character-encoding "utf8",
 :uri "/",
 :server-name nil,
 :query-string nil,
 :body #object[org.httpkit.BytesInputStream 0x4f078b2 "BytesInputStream[len=13]"],
 :scheme :http,
 :request-method :post}
-1
votes

You may wish to review the Clojure Cookbook, online here, or even better in print form.

Web development with Clojure is also a good book.

I also have a "Hello World" example using the Pedestal library here.


Here is a sample request map using Pedestal:

request => 
{:protocol "HTTP/1.1",
 :async-supported? true,
 :remote-addr "127.0.0.1",
 :servlet-response
 #object[io.pedestal.test$test_servlet_response$reify__34946 0x3e71aa38 "io.pedestal.test$test_servlet_response$reify__34946@3e71aa38"],
 :servlet
 #object[io.pedestal.http.servlet.FnServlet 0x7168112e "io.pedestal.http.servlet.FnServlet@7168112e"],
 :headers {"content-length" "0", "content-type" ""},
 :server-port -1,
 :servlet-request
 #object[io.pedestal.test$test_servlet_request$reify__34934 0x3422eca "io.pedestal.test$test_servlet_request$reify__34934@3422eca"],
 :content-length 0,
 :content-type "",
 :path-info "/echo/abcdef/12345",
 :character-encoding "UTF-8",
 :url-for #<Delay@5190186c: :not-delivered>,
 :uri "/echo/abcdef/12345",
 :server-name nil,
 :query-string nil,
 :path-params {:list-id "abcdef", :item-id "12345"},
 :body
 #object[io.pedestal.test.proxy$javax.servlet.ServletInputStream$ff19274a 0x2aff7cc4 "io.pedestal.test.proxy$javax.servlet.ServletInputStream$ff19274a@2aff7cc4"],
 :scheme nil,
 :request-method :get,
 :context-path ""}

Update - There is almost no parsing required. You can generate a ring response with a very simple map:

(defn respond-hello [request]  ; we ignore the request 
  {:status 200 :body "Hello, world!"})

Not sure what you are really after here.....? If you really want to do parsing from the ground up, the best option is Instaparse.