1. 27 Jan 2013

    Add async (long polling) to ring web application

    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, Clojure’s way of abstrating HTTP

    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 with 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}}
    1. async-response is macro, it gives you respond
    2. 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))))

    What’s goes on under the hook?

    • 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.
    • A callback is registed on IListenableFuture, waiting respond to return the actual response
    • respond send to actual response to client (response-level middlewares are not applied), client’s request returns

    Efficiency

    Keep 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/

    Open source

    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

  2. 22 Jan 2013

    介绍http-watcher,一前端利器

    前端同学的开发流程:

    前端开发流程

    如果有这样一个工具:监控代码目录,发现代码修改后,自动刷新浏览器,流程可减少一步:

    省去刷新

    减少一小步,效率提高一大步。因为在大多数情况下,另外两步也可以省掉,就变成了更改代码后,省掉窗口切换,用眼睛扫一下就可以。眼睛的速度很快的哦。专注于代码和思考

    省去切换

    http-watcher,为前端同学量身打造

    俺做前端开发好几年了,切换窗口了好几年。

    最近在做 [美味书签] (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,实时查看

    用法:动态程序(Java, go, Clojure, Python, etc)

    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,也可以下载

    下载地址

  3. 21 Jan 2013

    A new Clojure HTTP client, concurrent made easy by asynchronous and promise

    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.

    Why another Clojure HTTP client

    Compare with the very good Clojure HTTP client clj-http, a few highlights of http-kit:

    1. concurrent requests made easy by asynchronous and promise
    2. fast, keep-alive done right, designed with server side use in mind
    3. thread safe

    The New API

    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"]}}

    Examples

    ;; 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
                   )))

    Open source

    More information, feature suggestion, bug report on https://github.com/http-kit/http-kit

    Pull request welcome!

  4. 09 Dec 2012

    I make my Clojure web app restarts faster

    Rssminer restart slow, which causes seconds service outage

    Rssminer, http://rssminer.net is a web app write in Clojure. The app restart slow, which make me headache. The deployment process:

    1. ahead-of-time compile all Clojure code into JVM bytecode
    2. Pack them along with all dependencies into a single jar: rssminer-standalone.jar using lein uberjar
    3. rsync rssminer-standalone.jar to production server
    4. Kill previous Rssminer process
    5. Start a new one by running
    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.

    Solution: kill only after app started

    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)))))

    Testing

    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.

    About Rssminer

    Rssminer is a RSS reader build by me using Clojure. You can try a demo here

  5. 30 Nov 2012

    go语言学习笔记:B-tree

    这段时间对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)
    }