A programmer's site
点点滴滴:生活,技术
点点滴滴:生活,技术
This is the second post of introducing http-kit, the first one A new Clojure HTTP client, concurrent made easy by asynchronous and promise
Ring is defined in terms of handlers, middleware, adapters, requests maps, and response maps
(defn handler [request-req] ;; accept a reuqest-map, return a response-map
response-map)
;; request-map
{:uri "/index"
:query-string "a=b"
:request-method :get
:headers {"user-agent" "... Chrome/23.0.1271.40 Safari/537.11"
"cookie" "...."
"accept-charset" "UTF-8,*;q=0.5"}
...}
;; response-map
{:status 200
:headers {"Content-Type" "text/plain"}
:body "Hello world from ring"
:session ...}
;; adapter
(run-server handler {..options..}}
Request passed in, response expected. There is no support of async(long polling) in ring’s Spec. Most of the time, You don’t need async. When async is needed, like server need to push data to client in realtime, http-kit offers an option:
async-response
http-kit is a HTTP server/client written from scrach for Clojure. The server is a standard ring adapter with async and websocket support, a drop in replacement of ring-jetty-adapter.
[http-kit "2.0-rc1"] ;; add to project.clj
Only a single interface needed to async support: async-response
(defn handler [req-map]
(async-response respond
(future (respond {:status 200
:headers {"Content-Type" "text/plain"}
:body "async hello world"}))))
(run-server handler {:port 8000}}
async-response
is macro, it gives you respond
respond
is a function, cann be saved, used to send response to client, on any thread, at any time.Credit for this flexible API goes to Peter Taoussanis.
Code snippet:
(def clients (atom {}))
(async-response respond
;; save it
(swap! clients assoc respond (now-seconds)))
(on-fancy-event (fn [e]
(doseq [client (keys @clients)]
(client {:status 200
:body (str "Fancy event!" e)})
(swap! clients dissoc client))))
request
received by http-kit, goes though all request-level middlewares, get modified
async-response
return a standard ring repsonse, except the body is IListenableFuture. http-kit understand the body, holds on the request.respond
to return the actual responserespond
send to actual response to client (response-level middlewares are not applied), client’s request returnsKeep a connection costs nothing but a few kilobytes of RAM. http-kit runs happily with ~128M heap while ~10K high traffic concurrent connections.
# http-kit runs happily with 128M heap (-Xms128m -Xmx128m), ab confirms that
ab -n 500000 -c 10000 -k http://127.0.0.1:8080/
http-kit’s website: http-kit.org
Source code is hosted on github: github.com/http-kit/http-kit
To suggest a feature, report a bug, or general discussion: github.com/http-kit/http-kit/issues
前端同学的开发流程:
如果有这样一个工具:监控代码目录,发现代码修改后,自动刷新浏览器,流程可减少一步:
减少一小步,效率提高一大步。因为在大多数情况下,另外两步也可以省掉,就变成了更改代码后,省掉窗口切换,用眼睛扫一下就可以。眼睛的速度很快的哦。专注于代码和思考
俺做前端开发好几年了,切换窗口了好几年。
最近在做 [美味书签] (http://meiweisq.com),采用了一个支持HTTP长连(服务器可以实时push消息给客户端)的服务器, 试着加入一些代码,监控代码变化,发现变化,立刻刷新浏览器。用了后,还是比较实用。设计师使用后,也觉得不错。
元旦回家后,各方面原因,加上有些空闲时间,就用google发布的一个比较新的语言go,实现了一个小工具http-watcher,也当做学习这们语言。
http-watcher = HTTP文件服务器 + 文件监控器
cd {code-directory} && http-watcher -port=8000
# 详细帮助 http-watcher -h
# 打开浏览器,访问http://127.0.0.1:8000。同事们也可通过你的IP,实时查看
http-watcher = HTTP反向代理 + 文件监控器
http-watcher -port=8000 -root={代码目录} -proxy={动态程序端口}
# 详细帮助 http-watcher -h
# 打开浏览器,访问http://127.0.0.1:8000。同事们也可通过你的IP,实时查看
代码托管于github上面 http-watcher
可checkout 代码,自己编译 go build
,也可以下载
http-kit is a clean and compact event driven Clojure HTTP toolkit, writen from scratch for high performance Clojure applications. It consists 1. Ring compatible adapter with async and websocket support 2. Fast, asynchronous HTTP client, API modeled after clj-http
In 2.0, by leverageing promise
, deref
mechanism, and callback
, we redesigned the client’s API. Now, the asynchronous API is fast, flexible, and very easy to use. Thanks Peter Taoussanis and Max Penet for their great contributions.
After months’s tweaking and testing, it has finally reached the 2.0 release candidate phrase.
Compare with the very good Clojure HTTP client clj-http, a few highlights of http-kit:
The API is learn from clj-http: request
, get
, post
, put
, head
, delete
[http-kit "2.0-rc1"]
(:require [org.httpkit.client :as http])
; http/post, http/head, http/put, http/delete
; issue http request asynchronous. return a promise, deref to get the response
(http/get url & [options callback])
;; suppoted options
{:timeout 300 ;; read or connect timeout
:headers {"x-header" "value"}
:query-params {:ts (System/currentTimeMillis)}
:user-agent "user-agent-str"
:basic-auth ["user" "pass"]
:form-params {:name "http-kit"
:lang "Clojure"
:keyword ["async" "concurrent" "fast"]}}
;; get synchronously
(let [{:keys [error status headers body]} @(http/get "http://example.com")]
;; handle result or error
)
;; get concurrently, deal one by one
(let [r1 (http/get "http://example.com?page=1")
r2 (http/get "http://example.com" {:query-params {:page 2}})]
;; other keys: :headers, :request, :error
(println "First page, status: " (:status @r1) "html: " (:body @r1))
(println "Second page, status: " (:status @r2) "html: " (:body @r2)))
;; get the real urls concurrently
(let [resps (map http/head twitter-shorttened-urls)]
(doseq [resp resps]
(let [{:keys [request headers error]} @resp]
(if-not error ;; Exception: timeout, network error, etc
(println (:url request) "=>" (:location headers))
;; schedule retry?
))))
(let [options {:timeout 300ms
:headers {"x-header" "value"}
:query-params {:ts (System/currentTimeMillis)}
:user-agent "user-agent-str"
:basic-auth ["user" "pass"]
:form-params {:name "http-kit"
:lang "Clojure"
:keyword ["async" "concurrent" "fast"]}}]
(http/post "http://example.com" options
;asynchronous with callback
(fn [{:keys [status error body headers]}]
;; handle result or error
;; the callback's return value is delivered to the promise
)))
More information, feature suggestion, bug report on https://github.com/http-kit/http-kit
Pull request welcome!
Rssminer, http://rssminer.net is a web app write in Clojure. The app restart slow, which make me headache. The deployment process:
lein uberjar
nohup java -jar rssminer-standalone.jar # listens on port 8100, reverse proxy by Nginx
But there is a time window between program killed and restarted, in about 10 seconds or so
This is service outage, users will notice when are using it: Nginx returns 502 during the time
Which is bad, must be solved if I want users trust the service.
Rolling release is not possible since the app get only one production server.
Here is one working solution I found yesterday night, after get home from work:
After step 3 is done, do not kill process immediately. But run step 4. In rssminer-standalone.jar, there is some logic after program get loaded and initialized, but before socket is bind to 8100, something like this:
lsof -t -sTCP:LISTEN -i:8100 | xargs kill # kills old instance
After old instance get killed, bind to 8100.
The logic is easy to write, especially in Clojure. Here is the relevant code
(loop [i 1]
(let [pid (str/trim (:out (sh "lsof"
"-t" "-sTCP:LISTEN"
(str "-i:" (cfg :port)))))]
(when-not (str/blank? pid)
;; old program is runing, kill it
(info "kill pid" pid i "times, status" (:exit (sh "kill" pid)))))
(let [r (try
;; bind socket, start web server
(reset! server (run-server (app) {:port (cfg :port)
:ip (cfg :bind-ip)
:worker-name-prefix "w"
:thread (cfg :worker)}))
1 ;; return 1 means success
(catch java.net.BindException e
(if (> i 30) ; wait about 4.5s
(do
(info "giving up" e)
(throw e))
(Thread/sleep 150))))]
(when-not r
(recur (inc i)))))
Two shell running:
for in in {1..100}; do java -jar rssminer-standalone.jar; done
Rssminer starts, get killed, start again, killed again ….
I constantly refresh browser, No noticeable downtime.
Rssminer is a RSS reader build by me using Clojure. You can try a demo here
这段时间对google出的go语言比较感兴趣。比较看中的原因: 1. Robert Griesemer, Rob Pike, Ken Thompson。 Unix,UTF8,正则表达式等等有他诸多贡献。 Rob Pike
:Unix,UTF8,Plan 9等,并且几十年的并发开发。Robert Griesemer
: hotspot jvm。 他们都是计算机行业的牛人, 牛人出品,值得一试。 2. go简单明了 3. 通过go
goroutine
select
channel
来对解决并发问题。
用它写程序是一种学习方法,就试着写了一下B-tree,回忆一下大学的课程
package btree
import (
"bytes"
"fmt"
)
type Key int
type Node struct {
Leaf bool
N int
Keys []Key
Children []*Node
}
func (x *Node) Search(k Key) (n *Node, idx int) {
i := 0
for i < x.N && x.Keys[i] < k {
i += 1
}
if i < x.N && k == x.Keys[i] {
n, idx = x, i
} else if x.Leaf == false {
n, idx = x.Children[i].Search(k)
}
return
}
func newNode(n, branch int, leaf bool) *Node {
return &Node{
Leaf: leaf,
N: n,
Keys: make([]Key, branch*2-1),
Children: make([]*Node, branch*2),
}
}
func (parent *Node) Split(branch, idx int) { // idx is Children's index
full := parent.Children[idx]
// make a new node, copy full's right most to it
n := newNode(branch-1, branch, full.Leaf)
for i := 0; i < branch-1; i++ {
n.Keys[i] = full.Keys[i+branch]
n.Children[i] = full.Children[i+branch]
}
n.Children[branch-1] = full.Children[2*branch-1] // copy last child
full.N = branch - 1 // is half full now, copied to n(new one)
// shift parent, add new key and children
for i := parent.N; i > idx; i-- {
parent.Children[i] = parent.Children[i-1]
parent.Keys[i+1] = parent.Keys[i]
}
parent.Keys[idx] = full.Keys[branch-1]
parent.Children[idx+1] = n
parent.N += 1
}
func (tree *Btree) Insert(k Key) {
root := tree.Root
if root.N == 2*tree.branch-1 {
s := newNode(0, tree.branch, false)
tree.Root = s
s.Children[0] = root
s.Split(tree.branch, 0)
s.InsertNonFull(tree.branch, k)
} else {
root.InsertNonFull(tree.branch, k)
}
}
func (x *Node) InsertNonFull(branch int, k Key) {
i := x.N
if x.Leaf {
for i > 0 && k < x.Keys[i-1] {
x.Keys[i] = x.Keys[i-1]
i -= 1
}
x.Keys[i] = k
x.N += 1
} else {
for i > 0 && k < x.Keys[i-1] {
i -= 1
}
c := x.Children[i]
if c.N == 2*branch-1 {
x.Split(branch, i)
if k > x.Keys[i] {
i += 1
}
}
x.Children[i].InsertNonFull(branch, k)
}
}
func space(n int) string {
s := ""
for i := 0; i < n; i++ {
s += " "
}
return s
}
func (x *Node) String() string {
return fmt.Sprintf("{n=%d, Leaf=%v, Keys=%v, Children=%v}\n",
x.N, x.Leaf, x.Keys, x.Children)
}
func (tree *Btree) String() string {
return tree.Root.String()
}
type Btree struct {
Root *Node
branch int
}
func New(branch int) *Btree {
return &Btree{Root: newNode(0, branch, true), branch: branch}
}
func (tree *Btree) Search(k Key) (n *Node, idx int) {
return tree.Root.Search(k)
}