forked from manuelkiessling/nodebeginner.org
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index-zh-cn.html
2688 lines (2304 loc) · 217 KB
/
index-zh-cn.html
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
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Node入门 » 一本全面的Node.js教程</title>
<meta name="description" content="一本适合Node.js初学者的全面教程:教你如何使用服务端JavaScript来构建一个完整的web应用" />
<link rel="stylesheet" type="text/css" href="default.css" />
<style>
#book p {
text-align: left;
}
</style>
<script type="text/javascript">
// Google Analytics
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-2127388-6']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
// Disqus
var disqus_shortname = 'nodebeginner';
var disqus_identifier = 'nodebeginner-book-chinese';
var disqus_url = 'http://www.nodebeginner.org/index-zh-cn.html';
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
</head>
<body>
<img style="display: none;" src="the_node_beginner_book_cover_medium.png" height="256" width="171" />
<div id="forkmeongithub">
<a href="https://github.com/ManuelKiessling/NodeBeginnerBook"><img src="fork_me_on_github.png" width="149" height="149" alt="Fork me on GitHub" /></a>
</div>
<div id="translations">
<table>
<tr>
<td>
<a href="index-jp.html">
<div class="flag"><img src="jp-flag.png" width="24" height="24" alt="japanese flag" /></div>
<div class="text">日本語で読む</div>
</a>
</td>
<td>
<a href="index-es.html">
<div class="flag"><img src="es-flag.png" width="24" height="24" alt="spanish flag" /></div>
<div class="text">Lee este tutorial en Español</div>
</a>
</td>
<td>
<a href="index-kr.html">
<div class="flag"><img src="kr-flag.png" width="24" height="24" alt="korean flag" /></div>
<div class="text">이 튜토리얼을 한글로 보세요</div>
</a>
</td>
</tr>
<tr>
<td>
<a href="./">
<div class="flag"><img src="us-flag.png" width="24" height="24" alt="usa flag" /></div>
<div class="text">Read this tutorial in english</div>
</a>
</td>
<td>
<a href="index-zh-tw.html">
<div class="flag"><img src="tw-flag.png" width="24" height="24" alt="traditional chinese flag" /></div>
<div class="text">閱讀本書繁體中文版</div>
</a>
</td>
<td>
<a href="http://www.nodebeginner.ru">
<div class="flag"><img src="ru-flag.png" width="24" height="24" alt="russian flag" /></div>
<div class="text">Читать этот учебник на русском</div>
</a>
</td>
</tr>
</table>
</div>
<div class="buy-the-bundle cn">
<div class="cover">
<a href="/buy-chinese/"><img src="the_node_beginner_book_cover_medium_chinese.png" height="120" width="80" /></a>
</div>
<div class="description">
<p>
购买“Node入门”中文版电子书
</p>
<p>
<strong class="price dollarsign">$</strong><strong class="price">0.99</strong>
</p>
<p>
<a class="buttonlink" href="/buy-chinese/">
<div class="button">立即购买</div>
</a>
</p>
</div>
<div class="buy">
<p>
本书共42页
<br />
支持PDF格式,Kindle以及ePub格式
<br />
直接下载,免费更新
</p>
</div>
</div>
<div id="book">
<h1>Node入门</h1>
<div id="author">作者: <a href="http://twitter.com/manuelkiessling">Manuel Kiessling</a><br />
翻译: <a href="http://weibo.com/goddyzhao">goddyzhao</a> &
<a href="http://www.otakustay.com">GrayZhang</a> &
<a href="http://weibo.com/cmonday">MondayChen</a></div>
<a name="about"></a>
<h2>关于</h2>
<p>
本书致力于教会你如何用Node.js来开发应用,过程中会传授你所有所需的“高级”JavaScript知识。本书绝不是一本“Hello World”的教程。
</p>
<a name="status"></a>
<h3>状态</h3>
<p>
你正在阅读的已经是本书的最终版。因此,只有当进行错误更正以及针对新版本Node.js的改动进行对应的修正时,才会进行更新。
</p>
<p>
本书中的代码案例都在Node.js 0.6.11版本中测试过,可以正确工作。
</p>
<a name="intended-audience"></a>
<h3>读者对象</h3>
<p>
本书最适合与我有相似技术背景的读者: 至少对一门诸如Ruby、Python、PHP或者Java这样面向对象的语言有一定的经验;对JavaScript处于初学阶段,并且完全是一个Node.js的新手。
</p>
<p>
这里指的适合对其他编程语言有一定经验的开发者,意思是说,本书不会对诸如数据类型、变量、控制结构等等之类非常基础的概念作介绍。要读懂本书,这些基础的概念我都默认你已经会了。
</p>
<p>
然而,本书还是会对JavaScript中的函数和对象作详细介绍,因为它们与其他同类编程语言中的函数和对象有很大的不同。
</p>
<a name="structure"></a>
<h3>本书结构</h3>
<p>
读完本书之后,你将完成一个完整的web应用,该应用允许用户浏览页面以及上传文件。
</p>
<p>
当然了,应用本身并没有什么了不起的,相比为了实现该功能书写的代码本身,我们更关注的是如何创建一个框架来对我们应用的不同模块进行干净地剥离。 是不是很玄乎?稍后你就明白了。
</p>
<p>
本书先从介绍在Node.js环境中进行JavaScript开发和在浏览器环境中进行JavaScript开发的差异开始。
</p>
<p>
紧接着,会带领大家完成一个最传统的“Hello World”应用,这也是最基础的Node.js应用。
</p>
<p>
最后,会和大家讨论如何设计一个“真正”完整的应用,剖析要完成该应用需要实现的不同模块,并一步一步介绍如何来实现这些模块。
</p>
<p>
可以确保的是,在这过程中,大家会学到JavaScript中一些高级的概念、如何使用它们以及为什么使用这些概念就可以实现而其他编程语言中同类的概念就无法实现。
</p>
<p>
该应用所有的源代码都可以通过
<a href="https://github.com/ManuelKiessling/NodeBeginnerBook/tree/master/code/application">本书Github代码仓库</a>.
</p>
<div id="table-of-contents-headline">目录</div>
<div id="table-of-contents">
<ul>
<li><a href="#about">关于</a>
<ul>
<li><a href="#status">状态</a></li>
<li><a href="#intended-audience">读者对象</a></li>
<li><a href="#structure">本书结构</a></li>
</ul>
</li>
<li><a href="#javascript-and-nodejs">JavaScript与Node.js</a>
<ul>
<li><a href="#javascript-and-you">JavaScript与你</a></li>
<li><a href="#a-word-of-warning">简短申明</a></li>
<li><a href="#server-side-javascript">服务器端JavaScript</a></li>
<li><a href="#hello-world">“Hello World”</a></li>
</ul>
</li>
<li><a href="#a-full-blown-web-application-with-nodejs">一个完整的基于Node.js的web应用</a>
<ul>
<li><a href="#the-use-cases">用例</a></li>
<li><a href="#the-application-stack">应用不同模块分析</a></li>
</ul>
</li>
<li><a href="#building-the-application-stack">构建应用的模块</a>
<ul>
<li><a href="#a-basic-http-server">一个基础的HTTP服务器</a></li>
<li><a href="#analyzing-our-http-server">分析HTTP服务器</a></li>
<li><a href="#passing-functions-around">进行函数传递</a></li>
<li><a href="#how-function-passing-makes-our-http-server-work">函数传递是如何让HTTP服务器工作的</a></li>
<li><a href="#event-driven-callbacks">基于事件驱动的回调</a></li>
<li><a href="#how-our-server-handles-requests">服务器是如何处理请求的</a></li>
<li><a href="#finding-a-place-for-our-server-module">服务端的模块放在哪里</a>
</li>
<li><a href="#whats-needed-to-route-requests">如何来进行请求的“路由”</a></li>
<li><a href="#execution-in-the-kongdom-of-verbs">行为驱动执行</a></li>
<li><a href="#routing-to-real-request-handlers">路由给真正的请求处理程序</a></li>
<li><a href="#making-the-request-handlers-respond">让请求处理程序作出响应</a>
<ul>
<li><a href="#how-to-not-do-it">不好的实现方式</a></li>
<li><a href="#blocking-and-non-blocking">阻塞与非阻塞</a></li>
<li><a href="#responding-request-handlers-with-non-blocking-operations">以非阻塞操作进行请求响应</a>
</li>
</ul>
</li>
<li><a href="#serving-something-useful">更有用的场景</a>
<ul>
<li><a href="#handling-post-requests">处理POST请求</a></li>
<li><a href="#handling-file-uploads">处理文件上传</a></li>
</ul>
</li>
<li><a href="#conclusion-and-outlook">总结与展望</a></li>
</ul>
</li>
</ul>
</div>
<a name="javascript-and-nodejs"></a>
<h2>JavaScript与Node.js</h2>
<a name="javascript-and-you"></a>
<h3>JavaScript与你</h3>
<p>
抛开技术,我们先来聊聊你以及你和JavaScript的关系。本章的主要目的是想让你看看,对你而言是否有必要继续阅读后续章节的内容。
</p>
<p>
如果你和我一样,那么你很早就开始利用HTML进行“开发”,正因如此,你接触到了这个叫JavaScript有趣的东西,而对于JavaScript,你只会基本的操作——为web页面添加交互。
</p>
<p>
而你真正想要的是“干货”,你想要知道如何构建复杂的web站点 —— 于是,你学习了一种诸如PHP、Ruby、Java这样的编程语言,并开始书写“后端”代码。
</p>
<p>
与此同时,你还始终关注着JavaScript,随着通过一些对jQuery,Prototype之类技术的介绍,你慢慢了解到了很多JavaScript中的进阶技能,同时也感受到了JavaScript绝非仅仅是<em>window.open() </em>那么简单。 .
</p>
<p>
不过,这些毕竟都是前端技术,尽管当想要增强页面的时候,使用jQuery总让你觉得很爽,但到最后,你顶多是个JavaScript<em>用户</em>,而非JavaScript<em>开发者</em>。
</p>
<p>
然后,出现了Node.js,服务端的JavaScript,这有多酷啊?
</p>
<p>
于是,你觉得是时候该重新拾起既熟悉又陌生的JavaScript了。但是别急,写Node.js应用是一件事情;理解为什么它们要以它们书写的这种方式来书写则意味着——你要懂JavaScript。这次是玩真的了。
</p>
<p>
问题来了: 由于JavaScript真正意义上以两种,甚至可以说是三种形态存在(从中世纪90年代的作为对DHTML进行增强的小玩具,到像jQuery那样严格意义上的前端技术,一直到现在的服务端技术),因此,很难找到一个“正确”的方式来学习JavaScript,使得让你书写Node.js应用的时候感觉自己是在真正开发它而不仅仅是使用它。
</p>
<p>
因为这就是关键: 你本身已经是个有经验的开发者,你不想通过到处寻找各种解决方案(其中可能还有不正确的)来学习新的技术,你要确保自己是通过正确的方式来学习这项技术。
</p>
<p>
当然了,外面不乏很优秀的学习JavaScript的文章。但是,有的时候光靠那些文章是远远不够的。你需要的是指导。
</p>
<p>
本书的目标就是给你提供指导。
</p>
<a name="a-word-of-warning"></a>
<h3>简短申明</h3>
<p>
业界有非常优秀的JavaScript程序员。而我并非其中一员。
</p>
<p>
我就是上一节中描述的那个我。我熟悉如何开发后端web应用,但是对“真正”的JavaScript以及Node.js,我都只是新手。我也只是最近学习了一些JavaScript的高级概念,并没有实践经验。
</p>
<p>
因此,本书并不是一本“从入门到精通”的书,更像是一本“从初级入门到高级入门”的书。
</p>
<p>
如果成功的话,那么本书就是我当初开始学习Node.js最希望拥有的教程。
</p>
<a name="server-side-javascript"></a>
<h3>服务端JavaScript</h3>
<p>
JavaScript最早是运行在浏览器中,然而浏览器只是提供了一个上下文,它定义了使用JavaScript可以做什么,但并没有“说”太多关于JavaScript语言本身可以做什么。事实上,JavaScript是一门“完整”的语言: 它可以使用在不同的上下文中,其能力与其他同类语言相比有过之而无不及。
</p>
<p>
Node.js事实上就是另外一种上下文,它允许在后端(脱离浏览器环境)运行JavaScript代码。
</p>
<p>
要实现在后台运行JavaScript代码,代码需要先被解释然后正确的执行。Node.js的原理正是如此,它使用了Google的V8虚拟机(Google的Chrome浏览器使用的JavaScript执行环境),来解释和执行JavaScript代码。
</p>
<p>
除此之外,伴随着Node.js的还有许多有用的模块,它们可以简化很多重复的劳作,比如向终端输出字符串。
</p>
<p>
因此,Node.js事实上既是一个运行时环境,同时又是一个库。
</p>
<p>
要使用Node.js,首先需要进行安装。关于如何安装Node.js,这里就不赘述了,可以直接参考<a href="https://github.com/joyent/node/wiki/Installation" title="Building and Installing Node.js">官方的安装指南</a>。安装完成后,继续回来阅读本书下面的内容。
</p>
<a name="hello-world"></a>
<h3>“Hello World”</h3>
<p>
好了,“废话”不多说了,马上开始我们第一个Node.js应用:“Hello World”。
</p>
<p>
打开你最喜欢的编辑器,创建一个<em>helloworld.js</em>文件。我们要做就是向STDOUT输出“Hello World”,如下是实现该功能的代码:
</p>
<pre class="prettyprint lang-js"><span class="pln">console</span><span class="pun">.</span><span
class="pln">log</span><span class="pun">(</span><span class="str">"Hello World"</span><span class="pun">);</span></pre>
<p>
保存该文件,并通过Node.js来执行:
</p>
<pre>node helloworld.js</pre>
<p>
正常的话,就会在终端输出<em>Hello World</em> 。
</p>
<p>
好吧,我承认这个应用是有点无趣,那么下面我们就来点“干货”。
</p>
<a name="a-full-blown-web-application-with-nodejs"></a>
<h2>一个完整的基于Node.js的web应用</h2>
<a name="the-use-cases"></a>
<h3>用例</h3>
<p>我们来把目标设定得简单点,不过也要够实际才行:</p>
<ul>
<li>用户可以通过浏览器使用我们的应用。</li>
<li>当用户请求<em>http://domain/start</em>时,可以看到一个欢迎页面,页面上有一个文件上传的表单。</li>
<li>用户可以选择一个图片并提交表单,随后文件将被上传到<em>http://domain/upload</em>,该页面完成上传后会把图片显示在页面上。</li>
</ul>
<p>差不多了,你现在也可以去Google一下,找点东西乱搞一下来完成功能。但是我们现在先不做这个。</p>
<p>更进一步地说,在完成这一目标的过程中,我们不仅仅需要基础的代码而不管代码是否优雅。我们还要对此进行抽象,来寻找一种适合构建更为复杂的Node.js应用的方式。</p>
<h3>应用不同模块分析</h3>
<p>我们来分解一下这个应用,为了实现上文的用例,我们需要实现哪些部分呢?</p>
<ul>
<li>我们需要提供Web页面,因此需要一个<em>HTTP服务器</em></li>
<li>对于不同的请求,根据请求的URL,我们的服务器需要给予不同的响应,因此我们需要一个<em>路由</em>,用于把请求对应到请求处理程序(request handler)</li>
<li>当请求被服务器接收并通过路由传递之后,需要可以对其进行处理,因此我们需要最终的<em>请求处理程序</em></li>
<li>路由还应该能处理POST数据,并且把数据封装成更友好的格式传递给请求处理入程序,因此需要<em>请求数据处理功能</em></li>
<li>我们不仅仅要处理URL对应的请求,还要把内容显示出来,这意味着我们需要一些<em>视图逻辑</em>供请求处理程序使用,以便将内容发送给用户的浏览器</li>
<li>最后,用户需要上传图片,所以我们需要<em>上传处理功能</em>来处理这方面的细节</li>
</ul>
<p>我们先来想想,使用PHP的话我们会怎么构建这个结构。一般来说我们会用一个Apache HTTP服务器并配上mod_php5模块。<br />从这个角度看,整个“接收HTTP请求并提供Web页面”的需求根本不需要PHP来处理。</p>
<p>不过对Node.js来说,概念完全不一样了。使用Node.js时,我们不仅仅在实现一个应用,同时还实现了整个HTTP服务器。事实上,我们的Web应用以及对应的Web服务器基本上是一样的。</p>
<p>听起来好像有一大堆活要做,但随后我们会逐渐意识到,对Node.js来说这并不是什么麻烦的事。</p>
<p>现在我们就来开始实现之路,先从第一个部分--HTTP服务器着手。</p>
<a name="building-the-application-stack"></a>
<h2>构建应用的模块</h2>
<a name="a-basic-http-server"></a>
<h3>一个基础的HTTP服务器</h3>
<p>
当我准备开始写我的第一个“真正的”Node.js应用的时候,我不但不知道怎么写Node.js代码,也不知道怎么组织这些代码。
<br>
我应该把所有东西都放进一个文件里吗?网上有很多教程都会教你把所有的逻辑都放进一个用Node.js写的基础HTTP服务器里。但是如果我想加入更多的内容,同时还想保持代码的可读性呢?
</p>
<p>
实际上,只要把不同功能的代码放入不同的模块中,保持代码分离还是相当简单的。
</p>
<p>
这种方法允许你拥有一个干净的主文件(main file),你可以用Node.js执行它;同时你可以拥有干净的模块,它们可以被主文件和其他的模块调用。
</p>
<p>
那么,现在我们来创建一个用于启动我们的应用的主文件,和一个保存着我们的HTTP服务器代码的模块。
</p>
<p>
在我的印象里,把主文件叫做<em>index.js</em>或多或少是个标准格式。把服务器模块放进叫<em>server.js</em>的文件里则很好理解。
</p>
<p>
让我们先从服务器模块开始。在你的项目的根目录下创建一个叫<em>server.js</em>的文件,并写入以下代码:
</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> http </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"http"</span><span class="pun">);</span><span class="pln"><br><br>http</span><span
class="pun">.</span><span class="pln">createServer</span><span class="pun">(</span><span class="kwd">function</span><span
class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span
class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br> response</span><span
class="pun">.</span><span class="pln">writeHead</span><span class="pun">(</span><span
class="lit">200</span><span class="pun">,</span><span class="pln"> </span><span
class="pun">{</span><span class="str">"Content-Type"</span><span class="pun">:</span><span
class="pln"> </span><span class="str">"text/plain"</span><span class="pun">});</span><span
class="pln"><br> response</span><span class="pun">.</span><span class="pln">write</span><span
class="pun">(</span><span class="str">"Hello World"</span><span class="pun">);</span><span
class="pln"><br> response</span><span class="pun">.</span><span class="pln">end</span><span
class="pun">();</span><span class="pln"><br></span><span class="pun">}).</span><span
class="pln">listen</span><span class="pun">(</span><span class="lit">8888</span><span
class="pun">);</span></pre>
<p>
搞定!你刚刚完成了一个可以工作的HTTP服务器。为了证明这一点,我们来运行并且测试这段代码。首先,用Node.js执行你的脚本:
</p>
<pre>node server.js</pre>
<p>
接下来,打开浏览器访问<a href="http://localhost:8888/" rel="nofollow">http://localhost:8888/</a>,你会看到一个写着“Hello World”的网页。
</p>
<p>
这很有趣,不是吗?让我们先来谈谈HTTP服务器的问题,把如何组织项目的事情先放一边吧,你觉得如何?我保证之后我们会解决那个问题的。
</p>
<a name="analyzing-our-http-server"></a>
<h3>分析HTTP服务器</h3>
<p>
那么接下来,让我们分析一下这个HTTP服务器的构成。
</p>
<p>
第一行<em>请求(require)</em>Node.js自带的 <em>http</em> 模块,并且把它赋值给 <em>http</em> 变量。
</p>
<p>
接下来我们调用http模块提供的函数: <em>createServer</em> 。这个函数会返回一个对象,这个对象有一个叫做 <em>listen</em> 的方法,这个方法有一个数值参数,指定这个HTTP服务器监听的端口号。
</p>
<p>
咱们暂时先不管 <em>http.createServer</em> 的括号里的那个函数定义。
</p>
<p>
我们本来可以用这样的代码来启动服务器并侦听8888端口:
</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> http </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"http"</span><span class="pun">);</span><span class="pln"><br><br></span><span class="kwd">var</span><span
class="pln"> server </span><span class="pun">=</span><span class="pln"> http</span><span
class="pun">.</span><span class="pln">createServer</span><span class="pun">();</span><span
class="pln"><br>server</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span
class="lit">8888</span><span class="pun">);</span></pre>
<p>
这段代码只会启动一个侦听8888端口的服务器,它不做任何别的事情,甚至连请求都不会应答。
</p>
<p>
最有趣(而且,如果你之前习惯使用一个更加保守的语言,比如PHP,它还很奇怪)的部分是 <em>createSever()</em> 的第一个参数,一个函数定义。
</p>
<p>
实际上,这个函数定义是 <em>createServer()</em> 的第一个也是唯一一个参数。因为在JavaScript中,函数和其他变量一样都是可以被传递的。
</p>
<a name="passing-functions-around"></a>
<h3>进行函数传递</h3>
<p>
举例来说,你可以这样做:
</p>
<pre class="prettyprint lang-js"><span class="kwd">function</span><span class="pln"> say</span><span
class="pun">(</span><span class="pln">word</span><span class="pun">)</span><span
class="pln"> </span><span class="pun">{</span><span class="pln"><br> console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span
class="pln">word</span><span class="pun">);</span><span class="pln"><br></span><span
class="pun">}</span><span class="pln"><br><br></span><span class="kwd">function</span><span class="pln"> execute</span><span
class="pun">(</span><span class="pln">someFunction</span><span class="pun">,</span><span class="pln"> value</span><span
class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br> someFunction</span><span
class="pun">(</span><span class="pln">value</span><span class="pun">);</span><span
class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br>execute</span><span
class="pun">(</span><span class="pln">say</span><span class="pun">,</span><span
class="pln"> </span><span class="str">"Hello"</span><span class="pun">);</span></pre>
<p>
请仔细阅读这段代码!在这里,我们把 <em>say</em> 函数作为<em>execute</em>函数的第一个变量进行了传递。这里返回的不是 <em>say</em> 的返回值,而是 <em>say</em> 本身!
</p>
<p>
这样一来, <em>say</em> 就变成了<em>execute</em> 中的本地变量 <em>someFunction</em> ,execute可以通过调用 <em>someFunction()</em> (带括号的形式)来使用 <em>say</em> 函数。
</p>
<p>
当然,因为 <em>say</em> 有一个变量, <em>execute</em> 在调用 <em>someFunction</em> 时可以传递这样一个变量。
</p>
<p>
我们可以,就像刚才那样,用它的名字把一个函数作为变量传递。但是我们不一定要绕这个“先定义,再传递”的圈子,我们可以直接在另一个函数的括号中定义和传递这个函数:
</p>
<pre class="prettyprint lang-js"><span class="kwd">function</span><span class="pln"> execute</span><span
class="pun">(</span><span class="pln">someFunction</span><span class="pun">,</span><span class="pln"> value</span><span
class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br> someFunction</span><span
class="pun">(</span><span class="pln">value</span><span class="pun">);</span><span
class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br>execute</span><span
class="pun">(</span><span class="kwd">function</span><span class="pun">(</span><span
class="pln">word</span><span class="pun">){</span><span class="pln"> console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span
class="pln">word</span><span class="pun">)</span><span class="pln"> </span><span
class="pun">},</span><span class="pln"> </span><span class="str">"Hello"</span><span
class="pun">);</span></pre>
<p>
我们在 <em>execute</em> 接受第一个参数的地方直接定义了我们准备传递给 <em>execute</em> 的函数。
</p>
<p>
用这种方式,我们甚至不用给这个函数起名字,这也是为什么它被叫做 <em>匿名函数</em> 。
</p>
<p>
这是我们和我所认为的“进阶”JavaScript的第一次亲密接触,不过我们还是得循序渐进。现在,我们先接受这一点:在JavaScript中,一个函数可以作为另一个函数接收一个参数。我们可以先定义一个函数,然后传递,也可以在传递参数的地方直接定义函数。
</p>
<a name="how-function-passing-makes-our-http-server-work"></a>
<h3>函数传递是如何让HTTP服务器工作的</h3>
<p>带着这些知识,我们再来看看我们简约而不简单的HTTP服务器:</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> http </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"http"</span><span class="pun">);</span><span class="pln"><br><br>http</span><span
class="pun">.</span><span class="pln">createServer</span><span class="pun">(</span><span class="kwd">function</span><span
class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span
class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br> response</span><span
class="pun">.</span><span class="pln">writeHead</span><span class="pun">(</span><span
class="lit">200</span><span class="pun">,</span><span class="pln"> </span><span
class="pun">{</span><span class="str">"Content-Type"</span><span class="pun">:</span><span
class="pln"> </span><span class="str">"text/plain"</span><span class="pun">});</span><span
class="pln"><br> response</span><span class="pun">.</span><span class="pln">write</span><span
class="pun">(</span><span class="str">"Hello World"</span><span class="pun">);</span><span
class="pln"><br> response</span><span class="pun">.</span><span class="pln">end</span><span
class="pun">();</span><span class="pln"><br></span><span class="pun">}).</span><span
class="pln">listen</span><span class="pun">(</span><span class="lit">8888</span><span
class="pun">);</span></pre>
<p>现在它看上去应该清晰了很多:我们向 <em>createServer</em> 函数传递了一个匿名函数。 </p>
<p>用这样的代码也可以达到同样的目的: </p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> http </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"http"</span><span class="pun">);</span><span class="pln"><br><br></span><span class="kwd">function</span><span
class="pln"> onRequest</span><span class="pun">(</span><span class="pln">request</span><span
class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span
class="pln"> </span><span class="pun">{</span><span class="pln"><br> response</span><span
class="pun">.</span><span class="pln">writeHead</span><span class="pun">(</span><span
class="lit">200</span><span class="pun">,</span><span class="pln"> </span><span
class="pun">{</span><span class="str">"Content-Type"</span><span class="pun">:</span><span
class="pln"> </span><span class="str">"text/plain"</span><span class="pun">});</span><span
class="pln"><br> response</span><span class="pun">.</span><span class="pln">write</span><span
class="pun">(</span><span class="str">"Hello World"</span><span class="pun">);</span><span
class="pln"><br> response</span><span class="pun">.</span><span class="pln">end</span><span
class="pun">();</span><span class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br>http</span><span
class="pun">.</span><span class="pln">createServer</span><span class="pun">(</span><span class="pln">onRequest</span><span
class="pun">).</span><span class="pln">listen</span><span class="pun">(</span><span
class="lit">8888</span><span class="pun">);</span></pre>
<p>也许现在我们该问这个问题了:我们为什么要用这种方式呢? </p>
<a name="event-driven-callbacks"></a>
<h3>基于事件驱动的回调</h3>
<p>这个问题可不好回答(至少对我来说),不过这是Node.js原生的工作方式。它是事件驱动的,这也是它为什么这么快的原因。 </p>
<p>你也许会想花点时间读一下Felix Geisendörfer的大作<a href="http://debuggable.com/posts/understanding-node-js:4bd98440-45e4-4a9a-8ef7-0f7ecbdd56cb">Understanding node.js</a>,它介绍了一些背景知识。 </p>
<p>这一切都归结于“Node.js是事件驱动的”这一事实。好吧,其实我也不是特别确切的了解这句话的意思。不过我会试着解释,为什么它对我们用Node.js写网络应用(Web based application)是有意义的。 </p>
<p>当我们使用 <em>http.createServer</em> 方法的时候,我们当然不只是想要一个侦听某个端口的服务器,我们还想要它在服务器收到一个HTTP请求的时候做点什么。 </p>
<p>问题是,这是异步的:请求任何时候都可能到达,但是我们的服务器却跑在一个单进程中。 </p>
<p>写PHP应用的时候,我们一点也不为此担心:任何时候当有请求进入的时候,网页服务器(通常是Apache)就为这一请求新建一个进程,并且开始从头到尾执行相应的PHP脚本。 </p>
<p>那么在我们的Node.js程序中,当一个新的请求到达8888端口的时候,我们怎么控制流程呢? </p>
<p>嗯,这就是Node.js/JavaScript的事件驱动设计能够真正帮上忙的地方了——虽然我们还得学一些新概念才能掌握它。让我们来看看这些概念是怎么应用在我们的服务器代码里的。 </p>
<p>我们创建了服务器,并且向创建它的方法传递了一个函数。无论何时我们的服务器收到一个请求,这个函数就会被调用。 </p>
<p>我们不知道这件事情什么时候会发生,但是我们现在有了一个处理请求的地方:它就是我们传递过去的那个函数。至于它是被预先定义的函数还是匿名函数,就无关紧要了。 </p>
<p>这个就是传说中的 <em>回调</em> 。我们给某个方法传递了一个函数,这个方法在有相应事件发生时调用这个函数来进行 <em>回调</em> 。 </p>
<p>至少对我来说,需要一些功夫才能弄懂它。你如果还是不太确定的话就再去读读Felix的博客文章。 </p>
<p>让我们再来琢磨琢磨这个新概念。我们怎么证明,在创建完服务器之后,即使没有HTTP请求进来、我们的回调函数也没有被调用的情况下,我们的代码还继续有效呢?我们试试这个: </p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> http </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"http"</span><span class="pun">);</span><span class="pln"><br><br></span><span class="kwd">function</span><span
class="pln"> onRequest</span><span class="pun">(</span><span class="pln">request</span><span
class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span
class="pln"> </span><span class="pun">{</span><span class="pln"><br> console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request received."</span><span
class="pun">);</span><span class="pln"><br> response</span><span class="pun">.</span><span
class="pln">writeHead</span><span class="pun">(</span><span class="lit">200</span><span
class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span
class="str">"Content-Type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"text/plain"</span><span
class="pun">});</span><span class="pln"><br> response</span><span class="pun">.</span><span
class="pln">write</span><span class="pun">(</span><span class="str">"Hello World"</span><span
class="pun">);</span><span class="pln"><br> response</span><span class="pun">.</span><span
class="pln">end</span><span class="pun">();</span><span class="pln"><br></span><span
class="pun">}</span><span class="pln"><br><br>http</span><span class="pun">.</span><span class="pln">createServer</span><span
class="pun">(</span><span class="pln">onRequest</span><span class="pun">).</span><span class="pln">listen</span><span
class="pun">(</span><span class="lit">8888</span><span class="pun">);</span><span class="pln"><br><br>console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Server has started."</span><span
class="pun">);</span></pre>
<p>注意:在 <em>onRequest</em> (我们的回调函数)触发的地方,我用 <em>console.log</em> 输出了一段文本。在HTTP服务器开始工作<em>之后</em>,也输出一段文本。 </p>
<p>
当我们与往常一样,运行它<em>node server.js</em>时,它会马上在命令行上输出“Server has started.”。当我们向服务器发出请求(在浏览器访问<a href="http://localhost:8888/" rel="nofollow">http://localhost:8888/</a> ),“Request received.”这条消息就会在命令行中出现。
</p>
<p>这就是事件驱动的异步服务器端JavaScript和它的回调啦!</p>
<p>(请注意,当我们在服务器访问网页时,我们的服务器可能会输出两次“Request received.”。那是因为大部分服务器都会在你访问 http://localhost:8888 /时尝试读取 http://localhost:8888/favicon.ico )</p>
<a name="how-our-server-handles-requests"></a>
<h3>服务器是如何处理请求的</h3>
<p>好的,接下来我们简单分析一下我们服务器代码中剩下的部分,也就是我们的回调函数 <em>onRequest()</em> 的主体部分。 </p>
<p>当回调启动,我们的 <em>onRequest()</em> 函数被触发的时候,有两个参数被传入: <em>request</em> 和 <em>response</em> 。 </p>
<p>它们是对象,你可以使用它们的方法来处理HTTP请求的细节,并且响应请求(比如向发出请求的浏览器发回一些东西)。 </p>
<p>所以我们的代码就是:当收到请求时,使用 <em>response.writeHead()</em> 函数发送一个HTTP状态200和HTTP头的内容类型(content-type),使用 <em>response.write()</em> 函数在HTTP相应主体中发送文本“Hello World"。 </p>
<p>最后,我们调用 <em>response.end()</em> 完成响应。 </p>
<p>目前来说,我们对请求的细节并不在意,所以我们没有使用 <em>request</em> 对象。 </p>
<a name="finding-a-place-for-our-server-module"></a>
<h3>服务端的模块放在哪里</h3>
<p>OK,就像我保证过的那样,我们现在可以回到我们如何组织应用这个问题上了。我们现在在 <em>server.js</em> 文件中有一个非常基础的HTTP服务器代码,而且我提到通常我们会有一个叫 <em>index.js</em> 的文件去调用应用的其他模块(比如 <em>server.js</em> 中的HTTP服务器模块)来引导和启动应用。 </p>
<p>我们现在就来谈谈怎么把server.js变成一个真正的Node.js模块,使它可以被我们(还没动工)的 <em>index.js</em> 主文件使用。</p>
<p>也许你已经注意到,我们已经在代码中使用了模块了。像这样: </p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> http </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"http"</span><span class="pun">);</span><span class="pln"><br><br></span><span class="pun">...</span><span
class="pln"><br><br>http</span><span class="pun">.</span><span class="pln">createServer</span><span
class="pun">(...);</span></pre>
<p>Node.js中自带了一个叫做“http”的模块,我们在我们的代码中请求它并把返回值赋给一个本地变量。 </p>
<p>这把我们的本地变量变成了一个拥有所有 <em>http</em> 模块所提供的公共方法的对象。</p>
<p>给这种本地变量起一个和模块名称一样的名字是一种惯例,但是你也可以按照自己的喜好来: </p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> foo </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"http"</span><span class="pun">);</span><span class="pln"><br><br></span><span class="pun">...</span><span
class="pln"><br><br>foo</span><span class="pun">.</span><span class="pln">createServer</span><span
class="pun">(...);</span></pre>
<p>很好,怎么使用Node.js内部模块已经很清楚了。我们怎么创建自己的模块,又怎么使用它呢? </p>
<p>等我们把 <em>server.js</em> 变成一个真正的模块,你就能搞明白了。 </p>
<p>事实上,我们不用做太多的修改。把某段代码变成模块意味着我们需要把我们希望提供其功能的部分 <em>导出</em> 到请求这个模块的脚本。 </p>
<p>目前,我们的HTTP服务器需要导出的功能非常简单,因为请求服务器模块的脚本仅仅是需要启动服务器而已。 </p>
<p>我们把我们的服务器脚本放到一个叫做 <em>start</em> 的函数里,然后我们会导出这个函数。 </p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> http </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"http"</span><span class="pun">);</span><span class="pln"><br><br></span><span class="kwd">function</span><span
class="pln"> start</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span
class="pln"><br> </span><span class="kwd">function</span><span class="pln"> onRequest</span><span
class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span
class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br> console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request received."</span><span
class="pun">);</span><span class="pln"><br> response</span><span class="pun">.</span><span
class="pln">writeHead</span><span class="pun">(</span><span class="lit">200</span><span
class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span
class="str">"Content-Type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"text/plain"</span><span
class="pun">});</span><span class="pln"><br> response</span><span class="pun">.</span><span
class="pln">write</span><span class="pun">(</span><span class="str">"Hello World"</span><span
class="pun">);</span><span class="pln"><br> response</span><span class="pun">.</span><span
class="pln">end</span><span class="pun">();</span><span class="pln"><br> </span><span class="pun">}</span><span
class="pln"><br><br> http</span><span class="pun">.</span><span
class="pln">createServer</span><span class="pun">(</span><span class="pln">onRequest</span><span
class="pun">).</span><span class="pln">listen</span><span class="pun">(</span><span
class="lit">8888</span><span class="pun">);</span><span class="pln"><br> console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Server has started."</span><span
class="pun">);</span><span class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br>exports</span><span
class="pun">.</span><span class="pln">start </span><span class="pun">=</span><span
class="pln"> start</span><span class="pun">;</span></pre>
<p>这样,我们现在就可以创建我们的主文件 <em>index.js</em> 并在其中启动我们的HTTP了,虽然服务器的代码还在 <em>server.js</em> 中。 </p>
<p>创建 <em>index.js</em> 文件并写入以下内容: </p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> server </span><span class="pun">=</span><span
class="pln"> require</span><span class="pun">(</span><span class="str">"./server"</span><span
class="pun">);</span><span class="pln"><br><br>server</span><span class="pun">.</span><span class="pln">start</span><span
class="pun">();</span></pre>
<p>正如你所看到的,我们可以像使用任何其他的内置模块一样使用server模块:请求这个文件并把它指向一个变量,其中已导出的函数就可以被我们使用了。</p>
<p>好了。我们现在就可以从我们的主要脚本启动我们的的应用了,而它还是老样子:</p>
<pre>node index.js</pre>
<p>非常好,我们现在可以把我们的应用的不同部分放入不同的文件里,并且通过生成模块的方式把它们连接到一起了。 </p>
<p>我们仍然只拥有整个应用的最初部分:我们可以接收HTTP请求。但是我们得做点什么——对于不同的URL请求,服务器应该有不同的反应。 </p>
<p>对于一个非常简单的应用来说,你可以直接在回调函数 <em>onRequest()</em> 中做这件事情。不过就像我说过的,我们应该加入一些抽象的元素,让我们的例子变得更有趣一点儿。 </p>
<p>处理不同的HTTP请求在我们的代码中是一个不同的部分,叫做“路由选择”——那么,我们接下来就创造一个叫做 <em>路由</em> 的模块吧。 </p>
<a name="whats-needed-to-route-requests"></a>
<h3>如何来进行请求的“路由”</h3>
<p>我们要为路由提供请求的URL和其他需要的GET及POST参数,随后路由需要根据这些数据来执行相应的代码(这里“代码”对应整个应用的第三部分:一系列在接收到请求时真正工作的处理程序)。</p>
<p>因此,我们需要查看HTTP请求,从中提取出请求的URL以及GET/POST参数。这一功能应当属于路由还是服务器(甚至作为一个模块自身的功能)确实值得探讨,但这里暂定其为我们的HTTP服务器的功能。</p>
<p>我们需要的所有数据都会包含在request对象中,该对象作为<em>onRequest()</em>回调函数的第一个参数传递。但是为了解析这些数据,我们需要额外的Node.JS模块,它们分别是<em>url</em>和<em>querystring</em>模块。</p>
<pre> url.parse(string).query
|
url.parse(string).pathname |
| |
| |
------ -------------------
http://localhost:8888/start?foo=bar&hello=world
--- -----
| |
| |
querystring(string)["foo"] |
|
querystring(string)["hello"]
</pre>
<p>当然我们也可以用<em>querystring</em>模块来解析POST请求体中的参数,稍后会有演示。</p>
<p>现在我们来给<em>onRequest()</em>函数加上一些逻辑,用来找出浏览器请求的URL路径:</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> http </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"http"</span><span class="pun">);</span><span class="pln"><br></span><span
class="kwd">var</span><span class="pln"> url </span><span class="pun">=</span><span
class="pln"> require</span><span class="pun">(</span><span class="str">"url"</span><span
class="pun">);</span><span class="pln"><br><br></span><span class="kwd">function</span><span
class="pln"> start</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span
class="pln"><br> </span><span class="kwd">function</span><span class="pln"> onRequest</span><span
class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span
class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br> </span><span
class="kwd">var</span><span class="pln"> pathname </span><span class="pun">=</span><span class="pln"> url</span><span
class="pun">.</span><span class="pln">parse</span><span class="pun">(</span><span
class="pln">request</span><span class="pun">.</span><span class="pln">url</span><span
class="pun">).</span><span class="pln">pathname</span><span class="pun">;</span><span class="pln"><br> console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request for "</span><span
class="pln"> </span><span class="pun">+</span><span class="pln"> pathname </span><span
class="pun">+</span><span class="pln"> </span><span class="str">" received."</span><span
class="pun">);</span><span class="pln"><br> response</span><span class="pun">.</span><span
class="pln">writeHead</span><span class="pun">(</span><span class="lit">200</span><span
class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span
class="str">"Content-Type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"text/plain"</span><span
class="pun">});</span><span class="pln"><br> response</span><span class="pun">.</span><span
class="pln">write</span><span class="pun">(</span><span class="str">"Hello World"</span><span
class="pun">);</span><span class="pln"><br> response</span><span class="pun">.</span><span
class="pln">end</span><span class="pun">();</span><span class="pln"><br> </span><span class="pun">}</span><span
class="pln"><br><br> http</span><span class="pun">.</span><span
class="pln">createServer</span><span class="pun">(</span><span class="pln">onRequest</span><span
class="pun">).</span><span class="pln">listen</span><span class="pun">(</span><span
class="lit">8888</span><span class="pun">);</span><span class="pln"><br> console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Server has started."</span><span
class="pun">);</span><span class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br>exports</span><span
class="pun">.</span><span class="pln">start </span><span class="pun">=</span><span
class="pln"> start</span><span class="pun">;</span></pre>
<p>好了,我们的应用现在可以通过请求的URL路径来区别不同请求了--这使我们得以使用路由(还未完成)来将请求以URL路径为基准映射到处理程序上。</p>
<p>在我们所要构建的应用中,这意味着来自<em>/start</em>和<em>/upload</em>的请求可以使用不同的代码来处理。稍后我们将看到这些内容是如何整合到一起的。</p>
<p>现在我们可以来编写路由了,建立一个名为<em>router.js</em>的文件,添加以下内容:</p>
<pre class="prettyprint lang-js"><span class="kwd">function</span><span class="pln"> route</span><span
class="pun">(</span><span class="pln">pathname</span><span class="pun">)</span><span
class="pln"> </span><span class="pun">{</span><span class="pln"><br> console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"About to route a request for "</span><span
class="pln"> </span><span class="pun">+</span><span class="pln"> pathname</span><span
class="pun">);</span><span class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br>exports</span><span
class="pun">.</span><span class="pln">route </span><span class="pun">=</span><span
class="pln"> route</span><span class="pun">;</span></pre>
<p>如你所见,这段代码什么也没干,不过对于现在来说这是应该的。在添加更多的逻辑以前,我们先来看看如何把路由和服务器整合起来。</p>
<p>我们的服务器应当知道路由的存在并加以有效利用。我们当然可以通过硬编码的方式将这一依赖项绑定到服务器上,但是其它语言的编程经验告诉我们这会是一件非常痛苦的事,因此我们将使用依赖注入的方式较松散地添加路由模块(你可以读读<a href="http://martinfowler.com/articles/injection.html">Martin Fowlers关于依赖注入的大作</a>来作为背景知识)。</p>
<p>首先,我们来扩展一下服务器的<em>start()</em>函数,以便将路由函数作为参数传递过去:</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> http </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"http"</span><span class="pun">);</span><span class="pln"><br></span><span
class="kwd">var</span><span class="pln"> url </span><span class="pun">=</span><span
class="pln"> require</span><span class="pun">(</span><span class="str">"url"</span><span
class="pun">);</span><span class="pln"><br><br></span><span class="kwd">function</span><span
class="pln"> start</span><span class="pun">(</span><span class="pln">route</span><span
class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span
class="pln"><br> </span><span class="kwd">function</span><span class="pln"> onRequest</span><span
class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span
class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br> </span><span
class="kwd">var</span><span class="pln"> pathname </span><span class="pun">=</span><span class="pln"> url</span><span
class="pun">.</span><span class="pln">parse</span><span class="pun">(</span><span
class="pln">request</span><span class="pun">.</span><span class="pln">url</span><span
class="pun">).</span><span class="pln">pathname</span><span class="pun">;</span><span class="pln"><br> console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request for "</span><span
class="pln"> </span><span class="pun">+</span><span class="pln"> pathname </span><span
class="pun">+</span><span class="pln"> </span><span class="str">" received."</span><span
class="pun">);</span><span class="pln"><br><br> route</span><span class="pun">(</span><span
class="pln">pathname</span><span class="pun">);</span><span
class="pln"><br><br> response</span><span class="pun">.</span><span
class="pln">writeHead</span><span class="pun">(</span><span class="lit">200</span><span
class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span
class="str">"Content-Type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"text/plain"</span><span
class="pun">});</span><span class="pln"><br> response</span><span class="pun">.</span><span
class="pln">write</span><span class="pun">(</span><span class="str">"Hello World"</span><span
class="pun">);</span><span class="pln"><br> response</span><span class="pun">.</span><span
class="pln">end</span><span class="pun">();</span><span class="pln"><br> </span><span class="pun">}</span><span
class="pln"><br><br> http</span><span class="pun">.</span><span
class="pln">createServer</span><span class="pun">(</span><span class="pln">onRequest</span><span
class="pun">).</span><span class="pln">listen</span><span class="pun">(</span><span
class="lit">8888</span><span class="pun">);</span><span class="pln"><br> console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Server has started."</span><span
class="pun">);</span><span class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br>exports</span><span
class="pun">.</span><span class="pln">start </span><span class="pun">=</span><span
class="pln"> start</span><span class="pun">;</span></pre>
<p>同时,我们会相应扩展<em>index.js</em>,使得路由函数可以被注入到服务器中:</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> server </span><span class="pun">=</span><span
class="pln"> require</span><span class="pun">(</span><span class="str">"./server"</span><span
class="pun">);</span><span class="pln"><br></span><span class="kwd">var</span><span
class="pln"> router </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"./router"</span><span class="pun">);</span><span class="pln"><br><br>server</span><span
class="pun">.</span><span class="pln">start</span><span class="pun">(</span><span
class="pln">router</span><span class="pun">.</span><span class="pln">route</span><span
class="pun">);</span><span class="pln"><br></span></pre>
<p>在这里,我们传递的函数依旧什么也没做。</p>
<p>如果现在启动应用(<em>node index.js,始终记得这个命令行</em>),随后请求一个URL,你将会看到应用输出相应的信息,这表明我们的HTTP服务器已经在使用路由模块了,并会将请求的路径传递给路由:</p>
<pre>bash$ node index.js
Request for /foo received.
About to route a request for /foo</pre>
<p>(以上输出已经去掉了比较烦人的/favicon.ico请求相关的部分)。</p>
<a name="execution-in-the-kongdom-of-verbs"></a>
<h3>行为驱动执行</h3>
<p>请允许我再次脱离主题,在这里谈一谈函数式编程。</p>
<p>将函数作为参数传递并不仅仅出于技术上的考量。对软件设计来说,这其实是个哲学问题。想想这样的场景:在index文件中,我们可以将<em>router</em>对象传递进去,服务器随后可以调用这个对象的<em>route</em>函数。</p>
<p>就像这样,我们传递一个东西,然后服务器利用这个东西来完成一些事。嗨那个叫路由的东西,能帮我把这个路由一下吗?</p>
<p>但是服务器其实不需要这样的东西。它只需要把事情做完就行,其实为了把事情做完,你根本不需要东西,你需要的是动作。也就是说,你不需要<em>名词</em>,你需要<em>动词</em>。</p>
<p>理解了这个概念里最核心、最基本的思想转换后,我自然而然地理解了函数编程。</p>
<p>我是在读了Steve Yegge的大作<a href="http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html">名词王国中的死刑</a>之后理解函数编程。你也去读一读这本书吧,真的。这是曾给予我阅读的快乐的关于软件的书籍之一。</p>
<a name="routing-to-real-request-handlers"></a>
<h3>路由给真正的请求处理程序</h3>
<p>回到正题,现在我们的HTTP服务器和请求路由模块已经如我们的期望,可以相互交流了,就像一对亲密无间的兄弟。</p>
<p>当然这还远远不够,路由,顾名思义,是指我们要针对不同的URL有不同的处理方式。例如处理<em>/start</em>的“业务逻辑”就应该和处理<em>/upload</em>的不同。</p>
<p>在现在的实现下,路由过程会在路由模块中“结束”,并且路由模块并不是真正针对请求“采取行动”的模块,否则当我们的应用程序变得更为复杂时,将无法很好地扩展。</p>
<p>我们暂时把作为路由目标的函数称为请求处理程序。现在我们不要急着来开发路由模块,因为如果请求处理程序没有就绪的话,再怎么完善路由模块也没有多大意义。</p>
<p>应用程序需要新的部件,因此加入新的模块 -- 已经无需为此感到新奇了。我们来创建一个叫做requestHandlers的模块,并对于每一个请求处理程序,添加一个占位用函数,随后将这些函数作为模块的方法导出:</p>
<pre class="prettyprint lang-js"><span class="kwd">function</span><span class="pln"> start</span><span
class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br> console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request handler 'start' was called."</span><span
class="pun">);</span><span class="pln"><br></span><span class="pun">}</span><span
class="pln"><br><br></span><span class="kwd">function</span><span class="pln"> upload</span><span
class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br> console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request handler 'upload' was called."</span><span
class="pun">);</span><span class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br>exports</span><span
class="pun">.</span><span class="pln">start </span><span class="pun">=</span><span
class="pln"> start</span><span class="pun">;</span><span class="pln"><br>exports</span><span
class="pun">.</span><span class="pln">upload </span><span class="pun">=</span><span
class="pln"> upload</span><span class="pun">;</span></pre>
<p>这样我们就可以把请求处理程序和路由模块连接起来,让路由“有路可寻”。</p>
<p>在这里我们得做个决定:是将requestHandlers模块硬编码到路由里来使用,还是再添加一点依赖注入?虽然和其他模式一样,依赖注入不应该仅仅为使用而使用,但在现在这个情况下,使用依赖注入可以让路由和请求处理程序之间的耦合更加松散,也因此能让路由的重用性更高。</p>
<p>这意味着我们得将请求处理程序从服务器传递到路由中,但感觉上这么做更离谱了,我们得一路把这堆请求处理程序从我们的主文件传递到服务器中,再将之从服务器传递到路由。</p>
<p>那么我们要怎么传递这些请求处理程序呢?别看现在我们只有2个处理程序,在一个真实的应用中,请求处理程序的数量会不断增加,我们当然不想每次有一个新的URL或请求处理程序时,都要为了在路由里完成请求到处理程序的映射而反复折腾。除此之外,在路由里有一大堆<em>if request == x then call handler y</em>也使得系统丑陋不堪。</p>
<p>仔细想想,有一大堆东西,每个都要映射到一个字符串(就是请求的URL)上?似乎关联数组(associative array)能完美胜任。</p>
<p>不过结果有点令人失望,JavaScript没提供关联数组 -- 也可以说它提供了?事实上,在JavaScript中,真正能提供此类功能的是它的对象。</p>
<p>在这方面,<a href="http://msdn.microsoft.com/en-us/magazine/cc163419.aspx">http://msdn.microsoft.com/en-us/magazine/cc163419.aspx</a>有一个不错的介绍,我在此摘录一段:</p>
<blockquote>
<p>在C++或C#中,当我们谈到对象,指的是类或者结构体的实例。对象根据他们实例化的模板(就是所谓的类),会拥有不同的属性和方法。但在JavaScript里对象不是这个概念。在JavaScript中,对象就是一个键/值对的集合 -- 你可以把JavaScript的对象想象成一个键为字符串类型的字典。</p>
</blockquote>
<p>但如果JavaScript的对象仅仅是键/值对的集合,它又怎么会拥有方法呢?好吧,这里的值可以是字符串、数字或者……函数!</p>
<p>好了,最后再回到代码上来。现在我们已经确定将一系列请求处理程序通过一个对象来传递,并且需要使用松耦合的方式将这个对象注入到<em>route()</em>函数中。</p>