庄周梦蝶

生活、程序、未来
   :: 首页 ::  ::  :: 聚合  :: 管理

    Clojure-control is a clojure DSL for system admin and deployment with many remote machines via ssh. 
    
    I am pleased to annoucment that clojure-control 0.3.0 is out.It adds some  powerful features in this release ,includes:
  • ssh and scp both have a new option :sudo  to be executed as root on remote machines,for example:
    (ssh "/etc/init.d/ssh restart" :sudo true)
  • scp has a new  option :mode to change file modes copied from local: 
    (scp "start.sh" "/etc/init.d/start.sh" :sudo true :mode 755)
  • A  new function "exists?" to test if a file exists on remote machines:  
    (if (not (exists? (str "/home/deploy/.ssh")))
          (ssh (sudo (str "mkdir -p /home/deploy/.ssh"))))
  • Call other task in deftask with "call" function:
    (deftask :ps "A task to grep process" [process]
            (ssh (str "ps aux | grep " process)))
    (deftask :start_ha []
            (ssh "/etc/init.d/haproxy start")
            (call :ps "haproxy"))
  • A new function "append" to append a line to a file on remote machines:
    (ssh (append "/etc/hosts" "192.168.1.100 web" :sudo true))
  • A new function "sed" to replace lines in a file on remote machines,and comm/uncomm to comment/uncomment a line in a file:
          (sed <file> <before> <after> :flags <flags> :limit <limit> :backup <backup>)
    

    Equivalent to

          sed -i<backup> -r -e "<limit> s/<before>/<after>/<flags>g <filename>"
  • Limits max output line to 10000.
  • Adds more documents in wiki: https://github.com/killme2008/clojure-control/wiki 
   You can install the new version by :
    lein plugin install control 0.3.0           #For clojure 1.3
    lein plugin install control 0.3.1           #For clojure 1.2

    More information please visit it on github: https://github.com/killme2008/clojure-control

posted @ 2012-02-18 22:08 dennis 阅读(3431) | 评论 (0)编辑 收藏

    XML处理也是个常见的编程工作,虽然说在Clojure里你很少使用XML做配置文件,但是跟遗留系统集成或者处理和其他系统通讯,可能都需要处理XML。

    Clojure的标准库clojure.xml就是用来干这个事情的。一个简单的例子如下,首先我们要解析的是下面这个简单的XML:
<?xml version="1.0" encoding="UTF-8"?>
<books>
  
<book>
    
<title>The joy of clojure</title>
    
<author>Michael Fogus / Chris House</author>
  
</book>
  
<book>
    
<title>Programming clojure</title>
    
<author>Stuart Halloway</author>
  
</book>
  
<book>
    
<title>Practical clojure</title>
    
<author>Luke Van der Hart</author>
  
</book>
</books>

    解析xml用clojure.xml/parse方法即可,该方法返回一个clojure.xml/element这个struct-map组成的一棵树:
user=> (use '[clojure.xml])
nil
user
=> (parse "test.xml")
{:tag :books, :attrs nil, :content
 [{:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"The joy of clojure"]} {:tag :author, :attrs nil, :content ["Michael Fogus / Chris House"]}]}
 {:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"Programming clojure"]} {:tag :author, :attrs nil, :content ["Stuart Halloway"]}]}
 {:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"Practical clojure"]} {:tag :author, :attrs nil, :content ["Luke Van der Hart"]}]}]}

这是一个嵌套的数据结构,每个节点都是clojure.xml/element结构,element包括:
(defstruct element :tag :attrs :content)
   tag、attrs和content属性,tag就是该节点的标签,attrs是一个属性的map,而content是它的内容或者子节点。element是一个struct map,它也定义了三个方法来分别获取这三个属性:
user=> (def x (parse "test.xml"))
#'user/x
user=> (tag x)
:books
user
=> (attrs x)
nil
user
=> (content x)
[{:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"The joy of clojure"]} {:tag :author, :attrs nil, :content ["Michael Fogus / Chris House"]}]}
{:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"Programming clojure"]} {:tag :author, :attrs nil, :content ["Stuart Halloway"]}]}
{:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"Practical clojure"]} {:tag :author, :attrs nil, :content ["Luke Van der Hart"]}]}]
   books节点是root node,它的content就是三个book子节点,子节点组织成一个vector,我们可以随意操作:
user=> (tag (first (content x)))
:book
user
=> (content (first (content x)))
[{:tag :title, :attrs nil, :content [
"The joy of clojure"]} {:tag :author, :attrs nil, :content ["Michael Fogus / Chris House"]}]
user
=> (content (first (content (first (content x)))))
[
"The joy of clojure"]

     额外提下,clojure.xml是利用SAX API做解析的。同样它还有个方法,可以将解析出来的结构还原成xml,通过emit:
user=> (emit x)

<?xml version='1.0' encoding='UTF-8'?>
<books>
<book>
<title>
The joy of clojure
</title>
<author>
Michael Fogus / Chris House
</author>
</book>
<book>

     如果你要按照深度优先的顺序遍历xml,可以利用xml-seq将解析出来的树构成一个按照深度优先顺序排列节点的LazySeq,接下来就可以按照seq的方式处理,比如利用for来过滤节点:
user=> (for [node (xml-seq x)
                  :when (= :author (:tag node))]
            (first (:content node)))
("Michael Fogus / Chris House" "Stuart Halloway" "Luke Van der Hart")

    通过:when指定了条件,要求节点的tag是author,这样就可以查找出所有的author节点的content,是不是很方便?就像写英语描述一样。

    更进一步,如果你想操作parse解析出来的这棵树,你还可以利用clojure.zip这个标准库,它有xml-zip函数将xml转换成zipper结构,并提供一系列方法来操作这棵树:
user=>(def xz (xml-zip x))
#'user/xz
user=> (node (down xz))
{:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"The joy of clojure"]} {:tag :author, :attrs nil, :content ["Michael Fogus / Chris House"]}]}
user
=> (-> xz down right node)
{:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"Programming clojure"]} {:tag :author, :attrs nil, :content ["Stuart Halloway"]}]}
user
=> (-> xz down right right node)
{:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"Practical clojure"]} {:tag :author, :attrs nil, :content ["Luke Van der Hart"]}]}
user
=> (-> xz down right right lefts)
({:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"The joy of clojure"]} {:tag :author, :attrs nil, :content ["Michael Fogus / Chris House"]}]}
 {:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"Programming clojure"]} {:tag :author, :attrs nil, :content ["Stuart Halloway"]}]})

  是不是酷得一塌糊涂?可以通过up,down,left,right,lefts,rights,来查找节点的邻近节点,可以通过node来得到节点本身。一切显得那么自然。更进一步,你还可以“编辑“这棵树,比如删除The joy of clojure这本书:
user=>  (def loc-in-new-tree (remove (down xz)))
#'user/loc-in-new-tree
user=> (root loc-in-new-tree)
{:tag :books, :attrs nil, :content
[{:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"Programming clojure"]} {:tag :author, :attrs nil, :content ["Stuart Halloway"]}]}
{:tag :book, :attrs nil, :content [{:tag :title, :attrs nil, :content [
"Practical clojure"]} {:tag :author, :attrs nil, :content ["Luke Van der Hart"]}]}]}

   ok,只剩下两本书了,更多方法还包括replace做替换,edit更改节点等。因此编辑XML并重新生成,你一般可以利用clojure.zip来更改树,最后利用clojure.xml/emit将更改还原为xml。

    生成xml除了emit方法,还有一个contrib库,也就是prxml,这个库的clojure 1.3版本有人维护了一个分支,在这里。主要方法就是prxml,它可以将clojure的数据结构转换成xml:
user=>(prxml [:p [:raw! "<i>here & gone</i>"]])
<p><i>here & gone</i></p>
    显然,它也可以用于生成HTML。

    xpath的支持可以使用clj-xpath这个开源库,遗憾的是它目前仅支持clojure 1.2。

    转载请注明出处:http://www.blogjava.net/killme2008/archive/2012/02/18/370233.html   

posted @ 2012-02-18 12:21 dennis 阅读(4811) | 评论 (0)编辑 收藏

    文件读写是日常编程中最经常使用的操作之一。这篇blog将大概介绍下Clojure里对文件操作的常用类库。

    首先介绍标准库clojure.java.io,这是最经常用的IO库,定义了常见的IO操作。

    首先,直接看一个例子,可以熟悉下大多数常用的函数:
(ns io
  (:use [clojure.java.io]))

;;file函数,获取一个java.io.File对象
(
def f (file "a.txt"))

;;拷贝文件使用copy
(copy f (file 
"b.txt"))

;;删除文件,使用delete
-file
(delete
-file f)

;;更经常使用reader和writer
(
def rdr (reader "b.txt" :encoding "utf-8"))
(
def wtr (writer "c.txt" :append true))

;;copy可以接受多种类型的参数
(copy rdr wtr :buffer
-size 4096)

;;关闭文件
(.close wtr)
(.close rdr)



    这个例子基本上说明了大多数常见的操作。但是有些问题需要解释下。

    首先,通过file这个函数可以将各种类型的对象转化为java.io.File对象,file可以接受String,URL,URI以及java.io.File本身作为参数,并返回java.io.File。有了File对象,你就可以调用java.io.File类中的各种方法,比如判断文件是否存在:
(.exists (file "a.txt")) => true or false

    其次,可以通过delete-file来删除一个文件,它是调用File的delete方法来执行的,但是File.delete会返回一个布尔值告诉你成功还是失败,如果返回false,那么delete-file会抛出IO异常,如果你不想被这个异常打扰,可以让它“保持安静”:
    (delete-file f true)

    拷贝文件很简单,使用copy搞定,copy也可以很“宽容”,也可以接受多种类型的参数并帮你自动转换,input可以是InputStream, Reader, File, byte[] 或者String,而output可以是OutputStream, Writer或者File。是不是很给力?这都是通过Clojure的protocol和defmulti做到的。但是,copy并不帮你处理文件的关闭问题,假设你传入的input是一个File,output也是一个File,copy会自动帮你打开InputStream和OutputStream并建立缓冲区做拷贝,但是它不会帮你关闭这两个流,因此你要小心,如果你经常使用copy,这可能是个内存泄漏的隐患。

    更常用的,我们一般都是用reader和writer函数来打开一个BufferedReader和BufferedWriter做读写,同样reader和writer也可以接受多种多样的参数类型,甚至包括Socket也可以。因为writer打开的通常是一个BufferedWriter,所以你如果用它写文件,有时候发现write之后文件还是没有内容,这是因为数据暂时写到了缓冲区里,没有刷入到磁盘,可以明确地调用(.flush wtr)来强制写入;或者在wtr关闭后系统帮你写入。reader和writer还可以传入一些配置项,如:encoding指定读写的字符串编码,writer可以指定是否为append模式等。

    Clojure并没有提供关闭文件的函数或者宏,你简单地调用close方法即可。clojure.java.io的设计很有原则,它不准备将java.io都封装一遍,而是提供一些最常用方法的简便wrapper供你使用。

    刚才提到copy不会帮你关闭打开的文件流,但是我们可以利用with-open这个宏来自动帮你管理打开的流:
(with-open [rdr (reader "b.txt")
            wtr (writer 
"c.txt")]
  (copy rdr wtr))

   with-open宏会自动帮你关闭在binding vector里打开的流,你不再需要自己调用close,也不用担心不小心造成内存泄漏。因此我会推荐你尽量用reader和writer结合with-open来做文件操作,而不要使用file函数。file函数应该用在一些判断是否存在,判断文件是否为目录等操作上。

    在clojure.core里,还有两个最常用的函数slurp和spit,一个吃,一个吐,也就是slurp读文件,而spit写文件,他们类似Ruby的File里的read和write,用来完整地读或者写文件:
    (prn (slurp "c.txt"))
    (spit "c.txt" "hello world")

   用法简单明了,slurp将文件完整地读出并返回字符串作为结果,它还接受:encoding参数来指定字符串编码,你猜的没错,它就是用reader和with-open实现的。spit同样很简单,将content转化为字符串写入文件,也接受:encoding和:append参数。

    深度优先遍历目录,可以使用file-seq,返回一个深度优先顺序遍历的目录列表,这是一个LazySeq:
(user=> (file-seq (java.io.File. "."))


(
#<File .> #<File ./.gitignore> #<File ./.lein-deps-sum> #<File ./b.txt> #<File ./c.txt> #<File ./classes> ⋯⋯ )

    上面的介绍已经足以让你对付大多数需求了。接下来,介绍下几个开源库。首先是fs这个库,它封装了java.io.File类的大多数方法,让你用起来很clojure way,很舒服,例如:
(exists? "a.txt")
(directory? 
"file")
(file? 
"file")
(name 
"/home/dennis/.inputrc")
(mkdir 
"/var/data")
(rename 
"a.txt" "b.txt")
(
def tmp (temp-dir))
(glob 
#".*test.*")
(chmod 744 "a.txt")

⋯⋯

    更多介绍请看它的源码。读写二进制文件也是一个很常见的需求,Clojure有几个DSL库干这个事情,可以很直观地定义二进制格式来encode/decode,比如byte-spec这个库,看看它的例子:
defspec basic-spec
         :a :int8
         :b :int16
         :c :int32
         :d :float32
         :e :float64
         :f :string)

;; An object to serialize
(
def foo {:a 10 :b 20 :c 40 :d 23.2 :e 23.2 :f "asddf"})

;; And serialize it to a byte array like this:
(spec
-write-bytes basic-spec foo) ;; => [bytes]

;; reading 
in a byte array with the basic-spec format works like this:
(spec
-read-bytes basic-spec bytes)
    相当直观和给力吧。Gloss是一个更强大的DSL库,非常适合做网络通讯的协议处理。这里就不多做介绍了,你可以自己看它的例子和文档。
   
    转载请注明出处:http://www.blogjava.net/killme2008/archive/2012/02/16/370144.html
   

posted @ 2012-02-16 22:38 dennis 阅读(7071) | 评论 (3)编辑 收藏

    单元测试也是一个开发中最常见的需求,在Java里我们用JUnit或者TestNG,在clojure里也内置了单元测试的库。标准库的clojure.test,以及第三方框架midje。这里我将主要介绍clojure.test这个标准库,midje是个更加强大的测试框架,广告下,midje的介绍在第二次cn-clojure聚会上将有个Topic,我就不画蛇添足了。通常来说,clojure.test足够让你对付日常的测试。

    首先看一个最简单的例子,定义一个函数square来计算平方,然后我们测试这个函数:
;;引用clojure.test
(ns example
  (:use [clojure.test :only [deftest 
is run-tests]]))
;;定义函数
(defn square [x]
  (
* x x))
;;测试函数
(deftest test
-square
  (
is (= 4 (square 2)))
  (
is (= 9 (square -3))))
;;运行测试
(run
-tests 'example)

    执行输出:

Testing example

Ran 
1 tests containing 2 assertions.
0 failures, 0 errors.

    这个小例子基本说明了clojure.test的主要功能。首先是断言is,类似JUnit里的assertTrue,用来判断form是否为true,它还可以接受一个额外的msg参数来描述断言:
 (is (= 4 (square 2)) "a test")
    它还有两种变形,专门用来判断测试是否抛出异常:
 (is (thrown? RuntimeException (square "a")))
 (
is (thrown-with-msg? RuntimeException #"java.lang.String cannot be cast to java.lang.Number"  (square "a")))
    上面的例子故意求"a"的平方,这会抛出一个java.lang.ClassCastException,一个运行时异常,并且异常信息为java.lang.String cannot be cast to java.lang.Number。我们可以通过上面的方式来测试这种意外情况。clojure.test还提供了另一个断言are,用来判断多个form:
 (testing "test zero or one"
    (are
     (
= 0 (square 0))
     (
= 1 (square 1))))
    are接受多个form并判断是否正确。这里还用了testing这个宏来添加一段字符串来描述测试的内容。

    其次,我们用deftest宏定义了一个测试用例,deftest定义的测试用例也可以组合起来:
   (deftest addition
     (
is (= 4 (+ 2 2)))
     (
is (= 7 (+ 3 4))))
   (deftest subtraction
     (
is (= 1 (- 4 3)))
     (
is (= 3 (- 7 4))))
   (deftest arithmetic
     (addition)
     (subtraction))

    但是组合后的tests运行就不能简单地传入一个ns,而需要定义一个test-ns-hook指定要跑的测试用例,否则组合的用例如上面的addition和subtraction会运行两次。我们马上谈到。

    定义完用例后是运行测试,运行测试使用run-tests,可以指定要跑测试的ns,run-tests接受可变参数个的ns。刚才提到,组合tests的时候会有重复运行的问题,要防止重复运行,可以定义一个test-ns-hook的函数:
(defn test-ns-hook []
  (test
-square)
  (arithmetic))
    这样run-tests就会调用test-ns-hook按照给定的顺序执行指定的用例,避免了重复执行。

    在你的测试代码里明确调用run-tests执行测试是一种方式,不过我们在开发中更经常使用的是lein来管理project,lein会将src和test分开,将你的测试代码组织在专门的test目录,类似使用maven的时候我们将main和test分开一样。这时候就可以简单地调用:
        lein test
命令来执行单元测试,而不需要明确地在测试代码里调用run-tests并指定ns。更实用的使用例子可以看一些开源项目的组织。

    单元测试里做mock也是比较常见的需求,在clojure里做mock很容易,原来clojure.contrib有个mock库,基本的原理都是利用binding来动态改变被mock对象的功能,但是在clojure 1.3里,binding只能改变标注为dynamic的变量,并且clojure.contrib被废弃,部分被合并到core里面,Allen Rohner编译了一个可以用于clojure 1.3的clojure.contrib,不过需要你自己install到本地仓库,具体看这里。不过clojure.contrib.mock哪怕使用1.2的编译版本其实也是可以的。

    clojure.contrib最重要的是expect宏,它类似EasyMock里的expect方法,看一个例子:
(use [clojure.contrib.mock :only [times returns has-args expect]])

(deftest test
-square2
  (expect [square (has
-args [number?] (times 2 (returns 9)))]
          (
is (= 9 (square 4)))))

    has-args用来检测square的参数是不是number,times用来指定预期调用的次数,而returns用来返回mock值,是不是很像EasyMock?因为我们这个测试只调用了square一次,所以这个用例将失败:
Testing example
"Unexpected invocation count. Function name: square expected: 2 actual: 1"
   这个例子要在Clojure 1.3里运行,需要将square定义成dynamic:
(defn ^:dynamic square [x]
  (
* x x))
   否则会告诉你没办法绑定square:
actual: java.lang.IllegalStateException: Can't dynamically bind non-dynamic var: example/square


    额外提下,还有个轻量级的测试框架expections可以看一下,类似Ruby Facker的facker库提供一些常见的模拟数据,如名称地址等。
   
     转载请注明出处:http://www.blogjava.net/killme2008/archive/2012/02/15/370040.html

   

posted @ 2012-02-15 19:39 dennis 阅读(4993) | 评论 (0)编辑 收藏

    Clojure的REPL非常方便,可以随时随地试验你的想法,REPL是read-eval-print-loop的简称。默认clojure.contrib有带一个shell脚本来启动REPL,具体看这里。你也可以用JLine来增强REPL:
java -cp "%CLOJURE_DIR%\jline-VERSION.jar;%CLOJURE_JAR%" jline.ConsoleRunner clojure.main

    不过,其实你还可以用rlwrap这个GNU库来增强clojure REPL。使用它有如下好处:
1.Tab completion,使用tab做代码提示。
2.括号匹配
3.历史记录,哪怕你重启REPL
4.通过.inputrc来绑定vi或者emacs

    具体操作步骤如下:

1.首先,你需要在你的机器上安装rlwrap,你可以通过apt或者port,homebrew等工具安装或者自己下载安装:
sudo port install rlwrap

2.在你的home目录下创建一个clojure目录作为clojure home,并拷贝clojure.jar进去:
mkdir ~/clojure
cp .m2
/repository/org/clojure/clojure/1.3.0/clojure-1.3.0.jar ~/clojure/clojure.jar
我是从maven的本地仓库里拷贝了clojure 1.3的jar包过去,重命名为clojure.jar

3.创建一个shell脚本名为clj,并放入你的path变量,脚本内容:
#!/bin/sh
breakchars
="(){}[],^%$#@\"\";:''|\\"
CLOJURE_DIR
=~/clojure
CLOJURE_JAR
="$CLOJURE_DIR"/clojure.jar
JAVA_OPTS
="-Xmx512m -XX:MaxPermSize=256m -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:+CMSClassUnloadingEnabled"
if [ $# -eq 0 ]; then 
    exec rlwrap 
--remember --"$breakchars" \
   
-"$HOME"/.clj_completions \
   
-"Clojure REPL" \
   
-p red \
   
-"$CLOJURE_DIR"/.repl_history -1000\
   java 
"$JAVA_OPTS"  -cp "$CLOJURE_JAR" clojure.main
else
         exec java 
-cp "$CLOJURE_JAR" clojure.main $1 "$@"
fi
我们将命令历史输出到~/clojure/.repl_history文件中,并限制数目为1000。

4.clj脚本中通过-f选项指定了completions文件为~/.clj_completions,执行下列clojure程序生成此文件:
(def completions (keys (ns-publics (find-ns 'clojure.core))))
;(def completions (mapcat (comp keys ns
-publics) (all-ns)))
(
with-open [f (java.io.BufferedWriter. (java.io.FileWriter. (str (System/getenv "HOME""/.clj_completions")))]
  (.write f (apply str (interpose \newline completions))))
这个程序只生成clojure.core的completions文件,如果你想将所有ns都加入进去,注释掉第一行,使用第二行程序。

5.最后,配置下~/.inputrc文件:
set editing-mode emacs
tab: complete
set completion
-query-items 150
set completion
-ignore-case on
set blink
-matching-paren on
set bell
-style visible
我绑定为emacs,你可以选择vi。

6.一切搞定,接下来你可以敲入命令clj来使用rlwrap启动clojure REPL了,可以用tab做代码提示了,可以用Ctrl + r来搜索历史命令,运行截图:


参考:http://en.wikibooks.org/wiki/Clojure_Programming/Getting_Started#Enhancing_Clojure_REPL_with_rlwrap
转载请注明出处:http://www.blogjava.net/killme2008/archive/2012/02/14/369976.html

posted @ 2012-02-14 19:05 dennis 阅读(3691) | 评论 (1)编辑 收藏


    使用http client提交表单或者下载网页也是非常常见的任务,比如使用Java的时候可以用标准库的HttpURLConnection,也可以选择Apache Http Client。在clojure里也有这样的类库,这里我将介绍三个各有特色的http client实现。

    首先,我最先推荐使用clj-http这个类库,它是Apache HttpClient的clojure wrapper,是一个提供同步API的简单易用的Http Client。

名称: clj-http
主页:https://github.com/dakrone/clj-http
依赖:
[clj-http "0.3.1"]

例子:
(require '[clj-http.client :as client])
(client/get "http://google.com")
结果:
=> {:cookies {"NID" {:domain ".google.com.hk", :expires #<Date Tue Aug 14 18:20:38 CST 2012>, :path "/", :value "56=qn2OWtODE2D3fUKi_vbi44jZepOeLI9xC4Ta1JQLEicqUvIZAqr7TCmft_hq8i_FRwnFXdTK1jV2S5IrSZFyYhlAN2KcQEXgWX1iK36gM2iYPaKPihuUZDCqgiAamDOl", :version 0}, "PREF" {:domain ".google.com.hk", :expires #<Date Wed Feb 12 18:20:38 CST 2014>, :path "/", :value "ID=8b73a654ff0a2783:FF=0:NW=1:TM=1329128438:LM=1329128438:S=uEM4SsFuHlkqtVhp", :version 0}},
    :status 
200
    :headers {
"date" "Sun, 01 Aug 2010 07:03:49 GMT"
              
"cache-control" "private, max-age=0"
              
"content-type" "text/html; charset=ISO-8859-1"
              }
    :body 
"<!doctype html>"
    :trace
-redirects ["http://google.com" "http://www.google.com/" "http://www.google.fr/"]}
更多例子:
(client/get "http://site.com/resources/3" {:accept :json})

;; Various options:
(client
/post "http://site.com/api"
  {:basic
-auth ["user" "pass"]
   :body 
"{\"json\": \"input\"}"
   :headers {
"X-Api-Version" "2"}
   :content
-type :json
   :socket
-timeout 1000
   :conn
-timeout 1000
   :accept :json})

;; Need to contact a server with an untrusted SSL cert
?
(client
/get "https://alioth.debian.org" {:insecure? true})

;; If you don
't want to follow-redirects automatically:
(client/get "http://site.come/redirects-somewhere" {:follow-redirects false})

;; Only follow a certain number of redirects:
(client
/get "http://site.come/redirects-somewhere" {:max-redirects 5})

;; Throw an exception 
if redirected too many times:
(client
/get "http://site.come/redirects-somewhere" {:max-redirects 5 :throw-exceptions true})

;; Send form params as a urlencoded body
(client
/post "http//site.com" {:form-params {:foo "bar"}})

;; Multipart form uploads
/posts
;; a map or vector works as the multipart object. Use a vector of
;; vectors 
if you need to preserve order, a map otherwise.
(client
/post "http//example.org" {:multipart [["title" "My Awesome Picture"]
                                              [
"Content/type" "image/jpeg"]
                                              [
"file" (clojure.java.io/file "pic.jpg")]]})
;; Multipart values can be one of the following:
;; String, InputStream, File, or a 
byte-array

;; Basic authentication
(client
/get "http://site.com/protected" {:basic-auth ["user" "pass"]})
(client
/get "http://site.com/protected" {:basic-auth "user:pass"})

;; Query parameters
(client
/get "http://site.com/search" {:query-params {"q" "foo, bar"}})

    clj-http的API相当的简洁漂亮,使用起来非常便利,强烈推荐。题外,学习clojure的一个好方法就是为现有的java类库实现一些方便的clojure wrapper。

    如果你需要异步的http client,我会推荐http.async.client这个类库,它的API是异步形式的类似 Java的Future模式,对于clojure程序员来说应该更像是agent。

名称:http.async.client
主页:https://github.com/neotyk/http.async.client
依赖:
[http.async.client "0.4.1"]
例子:
(require '[http.async.client :as c])
(with-open [client (c/create-client)]
  (let [response (c
/GET client "http://neotyk.github.com/http.async.client/")]
    (prn (c
/done? response))
    (c
/await response)
    (prn (c
/string response))
    (prn (c
/status response))
    (prn (c
/done? response))))

输出:
false
<!DOCTYPE html 
{:code 
200, :msg "OK", :protocol "HTTP/1.1", :major 1, :minor 1}
true

更多例子:
(c/POST client "http://example.com" :body "hello world" :timeout 3000)
(c
/DELETE client "http://example.com")
(c
/POST client "http://example.com" :body "hello world" :auth {:type :basic :user "admin" :password "admin"})

请注意,这些方法都是异步调用的,你需要通过await来等待调用完成,或者通过done?来判断调用是否完成。
http.async.client有个比较重要的特性就是对Http Chunked编码的支持,分别通过LazySeq和callback的方式支持,首先看将Http chunked变成一个lazy seq:

(with-open [client (client/create-client)] ; Create client
  (let [resp (client
/stream-seq client :get url)]
    (doseq [s (s
/string resp)]
      (println s))))

这里非常关键的一点是stream-seq返回的chunk序列,每取一个就少一个(通过first函数),也就是说每次调用first取到的chunk都不一样,是顺序递增,不可重复获取的。

通过callback方式处理:
(with-open [client (client/create-client)] ; Create client
  (let [parts (ref #{})
        resp (client
/request-stream client :get url
                                    (fn [state body]
                                      (dosync (alter parts conj (string body)))
                                      [body :
continue]))]
    ;; 
do something to @parts
    ))
自己传入一个callback函数接收chunk,比如这里用一个ref累积。

http.async.client的详细文档看这里:http://neotyk.github.com/http.async.client/docs.html

最后,有兴趣还可以看下aleph这个异步通讯的框架,它支持Http协议,也提供了http server和client的实现。不过它的API就没有那么简单明了,它的模型是类似go语言里利用channel做异步通讯的模型,http只是它的一个模块罢了,这是另一个话题了。

转载请注明出处:http://www.blogjava.net/killme2008/archive/2012/02/13/369890.html

posted @ 2012-02-13 18:57 dennis 阅读(6218) | 评论 (1)编辑 收藏


    处理日志是任何一个产品级的程序都需要仔细处理的模块。在Java中,我们经常使用的是log4j就是一个日志框架。在clojure里,同样有一套日志框架——clojure.tools.logging,它不仅提供了常用的日志输出功能,还屏蔽了Java各种日志框架之间的差异,如slf4j,commons-logging,log4j,java.util.logging等,让你可以透明地使用这些框架来处理日志。

名称:clojure.tools.logging
主页:https://github.com/clojure/tools.logging
依赖:
[org.clojure/tools.logging "0.2.3"]

<dependency>
  
<groupId>org.clojure</groupId>
  
<artifactId>tools.logging</artifactId>
  
<version>0.2.3</version>
</dependency>

使用:
(ns example.core
  (:use [clojure.tools.logging :only (info error)]))

(defn divide [x y]
  (try
    (info "dividing" x "by" y)
    (/ x y)
    (catch Exception ex       (error ex "There was an error in calculation"))))

常用宏和方法:
1.除了上面例子的info和error宏,还可以包括warn,trace,debug,fatal等常用宏,分别对应相应的日志级别。这些方法会自动判断当前logger的级别是否有效,有效的前提下才会输出日志。也就是说在Java里,你经常需要这样:
if (logger.isDebugEnabled()) {
    logger.debug(x 
+ " plus " + y + " is " + (x + y));
}
在使用 tools.logging的时候是不需要的,因为这些宏帮你做了这个判断。另外,我们在使用log4j的时候需要指定log的namespace,在tools.logging里不需要,默认会取当前的namespace也就是*ns*。
最后,info还有个infof的方法,用于输出格式化日志:
(infof "%s is %d years old" "kid" 3)
日志输出:
2012-02-12 20:23:07,394 INFO  log: kid is 3 years old
其他方法也有类似的如warnf,debugf等。
2.spy宏,同时输出表达式的form和结果,例如
(spy (+1 2))
输出日志
2012-02-12 20:11:47,415 DEBUG log: (+ 1 2)
=> 3

3.with-logs宏可以在将*out*和*err*流重定向到日志的情况下求值表达式,例如:
(with-logs *ns* (prn "hello world"))
输出日志:
2012-02-12 20:17:32,592 INFO  log: "hello world"
with-logs需要明确指定log-ns,默认out的输出级别是info,而err的级别是error,可以指定输出级别(with-logs [*ns* :info :error] ......)

4.事务中(dosync中)的日志输出,tools.logging做了特殊处理,默认情况下当且仅当事务成功提交的时候并且日志级别是warn或者info会通过agent异步写入日志。tools.logging定义了一个全局的agent——*logging-agent*。当判断当前是在事务中调用log宏,并且日志级别在集合*tx-agent-levels*内,就会在事务提交成功的时候将日志发送给*logging-agent*异步处理。可以通过*tx-agent-levels*改变使用agent输出日志的级别范围,默认是#{:info :warn}。还可以通过改变*force*变量来强制使用direct或者agent的方式输出日志,*force*可以为:agent或者:direct。
(binding [*force* :agent]
  (log :info 
"hello world"))
这里特别使用了log宏,需要明确指定日志级别为info。

5.默认日志框架的是从classpath查找的,查找的顺序是sl4j,commons-logging,log4j,java.util.logging,找到哪个可用就用哪个。如果你的classpath里存在多个日志框架,如同时存在sl4j和commons-logging,那么如果你希望强制使用commons-logging,可以通过改变*logger-factory*变量来使用:
(ns example
  (:use [clojure.tools.logging.impl :only [cl
-factory]]))
(binding [
*logger-factory* (cl-factory)]
  (info 
"hello world"))

*logger-factory*是dynamic变量,可以通过binding改变(前面提到的*force*等变量也一样),如果不希望每次都用binding,而是全局改变,则需要特殊处理:
(alter-var-root (var *logger-factory*) (constantly (cl-factory)))
其他logger factory还包括slf4j-factory,log4j-factory,jul-factory。

6.每个日志框架的配置跟使用java没有什么两样,比如你用log4j,就需要在classpath下放置一个log4j.properties等。如果你希望用编程的方式配置,可以使用clj-logging-config

转载请注明出处:http://www.blogjava.net/killme2008/archive/2012/02/12/369822.html

posted @ 2012-02-12 20:53 dennis 阅读(4250) | 评论 (4)编辑 收藏

    年前一篇blog提过,写了一个stm-profiler用于统计clojure STM的运行状况,放在了github上:
https://github.com/killme2008/stm-profiler

   STM的事务在遇到写冲突(多个事务写同一个ref的时候)就会回滚事务并重试,通过stm-profiler你可以查看事务的重试次数,重试原因,以及每个reference的使用情况。使用很简单,在lein的project.clj引用stm-profiler:
[stm-profiler "1.0.2-SNAPSHOT"]

注意,目前stm profiler仅支持clojure 1.3。

我们写一个简单例子:
(use 'stm)
(def a (ref 1))
(def b (ref 2))

(dotimes [_ 100] (future (dosync (alter a + 1) (alter b - 1))))
(Thread/sleep 1000)
(prn @a)
(prn @b)
(Thread/sleep 1000)
(prn "stm statistics" (stm-stats))
(prn "reference a statistics" (ref-stats a))
(prn "reference b statistics" (ref-stats b))

定义了两个ref:a和b,然后用future启动100个线程并发地发起同一个事务操作,对a加一,对b减一。最后打印a和b的值,使用stm-stats函数获取stm的统计信息并打印,使用ref-stats获取a和b两个reference的统计信息并打印。

运行这个例子,在启动的时候会有些警告信息,忽略即可(主要是因为stm profiler重新定义了一些跟STM相关的函数和宏,如dosync等,但是仅仅是添加了统计功能,并没有修改他们原本的功能)。

在我机器上的一次输出:
101
-98
"stm statistics" {"(alter a + 1)(alter b - 1)" {:not-running 11, :average-retry 5, :total-cost 1233, :get-fault 44, :barge-fail 224, :change-committed 227, :total-times 100, :average-cost 12}}
"reference a statistics" {"(alter a + 1)(alter b - 1)" {:alter 609, :get-fault 44, :barge-fail 224, :change-committed 227}}
"reference b statistics" {"(alter a + 1)(alter b - 1)" {:alter 114, :not-running 11}}

a和b的结果都没问题。重点看打印的统计信息,(stm-stats)的输出结果是:
{"(alter a + 1)(alter b - 1)" {:not-running 11, :average-retry 5, :total-cost 1233, :get-fault 44, :barge-fail 224, :change-committed 227, :total-times 100, :average-cost 12}}

这个结果是一个map,key是事务的form,而value就是该form的统计信息,也是一个map,具体各项的含义如下:
total-cost
所有事务的总耗时
100个事务耗时1233毫秒
total-times
事务运行次数
100次
average-cost
平均每个事务耗时
平均一个事务耗时12毫秒
average-retry
平均每个事务的重试次数  平均每个事务重试了5次才成功
not-running  当前事务不处于running状态,可能是被其他事务打断(barge),需要重试  因为not-running的原因重试了11次
get-fault
 读取ref值的时候没有找到read point之前的值,被认为是一次读错误,需要重试
 因为读ref错误重试了44次
barge-fail  打断其他事务失败次数,需要重试  尝试打断其他事务失败而重试了224次
change-committed  在本事务read point之后有ref值获得提交,则需要重试
 因为ref值被其他事务提交而重试了227次

    从输出结果来看,这么简单的一个事务操作,每次事务要成功平均都需要经过5次的重试,最大的原因是因为ref的值在事务中被其他事务更改了,或者尝试打断其他正在运行的事务失败而重试。关于clojure STM的具体原理推荐看这篇文章《Software transactional memory》。STM不是完美的,事务重试和保存每个reference的历史版本的代价都不低。

    再看(ref-stats a)的输出:
{"(alter a + 1)(alter b - 1)" {:alter 609, :get-fault 44, :barge-fail 224, :change-committed 227}}
    可以看到a在所有事务中的统计信息,返回的结果同样是个map,key是使用了a的事务,value是具体的统计信息。各项的含义类似上表,不过这里精确到了具体的reference。其中alter项是指对a调用alter函数了609次。ref-stats会输出所有在事务中调用了a的函数的调用次数。

    通过stm profiler你可以分析具体每个事务的执行状况,甚至每个reference的运行状况,查找热点事务和热点reference等。stm-profiler还不完善,目前还不支持1.2(1.4测试是可以的)。希望有兴趣的朋友加入进来一起完善。

转载请注明出处:http://www.blogjava.net/killme2008/archive/2012/02/09/369694.html

posted @ 2012-02-09 20:55 dennis 阅读(3341) | 评论 (2)编辑 收藏


    去年(我靠,已经是去年了)首次在上海组织了第一次cn-clojure的线下聚会,详细可以看这篇blog。今年,我们将在北京举行第二次cn-clojure的聚会,时间大概在2月底或者3月初,具体地点待定,欢迎任何对clojure语言或者Lisp语言感兴趣的朋友参加,如果有想分享的技术topic更好 :D。

    如果你要参加,请参加下面的报名调查,填写真实的姓名和邮箱。如果有想分享的topic,可以填写调查或者直接邮件给我。
    报名链接:http://www.diaochapai.com/survey584561

    我们将在议程、时间和地点确定后发邮件给报名的朋友,确认参会的具体时间和地点。
    邮件列表:http://groups.google.com/group/cn-clojure

posted @ 2012-02-09 12:37 dennis 阅读(2808) | 评论 (0)编辑 收藏


    很久没写blog了,写写最近做的一些工作,给感兴趣的朋友做参考。
    首先是我们的kafka的“复制品”metamorphosis做了1.4版本,实现了同步复制方案,broker本身也做了很多优化,总体而言meta是一个非常成熟可用的产品了。甚至可以说是我在淘宝做的最好的一个产品。有些朋友总是问我们为什么不直接用kafka,而要另写一个?这里做个统一的解答。
(1)kafka是scala写的,我对scala不熟悉,也不待见,考虑到维护和语言熟悉程度,用java重写仍然是最好的选择。
(2)其次,kafka的整个社区非常不活跃,发展太慢,而我又不愿意去学习scala来参与社区发展,那么唯一的出路就是自己写。
(3)kafka的一些工作不能满足我们的要求,比如一开始它连producer的负载均衡都没有,它的消费者API设计还是比较蛋疼的。它也不支持事务,没有考虑作为一个通用的MQ系统来使用。并且它也没有高可用和数据高可靠的方案。
(4)我们做了什么呢?
a.用java彻底重写整个系统,除了原理一致,整个实现是彻底重新实现的。
b.我们提供了生产者的负载均衡(仍然是基于zk),重新设计了消费者API,更符合 JMS的使用习惯。
c.我们提供了事务实现,包括producer和consumer端的,包括本地事务和符合XA规范的分布式事务实现。
d.我们提供了两种数据高可靠方案:类似mysql的异步复制和同步复制方案。通过将消息复制到多个节点上来保证数据的高可靠。
e.我们提供了http协议的实现,并且本身使用协议也是类似memcached的文本协议,内部也增加了很多统计项目,可以以memcached的stats协议的方式来获取纯文本的统计信息。整个系统运维很方便。
f.提供了很多扩展应用:广播消费者的实现,多种offset存储的实现(默认的zookeeper,还有文件和mysql),tail4j用于作为agent发送日志,log4j appender扩展用于透明地使用log4j发送消息,hdfs writer用于将消息写入hdfs,storm spout用于将消息接入storm做实时分析,基本上形成一套完整的工具链和扩展。
g.一些其他功能点:group commit提升数据可靠性和吞吐量,连接复用,集群下的顺序消息发送,消息数据的无痛迁移和水平扩展,web管理平台等。

    meta未来会走开源的路子,不过不会是我来推动的,估计是在今年会有进展。

    我最近还写了一些小项目值得一提,首先是aviator这个轻量级的表达式执行引擎发布了2.2.1版本,主要是这么几个改进:
(1)支持多维数组变量的访问,如a[0][0]
(2)添加Expression#getVariableNames()用于返回表达式的变量列表
(3)添加AviatorEvaluator#exec方法来简化调用
(4)bug修正等。
    maven直接升级:
 <dependency>
                        
<groupId>com.googlecode.aviator</groupId>
                        
<artifactId>aviator</artifactId>
                        
<version>2.2.1</version>
        
</dependency>

    其次,hs4j这个handler socket的客户端,由新浪微博的@赵鹏城实现了inc/dec协议,添加了incr和decr方法用于更新计数,感谢他的贡献,如果你需要这两个功能可以自己从github拉取源码并构建打包,暂时不准备发布到maven。

    第三,关注高可用的Transaction Manager实现的可以关注下我的ewok项目,这是一个基于BTM这个开源JTA实现,提供基于bookkeeper的高可用的TM项目。将事务日志写到高可用的bookkeeper上,并利用zookeeper来做到故障的透明迁移,某个TM挂了,可以在其他机器上从bookkeeper拉取日志并恢复。代码已经稳定并做了性能测试,没有做进一步的破坏性测试。BTM是一个比JOTM和atomikos更靠谱的开源JTA实现,并且性能也好上很多,代码质量更不用说,建议有兴趣的可以看一下。我也为它贡献了一个事务日志写入优化的patch,日志写入性能提升了近一倍。

    最后,我在clojure上做了一些事情,首先是为storm项目贡献了两个patch:利用curator做zookeeper交互和添加storm.ui.context.path选项,前者被作者接受,后者暂时只对我们有用。前者让storm跟zk的交互更可用,后者是为storm ui添加了可选的相对路径。你都可以在我fork的分支上尝试,curator的patch在storm 0.6.2上发布,现在还是snapshot状态。昨天晚上牙痛睡不着,半夜写了个clojure STM profiler,用于统计分析clojure STM运行状况,诸如事务运行次数和时间,事务的重试原因和次数等,可以针对每个dosync的form做统计,有兴趣也可以看下。不过我其实更想将这个功能加入到clojure核心,会尝试提交下pull request。

   还有个工作上的变迁,我将在2月1号正式从呆了近三年的淘宝离职,加入一支充满活力的创业团队。从稳定的大公司出来,去加入一家初创公司,不能说没有风险,但是我还是想去接受新的挑战,愿意更新我的知识结构,愿意向牛人们学习。我在某个blog上说我今年遇到了人生中最大的挑战和转折,并不是说这个事情,而是我的儿子今年患了一场重病,庆幸在很多人的帮助和关心下,他勇敢地挺了过来,度过最困难的一关,现在还在继续治疗。我要感谢很多人,感谢淘宝,感谢我的TL华黎和锋寒,感谢我的同事和朋友林轩,感谢我们的HR,感谢三年后打交道的很多同事。没有他们,我今年真的过不了关,没有他们,我也不能进入淘宝并呆上快三年。

   最后的最后,我要特别感谢我的儿子,谢谢你的降生,谢谢你今年的勇敢,谢谢你给我们全家带来的快乐,谢谢你继续陪着我们 ,也希望你新年继续勇敢地坚持下去,我们必将战胜一切。     
   

posted @ 2012-01-15 12:50 dennis 阅读(5454) | 评论 (20)编辑 收藏

仅列出标题
共56页: 上一页 1 2 3 4 5 6 7 8 9 下一页 Last