forked from manuelkiessling/nodebeginner.org
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index-vi.html
1316 lines (1093 loc) · 90.4 KB
/
index-vi.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.js cho người mới bắt đầu » Tài liệu hướng dẫn toàn diện về Node.js</title>
<meta name="description" content="Một tài liệu hướng dẫn toàn diện cho người mới bắt đầu: Học cách xây dựng ứng dụng web sử dụng "server-side" Javascript" />
<link rel="icon" href="favicon.png" type="image/png" />
<link rel="stylesheet" type="text/css" href="default.css" />
</head>
<body>
<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="index-zh-cn.html">
<div class="flag"><img src="cn-flag.png" width="24" height="24" alt="chinese flag" /></div>
<div class="text">阅读本书中文版</div>
</a>
</td>
<td>
<a href="index-zh-tw.html">
<div class="flag"><img src="cn-flag.png" width="24" height="24" alt="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>
<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>
</td>
<td>
</td>
</tr>
</table>
</div>
<div class="buybox">
<div class="buy-the-bundle">
<div class="cover">
<p>
The perfect introduction plus the perfect reference in one bundle!
</p>
<a href="buy-bundle/index.html"><img src="the_node_beginner_book_cover_small.png" height="86" width="57" /></a>
<a href="buy-bundle/index.html"><img src="hands-on_node.js_cover.png" height="86" width="57" /></a>
</div>
<div class="description">
<p>
LeanBundle currently offers<br />
the final version of
<br />
<strong>The Node Beginner Book</strong>
<br />
plus Pedro Teixeira's excellent
<br />
<strong>Hands-on Node.js</strong> for only
<br />
<br />
<strong class="price dollarsign">$</strong><strong class="price">9.99</strong>
<br />
(regular price <del>$21.98</del>)
</p>
</div>
<div class="buy">
<p>
226 pages in total
<br />
PDF, ePub & MOBI
<br />
Direct download
<br />
Free updates
</p>
<a class="buttonlink" href="buy-bundle/index.html">
<div class="button">Buy this<br />bundle now</div>
</a>
</div>
</div>
</div>
<div id="book">
<div>
<h1>Node.js cho người mới bắt đầu</h1>
<div id="author">Tài liệu hướng dẫn về Node.js bởi <a href="http://twitter.com/manuelkiessling">Manuel Kiessling</a></br>
Dịch bởi <a href="http://twitter.com/tuanva">Tuan Vu</a>
</div>
<a name="about"></a>
<h2>Giới thiệu</h2>
<p>
Tài liệu này hướng tới mục tiêu giúp bạn làm quen với việc phát triển các ứng dụng sử dụng Node.js và dạy cho bạn tất cả những thứ bạn cần biết về Javascript "nâng cao", vượt xa các hướng dẫn "Hello World" bạn thường thấy.
</p>
<a name="status"></a>
<h3>Tình trạng</h3>
<p>
Bạn đang đọc phiên bản mới nhất của cuốn sách này, điều này có nghĩa là thay đổi chỉ được thực hiện để bắt kịp với các bản cập nhật cũng như vá lỗi của các phiên bản Node.js mới hơn. Thay đổi gần đây nhất vào ngày 01 tháng 07 năm 2013.
</p>
<p>
Mã nguồn các ví dụ trong cuốn sách này đã được kiểm tra và xác nhận hoạt động tốt với phiên bản Node.js 0.10.12.
</p>
<p>
Trang web này cho phép bạn đọc miễn phí từ trang 1 tới trang 21 của cuốn sách. Phiên bản đầy đủ được cung cấp miễn phí dưới dạng DRM-free eBook (PDF, ePub, và định dạng cho Kindle). Xem thêm thông tin phía cuối trang.
</p>
<a name="intended-audience"></a>
<h3>Đối tượng độc giả</h3>
<p>
Tôi chắc chắn rằng cuốn sách này phù hợp với các độc giả có kiến thức về lập trình tương đương với tôi, tức là: đã có kinh nghiệm với ít nhất một ngôn ngữ lập trình hướng đối tượng như Ruby, Python, PHP hay Java, một chút kinh nghiệm về Javascript và chưa biết gì về Node.js.
</p>
<p>
Hướng tới đối tượng là các lập trình viên đã có kinh nghiệm với ít nhất một ngôn ngữ lập trình khác đồng nghĩa với việc cuốn sách này sẽ không đề cập tới những khái niệm cơ bản trong lập trình như: kiểu dữ liệu, biến, các cấu trúc điều khiển v.v. Để hiểu tốt hơn về cuốn sách này, bạn cần nắm vững những khái niệm trên.
</p>
<p>
Tuy nhiên, hàm và đối tượng trong JavaScript có đôi chút khác biệt so với các ngôn ngữ khác nên nó sẽ được giải thích chi tiết hơn.
</p>
<a name="structure"></a>
<h3>Cấu trúc</h3>
<p>
Đọc xong cuốn sách này cũng đồng nghĩa với việc bạn đã xây dựng được một ứng dụng web hoàn chỉnh có khả năng cho phép người dùng truy cập vào qua các trang khác nhau và tải file lên.
</p>
<p>
Tuy những gì được đề cập trong cuốn sách này không mang tính chất "thay đổi thế giới", nhưng chúng ta sẽ tiến xa hơn là chỉ dừng lại ở việc thực hiện các ví dụ bằng cách xây dựng một framework có các thành phần được tổ chức, bố trí rõ ràng. Bạn sẽ được thấy nó trong ít phút nữa.
</p>
<p>
Chúng ta sẽ bắt đầu bằng việc so sánh sự khác nhau trong quá trình phát triển của JavaScript trong Node.js và trong trình duyệt.
</p>
<p>
Tiếp đó, chúng ta sẽ bắt tay vào thực hiện ví dụ "Hello World" truyền thống, ứng dụng Node.js cơ bản nhất.
</p>
<p>
Cuối cùng chúng ta sẽ thảo luận về một ứng dụng có tính thực tiễn mà chúng ta muốn xây dựng, phân tích kỹ lưỡng các thành phần cơ bản không thể thiếu của nó, và bắt tay lần lượt vào từng phần.
</p>
<p>
Như đã đề cập, song song đó chúng ta sẽ học một số khái niệm nâng cao về JavaScript, học cách áp dụng chúng, và cùng nghiên cứu tại sao nó lại tốt hơn những khái niệm tương tự trong các ngôn ngữ lập trình khác.
</p>
<p>
Mã nguồn hoàn thiện của ứng dụng có thể tải về tại địa chỉ
<a href="https://github.com/ManuelKiessling/NodeBeginnerBook/tree/master/code/application">the
NodeBeginnerBook Github repository</a>.
</p>
<div id="table-of-contents-headline">Mục lục</div>
<div id="table-of-contents">
<ul>
<li><a href="#about">Giới thiệu</a>
<ul>
<li><a href="#status">Trạng thái</a></li>
<li><a href="#intended-audience">Đối tượng độc giả</a></li>
<li><a href="#structure">Cấu trúc</a></li>
</ul>
</li>
<li><a href="#javascript-and-nodejs">JavaScript và Node.js</a>
<ul>
<li><a href="#javascript-and-you">JavaScript và Bạn</a></li>
<li><a href="#a-word-of-warning">Đôi lời nhắc nhở</a></li>
<li><a href="#server-side-javascript">Server-side JavaScript</a></li>
<li><a href="#hello-world">"Hello World"</a></li>
</ul>
</li>
<li><a href="#a-full-blown-web-application-with-nodejs">Một ứng dụng web hoàn thiện sử dụng Node.js</a>
<ul>
<li><a href="#the-use-cases">The use cases</a></li>
<li><a href="#the-application-stack">Cấu trúc ứng dụng</a></li>
</ul>
</li>
<li><a href="#building-the-application-stack">Xây dựng cấu trúc của ứng dụng</a>
<ul>
<li><a href="#a-basic-http-server">Một máy chủ HTTP đơn giản</a></li>
<li><a href="#analyzing-our-http-server">Phân tích máy chủ HTTP</a></li>
<li><a href="#passing-functions-around">Sử dụng hàm trong Node.js</a></li>
<li><a href="#how-function-passing-makes-our-http-server-work">Sử dụng hàm như vậy giúp máy chủ HTTP hoạt động như thế nào</a></li>
<li><a href="#event-driven-callbacks">Gọi ngược không đồng bộ dựa trên sự kiện</a></li>
<li><a href="#how-our-server-handles-requests">Máy chủ xử lý yêu cầu như thế nào</a></li>
<li><a href="#finding-a-place-for-our-server-module">Nơi lưu trữ "module" của máy chủ</a>
</li>
<li><a href="#whats-needed-to-route-requests">Cần những gì để điều hướng các request?</a></li>
<li><a href="#execution-in-the-kongdom-of-verbs">Thực thi và sự thực thi</a></li>
<li><a href="#routing-to-real-request-handlers">Sự điều hướng request tới request handlers</a></li>
<li><strong>Các chương chỉ có trong sách:</strong>
<li>Making the request handlers respond
<ul>
<li>How to not do it</li>
<li>Blocking and non-blocking</li>
<li>Responding request handlers with non-blocking operation</li>
</ul>
</li>
<li>Serving something useful
<ul>
<li>Handling POST requests</li>
<li>Handling file uploads</li>
</ul>
</li>
<li>Conclusion and outlook</li>
</li>
</ul>
</li>
</ul>
</div>
<a name="javascript-and-nodejs"></a>
<h2>JavaScript và Node.js</h2>
<a name="javascript-and-you"></a>
<h3>JavaScript và Bạn</h3>
<p>
Trước khi đề cập đến các vấn đề mang tính kỹ thuật, hãy dành một chút thời gian để nói về bản thân bạn và mối quan hệ của bạn với JavaScript. Sự hiện diện của chương này cho phép bạn có thể tự đánh giá được khả năng hiểu/lĩnh hội các nội dung tiếp theo.
</p>
<p>
Nếu bạn giống tôi, bắt đầu từ "lập trình" HTML, bằng việc viết các tài liệu HTML. Bạn được biết đến với một thứ rất hay có tên JavaScript, nhưng từ trước tới nay mới bạn chỉ sử dụng nó ở mức độ hết sức cơ bản là tăng sự tương tác với người dùng cho các trang web của bạn.
</p>
<p>
Cái mà bạn thật sự muốn là "cái gì đó mang tính thực tế", bạn muốn biết làm thế nào để xây dựng những website lớn và phức tạp - bạn học một ngôn ngữ lập trình chẳng hạn như PHP, Ruby, Java, và bắt đầu viết "backend" code. (Phần code sinh ra mã HTML).
</p>
<p>
Tuy nhiên, bạn vẫn quan tâm đến JavaScript, bạn thấy chúng qua các bài giới thiệu về jQuery, Prototype, hay tương tự như thế, khiến JavaScript trở nên cao cấp và phức tạp hơn với bạn, không hẳn chỉ dừng lại ở <em>window.open()</em>.
</p>
<p>
Xét cho cùng thì tất cả vẫn chỉ dừng lại ở phía frontend (thực thi trên trình duyệt), tuy rằng bạn biết thêm "gia vị" cho trang web của bạn bằng việc sử dụng jQuery, nhưng lạc quan mà nói, bạn vẫn chỉ là một <em>người dùng</em> JavaScript chứ không phải một <em>lập trình viên</em> JavaScript.
</p>
<p>
Và Node.js ra đời. JavaScript chạy phía máy chủ, thật tuyệt phải không?
</p>
<p>
Bạn quyết định sớm muộn gì cũng phải tìm hiểu về cái JavaScript cũ, mới đó. Nhưng đừng nóng vội, viết được ứng dụng sử dụng Node.js chưa phải là tất cả, hiểu được tại sao nó lại được viết như vậy mới gọi là - hiểu/nắm vững JavaScript. Và lần này mới là thực tế.
</p>
<p>
Vấn đề ở đây là: JavaScript thực sự có đến hai, thậm chí ba loại khác nhau (từ DHTML helper giữa những năm 90, đến các thư viện phía client/máy khách như jQuery, và bây giờ là phía máy chủ), không dễ dàng gì để tìm tài liệu giúp bạn học JavaScript đúng cách; để viết các ứng dụng sử dụng Node.js tạm ổn khiến bạn không cảm thấy giống như đang <em>sử dụng</em> JavaScript mà thực sự đang <em>phát triển</em> nó.
</p>
<p>
Hướng giải quyết: bạn đã là một lập trình viên có kinh nghiệm, bạn không muốn học một công nghệ/kỹ thuật mới chỉ bằng việc mổ xẻ lung tung hoặc dùng sai mục đích; bạn muốn chắc chắn rằng bạn đang tiếp cận nó từ một góc nhìn đúng đắn.
</p>
<p>
Tuy rằng luôn có các tài liệu tham khảo chính thức rất đầy đủ tồn tại. Nhưng chỉ tài liệu tham khảo không thôi thì chưa đủ. Cái bạn cần ở đây là sự hướng dẫn.
</p>
<p>
Vì thế mục đích của tôi là cung cấp một "người hướng dẫn" cho bạn.
</p>
<a name="a-word-of-warning"></a>
<h3>Vài lời nhắc nhở</h3>
<p>
Có rất nhiều lập trình viên JavaScript dày dặn kinh nghiệm ngoài kia, nhưng tôi không phải một trong số họ.
</p>
<p>
Tôi thực sự chỉ là người vừa được đề cập tới ở đoạn trước. Tôi biết một hoặc hai "tí" về phát triển ứng dụng web ở phía backend, JavaScript "thực thụ" vẫn còn là mới mẻ đối với tôi, cả Node.js cũng vậy. Tôi mới chỉ học được một vài khía cạnh nâng cao của JavaScript gần đây. Tôi không phải là "kẻ lão luyện".
</p>
<p>
Đó cũng là lý do vì sao đây không phải là cuốn sách để biến bạn "từ lính mới thành chuyên gia". Chính xác mà nói, nó giúp bạn "từ lính mới trở thành lính mới kỳ cựu" hơn.
</p>
<p>
Nếu không nhầm thì tôi đã khao khát có được một cuốn sách như thế này khi tôi bắt đầu tìm hiểu về Node.js.
</p>
<a name="server-side-javascript"></a>
<h3>Server-side JavaScript</h3>
<p>
Những "hiện thân" đầu tiên của JavaScript tồn tại trên trình duyệt. Nhưng đây thực sự chỉ là ngữ cảnh của nó. Người ta vạch rõ những cái mà bạn có thể làm với JavaScript chứ không hề đề cập tới những cái mà bản thân nó có thể làm được. JavaScript là một ngôn ngữ "hoàn thiện": bạn có thể sử dụng nó ở nhiều ngữ cảnh và đạt được kết quả tương tự như với bất kỳ một ngôn ngữ đã "hoàn thiện" nào khác.
</p>
<p>
Và Node.js thực tế chỉ là một ngữ cảnh khác: nó cho phép bạn chạy mã JavaScript ở phía backend, vượt ra khỏi phạm vi trình duyệt.
</p>
<p>
Để chạy được JavaScript phía backend, mã nguồn cần phải được biên dịch, và ...chạy. Đây chính là nhiệm vụ mà Node.js đảm nhiệm, bằng việc sử dụng lại máy ảo V8 của Google, hay còn được biết đến là môi trường chạy của JavaScript trên trình duyệt Google Chrome.
</p>
<p>
Thêm vào đó, Node.js còn cung cấp rất nhiều thư viện bổ sung (module) hữu ích, vì thế bạn sẽ không phải viết ứng dụng của bạn từ con số 0, ví dụ đơn giản nhất như hiển thị một dòng chữ nào đó ra màn hình.
</p>
<p>
Vì vậy có thể nói Node.js bao gồm 2 trong 1: một môi trường chạy (runtime environment) và một thư viện.
</p>
<p>
Để có thể tận dụng được hết những tính năng này, bạn cần phải cài đặt Node.js. Thay vì lặp lại các bước hướng dẫn cài đặt ở đây, bạn vui lòng làm theo
<a href="https://github.com/joyent/node/wiki/Installation" title="Building and Installing Node.js">hướng dẫn cài đặt chính thức tại đây</a>. Sau đó quay trở lại khi đã hoàn tất.
</p>
<a name="hello-world"></a>
<h3>"Hello World"</h3>
<p>
Ok, hãy cùng bắt tay vào viết ứng dụng sử dụng Node.js đầu tiên của chúng ta: "Hello World".
</p>
<p>
Hãy mở chương trình biên tập (editor) yêu thích của bạn ra và tạo một file mới có tên là <em>helloworld.js</em>. Chúng ta muốn in "Hello World"
ra STDOUT, và đây là tất cả những gì chúng ta cần:
</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>
Lưu file lại và chạy nó thông qua Node.js:
</p>
<pre>node helloworld.js</pre>
<p>
Chương trình sẽ in <em>Hello World</em> ra màn hình terminal hoặc command prompt của bạn.
</p>
<p>
Hơi buồn tẻ một chút phải không? Hãy cùng làm cái gì đó thực tế hơn.
</p>
<a name="a-full-blown-web-application-with-nodejs"></a>
<h2>Một ứng dụng web hoàn chính sử dụng Node.js</h2>
<a name="the-use-cases"></a>
<h3>The use cases</h3>
<p>
Đơn giản thôi, nhưng thực tế:
</p>
<ul>
<li>
Người dùng có thể truy cập vào ứng dụng trên một trình duyệt bất kỳ.
</li>
<li>
Người dùng sẽ nhìn thấy một trang chào đón có hiển thị một form cho phép tải file lên khi truy cập vào địa chỉ http://<em>domain</em>/start
</li>
<li>
Sau khi chọn một bức ảnh và ấn gửi đi, bức ảnh đó sẽ được gửi đến http://<em>domain</em>/upload, nơi nó sẽ được hiển thị sau khi quá trình tải lên hoàn tất.
</li>
</ul>
<p>
Thế là đủ rồi. Bây giờ, bạn có thể đạt được mục tiêu này bằng cách ... google và sửa code của một <em>ai đó</em>. Nhưng đó không phải là cách mà chúng ta làm ở đây.
</p>
<p>
Hơn nữa, chúng ta không muốn viết code đơn giản nhất có thể để "miễn sao xong là được", cái chúng ta muốn ở đây là chính xác và rõ ràng nhất có thể. Chúng ta sẽ sử dụng trừu tượng hoá nhiều hơn nhằm mục đích giúp bạn làm quen dần với việc xây dựng các ứng dụng Node.js phức tạp hơn.
</p>
<a name="the-application-stack"></a>
<h3>Cấu trúc của ứng dụng</h3>
<p>
Hãy cùng chia nhỏ ứng dụng của chúng ta ra để xem phần nào cần được thực hiện để thoả mãn yêu cầu đã đề ra (use cases).
</p>
<ul>
<li>
Chúng ta muốn cho phép người dùng truy cập vào các trang web, vì thế chúng ta cần một <strong>máy chủ HTTP</strong>
</li>
<li>
Máy chủ của chúng ta phải phản hồi/trả lời được các yêu cầu khác nhau, tuỳ thuộc vào địa chỉ (URL) nào được yêu cầu, vì thể chúng ta cần cái gì đó có chức năng giống như <strong>router</strong> (cầu nối/dẫn) để "nối" các yêu cầu đến nơi chuyên xử lý các yêu cầu (request handler) đó.
</li>
<li>
Để đáp ứng các yêu cầu nhận được ở phía máy chủ và đã được dẫn hướng thông qua router, chúng ta cần các <strong>request handlers</strong> thật sự.
</li>
<li>
Chắc chắn router của chúng ta sẽ xử lý tất cả các dữ liệu được gửi đến qua phương thức POST rồi gửi nó đi dưới một định dạng thuận tiện đến các request handlers, vì thế chúng ta cần <strong>request data handling</strong>. (Có thể hiểu là xử lý dữ liệu đầu vào).
</li>
<li>
Chúng ta không chỉ muốn quản lý các request theo đường dẫn mà còn hiển thị nội dung khi một đường dẫn nào đó được gọi, điều này có nghĩa chúng ta cần một <strong>view logic</strong> mà request handlers có thể sử dụng để gửi lại nội dung về trình duyệt của yêu cầu.
</li>
<li>
Cuối cùng, người dùng có thể tải ảnh lên, vì thể chúng ta cần bước <strong>upload handling</strong> để xử lý việc upload.
</li>
</ul>
<p>
Hãy dành một chút thời gian để nghĩ xem chúng ta sẽ xây dựng cấu trúc này với PHP như thế nào. Không quá khó để đoán ra, mô hình phổ biến sẽ là máy chủ web Apache và mod_php5.
<br>
Điều này đồng nghĩa với việc nhận, gửi và xử lý các yêu cầu không xảy ra trong bản thân PHP.
</p>
<p>
Với Node.js thì khác. Bởi vì chúng ta không chỉ viết ứng dụng, mà còn cả máy chủ HTTP. Thực tế, ứng dụng web của chúng ta và máy chủ web của nó về cơ bản là giống nhau.
</p>
<p>
Có vẻ như có rất nhiều việc phải làm, nhưng lát nữa bạn sẽ thấy với Node.js, mọi thứ không quá khó đến vậy.
</p>
<p>
Hãy cùng bắt đầu từ vạch xuất phát và viết phần đầu tiên trong cấu trúc của ứng dụng của chúng ta, máy chủ HTTP.
</p>
<a name="building-the-application-stack"></a>
<h2>Xây dựng cấu trúc ứng dụng</h2>
<a name="a-basic-http-server"></a>
<h3>Một máy chủ HTTP cơ bản</h3>
<p>
Ở cái thời điểm mà tôi muốn bắt đầu phát triển ứng dụng Node.js có tính thực tế đầu tiên của tôi, tôi không chỉ băn khoăn xem phát triển nó như thế nào mà còn tổ chức nó ra làm sao.
<br>
Tôi có nên viết tất cả vào trong một file? Đa số các hướng dẫn viết một máy chủ HTTP sử dung Node.js cơ bản đều gộp tất cả vào một chỗ. Vậy nếu tôi muốn đảm bảo rằng code của tôi luôn rõ ràng và dễ hiểu khi ứng dụng của tôi ngày một mở rộng lên thì sao?
</p>
<p>
Hoá ra, cũng không quá khó để tách biệt những thứ không có liên quan tới nhau ra, và sắp xếp chúng vào các module khác nhau.
</p>
<p>
Cách này cho phép chúng ta giữ được file chính (file khởi động ứng dụng) và các module luôn rõ ràng, dễ đọc, dễ bảo trì và các module còn có thể được sử dụng lại ở nhiều nơi khác nhau.
</p>
<p>
Bây giờ hãy tạo một file chính, nơi khởi chạy ứng dụng, và một file module nơi chứa mã nguồn cho máy chủ HTTP.
</p>
<p>
Theo quan điểm cá nhân của tôi, tiêu chuẩn để đặt tên file chính nên là <em>index.js</em> và nó sẽ khiến việc đặt tên module máy chủ <em>server.js</em> trở nên hợp lý và logic hơn.
</p>
<p>
Hãy cùng bắt đầu viết module cho máy chủ. Tạo mới file <em>server.js</em> trong thư mục gốc project của bạn, và viết vào đoạn code sau:
</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>
Chỉ thế thôi! Bạn vừa viết xong một máy chủ HTTP có khả năng hoạt động tốt. Hãy xác thực lại việc đó bằng cách chạy và kiểm tra nó. Đầu tiên, hãy chạy lệnh sau bằng Node.js:
</p>
<pre>node server.js</pre>
<p>
Bây giờ hãy mở trình duyệt của bạn ra và truy cập vào địa chỉ sau <a href="http://localhost:8888/" rel="nofollow">http://localhost:8888/</a>. Nếu đúng như mong đợi, những gì chúng ta thấy sẽ là "Hello World".
</p>
<p>
Thú vị phải không? Bạn nghĩ sao về việc cùng tìm hiểu xem nó hoạt động như thế nào? Hãy tạm thời để câu hỏi làm thế nào để tổ chức dự án của chúng ta lại đó. Tôi hứa sẽ đề cập lại tới nó trong những phần tiếp theo.
</p>
<a name="analyzing-our-http-server"></a>
<h3>Phân tích máy chủ HTTP</h3>
<p>
Chúng ta hãy cùng phân tích xem nó hoạt động như thế nào.
</p>
<p>
Dòng đầu tiên <em>khai báo</em> (chính xác hơn là yêu cầu, giống với import và using) rằng chúng ta sẽ sử dụng module <em>http</em> có sẵn trong Node.js vào trong ứng dụng và sẽ gọi đến nó thông qua biến có tên <em>http</em>.
</p>
<p>
Tiếp đến chúng ta sẽ gọi một trong các hàm có sẵn của module http: <em>createServer</em>. Hàm này sẽ trả về một đối tượng, đối tượng này chứa một hàm/phương thức khác gọi là <em>listen</em>, hàm này nhận vào một tham số kiểu số để sử dụng làm cổng lắng nghe cho máy chủ HTTP.
</p>
<p>
Bạn đừng chú ý gì tới cái hàm được khai báo sau dấu mở ngoặc của hàm <em>http.createServer</em> vội.
</p>
<p>
Chúng ta đã có thể viết code để khởi động máy chủ của chúng ta ở cổng 8888 như sau:
</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>
Đoạn code này sẽ khởi động một máy chủ HTTP, lắng nghe ở cổng 8888 nhưng không làm gì cả (thậm chí không trả lời các yêu cầu được gửi tới).
</p>
<p>
Điều thực sự thú vị (nhìn nó hơi buồn cười nếu như bạn đã biết về PHP) ở đây chính là phần định nghĩa/khai báo của hàm được sử dụng nơi mà đúng ra nó phải là một tham số cho <em>createServer()</em>.
</p>
<p>
Hoá ra, định nghĩa hàm kia lại chính là giá trị đầu tiên (và duy nhất) mà chúng ta truyền cho lời gọi hàm <em>createServer()</em>. Bởi vì trong JavaScript, hàm có thể được sử dụng như là tham số đầu vào.
</p>
<a name="passing-functions-around"></a>
<h3>Về cách sử dụng hàm</h3>
<p>
Ví dụ, bạn có thể viết như sau:
</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>
Hãy đọc thật kĩ! Cái chúng ta viết ở đây là, hàm <em>say</em> được sử dụng như là tham số đầu vào cho hàm <em>execute</em>. Không phải giá trị trả về của <em>say</em>, mà chính bản thân nó!
</p>
<p>
Vì thế, <em>say</em> trở thành biến nội bộ <em>someFunction</em> trong hàm <em>execute</em>, và <em>execute</em> có thể gọi bất kỳ hàm nào trong biến <em>someFunction</em> bằng việc gọi đến hàm (biến) đó (bằng cách thêm dấu ngoặc).
</p>
<p>
Tất nhiên, vì <em>say</em> nhận một tham số đầu vào, <em>execute</em> cũng có thể truyền một tham số tương tự như thế khi gọi hàm <em>someFunction</em>.
</p>
<p>
Chúng ta có thể làm, chúng ta đã làm, là sử dụng hàm như một tham số đầu vào cho hàm khác bằng cách sử dụng tên của chúng. Nhưng, chúng ta không nhất thiết phải đi theo hướng vòng vo thế này, từ định nghĩa rồi mới đến sử dụng - chúng ta có thể kết hợp hai bước này lại với nhau ở chung một vị trí/ thời điểm:
</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>
Chúng ta định nghĩa hàm muốn <em>thực thi</em> ở ngay chính nơi chúng ta truyền tham số đầu vào.
</p>
<p>
Với cách này, chúng ta thậm chí không cần phải đặt tên cho hàm đó, nó còn được biết đến với cái tên <em>hàm vô danh</em> (anonymous function).
</p>
<p>
Đây mới chỉ là thoáng qua cái chúng ta gọi là JavaScript "nâng cao", đừng nôn nóng vội, chúng ta sẽ làm quen với nó từng bước. Hãy tạm thời chấp nhập rằng trong JavaScript chúng ta có thể sử dụng hàm như là một tham số đầu vào cho một hàm khác. Chúng ta có thể gán hàm đó vào một biến nào đó hoặc định nghĩa đồng thời với hàm mà chúng ta muốn truyền vào.
</p>
<a name="how-function-passing-makes-our-http-server-work"></a>
<h3>Sử dụng hàm như vậy giúp máy chủ HTTP của chúng ta hoạt động như thế nào</h3>
<p>
Với những gì bạn vừa học được, hãy quay lại và cải tiến máy chủ HTTP của chúng ta:
</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>
Đến bây giờ thì mọi thứ dường như đã được làm sáng tỏ, thực sự cái chúng ta đang làm là truyền cho hàm <em>createServer</em> một hàm vô danh.
</p>
<p>
Chúng ta cũng có thể đạt được kết quả tương tự với đoạn code sau:
</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>
Có thể bây giờ đã đến thời điểm thích hợp để hỏi: Tại sao chúng ta lại thực hiện theo cách đó?
</p>
<a name="event-driven-callbacks"></a>
<h3>Gọi ngược không đồng bộ dựa trên sự kiện</h3>
<p>
Để hiểu được tại sao các ứng dụng sử dụng Node.js lại phải viết theo cách như vậy, chúng ta cần hiểu được cách Node.js thực thi code như thế nào. Không phải duy nhất Node.js tiếp cận vấn đề bằng phương pháp này, mỗi mô hình thực thi code cơ bản (underlying execution model) khác nhau ở từng môi trường chạy (runtime environment) như Python, Ruby, PHP, hay Java.
</p>
<p>
Hãy cùng xem đoạn code đơn giản dưới đây:
</p>
<pre class="prettyprint lang-js">var result = database.query("SELECT * FROM hugetable");
console.log("Hello World");</pre>
<p>
Hãy tạm thời bỏ qua các tương tác với database mà chúng ta chưa đề cập tới - đây chỉ là ví dụ mà thôi. Dòng đầu tiên truy vấn đến database và có thể trả về rất nhiều bản ghi, dòng thứ hai đơn giản chỉ in ra màn hình "Hello World".
</p>
<p>
Giả sử thao tác đến database diễn ra rất chậm, có rất nhiều bản ghi thoả mãn yêu cầu, thời gian thực thi có thể mất mất vài giây.
</p>
<p>
Với cách mà chúng ta đang làm, trình biên dịch (interpreter) JavaScript của Node.js trước tiên sẽ phải chờ khi thao tác tới database hoàn tất, sau đó mới thực thi lệnh <em>console.log()</em>.
</p>
<p>
Nếu đoạn code này được viết bằng PHP, nó cũng sẽ được thực thi tương tự: đầu tiên là chờ và đọc tất cả các kết quả trả về, sau đó mới thực thi dòng tiếp theo. Nếu đoạn code này là một phần của một trang web, thời gian tải trang người dùng phải chờ có thể lên tới vài giây.
</p>
<p>
Tuy nhiên, trong mô hình thực thi của PHP thì đó không phải là vấn đề đáng quan tâm: máy chủ web PHP khởi tạo một process riêng cho mỗi request nó nhận được. Nếu một trong những request này chậm hơn bình thường, nó chỉ có thể ảnh hưởng đến thời gian tải trang của người tạo ra request đó, chứ không gây ảnh hưởng đến những người dùng khác.
</p>
<p>
Mô hình thực hiện của Node.js không giống như vậy - nó chỉ dùng duy nhất một process. Nếu có truy vấn tới database nào đó tốn nhiều thời gian, nó sẽ làm chậm toàn bộ process - mọi thứ sẽ bị dừng lại cho đến khi truy vấn kia kết thúc.
</p>
<p>
Để tránh tình trạng này xảy ra, JavaScript và Node.js đưa ra khái niệm "dựa theo sự kiện" (event-driven), gọi ngược không đồng bộ (asynchronous callback), bằng cách sử dụng một "vòng lặp sự kiện" (event loop).
</p>
<p>
Chúng ta sẽ hiểu rõ khái niệm này hơn bằng cách phân tích phiên bản đã được cải thiện của ví dụ vừa rồi:
</p>
<pre class="prettyprint lang-js">database.query("SELECT * FROM hugetable", function(rows) {
var result = rows;
});
console.log("Hello World");</pre>
<p>
Ở đây, thay vì chờ đợi <em>database.query()</em> trực tiếp trả về kết quả, chúng ta truyền nó như là một tham số, hay nói cách khác là một hàm vô danh.
</p>
<p>
Trong phiên bản cũ, code của chúng ta được thực hiện một cách "đồng bộ": <em>(1) trước tiên</em> truy vấn tới database, chỉ sau khi truy vấn này hoàn tất, (2) <em>(2) mới</em> thực hiện lệnh in ra màn hình.
</p>
<p>
Bây giờ Node.js đã có thể xử lý các truy vấn tới database một cách không đồng bộ. Giả sử <em>database.query()</em> được cung cấp sẵn bởi một thư viện chuyên xử không đồng bộ (asynchronous library), Node.js sẽ xử lý như sau: cũng giống như trước, nó sẽ gửi truy vấn tới database. Nhưng, thay vì chờ kết quả khi truy vấn đó kết thúc, nó sẽ ghi nhớ rằng "Đến một thời điểm nào đó trong tương lai - khi truy vấn kết thúc, kết quả được trả về - nó sẽ phải thực hiện những gì được viết trong hàm vô danh (hàm được truyền như là tham số cho <em>database.query()</em>)" kia.
</p>
<p>
Lúc đó nó sẽ ngay lập tức thực thi <em>console.log()</em>, và sau đó bắt đầu một vòng lặp vô tận, và cứ thế chờ đợi, không xử lý bất kỳ gì khác cho đến khi có một sự kiện nào đó đánh thức nó, ví dụ như truy vấn tới database đã có dữ liệu trả về.
</p>
<p>
Điều này cũng giải thích cho việc tại sao máy chủ HTTP của chúng ta cần một hàm để nó có thể gọi tới khi nhận được request - nếu Node.js khởi động rồi dừng lại để chờ request khác, chỉ tiếp tục khi nhận được request mới thì sẽ rất kém hiệu quả. Nếu một người dùng thứ hai nào đó gửi một request lên trong khi máy chủ của chúng ta còn đang xử lý request thứ nhất thì request thứ hai chỉ có thể được xử lý sau khi xong request thứ nhất - có nghĩa là chỉ ngay khi bạn có khoảng "vài" (a handful of) request trên một giây nó sẽ không thể hoạt động được nữa.
</p>
<p>
Có điều quan trọng bạn phải ghi nhớ là mô hình thực thi không đồng bộ, đơn luồng, và dựa trên sự kiện này không phải thứ gì đó hoàn hảo tuyệt đối. Nó chỉ là một trong nhiều mô hình đang tồn tại, nó cũng có những nhược điểm, một trong số đó chính là: nó chỉ có thể chạy trên một nhân của CPU mà thôi. Cá nhân tôi cho thằng, mô hình này là chấp nhận được (approachable), vì chúng ta có thể dùng nó để xây dựng các ứng dụng "thời gian thực" (concurrency) có hiệu quả cao mà không mấy khó khăn.
</p>
<p>
Bạn có thể giành thời gian đọc thêm bài viết tuyệt vời của Felix
Geisendörfer's <a href="http://debuggable.com/posts/understanding-node-js:4bd98440-45e4-4a9a-8ef7-0f7ecbdd56cb">Understanding
node.js</a> để hiểu sâu hơn về Node.js cũng như cách thức hoạt động của nó.
</p>
<p>
Hãy thử một vài ví dụ với khái niệm mới này xem sao. Chúng ta có thể chắc chắn rằng code của chúng ta vẫn hoạt động tốt sau khi đã tạo máy chủ hay không? thậm chí khi không có request nào được xử lý và hàm gọi ngược (callback function) không được gọi? Cùng thử xem sao:
</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>
Chú ý là tôi sử dụng <em>console.log</em> để hiển thị chuỗi ra màn hình mỗi khi <em>onRequest</em> được gọi, và một chuỗi khác <em>ngay sau khi</em> khởi động máy chủ HTTP.
</p>
<p>
Khi chúng ta chạy lệnh (<em>node server.js</em>, như thường lệ), nó sẽ in ra màn hình "Server has started." ngay lập tức. Và mỗi khi chúng ta gửi request đến máy chủ (bằng cách truy cập vào <a href="http://localhost:8888/" rel="nofollow">http://localhost:8888/</a> trên trình duyệt), thông báo "Request received." sẽ được hiển thị ra màn hình console.
</p>
<p>
"Event-driven asynchronous server-side JavaScript with
callbacks in action" :-)
</p>
<p>
(Lưu ý là máy chủ sẽ hiện thông báo: "Request received." ra STDOUT (màn hình/console) hai lần mỗi khi một trang nào đó được truy cập. Vì đa số trình duyệt gửi đi hai request, trong đó một là http://localhost:8888/favicon.ico (biểu tượng nhỏ của mỗi trang nếu có) và bản thân trang http://localhost:8888/).
</p>
<a name="how-our-server-handles-requests"></a>
<h3>Máy chủ xử lý yêu cầu như thế nào</h3>
<p>
Hãy cùng phân tích nhanh phần code còn lại của máy chủ, phần nội dung của hàm gọi ngược <em>onRequest()</em>.
</p>
<p>
Khi hàm gọi ngược <em>onRequest()</em> được gọi đến bởi một sự kiện nào đó, hai tham số: <em>request</em> và <em>response</em> sẽ được truyền vào cho nó.
</p>
<p>
Chúng là các đối tượng (object), bạn có thể sử dụng các hàm của chúng để xử lý các chi tiết của HTTP request nhận được và phản hồi lại các request đó (ví dụ như, trả về dữ liệu gì đó cho trình duyệt).
</p>
<p>
Và đoạn code của chúng ta thực hiện: Mỗi khi nhận được request, nó gọi đến hàm <em>response.writeHead()</em> để ghi một mã trạng thái ở dạng số: 200 cũng như loại/kiểu nội dung sẽ được gửi về trong đoạn đầu (header) của phản hồi và dùng hàm <em>response.write()</em> để ghi chuỗi "Hello World" vào phần nội dung của phản hồi (HTTP response).
</p>
<p>
Cuối cùng, chúng ta dùng <em>response.end()</em> để chính thức kết thúc phản hồi.
</p>
<p>
Cho tới thời điểm này, chúng ta vẫn chưa hề sử dụng đến tham số <em>request</em>.
</p>
<a name="finding-a-place-for-our-server-module"></a>
<h3>Nơi lưu trữ module máy chủ</h3>
<p>
Như tôi đã hứa trước là chúng ta sẽ quay lại với chủ đề: làm thế nào để tổ chức tốt ứng dụng của chúng ta. Chúng ta đã hoàn thành xong một máy chủ web hết sức đơn giản và được lưu vào file <em>server.js</em>, như tôi đã đề cập, mọi người thường đặt tên <em>index.js</em> cho file chính của ứng dụng, file này có nhiệm vụ "mồi" và rồi khởi động ứng dụng của chúng ta bằng cách sử dụng các module (ví dụ như module <em>server.js</em> của chúng ta).
</p>
<p>
Hãy cùng xem làm như thế nào để biến <em>server.js</em> trở thành một module thực sự của Node.js mà có thể sử dụng cho file <em>index.js</em> sắp được viết của chúng ta.
</p>
<p>
Có thể bạn đã nhận ra là chúng ta đã sử dụng module ở trong đoạn code của chúng ta:
</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>
Ở đâu đó bên trong Node.js, có một module gọi là "http", vì thế nên chúng ta mới có thể sử dụng nó bằng cách tham chiếu dến và gán nó cho một biến nội bộ trong chương trình của chúng ta.
</p>
<p>
Cách này giúp chúng ta sử dụng được tất cả các hàm được cung cấp công khai của đối tượng hay module <em>http</em> đó giống như bất kỳ một đối tượng nào khác.
</p>
<p>
Thói quen tốt để ghi nhớ là đặt tên các biến nội bộ trùng với tên của module, mặc dù về mặt lý thuyết bạn có thể đặt tên biến tự do:
</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>
Bây giờ việc sử dụng các module các module có sẵn của Node.js đã trở nên dễ dàng. Vậy làm thể nào để chúng ta tạo được các module riêng của chúng ta, và sử dụng chúng như thế nào?
</p>
<p>
Hãy cùng trả lời câu hỏi đó bằng cách biến <em>server.js</em> trở thành một module thực sự.
</p>
<p>
Thật ra thì, chúng ta không phải thay đổi nhiều lắm. Biến một đoạn code thành một module có nghĩa là chúng ta phải <em>trích xuất</em> (export) một phần chức năng mà chúng ta muốn (từ một file nào đó, hay cả file) để đưa vào module kia.
</p>
<p>
Bây giờ, phần chức năng của máy chủ web cần được xuất ra rất đơn giản: đó là những gì cần và đủ để khởi động máy chủ.
</p>
<p>
Để có thể làm được điều này, chúng ta gộp tất cả code lại vào trong một hàm <em>start</em>, rồi sẽ export nó ra:
</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>
Theo cách này, bây giờ chúng ta có thể tạo mới file chính <em>index.js</em>,
và khởi động ứng dụng của chúng ta trong đó, mặc dù tất cả code cần thiết để chạy máy chủ nằm ở file <em>server.js</em>.
</p>
<p>
Tạo mới file <em>index.js</em> với nội dung sau:
</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>
Như bạn thấy, chúng ta có thể sử dụng module máy chủ giống như bất kỳ một module tích hợp sẵn nào khác trong Node.js: bằng cách tham chiếu đến file đó và gán nó vào một biến nội bộ nào đó, bất kỳ hàm được cung cấp một cách công khai (export) là chúng ta có thể gọi được.
</p>
<p>
Xong rồi đó. Bây giờ chúng ta có thể chạy ứng dụng của chúng ta thông qua file <em>index.js</em>, và cũng cho kết quả tương tự:
</p>
<pre>node index.js</pre>
<p>
Tuyệt vời, giờ chúng ta đã có thể bố trí các thành phần khác nhau trong ứng dụng của chúng ta ra từng file riêng biệt và kết nối chúng lại với nhau thông qua thông qua việc tạo module.
</p>
<p>
Khả năng duy nhất mà ứng dụng của chúng ta có thể thực hiện cho đến lúc này là: nhận request. Nhưng việc chúng ta còn phải làm nữa là xử lý các request đó - mỗi request cần được xử lý hoàn toàn khác nhau.
</p>
<p>
Đối với những ứng dụng đơn giản, bạn có thể xử lý vấn đề này trực tiếp bên trong hàm gọi ngược <em>onRequest()</em>. Nhưng như tôi đã nói, chúng ta sẽ áp dụng nhiều sự trừu tượng hoá để làm cho ứng dụng này trở nên thú vị hơn.
</p>
<p>
Việc hướng cho các request khác nhau trỏ tới các phần khác nhau trong ứng dụng của chúng ta được gọi là "điều hướng" (routing) - vậy nên, hãy cùng tạo một module mới có tên <em>router</em>.
</p>
<a name="whats-needed-to-route-requests"></a>
<h3>Cần những gì để điều hướng các request?</h3>
<p>
Chúng ta cần có khả năng cung cấp cho "router" địa chỉ của request nhận được và có thể các tham số bổ sung như GET hoặc POST, và dựa vào những thông tin này router cần có khả năng quyết định được xem phần code nào sẽ được thực thi ("code cần được thực thi" là phần thứ ba của ứng dụng: tập hợp các thành phần xử lý request mà thực sự làm những việc được yêu cầu).
</p>
<p>
Vì thế, chúng ta cần tìm hiểu thêm bên trong một HTTP request và tách được địa chỉ được yêu cầu cũng như tham số GET/POST. Có người cho rằng đây là một phần của router nhưng cũng có người khác tin tưởng nó là một phần của server (hay thậm chí cũng là một module độc lập), nhưng cứ tạm thời cho nó là một phần của máy chủ (HTTP server) đã.
</p>
<p>
Tất cả thông tin chúng ta cần đều có trong đối tượng <em>request</em>, được truyền vào là tham số đầu tiên của hàm gọi ngược <em>onRequest()</em>. Nhưng để biên dịch được những thông tin này, chúng ta cần thêm một số module bổ sung của Node.js, cụ thể là <em>url</em> và <em>querystring</em>.
</p>
<a name="head20"></a>
<p>
Module <em>url</em> cung cấp các phương thức cho phép chúng ta trích xuất các thành phần khác nhau của một địa chỉ URL (ví dụ như: địa chỉ yêu cầu và các truy vấn đi kèm (query string) ), và module <em>querystring</em> được dùng để phân tích cú pháp của các địa chỉ yêu cầu để lấy các tham số:
</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>
Đương nhiên là chúng ta cũng có thể sử dụng <em>querystring</em> để phân tích phần nội dung của một yêu cầu dạng POST để lấy ra các tham số, sẽ đề cập ở phần sau.
</p>
<p>
Hãy cùng bổ sung logic cần thiết cho hàm <em>onRequest()</em> để tìm xem đường dẫn nào được yêu cầu: