forked from anjuke/anjuke.github.com
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
5302 lines (3923 loc) · 400 KB
/
atom.xml
File metadata and controls
5302 lines (3923 loc) · 400 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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title><![CDATA[Anjuke Engineering]]></title>
<link href="http://arch.corp.anjuke.com/atom.xml" rel="self"/>
<link href="http://arch.corp.anjuke.com/"/>
<updated>2013-03-13T12:15:00+08:00</updated>
<id>http://arch.corp.anjuke.com/</id>
<author>
<name><![CDATA[Anjuke Inc.]]></name>
</author>
<generator uri="http://octopress.org/">Octopress</generator>
<entry>
<title type="html"><![CDATA[使用安居客提供的PyPI镜像]]></title>
<link href="http://arch.corp.anjuke.com/blog/2013/03/01/using-pypi-provided-by-anjuke/"/>
<updated>2013-03-01T10:52:00+08:00</updated>
<id>http://arch.corp.anjuke.com/blog/2013/03/01/using-pypi-provided-by-anjuke</id>
<content type="html"><![CDATA[<p>主要提示:安居客现在拥有了自己的PyPI镜像,直接使用http://pypi.corp.anjuke.com/simple ,就可以节省重复去公网下载模块的流量与时间。</p>
<h2>Welcome to the Python World!</h2>
<p>这篇文章的目标读者是还刚接触Python以及安居客将要使用Python的各位开发者,强烈建议把文中提到的延伸信息都阅读一下,百利无害。</p>
<p>使用Python开发程序是一件轻松惬意的事情,它的第三方模块分发机制让开发者能够很方便快速的发布自己的代码、安装部署使用其他开发者做的模块。(详细请参考<a href="http://docs.python.org/2/install/index.html">安装Python模块</a>,<a href="http://docs.python.org/2/distutils/index.html">发布Python模块</a>)</p>
<p>关于Python打包的方式以及开源软加架构里打包的背景信息,可以阅读<a href="http://www.ituring.com.cn/article/19090">这篇内容</a>了解更多。</p>
<h2>如何安装第三方的Python模块</h2>
<p>标准的Python模块源码包里都会带上一个<code>setup.py</code>,它是Python的模块分发机制中的一部分,其中会定义模块的基本信息以及 <strong>依赖</strong> ,后者就是我们重点需要关注的问题。</p>
<p>通常我们只需要执行</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>python setup.py install</span></code></pre></td></tr></table></div></figure>
<p>即可完成安装。</p>
<h2>Python模块的分发</h2>
<p>传统模块的分发如果采用原始的人肉方式那就显得不够好了;所以Python社区有了PyPI(Python Package Index),官方的地址是<a href="https://pypi.python.org">pypi.python.org</a>,全世界各地还有几个<a href="http://www.pypi-mirrors.org">镜像</a>;以及配套使用的客户端程序,<code>setuptools</code>和<code>pip</code></p>
<p><strong>我们建议使用<code>pip</code></strong></p>
<p>前者一般在Python安装的时候随同一起分发,后者需要手工安装(<code>pythonbrew</code>里则是会自动一起安装)</p>
<p>对于想要使用的模块依赖,使用</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>easy_install foobar
</span><span class='line'>
</span><span class='line'>#或者
</span><span class='line'>pip install foobar</span></code></pre></td></tr></table></div></figure>
<p>它的过程就像是ubuntu下的<code>apt-get</code>,自动从配置的镜像(默认是官方)自动查找目标模块,下载,(编译)安装,整个过程轻松简单。</p>
<h2>为什么我们要提供安居客的镜像</h2>
<p>出于安居客未来架构发展的需要,我们在开发、生产环境的部署将会更多的采用自动化;项目的依赖是其中一个不可忽视的环节。对于Python来说,快速高效地安装依赖能提高效率,减少错误。</p>
<p>还有一点是,每次安装都从官方下载会造成流量和时间的浪费;更多的,因为某些大家都懂的原因造成一个项目依赖无法安装,很令人恼火。做镜像提高速度,减少问题也是让工程师快乐的一个体现。</p>
<h2>如何使用安居客的PyPI镜像</h2>
<p><strong>更详细的使用方法请参考<code>easy_install</code>和<code>pip</code>的文档</strong></p>
<h3>安装 (install)</h3>
<ul>
<li>easy_install</li>
</ul>
<p>直接使用easy_install的命令,带上参数指定</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>easy_install -i http://pypi.corp.anjuke.com/simple FOOBAR</span></code></pre></td></tr></table></div></figure>
<p>或者写配置文件<code>~/.pydistutils.cfg</code>,内容如下</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>[easy_install]
</span><span class='line'>index_url = http://pypi.corp.anjuke.com/simple</span></code></pre></td></tr></table></div></figure>
<ul>
<li>pip</li>
</ul>
<p>直接使用pip命令,带上参数</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>pip -i http://pypi.corp.anjuke.com/simple install FOOBAR</span></code></pre></td></tr></table></div></figure>
<p>或者写配置文件<code>~/.pip/pip.conf</code>,内容如下</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>[global]
</span><span class='line'>index-url = http://pypi.corp.anjuke.com/simple</span></code></pre></td></tr></table></div></figure>
<h3>搜索 (search)</h3>
<p><strong>暂未支持</strong></p>
<h2>最后</h2>
<p>我们希望为工程师、开发者提供更好的环境来支持开发工作。</p>
<p>如果大家在使用中有各种问题,意见和建议,欢迎联系我们。</p>
<p>Happy Hacking ;)</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Homebrew is just like gentoo portage]]></title>
<link href="http://arch.corp.anjuke.com/blog/2013/01/20/homebrew-is-just-like-gentoo-portage/"/>
<updated>2013-01-20T14:28:00+08:00</updated>
<id>http://arch.corp.anjuke.com/blog/2013/01/20/homebrew-is-just-like-gentoo-portage</id>
<content type="html"><![CDATA[<p><img class="right" src="http://0.gravatar.com/avatar/ce1e13bbf946c92e2abf740f8909bafa"></p>
<h2>一句话提示:</h2>
<blockquote><p>利用<code>brew versions</code>和<code>brew switch</code>命令也可以实现gentoo portage里eselect类似的版本切换功能!</p></blockquote>
<p>老规矩,喜欢听故事的继续看下去XDDD</p>
<!-- more -->
<h2>您使用何种包管理?</h2>
<p>MacOS下安装软件是件很惬意的事情,因为DMG或者PKG一路NEXT魔法即可(<strike>这么说Windows其实也是XDD</strike>),卸载也不是什么难事。Linux下安装软件同样…也不能算是一件难事,因为我们有apt、pacman、emerge<strike>甚至是yum</strike>这样的包管理器,一条命令随叫随到。</p>
<p>那么有在Mac下像Linux那样安装软件包的工具吗?如今大家可能第一想到的是<a href="https://github.com/mxcl/homebrew">Homebrew</a>(以下简称brew),没错,在他之前就有大名鼎鼎的<a href="http://www.macports.org/">Macports</a>,号称和BSD的ports一样自动下载源代码编译安装的工具。那么为什么Homebrew如今却广受欢迎呢,我想了想</p>
<ul>
<li>支持最新的MacOS</li>
<li>包含着常用的大多数软件包</li>
<li>更新很快</li>
<li>够Cool! (使用Git方式发布,项目托管在Github上)</li>
<li><strike>是用ruby写的XDDD</strike></li>
</ul>
<p>brew里的软件是通过Formula方式写成一个打包文件,git同步后,让brew系统来读取这个Formula进行常见的下载、编译、安装过程。看起来和gentoo portage或者BSD的ports如出一辙(如果使用archlinux,可以理解成ABS)。</p>
<p>平时在使用系统的时候比较大的一项烦恼是,我有同一个软件,有不同的版本想要同时使用?如果软件包本身不妨碍多个版本共存那自然是皆大欢喜,不幸的是多数软件都不支持。进一步讲,我是一个开发者,需要引用不同的第三方库的时候这个问题怎么解决呢?</p>
<p>大家也许想到了开发语言自带的包管理器和版本控制工具,这方面的例子,rvm与gem/bundler首当其冲。假使您还不了解其中的重要性,推荐阅读一下<a href="http://www.12factor.net/dependencies">『The Twelve-Factor App』里关于项目依赖的部分</a>。其他的例子,还有python的setuptools/pip等等。自然的,<a href="http://blog.astrumfutura.com/2007/10/to-pear-or-not-to-pear-and-how-to-pear-anyway/">这个方面做的比较差的也有</a>。</p>
<p>更进一步讲,要是我使用的是系统的库呢,除了像<a href="https://github.com/CocoaPods/CocoaPods">CocoaPods</a>这种吸收了gem的新兴工具外,似乎走进了一个死胡同。其实不然,我们可以选择带有这样功能的包管理器或者系统!</p>
<h2>这就是为什么我喜爱gentoo与homebrew!</h2>
<p><a href="http://www.gentoo.org/doc/en/handbook/handbook-x86.xml?part=2&chap=1#doc_chap5">gentoo portage里有一个叫做slot的功能</a>,简单地解释一下,在portage的版本树里一个软件包可以有多个版本的ebuild文件,并把他们划分成不同地slot——例如python——大家都知道python目前有2.x和3.x的大版本区别,在portage里python就有几个slot(见下面的portage记录,版本之前括号里的就是slot),其中有2.7和3.2,他们属于同一个包下的多个slot版本;portage允许同一个包里多个slot并存,一般还会附赠一个<code>eselect-foobar</code>的包来提供版本的选择</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>[I] dev-lang/python
</span><span class='line'> Available versions:
</span><span class='line'> (2.2) ~2.2-r8[1]
</span><span class='line'> (2.5) 2.5.4-r4 ~2.5.4-r5
</span><span class='line'> (2.6) 2.6.8 ~2.6.8-r1
</span><span class='line'> (2.7) 2.7.3-r2 ~2.7.3-r3
</span><span class='line'> (3.1) 3.1.5 ~3.1.5-r1
</span><span class='line'> (3.2) 3.2.3 ~3.2.3-r1 ~3.2.3-r2
</span><span class='line'> (3.3) **3.3.0 **3.3.0-r1
</span><span class='line'>
</span><span class='line'> Installed versions: 2.7.3-r2(2.7)(03:02:44 PM 01/19/2013)(gdbm ipv6 ncurses readline sqlite ssl threads wide-unicode xml -berkdb -build -doc -elibc_uclibc -examples -tk -wininst) 3.2.3(3.2)(03:04:49 PM 01/19/2013)(gdbm ipv6 ncurses readline sqlite ssl threads wide-unicode xml -build -doc -elibc_uclibc -examples -tk -wininst)
</span><span class='line'> Homepage: http://www.python.org
</span><span class='line'> Description: A really great language
</span><span class='line'>
</span><span class='line'>[I] app-admin/eselect-python
</span><span class='line'> Available versions: 20091230 20100321 ~20111108 **99999999
</span><span class='line'> Installed versions: 20100321(02:18:04 PM 11/07/2012)
</span><span class='line'> Homepage: http://www.gentoo.org
</span><span class='line'> Description: Eselect module for management of multiple Python versions</span></code></pre></td></tr></table></div></figure>
<p>对于gentoo portage来说,不同的包版本安装的路径是不同的,例如2.7和3.2分别安装在<code>/usr/lib/python2.7</code>与<code>/usr/lib/python3.2</code>下,利用特殊的机制(eselect和软链等,python在gentoo下使用了python-wrapper)来实现多版本并存的功能。</p>
<p>不过可以留意到的是,在portage里同一个slot下也会有多个版本,他们之间是无法并存的XDD</p>
<h3>那么故事讲了这么多Homebrew到底有什么能耐呢?</h3>
<p>其实我前几天在自己的Mac姬上需要测试最新版本的zeromq 3.x,而同时另外有一个项目的zeromq是要求2.1.x版本的;按照常规做法,要么我自己手工编译两个lib分开放(意味着编译链接的FLAG都要自己设置,不开玩笑…),要么在开发项目A时重新安装3.x,开发项目B时重新安装2.1.x,岂不蛋疼?</p>
<p>让我们回想一下Homebrew的目录结构:官方建议安装在/usr/local下(因为MacOS不会使用这个目录)</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>├── CONTRIBUTING.md
</span><span class='line'>├── Cellar
</span><span class='line'>├── Library
</span><span class='line'>├── README.md
</span><span class='line'>├── bin
</span><span class='line'>├── etc
</span><span class='line'>├── include
</span><span class='line'>├── lib
</span><span class='line'>├── opt
</span><span class='line'>├── sbin
</span><span class='line'>├── share
</span><span class='line'>├── tmp
</span><span class='line'>└── var</span></code></pre></td></tr></table></div></figure>
<p>bin和lib下是常见的直接使用的文件,实际上是做了软链接到Cellar下具体某个包、某个版本下,例如</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>ls -lah lib/libzmq.a
</span><span class='line'>lrwxr-xr-x 1 aleiphoenix wheel 36B Jan 16 15:54 lib/libzmq.a -> ../Cellar/zeromq/2.1.11/lib/libzmq.a</span></code></pre></td></tr></table></div></figure>
<p>这下明白了吧,由于brew的实现已经是同一个包不同版本的分开放置,所以要支持多版本切换,只是写一个切换软链接的工具而已,而brew里已经自带了这个功能;并且这个功能比gentoo portage的slot更自由一些,因为任意版本之间都是可以并存的XDDD</p>
<ul>
<li>查看已经安装的版本 <code>brew info</code></li>
</ul>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>brew info zeromq
</span><span class='line'>zeromq: stable 3.2.2, HEAD
</span><span class='line'>http://www.zeromq.org/
</span><span class='line'>Depends on: pkg-config
</span><span class='line'>/usr/local/Cellar/zeromq/2.1.11 (41 files, 1.6M) *
</span><span class='line'>/usr/local/Cellar/zeromq/2.2.0 (41 files, 1.6M)
</span><span class='line'>/usr/local/Cellar/zeromq/3.2.2 (54 files, 2.2M)</span></code></pre></td></tr></table></div></figure>
<ul>
<li>查看所有版本 <code>brew versions</code></li>
</ul>
<p>这里有个技巧,由于brew的目录是用git管理的,所以对于Formula来说始终是一个文件(名),利用git命令把需要的版本checkout出来,就可以使用了XDDD</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>brew versions zeromq
</span><span class='line'>3.2.2 git checkout ab8de4b Library/Formula/zeromq.rb
</span><span class='line'>2.2.0 git checkout 6a2e6ef Library/Formula/zeromq.rb
</span><span class='line'>2.1.11 git checkout 497b13a Library/Formula/zeromq.rb
</span><span class='line'>2.1.10 git checkout 4c8ed3a Library/Formula/zeromq.rb
</span><span class='line'>2.1.9 git checkout 381c97f Library/Formula/zeromq.rb
</span><span class='line'>2.1.7 git checkout ed41f79 Library/Formula/zeromq.rb
</span><span class='line'>2.1.8 git checkout 8e045d5 Library/Formula/zeromq.rb
</span><span class='line'>2.1.6 git checkout 460a168 Library/Formula/zeromq.rb
</span><span class='line'>2.1.4 git checkout 83ed494 Library/Formula/zeromq.rb
</span><span class='line'>2.1.3 git checkout f4a925d Library/Formula/zeromq.rb
</span><span class='line'>2.1.2 git checkout 3017b39 Library/Formula/zeromq.rb
</span><span class='line'>2.1.1 git checkout 0476235 Library/Formula/zeromq.rb
</span><span class='line'>2.0.10 git checkout 00e1ae3 Library/Formula/zeromq.rb
</span><span class='line'>2.0.9 git checkout 0527b6f Library/Formula/zeromq.rb
</span><span class='line'>2.0.8 git checkout 1f252bf Library/Formula/zeromq.rb
</span><span class='line'>2.0.7 git checkout ce8d2f5 Library/Formula/zeromq.rb</span></code></pre></td></tr></table></div></figure>
<ul>
<li>切换至目标版本 <code>brew switch</code></li>
</ul>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>brew switch zeromq 3.2.2
</span><span class='line'>Cleaning /usr/local/Cellar/zeromq/2.1.11
</span><span class='line'>Cleaning /usr/local/Cellar/zeromq/2.2.0
</span><span class='line'>Cleaning /usr/local/Cellar/zeromq/3.2.2
</span><span class='line'>49 links created for /usr/local/Cellar/zeromq/3.2.2</span></code></pre></td></tr></table></div></figure>
<p>当然,这个并不表示brew一点问题也没有,比如某个libA引用的是特定版本的libB,在libB切换版本后libA可能就出错了。</p>
<blockquote><p>“It’s up to you, Mason. It’s all up to you.”</p></blockquote>
<p>所以XDDDD</p>
<p>总体而言,brew这个功能给开发者(<strike>不折腾会死星人</strike>)提供了莫大的便利。那天发现这个功能时,不禁感叹,『这不就是gentoo portage么!』 XDDDDDDDDDD</p>
<p>__END__</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Nginx热升级]]></title>
<link href="http://arch.corp.anjuke.com/blog/2012/12/23/nginx-live-upgrade/"/>
<updated>2012-12-23T22:11:00+08:00</updated>
<id>http://arch.corp.anjuke.com/blog/2012/12/23/nginx-live-upgrade</id>
<content type="html"><![CDATA[<p>系统管理员可以使用Nginx提供的信号机制来对其进行维护,比较常用的是<code>kill -HUP <master pid></code>命令,它能通知Nginx使用新的配置文件启动工作进程,并逐个关闭旧进程,完成平滑切换。当需要对Nginx进行版本升级或增减模块时,为了不丢失请求,可以结合使用<code>USR2</code>、<code>WINCH</code>等信号进行平滑过度,达到热升级的目的。如果中途遇到问题,也能立刻回退至原版本。</p>
<h2>操作步骤</h2>
<p>1、备份原Nginx二进制文件;</p>
<p>2、编译新Nginx源码,安装路径需与旧版一致;</p>
<p>3、向主进程发送<code>USR2</code>信号,Nginx会启动一个新版本的master进程和工作进程,和旧版一起处理请求:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'>prey:~ root# ps -ef|grep nginx
</span><span class='line'> 127 1 nginx: master process /usr/local/nginx-1.2.4/sbin/nginx
</span><span class='line'> 129 127 nginx: worker process
</span><span class='line'>prey:~ root# <span class="nb">kill</span> -USR2 127
</span><span class='line'>prey:~ root# ps -ef|grep nginx
</span><span class='line'> 127 1 nginx: master process /usr/local/nginx-1.2.4/sbin/nginx
</span><span class='line'> 129 127 nginx: worker process
</span><span class='line'>5180 127 nginx: master process /usr/local/nginx-1.2.4/sbin/nginx
</span><span class='line'>5182 5180 nginx: worker process
</span></code></pre></td></tr></table></div></figure>
<!-- more -->
<p>4、向原Nginx主进程发送<code>WINCH</code>信号,它会逐步关闭旗下的工作进程(主进程不退出),这时所有请求都会由新版Nginx处理:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'>prey:~ root# <span class="nb">kill</span> -WINCH 127
</span><span class='line'>prey:~ root# ps -ef|grep nginx
</span><span class='line'> 127 1 nginx: master process /usr/local/nginx-1.2.4/sbin/nginx
</span><span class='line'>5180 127 nginx: master process /usr/local/nginx-1.2.4/sbin/nginx
</span><span class='line'>5182 5180 nginx: worker process
</span></code></pre></td></tr></table></div></figure>
<p>5、如果这时需要回退,可向原Nginx主进程发送<code>HUP</code>信号,它会重新启动工作进程, <strong>仍使用旧版配置文件</strong> 。尔后可以将新版Nginx进程杀死(使用<code>QUIT</code>、<code>TERM</code>、或者<code>KILL</code>):</p>
<p>6、如果不需要回滚,可以将原Nginx主进程杀死,至此完成热升级。</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'>prey:~ root# <span class="nb">kill </span>127
</span><span class='line'>prey:~ root# ps -ef|grep nginx
</span><span class='line'>5180 1 nginx: master process /usr/local/nginx-1.2.4/sbin/nginx
</span><span class='line'>5182 5180 nginx: worker process
</span></code></pre></td></tr></table></div></figure>
<p>切换过程中,Nginx会将旧的<code>.pid</code>文件重命名为<code>.pid.oldbin</code>文件,并在旧进程退出后删除。</p>
<h2>原理简介</h2>
<h3>多进程模式下的请求分配方式</h3>
<p>Nginx默认工作在多进程模式下,即主进程(master process)启动后完成配置加载和端口绑定等动作,<code>fork</code>出指定数量的工作进程(worker process),这些子进程会持有监听端口的文件描述符(fd),并通过在该描述符上添加监听事件来接受连接(accept)。</p>
<h3>信号的接收和处理</h3>
<p>Nginx主进程在启动完成后会进入等待状态,负责响应各类系统消息,如SIGCHLD、SIGHUP、SIGUSR2等。</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
</pre></td><td class='code'><pre><code class='c'><span class='line'><span class="c1">// src/os/unix/ngx_process_cycle.c</span>
</span><span class='line'><span class="kt">void</span>
</span><span class='line'><span class="nf">ngx_master_process_cycle</span><span class="p">(</span><span class="n">ngx_cycle_t</span> <span class="o">*</span><span class="n">cycle</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'> <span class="n">sigset_t</span> <span class="n">set</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'> <span class="n">sigemptyset</span><span class="p">(</span><span class="o">&</span><span class="n">set</span><span class="p">);</span>
</span><span class='line'> <span class="n">sigaddset</span><span class="p">(</span><span class="o">&</span><span class="n">set</span><span class="p">,</span> <span class="n">SIGCHLD</span><span class="p">);</span>
</span><span class='line'> <span class="n">sigaddset</span><span class="p">(</span><span class="o">&</span><span class="n">set</span><span class="p">,</span> <span class="n">ngx_signal_value</span><span class="p">(</span><span class="n">NGX_RECONFIGURE_SIGNAL</span><span class="p">));</span>
</span><span class='line'> <span class="n">sigaddset</span><span class="p">(</span><span class="o">&</span><span class="n">set</span><span class="p">,</span> <span class="n">ngx_signal_value</span><span class="p">(</span><span class="n">NGX_CHANGEBIN_SIGNAL</span><span class="p">));</span>
</span><span class='line'>
</span><span class='line'> <span class="k">if</span> <span class="p">(</span><span class="n">sigprocmask</span><span class="p">(</span><span class="n">SIG_BLOCK</span><span class="p">,</span> <span class="o">&</span><span class="n">set</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">)</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'> <span class="n">ngx_log_error</span><span class="p">(</span><span class="n">NGX_LOG_ALERT</span><span class="p">,</span> <span class="n">cycle</span><span class="o">-></span><span class="n">log</span><span class="p">,</span> <span class="n">ngx_errno</span><span class="p">,</span>
</span><span class='line'> <span class="s">"sigprocmask() failed"</span><span class="p">);</span>
</span><span class='line'> <span class="p">}</span>
</span><span class='line'>
</span><span class='line'> <span class="k">for</span> <span class="p">(</span> <span class="p">;;</span> <span class="p">)</span> <span class="p">{</span>
</span><span class='line'>
</span><span class='line'> <span class="n">sigsuspend</span><span class="p">(</span><span class="o">&</span><span class="n">set</span><span class="p">);</span> <span class="c1">// 等待信号</span>
</span><span class='line'>
</span><span class='line'> <span class="c1">// 信号回调函数定义在 src/os/unix/ngx_process.c 中,</span>
</span><span class='line'> <span class="c1">// 它只负责设置全局变量,实际处理逻辑在这里。</span>
</span><span class='line'> <span class="k">if</span> <span class="p">(</span><span class="n">ngx_change_binary</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'> <span class="n">ngx_change_binary</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span><span class='line'> <span class="n">ngx_log_error</span><span class="p">(</span><span class="n">NGX_LOG_NOTICE</span><span class="p">,</span> <span class="n">cycle</span><span class="o">-></span><span class="n">log</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="s">"changing binary"</span><span class="p">);</span>
</span><span class='line'> <span class="n">ngx_new_binary</span> <span class="o">=</span> <span class="n">ngx_exec_new_binary</span><span class="p">(</span><span class="n">cycle</span><span class="p">,</span> <span class="n">ngx_argv</span><span class="p">);</span>
</span><span class='line'> <span class="p">}</span>
</span><span class='line'>
</span><span class='line'> <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>
<p>上述代码中的<code>ngx_exec_new_binary</code>函数会调用<code>execve</code>系统函数运行一个新的Nginx主进程,并将当前监听端口的文件描述符通过环境变量的方式传递给新的主进程,这样新的主进程<code>fork</code>出的子进程就同样能够对监听端口添加事件回调,接受连接,从而使得新老Nginx共同处理用户请求。</p>
<h2>其它方式</h2>
<p>除了使用上述方式进行Nginx热升级,还可以选择以下两种方式:</p>
<h3>Keepalived主备切换</h3>
<p>Nginx用作LB时一般会用<a href="http://www.keepalived.org/">Keepalived</a>做热备,即DNS解析指向一个虚拟IP(VIP),主备服务器上分别启动Keepalived进程,当Master健康检查失败,Slave会自动抢夺VIP,完成切换。</p>
<p>在进行热升级时就可以使用这种方式,在Slave上进行Nginx升级,然后关闭Master的Keepalived进程,完成VIP的漂移。测试完成后可以对继续对Master进行升级操作,或选择回滚。</p>
<h3>Tengine动态模块</h3>
<p>当需要增加Nginx模块时,必须对Nginx源码进行重新编译,然后采用上面提到的方式进行热升级。Nginx官网上说未来并无打算增加动态模块加载的功能,至少1.x中不会。</p>
<p>如果你愿意使用<a href="http://tengine.taobao.org/">Tengine</a>,淘宝开发的一个Nginx分支,它提供了动态模块加载(DSO)功能。这里简单介绍一下使用方法:</p>
<h4>动态添加内部模块</h4>
<p>以<code>http_sub_module</code>为例,到Nginx源码目录执行以下命令:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="nv">$ </span>./configure --with-http_sub_module<span class="o">=</span>shared
</span><span class='line'><span class="nv">$ </span>make
</span><span class='line'><span class="nv">$ </span>sudo make dso_install
</span></code></pre></td></tr></table></div></figure>
<p>它会将编译好的<code>ngx_http_sub_filter_module.so</code>文件复制到<code>/usr/local/nginx/modules</code>目录下。随后在Nginx配置文件中的最外层添加以下内容:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'>dso <span class="o">{</span>
</span><span class='line'> load ngx_http_sub_filter_module.so;
</span><span class='line'><span class="o">}</span>
</span></code></pre></td></tr></table></div></figure>
<p>执行<code>sbin/nginx -s reload</code>,就能完成模块的加载。</p>
<h4>第三方模块</h4>
<p>对于第三方模块,Tengine提供了<code>dso_tool</code>命令,能够一步安装:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'>sbin/dso_tool --add-module<span class="o">=</span>/home/dso/lua-nginx-module
</span></code></pre></td></tr></table></div></figure>
<h2>Nginx信号汇总</h2>
<p>以下内容译自:http://wiki.nginx.org/CommandLine</p>
<h3>主进程支持的信号</h3>
<ul>
<li><code>TERM</code>, <code>INT</code>: 立刻退出</li>
<li><code>QUIT</code>: 等待工作进程结束后再退出</li>
<li><code>KILL</code>: 强制终止进程</li>
<li><code>HUP</code>: 重新加载配置文件,使用新的配置启动工作进程,并逐步关闭旧进程。</li>
<li><code>USR1</code>: 重新打开日志文件</li>
<li><code>USR2</code>: 启动新的主进程,实现热升级</li>
<li><code>WINCH</code>: 逐步关闭工作进程</li>
</ul>
<h3>工作进程支持的信号</h3>
<ul>
<li><code>TERM</code>, <code>INT</code>: 立刻退出</li>
<li><code>QUIT</code>: 等待请求处理结束后再退出</li>
<li><code>USR1</code>: 重新打开日志文件</li>
</ul>
<h3>nginx -s signal 支持的信号</h3>
<ul>
<li><code>stop</code>: 等价于<code>TERM</code>, <code>INT</code></li>
<li><code>quit</code>: <code>QUIT</code></li>
<li><code>reopen</code>: <code>USR1</code></li>
<li><code>reload</code>: <code>HUP</code></li>
</ul>
<h3>使用方法</h3>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="nv">$ </span>sbin/nginx -s reload
</span><span class='line'><span class="nv">$ </span><span class="nb">kill</span> -HUP <span class="k">$(</span>cat logs/nginx.pid<span class="k">)</span>
</span></code></pre></td></tr></table></div></figure>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Clojure实战(3):使用Noir框架开发博客(下)]]></title>
<link href="http://arch.corp.anjuke.com/blog/2012/12/16/cia-noir-3/"/>
<updated>2012-12-16T20:20:00+08:00</updated>
<id>http://arch.corp.anjuke.com/blog/2012/12/16/cia-noir-3</id>
<content type="html"><![CDATA[<h2>Session和Cookie</h2>
<p>做网络编程的人肯定对这两个概念不陌生,因此这里就不介绍它们的定义和作用了。我们要实现的需求也很简单:用户通过一个表单登录,在当前窗口中保持登录状态,并可以选择“记住我”来免去关闭并新开窗口之后的重登录。显然,前者使用Session,后者使用Cookie。下面我们就来看Noir对这两者的支持。</p>
<h3>Session</h3>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='clojure'><span class='line'><span class="p">(</span><span class="nf">require</span> <span class="ss">'noir.session</span><span class="p">)</span>
</span><span class='line'><span class="p">(</span><span class="nf">noir.session/put!</span> <span class="ss">:username</span> <span class="s">"john"</span><span class="p">)</span>
</span><span class='line'><span class="p">(</span><span class="nf">noir.session/get</span> <span class="ss">:username</span> <span class="s">"nobody"</span><span class="p">)</span>
</span><span class='line'><span class="p">(</span><span class="nf">noir.session/clear!</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>
<p>很简单的API。注意<code>put!</code>函数中的<code>!</code>,和之前遇到的<code>?</code>一样,这种特殊字符是合法的函数名,但<code>!</code>习惯用来表示该方法会改变某个对象的状态,这里<code>put!</code>就表示会改变Session的状态。</p>
<p>Noir还提供了一种“闪信(Flash)”机制,主要用于在页面跳转之间暂存消息。如用户登录后会跳转到首页,如果想在首页显示“登录成功”的信息,就需要用到闪信了。闪信的API也放置在<code>noir.session</code>命名空间下:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='clojure'><span class='line'><span class="p">(</span><span class="nf">noir.session/flash-put!</span> <span class="s">"登录成功"</span><span class="p">)</span>
</span><span class='line'><span class="p">(</span><span class="nf">noir.session/flash-get</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>
<p>闪信的生命周期是一次请求,即在设置了闪信后的下一个请求中,可以多次<code>flash-get</code>,但再下一次请求就获取不到值了。</p>
<!-- more -->
<h3>Cookie</h3>
<p>Cookie的API示例如下:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='clojure'><span class='line'><span class="p">(</span><span class="nf">require</span> <span class="ss">'noir.cookies</span><span class="p">)</span>
</span><span class='line'><span class="p">(</span><span class="nf">noir.cookies/put!</span> <span class="ss">:user_id</span> <span class="p">(</span><span class="nb">str </span><span class="mi">1</span><span class="p">))</span>
</span><span class='line'><span class="p">(</span><span class="nf">noir.cookies/get</span> <span class="ss">:user_id</span><span class="p">)</span>
</span><span class='line'><span class="p">(</span><span class="nf">noir.cookies/put!</span> <span class="ss">:tracker</span> <span class="p">{</span><span class="ss">:value</span> <span class="p">(</span><span class="nb">str </span><span class="mi">29649</span><span class="p">)</span> <span class="ss">:path</span> <span class="s">"/"</span> <span class="ss">:max-age</span> <span class="mi">3600</span><span class="p">})</span>
</span></code></pre></td></tr></table></div></figure>
<p>需要注意的是,<code>put!</code>函数只支持字符串类型;对于Cookie超时时间的设置,一种是上面所写的多少秒过期,另一种是传入一个DateTime对象。对于时间日期的处理,Java自带的类库可能不太好用,这里推荐<a href="http://joda-time.sourceforge.net/">Joda Time</a>,它有更丰富的功能和更友善的API。</p>
<h2>登录页面</h2>
<p>这里我们跳过注册页面,因为它实现的功能和新建一篇文章很相近,所以读者可以自己完成。我们假定用户信息表的格式如下:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='sql'><span class='line'><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="o">`</span><span class="k">user</span><span class="o">`</span> <span class="p">(</span>
</span><span class='line'> <span class="o">`</span><span class="n">id</span><span class="o">`</span> <span class="nb">int</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="n">AUTO_INCREMENT</span><span class="p">,</span>
</span><span class='line'> <span class="o">`</span><span class="n">username</span><span class="o">`</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
</span><span class='line'> <span class="o">`</span><span class="n">password</span><span class="o">`</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">32</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
</span><span class='line'> <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="o">`</span><span class="n">id</span><span class="o">`</span><span class="p">)</span>
</span><span class='line'><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>
<p>其中password字段保存的是密码的MD5值(32位16进制字符串)。Clojure中没有提供专门的类库,因此需要调用Java来实现。下文会贴出它的实现代码。</p>
<p>我们重点来看对登录页面表单的处理。新建<code>src/blog/views/login.clj</code>文件,添加对<code>/login</code>的路由,显示一个包含用户名、密码、以及“记住我”复选框的表单。用户提交后,若验证成功,会跳转至<code>/whoami</code>页面,用来显示保存在session或者cookie中的信息。以下是关键代码:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
</pre></td><td class='code'><pre><code class='clojure'><span class='line'><span class="p">(</span><span class="nf">defpage</span> <span class="p">[</span><span class="ss">:post</span> <span class="s">"/login"</span><span class="p">]</span> <span class="p">{</span><span class="ss">:as</span> <span class="nv">forms</span><span class="p">}</span>
</span><span class='line'> <span class="p">(</span><span class="k">let </span><span class="p">[</span><span class="nv">userid</span> <span class="p">(</span><span class="nf">model-user/get-id</span> <span class="p">(</span><span class="ss">:username</span> <span class="nv">forms</span><span class="p">)</span> <span class="p">(</span><span class="ss">:password</span> <span class="nv">forms</span><span class="p">))]</span>
</span><span class='line'> <span class="p">(</span><span class="k">if </span><span class="nv">userid</span>
</span><span class='line'> <span class="p">(</span><span class="k">do </span><span class="p">(</span><span class="nf">session/put!</span> <span class="ss">:userid</span> <span class="nv">userid</span><span class="p">)</span>
</span><span class='line'> <span class="p">(</span><span class="nf">session/put!</span> <span class="ss">:username</span> <span class="p">(</span><span class="ss">:username</span> <span class="nv">forms</span><span class="p">))</span>
</span><span class='line'> <span class="p">(</span><span class="nb">when </span><span class="p">(</span><span class="nb">= </span><span class="p">(</span><span class="ss">:remember-me</span> <span class="nv">forms</span><span class="p">)</span> <span class="s">"1"</span><span class="p">)</span> <span class="c1">; “记住我”复选框</span>
</span><span class='line'> <span class="p">(</span><span class="nf">cookies/put!</span> <span class="ss">:userid</span> <span class="p">{</span><span class="ss">:value</span> <span class="p">(</span><span class="nb">str </span><span class="nv">userid</span><span class="p">)</span> <span class="ss">:max-age</span> <span class="mi">86400</span><span class="p">})</span> <span class="c1">; 保存登录状态,时限1天。</span>
</span><span class='line'> <span class="p">(</span><span class="nf">cookies/put!</span> <span class="ss">:username</span> <span class="p">{</span><span class="ss">:value</span> <span class="p">(</span><span class="ss">:username</span> <span class="nv">forms</span><span class="p">)</span> <span class="ss">:max-age</span> <span class="mi">86400</span><span class="p">}))</span>
</span><span class='line'> <span class="p">(</span><span class="nf">response/redirect</span> <span class="s">"/whoami"</span><span class="p">))</span> <span class="c1">; noir.response/redirect 302跳转</span>
</span><span class='line'> <span class="p">(</span><span class="nf">render</span> <span class="s">"/login"</span> <span class="nv">forms</span><span class="p">))))</span>
</span><span class='line'>
</span><span class='line'><span class="p">(</span><span class="nf">defpage</span> <span class="s">"/whoami"</span> <span class="p">[]</span> <span class="c1">; 先检测Session,再检测Cookie。</span>
</span><span class='line'> <span class="p">(</span><span class="nb">if-let </span><span class="p">[</span><span class="nv">userid</span> <span class="p">(</span><span class="nf">session/get</span> <span class="ss">:userid</span><span class="p">)]</span>
</span><span class='line'> <span class="p">(</span><span class="nf">session/get</span> <span class="ss">:username</span><span class="p">)</span>
</span><span class='line'> <span class="p">(</span><span class="nb">if-let </span><span class="p">[</span><span class="nv">userid</span> <span class="p">(</span><span class="nf">cookies/get</span> <span class="ss">:userid</span><span class="p">)]</span>
</span><span class='line'> <span class="p">(</span><span class="nf">do</span>
</span><span class='line'> <span class="p">(</span><span class="nf">session/put!</span> <span class="ss">:userid</span> <span class="nv">userid</span><span class="p">)</span>
</span><span class='line'> <span class="p">(</span><span class="k">let </span><span class="p">[</span><span class="nv">username</span> <span class="p">(</span><span class="nf">cookies/get</span> <span class="ss">:username</span><span class="p">)]</span>
</span><span class='line'> <span class="p">(</span><span class="nf">session/put!</span> <span class="ss">:username</span> <span class="nv">username</span><span class="p">)</span>
</span><span class='line'> <span class="nv">username</span><span class="p">))</span>
</span><span class='line'> <span class="s">"unknown"</span><span class="p">)))</span>
</span></code></pre></td></tr></table></div></figure>
<p>其中<code>if-let</code>和以下代码是等价的,类似的有<code>when-let</code>。</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='clojure'><span class='line'><span class="p">(</span><span class="k">let </span><span class="p">[</span><span class="nv">userid</span> <span class="p">(</span><span class="nf">session/get</span> <span class="ss">:userid</span><span class="p">)]</span>
</span><span class='line'> <span class="p">(</span><span class="k">if </span><span class="nv">userid</span>
</span><span class='line'> <span class="p">(</span><span class="k">do </span><span class="nv">...</span><span class="p">)</span>
</span><span class='line'> <span class="s">"unkown"</span><span class="p">))</span>
</span></code></pre></td></tr></table></div></figure>
<p>对用户表的操作我们放到<code>src/blog/models/user.clj</code>文件中:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
</pre></td><td class='code'><pre><code class='clojure'><span class='line'><span class="p">(</span><span class="kd">ns </span><span class="nv">blog.models.user</span>
</span><span class='line'> <span class="p">(</span><span class="ss">:require</span> <span class="p">[</span><span class="nv">clojure.java.jdbc</span> <span class="ss">:as</span> <span class="nv">sql</span><span class="p">]</span>
</span><span class='line'> <span class="p">[</span><span class="nv">blog.util</span> <span class="ss">:as</span> <span class="nv">util</span><span class="p">])</span>
</span><span class='line'> <span class="p">(</span><span class="ss">:use</span> <span class="p">[</span><span class="nv">blog.database</span> <span class="ss">:only</span> <span class="p">[</span><span class="nv">db-spec</span><span class="p">]]))</span>
</span><span class='line'>
</span><span class='line'><span class="p">(</span><span class="kd">defn </span><span class="nv">get-id</span> <span class="p">[</span><span class="nv">username</span> <span class="nv">password</span><span class="p">]</span>
</span><span class='line'> <span class="p">(</span><span class="k">let </span><span class="p">[</span><span class="nv">password-md5</span> <span class="p">(</span><span class="nf">util/md5</span> <span class="nv">password</span><span class="p">)]</span>
</span><span class='line'> <span class="p">(</span><span class="nf">sql/with-connection</span> <span class="nv">db-spec</span>
</span><span class='line'> <span class="p">(</span><span class="nf">sql/with-query-results</span> <span class="nv">rows</span>
</span><span class='line'> <span class="p">[</span><span class="s">"SELECT `id` FROM `user` WHERE `username` = ? AND `password` = ?"</span>
</span><span class='line'> <span class="nv">username</span> <span class="nv">password-md5</span><span class="p">]</span> <span class="c1">; 不要采用直接拼接字符串的方式,有SQL注入的危险。</span>
</span><span class='line'> <span class="p">(</span><span class="ss">:id</span> <span class="p">(</span><span class="nb">first </span><span class="nv">rows</span><span class="p">))))))</span>
</span></code></pre></td></tr></table></div></figure>
<p>最后,我们将MD5加密这类的函数放到<code>src/blog/util.clj</code>文件中:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='clojure'><span class='line'><span class="p">(</span><span class="kd">ns </span><span class="nv">blog.util</span>
</span><span class='line'> <span class="p">(</span><span class="ss">:import</span> <span class="nv">java.security.MessageDigest</span>
</span><span class='line'> <span class="nv">java.math.BigInteger</span><span class="p">))</span>
</span><span class='line'>
</span><span class='line'><span class="p">(</span><span class="kd">defn </span><span class="nv">md5</span> <span class="p">[</span><span class="nv">s</span><span class="p">]</span>
</span><span class='line'> <span class="p">(</span><span class="k">let </span><span class="p">[</span><span class="nv">algorithm</span> <span class="p">(</span><span class="nf">MessageDigest/getInstance</span> <span class="s">"MD5"</span><span class="p">)</span>
</span><span class='line'> <span class="nv">size</span> <span class="p">(</span><span class="nb">* </span><span class="mi">2</span> <span class="p">(</span><span class="nf">.getDigestLength</span> <span class="nv">algorithm</span><span class="p">))</span>
</span><span class='line'> <span class="nv">raw</span> <span class="p">(</span><span class="nf">.digest</span> <span class="nv">algorithm</span> <span class="p">(</span><span class="nf">.getBytes</span> <span class="nv">s</span><span class="p">))</span>
</span><span class='line'> <span class="nv">sig</span> <span class="p">(</span><span class="nf">.toString</span> <span class="p">(</span><span class="nf">BigInteger.</span> <span class="mi">1</span> <span class="nv">raw</span><span class="p">)</span> <span class="mi">16</span><span class="p">)</span>
</span><span class='line'> <span class="nv">padding</span> <span class="p">(</span><span class="nb">apply str </span><span class="p">(</span><span class="nb">repeat </span><span class="p">(</span><span class="nb">- </span><span class="nv">size</span> <span class="p">(</span><span class="nb">count </span><span class="nv">sig</span><span class="p">))</span> <span class="s">"0"</span><span class="p">))]</span>
</span><span class='line'> <span class="p">(</span><span class="nb">str </span><span class="nv">padding</span> <span class="nv">sig</span><span class="p">)))</span>
</span></code></pre></td></tr></table></div></figure>
<p><code>padding</code>的作用是当计算得到的MD5字符串不足32位时做补零的操作。如何得到一个包含N个”0”的字符串?这就是<code>(apply...)</code>那串代码做的工作。简单来说,<code>repeat</code>函数会返回一个序列,<code>apply</code>函数首先使用第1、第2个元素作为参数调用<code>str</code>函数,然后将执行结果和第3个元素作为参数调用<code>str</code>,依此类推。因此,<code>(apply str [1 2 3])</code>等价于<code>(str (str 1 2) 3)</code>。<code>clojure.string/join</code>提供了将序列连接为字符串的功能,用法是<code>(clojure.string/join (repeat ...))</code>,查看它的源码<code>(source clojure.string/join)</code>可以发现,它实质上也是采用了<code>apply</code>函数。</p>
<p>序列是Clojure的一个很重要的数据结构,有多种函数和惯用法,需要逐步积累这些知识。</p>
<h2>中间件</h2>
<p>如果需要在程序的多个地方获取用户的登录状态,可以将上述<code>/whoami</code>中的方法封装成函数,但是每次都要执行一次似乎有些冗余,因此我们可以将它放到中间件(Middleware)中。</p>
<p>中间件是<a href="http://en.wikipedia.org/wiki/Web_Server_Gateway_Interface">WSGI</a>类的网站程序中很重要的特性。如果将用户的一次访问分解成<code>请求->处理1->处理2->应答</code>,那么中间件就是其中的“处理”部分,可以增加任意多个。Noir的很多功能,像路由、Session等,都是通过中间件的形式进行组织的。</p>
<p>以下是一个空的中间件代码:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='clojure'><span class='line'><span class="p">(</span><span class="kd">ns </span><span class="nv">...</span>
</span><span class='line'> <span class="p">(</span><span class="ss">:require</span> <span class="p">[</span><span class="nv">noir.server</span> <span class="ss">:as</span> <span class="nv">server</span><span class="p">]))</span>
</span><span class='line'>
</span><span class='line'><span class="p">(</span><span class="kd">defn </span><span class="nv">my-middleware</span> <span class="p">[</span><span class="nv">handler</span><span class="p">]</span>
</span><span class='line'> <span class="p">(</span><span class="k">fn </span><span class="p">[</span><span class="nv">request</span><span class="p">]</span>
</span><span class='line'> <span class="p">(</span><span class="nf">handler</span> <span class="nv">request</span><span class="p">)))</span>
</span><span class='line'>
</span><span class='line'><span class="p">(</span><span class="nf">erver/add-middleware</span> <span class="nv">my-middleware</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>
<p>上述代码添加到<code>src/blog/server.clj</code>中可以直接运行,只是这个中间件没有做任何工作。中间件是一个函数,返回值是一个匿名函数(<code>defn</code>是基于<code>fn</code>的,详情可见<code>(doc defn)</code>)。<code>handler</code>参数则是前一个中间件返回的匿名函数,<code>request</code>是用户发送过来的请求(map形式)。这些中间件组合起来就成为了一条处理链。<code>add-middleware</code>则是Noir定义的函数,将用户自定义的中间件添加到处理链中。</p>
<p>下面我们就写这样一个中间件,每次请求时都去检测Session和Cookie中是否包含用户的登录信息,并将该信息放到<code>request</code>的map中:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class='clojure'><span class='line'><span class="p">(</span><span class="kd">defn </span><span class="nv">authenticate</span> <span class="p">[</span><span class="nv">handler</span><span class="p">]</span>
</span><span class='line'> <span class="p">(</span><span class="k">fn </span><span class="p">[</span><span class="nv">request</span><span class="p">]</span>
</span><span class='line'> <span class="p">(</span><span class="k">let </span><span class="p">[</span><span class="nv">user</span> <span class="p">(</span><span class="nb">if-let </span><span class="p">[</span><span class="nv">userid</span> <span class="p">(</span><span class="nf">session/get</span> <span class="ss">:userid</span><span class="p">)]</span>
</span><span class='line'> <span class="p">[</span><span class="nv">userid</span> <span class="p">(</span><span class="nf">session/get</span> <span class="ss">:username</span><span class="p">)]</span>
</span><span class='line'> <span class="p">(</span><span class="nb">when-let </span><span class="p">[</span><span class="nv">userid</span> <span class="p">(</span><span class="nf">cookies/get</span> <span class="ss">:userid</span><span class="p">)]</span>
</span><span class='line'> <span class="p">(</span><span class="k">let </span><span class="p">[</span><span class="nv">username</span> <span class="p">(</span><span class="nf">cookies/get</span> <span class="ss">:username</span><span class="p">)]</span>
</span><span class='line'> <span class="p">(</span><span class="nf">do</span>
</span><span class='line'> <span class="p">(</span><span class="nf">session/put!</span> <span class="ss">:userid</span> <span class="nv">userid</span><span class="p">)</span>
</span><span class='line'> <span class="p">(</span><span class="nf">session/put!</span> <span class="ss">:username</span> <span class="nv">username</span><span class="p">)</span>
</span><span class='line'> <span class="p">[</span><span class="nv">userid</span> <span class="nv">username</span><span class="p">]))))</span>
</span><span class='line'> <span class="nv">req</span> <span class="p">(</span><span class="k">if </span><span class="nv">user</span>
</span><span class='line'> <span class="p">(</span><span class="nb">assoc </span><span class="nv">request</span> <span class="ss">:user</span> <span class="p">(</span><span class="nb">zipmap </span><span class="p">[</span><span class="ss">:userid</span> <span class="ss">:username</span><span class="p">]</span> <span class="nv">user</span><span class="p">))</span>
</span><span class='line'> <span class="nv">request</span><span class="p">)]</span>
</span><span class='line'> <span class="p">(</span><span class="nf">handler</span> <span class="nv">req</span><span class="p">))))</span>
</span></code></pre></td></tr></table></div></figure>
<p>这段代码中对于session和cookies的调用和上面没有差异,比较陌生的可能是<code>assoc</code>和<code>zipmap</code>方法,他们都是用来操作map数据类型的:前者会向一个map对象添加键值,并返回一个新的map;后者则会接收两个序列作为参数,两两组合成一个map并返回。</p>
<p>这样我们就能将<code>/whoami</code>的代码修改为:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='clojure'><span class='line'><span class="p">(</span><span class="kd">ns </span><span class="nv">...</span>
</span><span class='line'> <span class="p">(</span><span class="ss">:require</span> <span class="p">[</span><span class="nv">noir.request</span> <span class="ss">:as</span> <span class="nv">request</span><span class="p">]))</span>
</span><span class='line'>
</span><span class='line'><span class="p">(</span><span class="nf">defpage</span> <span class="s">"/whoami"</span> <span class="p">[]</span>
</span><span class='line'> <span class="p">(</span><span class="nb">if-let </span><span class="p">[</span><span class="nv">user</span> <span class="p">(</span><span class="ss">:user</span> <span class="p">(</span><span class="nf">request/ring-request</span><span class="p">))]</span>
</span><span class='line'> <span class="p">(</span><span class="ss">:username</span> <span class="nv">user</span><span class="p">)</span>
</span><span class='line'> <span class="s">"unkown"</span><span class="p">))</span>
</span></code></pre></td></tr></table></div></figure>
<p>其中,<code>ring-request</code>用来获得用户的<code>request</code>map对象。</p>
<h2>程序发布</h2>
<p>这里介绍三种Web应用程序的发布方式。</p>
<h3>直接使用Leiningen</h3>
<p>如果服务器上安装有<code>lein</code>环境,则可以直接调用它来启动程序。只有一点需要注意,因为在默认情况下,<code>lein run</code>启动的程序会被包装在Leiningen的JVM中,这样会占用一些额外的内存,同时引起一些<code>stdin</code>方面的问题。解决方法是使用<code>lein trampoline run</code>命令来启动程序,这样Leiningen为程序启动一个独立的JVM,并退出自己的JVM。</p>
<h3>编译为独立Jar包</h3>
<p><code>lein uberjar</code>命令可以将项目编译后的代码及其所有的依赖包打入一个Jar文件中,和Maven的assembly插件类似。需要注意的是,Clojure文件在默认情况下是不会生成类文件的,而是在运行时进行解析。这样一来,当使用<code>java -jar</code>命令执行时会提示找不到类定义的错误。解决方法是为包含入口函数的模块生成类文件,需要在<code>src/blog/server.clj</code>的<code>ns</code>声明中添加<code>gen-class</code>标识:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='clojure'><span class='line'><span class="p">(</span><span class="kd">ns </span><span class="nv">blog.server</span>
</span><span class='line'> <span class="nv">...</span>
</span><span class='line'> <span class="p">(</span><span class="ss">:gen-class</span><span class="p">))</span>
</span></code></pre></td></tr></table></div></figure>
<p>然后就能打包运行了:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="nv">$ </span>lein uberjar
</span><span class='line'><span class="nv">$ </span>java -jar blog-0.1.0-SNAPSHOT-standalone.jar
</span><span class='line'>2012-12-23 00:07:47.417:INFO::jetty-6.1.x
</span><span class='line'>2012-12-23 00:07:47.430:INFO::Started SocketConnector@0.0.0.0:8080
</span></code></pre></td></tr></table></div></figure>
<p>可以在程序前部署一个Nginx代理做转发,配置方法就不在这里赘述了。</p>
<h3>使用Tomcat</h3>
<p>以上两种方法使用的都是Jetty这个Web容器,虽然比较方便,但在生产环境中我们更倾向于使用Tomcat。</p>
<p>对于Tomcat的安装这里不做讲解,读者可以到<a href="http://tomcat.apache.org/">Tomcat官网</a>查阅。</p>
<p>Clojure代码也需要做一些修改,我们需要提供一个接口供Tomcat调用,也就是<code>Handler</code>。在<code>src/blog/server.clj</code>中添加以下代码:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='clojure'><span class='line'><span class="p">(</span><span class="k">def </span><span class="nv">handler</span> <span class="p">(</span><span class="nf">server/gen-handler</span>
</span><span class='line'> <span class="p">{</span><span class="ss">:mode</span> <span class="ss">:prod</span>,
</span><span class='line'> <span class="ss">:ns</span> <span class="ss">'blog</span><span class="p">}))</span>
</span></code></pre></td></tr></table></div></figure>
<p><code>gen-handler</code>是Noir的函数,用来生成一个<code>Handler</code>。<code>'blog</code>前的单引号大家应该还有印象,它表示命名空间。</p>
<p><code>server.clj</code>还有一项内容需要修改:删除<code>load-views</code>,改为显式的<code>require</code>,这样才能保证在编译期间就加载路由配置,Tomcat才会认可。代码如下:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='clojure'><span class='line'><span class="p">(</span><span class="kd">ns </span><span class="nv">...</span>
</span><span class='line'> <span class="p">(</span><span class="ss">:require</span> <span class="p">[</span><span class="nv">blog.views</span> <span class="nv">welcome</span> <span class="nv">article</span><span class="p">]))</span>
</span><span class='line'>
</span><span class='line'><span class="c1">; (server/load-views "src/blog/views")</span>
</span></code></pre></td></tr></table></div></figure>
<p>和<code>uberjar</code>类似,我们需要使用<code>uberwar</code>来打包成一个包含所有依赖项的war包。不过这个工具是由一个Leiningen插件提供的:<code>lein-ring</code>,安装过程和<code>lein-noir</code>类似,首先在<code>project.clj</code>添加dev依赖,然后执行<code>lein deps</code>安装。要使上述<code>handler</code>生效,<code>project.clj</code>中还需要增加一项名为<code>:ring</code>的配置:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='clojure'><span class='line'><span class="p">(</span><span class="kd">defproject </span><span class="nv">blog</span> <span class="nv">...</span>
</span><span class='line'> <span class="nv">...</span>
</span><span class='line'> <span class="ss">:dev-dependencies</span> <span class="p">[</span><span class="nv">...</span>
</span><span class='line'> <span class="p">[</span><span class="nv">lein-ring</span> <span class="s">"0.7.5"</span><span class="p">]]</span>
</span><span class='line'> <span class="ss">:ring</span> <span class="p">{</span><span class="ss">:handler</span> <span class="nv">blog.server/handler</span><span class="p">})</span>
</span></code></pre></td></tr></table></div></figure>
<p>执行<code>lein ring uberwar</code>命令,将生成的war包放置到Tomcat的webapps目录中,命名为ROOT.war,也可以设置<a href="http://tomcat.apache.org/tomcat-7.0-doc/virtual-hosting-howto.html">Virtual Hosting</a>。片刻后,Tomcat会应用这个新的程序,我们就能在浏览器中访问了。</p>
<h2>发布至云端Heroku</h2>
<p>最后,我们来尝试将这个博客程序部署到线上环境中。如今云计算已经非常流行,有许多优秀的<a href="http://en.wikipedia.org/wiki/Platform_as_a_service">PaaS</a>平台,<a href="http://www.heroku.com">Heroku</a>就是其中之一。在Heroku上部署一个小型的应用是完全免费的,这里我们简述一下步骤,更详细的操作方法可以参考它的<a href="https://devcenter.heroku.com/articles/clojure">帮助文档</a>。</p>
<ul>
<li>登录Heroku网站并注册账号;</li>
<li>安装<a href="https://toolbelt.heroku.com/">Tookbelt</a>,从而能在命令行中使用<code>heroku</code>命令;</li>
<li>执行<code>heroku login</code>命令,输入账号密码,完成验证;</li>
<li>新建<code>src/Procfile</code>文件,输入<code>web: lein trampoline run blog.server</code>;</li>
<li>执行<code>foreman start</code>命令,可以在本地测试程序;</li>
<li>执行<code>heroku create</code>,Heroku会为你分配一个空间;</li>
<li>执行<code>git push heroku master</code>,将本地代码推送至云端,可以看到编译信息,并得到一个URL,通过它就能访问我们的应用程序了。</li>
</ul>
<p>以上步骤省略了数据库的配置,读者可以自行到<a href="https://addons.heroku.com/cleardb">Heroku ClearDB</a>页面查看配置方法。</p>
<h2>小结</h2>
<p>至此我们完成了对Noir网站开发框架的简介,也完成了对Clojure这门语言的入门介绍。不过《Clojure实战》系列还远没有结束,下一章开始我们会进入Clojure语言更擅长的领域——计算。我们会陆续介绍如何使用Clojure编写<a href="http://hadoop.apache.org">Hadoop</a> MapReduce脚本、编写<a href="http://www.storm-project.net">Storm</a> Topology、以及如何使用<a href="http://incanter.org/">Incanter</a>进行可视化数据分析。不过在此之前,我强烈建议读者能够回头看看第一章中提到的几个Clojure教程,这样能对Clojure语言的整体架构有一个印象,接下来的学习才会更为顺畅。</p>
<h3>PS</h3>
<p>在撰写这份Noir框架教程时,Noir作者宣布停止对Noir的开发和维护,鼓励开发者转而使用Ring+Compojure+lib-noir的方式进行开发。这对我们并无太大影响,毕竟我们只是利用Noir来学习Clojure,而且前文提过Noir本身就是基于Ring和Compojure这两个类库的,迁移起来非常方便,我会为此再写一篇博客的。</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Clojure实战(2):使用Noir框架开发博客(中)]]></title>
<link href="http://arch.corp.anjuke.com/blog/2012/12/08/cia-noir-2/"/>
<updated>2012-12-08T12:09:00+08:00</updated>
<id>http://arch.corp.anjuke.com/blog/2012/12/08/cia-noir-2</id>
<content type="html"><![CDATA[<h2>在Eclipse中编写Clojure代码</h2>