I'm trying to write an HTTP proxy that proxies to the proper upstream host based on the Host header.
Right now I have hardcoded upstream host to localhost:3000.
What I want to achieve is to pass the response body channel from the http-request
as a response body of the current request. I have the following code:
(ns clj-frontal.core
(:gen-class))
(use 'lamina.core 'aleph.http 'aleph.formats)
(defn get-upstream-url [request]
(let [host "localhost:3000"]
(str "http://" host (request :uri) (request :query-string))))
(defn hello-world [ch request]
(let [method (request :request-method)
headers (request :headers)
body (request :body)
url (get-upstream-url request)]
(on-realized (http-request {:method method, :url url, :headers headers :body body})
(fn [response]
(let [status (response :status)
headers (response :headers)
body (response :body)]
(prn body)
(enqueue ch {:status status :headers headers :body body})
))
#(prn %))))
(defn -main [& args]
(start-http-server hello-world {:port 8000}))
When I issue a request to localhost:8000 it prints "<== [ … ]" so I see the "body" is a channel.
And sends the headers correctly:
HTTP/1.1 200 OK
Cache-Control: max-age=0, private, must-revalidate
Connection: close
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Wed, 08 May 2013 08:38:37 GMT
Server: aleph/0.3.0
Server: thin 1.5.0 codename Knife
Transfer-Encoding: chunked
...
But it doesn't send the body - curl waits for it and times out.
In order to debug it I wanted to print body channel by adding following line just before enqueue:
(doseq [s (channel->lazy-seq body)] prn s)
But it hangs on it without printing anything. Apparently channel->lazy-seq returns a blocking sequence, but I don't know why it doesn't print anything.
So I used receive-all to print the body channel contents in an async way:
(receive-all body-channel #(prn %))
I got a series of following:
#<BigEndianHeapChannelBuffer BigEndianHeapChannelBuffer(ridx=0, widx=406, cap=406)>
#<BigEndianHeapChannelBuffer BigEndianHeapChannelBuffer(ridx=0, widx=2048, cap=2048)>
#<BigEndianHeapChannelBuffer BigEndianHeapChannelBuffer(ridx=0, widx=2048, cap=2048)>
#<BigEndianHeapChannelBuffer BigEndianHeapChannelBuffer(ridx=0, widx=3072, cap=3072)>
#<BigEndianHeapChannelBuffer BigEndianHeapChannelBuffer(ridx=0, widx=3067, cap=3067)>
So there's some response coming in. I used bytes->string function to map the channel like this:
(let [body-channel (map* bytes->string body)]
(receive-all body-channel #(prn %)))
It nicely prints incoming response to the terminal but it still blocks when passed to the response as a body.
Also, I found out that when I create a new channel, enqueue some string on it, close it and pass it as the response body it works fine:
(let [resp-body (channel)]
(enqueue resp-body "foo")
(enqueue resp-body "bar")
(close resp-body)
(enqueue ch {:status status :headers headers :body resp-body}))
If I don't close the above channel it hangs. So the key thing here is the channel needs to be closed before passing it as the response and I'm not sure why.
In my case (and in general case of streaming inifnite amount of data) closing the channel doesn't make sense.
Am I missing something obvious here? Thanks!