forked from http-kit/http-kit.github.com
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathserver.html
More file actions
239 lines (204 loc) · 9.88 KB
/
server.html
File metadata and controls
239 lines (204 loc) · 9.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
---
layout: doc
title: HTTP server
---
<h2>Ring adapter(HTTP server) with async and websocket extenstion</h2>
{% highlight clojure %}
(:use org.httpkit.server)
{% endhighlight %}
<p>The server uses an event-driven, non-blocking I/O model that makes it lightweight and scalable.
It's written to conform to the standard Clojure web server
<a href="https://github.com/ring-clojure/ring">Ring spec</a>, with asynchronous and websocket extenstion.
HTTP Kit is (<a href="migration.html">almost</a>) drop-in replacement of ring-jetty-adapter
</p>
<h3 id="options" class="anchor">Hello, Clojure HTTP server</h3>
<p> <code>run-server</code> starts a Ring-compatible HTTP server. You may want to do <a href="#routing">routing with compojure</a></p>
{% highlight clojure %}
(defn app [req]
{:status 200
:headers {"Content-Type" "text/html"}
:body "hello HTTP!"})
(run-server app {:port 8080})
{% endhighlight %}
<h4>Options:</h4>
<ul>
<li><code>:ip</code>: which IP to bind, default to <code>0.0.0.0</code></li>
<li><code>:port</code>: which port listens incomming request, default to 8090</li>
<li><code>:thread</code>: How many threads to compute response from request, default to 4</li>
<li><code>:worker-name-prefix</code>: woker thread name prefix, default to <code>worker-</code>: <code>worker-1</code> <code>worker-2</code>....</li>
<li><code>:queue-size</code>: max requests queued waiting for threadpool to compute response before reject, 503(Service Unavailable) is returned to client if queue is full, default to 20K</li>
<li><code>:max-body</code>: length limit for request body in bytes, 413(Request Entity Too Large) is returned if exceeds this limit, default to 8388608(8M)</li>
<li><code>:max-line</code>: length limit for HTTP inital line and per header,
414(Request-URI Too Long) will be returned if exceeds this limit, default to 4096(4K),
<a href="http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url">relevant discusstion on stackoverflow</a></li>
</ul>
<h3 class="anchor" id="channel">Unified Async/Websocket With Channel</h3>
<p class="red">The with-channel API is not compatible with the RC releases. The new is better and much easier to understand and use. The old's documentation is <a href="server_old.html">here</a></p>
<p>Unified asynchronous channel interface for HTTP (streaming or long-polling) and WebSocket.</p>
Channel defines the following contract:
<ul>
<li>
<code>open?</code>: Returns true iff channel is open.
</li>
<li>
<code>close</code>: Closes the channel. Idempotent: returns true if the channel was actually closed, or false if it was already closed.
</li>
<li>
<code>websocket?</code>: Returns true iff channel is a WebSocket.
</li>
<li>
<code>send!</code>:
Sends data to client and returns true if the data was successfully sent, or false if the channel is closed. Normally, check the returned value is not needed.
<div class="send-doc">
<p class="red">Data is sent directly to the client, NO RING MIDDLEWARE IS APPLIED.</p>
<p>Data form: {:headers _ :status _ :body _} or just body. Note that :headers
and :status will be stripped for WebSockets and for HTTP streaming responses
after the first.</p>
</div>
</li>
<li>
<code>on-receive</code>:
Sets handler (fn [message-string || byte[]) for notification of client WebSocket
messages. Message ordering is guaranteed by server.
</li>
<li>
<code>on-close</code>:
Sets handler (fn [status]) for notification of channel being closed by the
server or client. Handler will be invoked at most once. Useful for clean-up
</li>
</ul>
<!-- <p>A realtime chart example: <a href="https://github.com/http-kit/chat-websocket">https://github.com/http-kit/chat-websocket</a></p> -->
{% highlight clojure %}
(defn handler [req]
(with-channel req channel ; get the channel
;; communicate with client using method defined above
(on-close channel (fn [status]
(println "channel closed")))
(if (websocket? channel)
(println "WebSocket channel")
(println "HTTP channel"))
(on-receive channel (fn [data] ; data received from client
;; An optional param can pass to send!: close-after-send?
;; When unspecified, `close-after-send?` defaults to true for HTTP channels
;; and false for WebSocket. (send! channel data close-after-send?)
(send! channel data))))) ; data is sent directly to the client
(run-server chat-handler {:port 8080})
{% endhighlight %}
<h3 class="anchor" id="async">HTTP Streaming example</h3>
<ul>
<li>First call of <code>send!</code>,
send HTTP status and Headers to client</li>
<li>After the first, <code>send!</code> send a chunk to client</li>
<li> <code>close</code> send an empty chunk to client, mark as end of the response </li>
<li>Client close notification via <code>on-close</code></li>
</ul>
{% highlight clojure %}
(defn handler [request]
(with-channel request channel
(on-close channel (fn [status] (println "channel closed, " status)))
(loop [id 0]
(when (< id 10)
(schedule-task (* id 200) ;; sent a message every 200ms
(send! channel (str "message from server #" id) false)) ; false => don't close after send
(recur (inc id))))
(schedule-task 10000 (close channel)))) ;; close in 10s.
;;; open you browser http://127.0.0.1:9090, a new message show up every 200ms
(run-server handler {:port 9090})
{% endhighlight %}
<h3 id="polling" class="anchor">Long polling example</h3>
<p>long polling is very much like streaming</p>
{% highlight clojure %}
(defn handler [request]
(with-channel request channel
(on-some-event ;; wait for event
(send! channel {:status 200
:headers {"Content-Type" "application/json; charset=utf-8"}
:body "polling result"}
; send! and close. just be explicit, true is the default for streaming/polling,
true))))
(run-server handler {:port 9090})
{% endhighlight %}
<h3 id="websocket" class="anchor">Websocket example</h3>
<ul>
<li>Two way communication bettween client and server</li>
<li>Can Easy degradate to HTTP long polling/streaming, since the unified API</li>
<li>
<code>send!</code> with <code>java.lang.String</code>, a text frame assembled and sent to client
</li>
<li>
<code>send!</code> with <code>java.io.InputStream</code> or <code>byte[]</code>, a binary frame assembled and sent to client
</li>
<li>For WebSocket Secure connection, one option is <a href="https://github.com/bumptech/stud">stud</a> (self-signed certificate may not work with websocket).
<a href="http://nginx.com/news/nginx-websockets.html">Nginx</a> can do it too.</li>
</ul>
{% highlight clojure %}
(defn handler [request]
(with-channel request channel
(on-close channel (fn [status] (println "channel closed: " status)))
(on-receive channel (fn [data] ;; echo it back
(send! channel data)))))
(run-server handler {:port 9090})
{% endhighlight %}
<h3 id="routing" class="anchor">Routing with Compojure</h3>
<p>
<a href="https://github.com/weavejester/compojure">Compojure</a> Can be used to do the routing, based on uri and method
</p>
{% highlight clojure %}
(:use [compojure.route :only [files not-found]]
[compojure.handler :only [site]] ; form, query params decode; cookie; session, etc
[compojure.core :only [defroutes GET POST DELETE ANY context]]
org.httpkit.server)
(defn show-landing-page [req] ;; ordinary clojure function, accepts a request map, returns a response map
;; return landing page's html string. possible template library:
;; mustache (https://github.com/shenfeng/mustache.clj, https://github.com/fhd/clostache...)
;; enlive (https://github.com/cgrand/enlive)
;; hiccup(https://github.com/weavejester/hiccup)
)
(defn update-userinfo [req] ;; ordinary clojure function
(let [user-id (-> req :params :id) ; param from uri
password (-> req :params :password)] ; form param
....
))
(defroutes all-routes
(GET "/" [] show-landing-page)
(GET "/ws" [] chat-handler) ;; websocket
(GET "/async" [] async-handler) ;; asynchronous(long polling)
(context "/user/:id" []
(GET / [] get-user-by-id)
(POST / [] update-userinfo))
(route/files "/static/") ;; static file url prefix /static, in `public` folder
(route/not-found "<p>Page not found.</p>")) ;; all other, return 404
(run-server (site #'all-routes) {:port 8080})
{% endhighlight %}
<h3 class="anchor" id="deploy">Recommended server deployment</h3>
<p>http-kit runs alone happily, handy for development and quick deployment.
Use reverse proxy like <a href="http://wiki.nginx.org/Main">Nginx</a>,
<a href="http://www.lighttpd.net/">Lighthttpd</a>, etc in serious production is encouraged.
They also be used to<a href="migration.html#https">add https support</a>.</p>
<ul>
<li>They are fast, heavily optimized for static contents.</li>
<li>They can be configured to compress the content sent to browsers</li>
</ul>
<p>Sample Nginx configration:</p>
{% highlight sh %}
upstream http_backend {
server 127.0.0.1:8090; # http-kit listen on 8090
# put more server here for load balance
# keepalive(resue TCP connection) improves performance
keepalive 32; # both http-kit and nginx are good at concurrency
}
server {
location /static/ { # static contents
alias /var/www/xxxx/public/;
}
location / {
proxy_pass http://http_backend;
# tell http-kit to keep the connection
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
access_log /var/log/nginx/xxxx.access.log;
}
}
{% endhighlight %}