Ứng dụng web trong Phần 3 đã dùng một từ khóa để thực hiện một tìm kiếm trên cả Google lẫn
Twitter. Về phía máy khách của của ứng dụng, bạn chỉ cần tạo mô hình gi ả định các kết quả từ
máy chủ. Để thực sự thực hiện các hàm như vậy, bạn cần phía máy chủ của ứng dụng để gọi các
dịch vụ web được Google và Twitter cung cấp. Cả hai công ty đều cung cấp các dịch vụ t ìm
kiếm rất đơn giản. Tất cả những gì bạn cần làm là tạo ra các yêu cầu HTTP GET với các dịch vụ
tìm kiếm. Liệt kê 1 cho thấy một hàm tổng quát để tạo ra các yêu cầu HTTP GET
9 trang |
Chia sẻ: lylyngoc | Lượt xem: 1759 | Lượt tải: 1
Bạn đang xem nội dung tài liệu Sử dụng CoffeeScript trên máy chủ, để tải tài liệu về máy bạn click vào nút DOWNLOAD ở trên
Sử dụng CoffeeScript trên máy chủ
Gọi tất cả các dịch vụ web
Ứng dụng web trong Phần 3 đã dùng một từ khóa để thực hiện một tìm kiếm trên cả Google lẫn
Twitter. Về phía máy khách của của ứng dụng, bạn chỉ cần tạo mô hình giả định các kết quả từ
máy chủ. Để thực sự thực hiện các hàm như vậy, bạn cần phía máy chủ của ứng dụng để gọi các
dịch vụ web được Google và Twitter cung cấp. Cả hai công ty đều cung cấp các dịch vụ tìm
kiếm rất đơn giản. Tất cả những gì bạn cần làm là tạo ra các yêu cầu HTTP GET với các dịch vụ
tìm kiếm. Liệt kê 1 cho thấy một hàm tổng quát để tạo ra các yêu cầu HTTP GET.
Liệt kê 1. Tìm nạp tài nguyên web
http = require "http"
fetchPage = (host, port, path, callback) ->
options =
host: host
port: port
path: path
req = http.get options, (res) ->
contents = ""
res.on 'data', (chunk) ->
contents += "#{chunk}"
res.on 'end', () ->
callback(contents)
req.on "error", (e) ->
console.log "Erorr: {e.message}"
Ở phần đầu của kịch bản lệnh này là câu lệnh require (yêu cầu), mà bạn đã thấy tóm tắt trong
Phần 1 của loạt bài này. Đây là cú pháp nhập khẩu mô đun của Node.js hoặc ít nhất là phiên bản
CoffeeScript của nó. Phiên bản "nguyên gốc" sẽ là var http = require("http");. Trong bài
này, bạn sẽ sử dụng một số mô đun lõi của Node.js. (Thông tin chi tiết về việc cách hoạt động
của các mô đun nằm ngoài phạm vi của bài viết này). Tất cả các mô đun được sử dụng trong bài
này đều có sẵn cho bạn nếu bạn đã cài đặt Node.js (xem Phần 1). Với ví dụ trong Liệt kê 1, bạn
đang sử dụng mô đun http, có cung cấp một vài lớp và các hàm có ích cho cả việc tạo ra lẫn tiếp
nhận các yêu cầu HTTP.
Sau đó Liệt kê 1 định nghĩa một hàm fetchPage nhận bốn tham số:
Tên host (máy chủ) của tài nguyên.
port (cổng) của tài nguyên.
path (đường dẫn) của tài nguyên.
Một hàm callback (gọi lại).
Bất kỳ kiểu hàm Vào/Ra (I/O) nào trong Node.js về bản chất sẽ là không đồng bộ và do
đó sẽ cần một hàm callback để gọi khi nó hoàn thành. Hàm fetchPage nhận một hàm
callback làm tham số thứ tư của mình. Hàm fetchPage sẽ sử dụng ba tham số đầu tiên
để tạo ra một yêu cầu HTTP GET bằng cách sử dụng hàm get của mô đun http.
Hàm fetchPage cũng nhận một hàm callback đã chuyển đi một cá thể ClientResponse.
ClientResponse, là một đối tượng được định nghĩa trong mô đun http, thực hiện giao diện
ReadableStream (một giao diện cốt lõi trong Node.js). Nó là một giao diện không đồng bộ nhận
hai sự kiện: data (dữ liệu) và end (kết thúc). Hàm duy nhất của nó được sử dụng để đăng ký gọi
lại đến các sự kiện này. Sự kiện dữ liệu xảy ra khi tiếp nhận dữ liệu từ tài nguyên mà bạn đã tạo
ra yêu cầu HTTP GET để lấy.
Tất cả dữ liệu có thể được trả về cùng một lúc từ tài nguyên, nhưng phổ biến hơn, dữ liệu sẽ
được gửi theo các bó (chunk). Khi nhận được mỗi bó, sự kiện dữ liệu được khởi động và hàm gọi
lại được gọi ra. Bạn đã tạo ra một biến có tên là contents (các nội dung); mỗi khi bạn nhận
được một bó khác, bạn chỉ cần gắn thêm nó vào contents. Khi đã nhận được tất cả dữ liệu, sự
kiện end được khởi động. Bây giờ bạn có tất cả dữ liệu, vì vậy bạn có thể chuyển contents trở
lại cho hàm callback đã được chuyển tới hàm fetchPage. Khi đã định nghĩa hàm đa năng này,
chúng ta hãy tạo ra một số hàm chuyên dụng hơn cho các API tìm kiếm của Google và Twitter,
như trong Liệt kê 2.
Liệt kê 2. Các hàm tìm kiếm Google và Twitter
googleSearch = (keyword, callback) ->
host = "ajax.googleapis.com"
path = "/ajax/services/search/web?v=1.0&q=#{encodeURI(keyword)}"
fetchPage host, 80, path, callback
twitterSearch = (keyword, callback) ->
host = "search.twitter.com"
path = "/search.json?q=#{encodeURI(keyword)}"
fetchPage host, 80, path, callback
Có hai hàm được định nghĩa trong Liệt kê 2:
googleSearch, nhận một keyword (từ khóa) và một hàm callback. Nó ấn định máy
chủ, tạo động đường dẫn bằng cách sử dụng phép nội suy chuỗi của CoffeeScript và sau
đó sử dụng hàm fetchPage.
twitterSearch, rất giống với googleSearch nhưng có máy chủ và giá trị đường dẫn
khác.
Đối với cả hai giá trị đường dẫn, bạn sử dụng phép nội suy chuỗi và hàm encodeURI tiện dụng
của JavaScript để xử lý bất kỳ khoảng trống hoặc các ký tự đặc biệt khác nào. Vì bạn có các hàm
tìm kiếm này, nên bạn có thể tạo ra một hàm chuyên dụng cho một kịch bản tìm kiếm kết hợp.
Về đầu trang
Kết hợp các hàm không đồng bộ
Có một vài cách để bạn có thể thực hiện tìm kiếm kết hợp trên Google và Twitter. Bạn có thể gọi
googleSearch và sau đó, trong hàm callback, gọi twitterSearch, hoặc ngược lại. Tuy nhiên,
kiến trúc không đồng bộ/gọi lại của Node.js cho phép bạn làm những việc này một cáchđẹp đẽ
hơn và hiệu quả hơn. Liệt kê 3 hiển thị tìm kiếm kết hợp.
Liệt kê 3. Tìm kiếm cả Google lẫn Twitter
combinedSearch = (keyword, callback) ->
data =
google : ""
twitter : ""
googleSearch keyword, (contents) ->
contents = JSON.parse contents
data.google = contents.responseData.results
if data.twitter != ""
callback(data)
twitterSearch keyword, (contents) ->
contents = JSON.parse contents
data.twitter = contents.results
if data.google != ""
callback(data)
Hàm combinedSearch có một chữ ký mà bây giờ đã quen thuộc: nhận một từ khóa và một hàm
gọi lại. Sau đó, nó tạo ra một cấu trúc dữ liệu cho các kết quả tìm kiếm kết hợp được gọi là data.
Đối tượng data có một trường google và một trường twitter, cả hai đều được khởi tạo như các
chuỗi rỗng. Bước tiếp theo gọi hàm googleSearch. Trong hàm gọi lại của nó, bạn phân tích cú
pháp các kết quả từ Google bằng cách sử dụng hàm JSON.parse tiêu chuẩn. Văn bản JSON
được trả về từ Google được phân tích cú pháp thành một đối tượng JavaScript. Sử dụng đối
tượng này để thiết lập giá trị của trường data.google. Sau khi gọi googleSearch, hãy gọi
twitterSearch. Hàm callback của nó rất giống với hàm callback dùng cho googleSearch.
Điều quan trọng cần hiểu rằng trong cả hai hàm gọi lại mà bạn kiểm tra để xem liệu bạn có dữ
liệu từ hàm gọi lại khác không. Bạn không biết hàm gọi lại nào sẽ hoàn thành trước tiên. Vì vậy,
hãy kiểm tra để xem liệu bạn có dữ liệu từ cả Google lẫn Twitter không. Một khi bạn làm điều
đó, bạn gọi hàm callback đã được chuyển tới hàm combinedSearch. Bây giờ bạn có một hàm
sẽ tìm kiếm cả Google lẫn Twitter và đưa ra các kết quả kết hợp. Nhiệm vụ tiếp theo là trưng ra
hàm này cho trang web mà bạn đã tạo ra trong Phần 3 của loạt bài này. Tất cả những gì bạn phải
làm là viết một máy chủ web.
Về đầu trang
Một máy chủ web CoffeeScript
Lúc này, bạn có:
Một trang web có thể gửi các từ khoá và hiển thị các kết quả tìm kiếm.
Một hàm có thể dùng một từ khóa và tạo ra các kết quả tìm kiếm từ Google và Twitter.
Cái gì gắn kết những thứ này lại với nhau? Bạn có thể gọi nó là một máy chủ web, máy chủ ứng
dụng, hoặc thậm chí phần mềm trung gian. Bất kể bạn muốn gọi nó là gì, không cần dùng nhiều
mã CoffeeScript để viết nó.
Máy chủ web cần đáp ứng hai mục đích. Rõ ràng, nó cần nhận các yêu cầu cho việc tìm kiếm kết
hợp. Nó cũng cần cung cấp tài nguyên tĩnh mà bạn đã tạo ra trong Phần 3. Bạn đang tạo ra một
ứng dụng web, do đó, bạn phải chú ý đến các quy định. Các cuộc gọi tìm kiếm phải đi tới cùng
một nơi đã tạo ra trang web. Trước tiên hãy xử lý tài nguyên tĩnh. Liệt kê 4 cho thấy một hàm để
cung cấp tài nguyên tĩnh.
Liệt kê 4. Cung cấp tài nguyên tĩnh
path = require "path"
fs = require "fs"
serveStatic = (uri, response) ->
fileName = path.join process.cwd(), uri
path.exists fileName, (exists) ->
if not exists
response.writeHead 404, 'Content-Type': 'text/plain'
response.end "404 Not Found #{uri}!\n"
return
fs.readFile fileName, "binary", (err,file) ->
if err
response.writeHead 500,
'Content-Type': 'text/plain'
response.end "Error #{uri}: #{err} \n"
return
response.writeHead 200
response.write file, "binary"
response.end()
Hàm serveStatic xử lý các yêu cầu về nguồn tài nguyên tĩnh trong ứng dụng web. Lưu ý rằng
bạn cần sử dụng thêm hai mô đun Node.js nữa:
path chỉ đơn giản là một thư viện tiện ích để xử lý các đường dẫn tệp.
Hệ thống tệp, hoặc fs, cung cấp tất cả Vào/Ra của tệp trong Node.js và về cơ bản là một
trình bao gói dựa trên các hàm POSIX tiêu chuẩn.
Hàm serveStatic nhận hai tham số:
uri về bản chất là một đường dẫn tương đối đến tệp tĩnh đang được một trình duyệt web
yêu cầu.
Một đối tượng ServerResponse, là một kiểu khác được định nghĩa trong mô đun http.
Ngoài nhiều thứ khác, nó mang lại cho bạn một luồng để ghi dữ liệu vào bất cứ thứ gì đã
tạo ra yêu cầu HTTP GET về tài nguyên.
Trong hàm serveStatic, chuyển đường dẫn tương đối đến tệp thành một đường dẫn tuyệt đối
bằng cách sử dụng process.cwd. Đối tượng process (tiến trình) là một đối tượng chung
(global), đại diện cho tiến trình hệ thống mà Node.js đang chạy trên đó. Phương thức cwd của nó
cung cấp thư mục làm việc hiện tại. Sử dụng mô đun path để kết hợp thư mục làm việc hiện tại
và đường dẫn tương đối đến tệp mà bạn muốn; kết quả là đường dẫn tuyệt đối. Với đường dẫn
tuyệt đối, bạn có thể sử dụng lại mô đun path để kiểm tra xem tệp có tồn tại hay không. Việc
kiểm tra xem tệp có tồn tại không liên quan đến I/O (Vào/Ra), cho nên nó là một hàm không
đồng bộ. Chuyển cho nó fileName (tên tệp) và một hàm gọi lại. Hàm gọi lại nhận một giá trị
Boolean, cho bạn biết liệu tệp tồn tại hay không. Nếu nó không tồn tại, thì bạn cần viết một
thông báo HTTP 404 " không tìm thấy tệp".
Nếu tệp tồn tại, thì bạn cần đọc nội dung của nó bằng cách sử dụng mô đun fs và phương thức
readFile, phương thức readFile là không đồng bộ. Nó nhận fileName, một kiểu và một hàm
gọi lại. Hàm gọi lại nhận hai thông số:
Một tham số lỗi cho biết bất kỳ vấn đề gì khi đọc tài nguyên từ hệ thống tệp. Nếu có vấn
đề, một thông báo lỗi HTTP 500 được trả ngược lại tới phía máy khách.
Nếu không có vấn đề gì, một thông báo HTTP 200 OK được viết và các nội dung của tệp
được gửi lại tới phía máy khách.
Hàm này xử lý trường hợp cung cấp các tệp tĩnh tương đối dễ dàng. Phần tiếp theo sẽ thảo luận
về kịch bản khó hơn, ở đó bạn cần đáp ứng động với một yêu cầu tìm kiếm.
Về đầu trang
Các đáp ứng động và máy chủ
Ví dụ máy chủ web chủ yếu xử lý các yêu cầu tài nguyên tĩnh và các yêu cầu tìm kiếm động.
Chiến lược là sử dụng một URL cụ thể để xử lý các yêu cầu tìm kiếm và sau đó chuyển các yêu
cầu khác tới hàm serveStatic. Hãy sử dụng URL tương đối là /doSearch cho các yêu cầu tìm
kiếm. Liệt kê 5 cho thấy mã của máy chủ web.
Liệt kê 5. Máy chủ web CoffeeScript
url = require "url"
server = http.createServer (request, response) ->
uri = url.parse(request.url)
if uri.pathname is "/doSearch"
doSearch uri, response
else
serveStatic uri.pathname, response
server.listen 8080
console.log "Server running at "
Một lần nữa, kịch bản lệnh này bắt đầu bằng cách nạp một mô đun Node.js. Mô đun url là một
thư viện có ích để phân tích cú pháp các URL. Bước tiếp theo là tạo ra máy chủ web bằng cách
sử dụng mô đun http mà bạn nạp trong Liệt kê 1. Sử dụng phương thức createServer của mô
đun đó, phương thức này nhận một hàm gọi lại sẽ được gọi mỗi khi có yêu cầu được gửi đến
máy chủ web. Hàm gọi lại có hai tham số: một cá thể ServerRequest và một cá thể
ServerResponse. Cả hai kiều đều được định nghĩa trong mô đun http. Trong hàm gọi lại này,
việc phân tích cú pháp URL của yêu cầu đã được gửi đến máy chủ bằng cách sử dụng phương
thức parse (phân tích cú pháp) của mô đun url. Việc này sẽ cung cấp cho bạn một đối tượng
URL và bạn có thể sử dụng thuộc tính pathname (tên đường dẫn) của nó để nhận được đường
dẫn tương đối. Nếu pathname là /doSearch, bạn gọi hàm doSearch (được thảo luận dưới đây).
Nếu không, hãy gọi hàm serveStatic từ Liệt kê 5. Liệt kê 6 cho thấy cách doSearch hoạt
động.
Liệt kê 6. Xử lý các yêu cầu tìm kiếm
doSearch = (uri, response) ->
query = uri.query.split "&"
params = {}
query.forEach (nv) ->
nvp = nv.split "="
params[nvp[0]] = nvp[1]
keyword = params["q"]
combinedSearch keyword, (results) ->
response.writeHead 200, 'Content-Type': 'text/plain'
response.end JSON.stringify results
Hàm doSearch phân tích cú pháp chuỗi truy vấn đối với URL, mà có thể tìm thấy nó trong thuộc
tính query (truy vấn) của đối tượng uri. Chia nhỏ chuỗi truy vấn này ra bằng cách tách chuỗi
này tại vị trí các ký tự &. Sau đó chia tách từng chuỗi con tại vị trí dấu bằng để nhận được các
cặp giá trị- tên từ chuỗi truy vấn. Hãy lưu trữ từng cặp tên-giá trị vào đối tượng params. Lấy ra
tham số "q" để có được từ khóa mà bạn muốn tìm kiếm. Chuyển từ khóa này tới hàm
combinedSearch trong Liệt kê 3. Bạn phải chuyển cho hàm này một hàm gọi lại. Hàm gọi lại ví
dụ chỉ cần viết một thông báo HTTP 200 OK và chuyển các kết quả tìm kiếm thành một chuỗi
bằng cách sử dụng hàm tiêu chuẩn JSON.stringify.
Đó là tất cả những gì bạn cần cho máy chủ. Trong phần tiếp theo, hãy xem cách móc nối máy
chủ này vào mã máy khách trong Phần 3 của loạt bài này.
Về đầu trang
Gọi máy chủ tìm kiếm
Trong Phần 3 bạn đã có một lớp MockSearch đã sử dụng dữ liệu giả đinh để cung cấp các kết
quả tìm kiếm. Bây giờ bạn sẽ định nghĩa một lớp mới để thực hiện một tìm kiếm thực gọi máy
chủ tìm kiếm. Liệt kê 7 cho thấy lớp tìm kiếm mới.
Liệt kê 7. Lớp tìm kiếm thực
class CombinedSearch
search: (keyword, callback) ->
xhr = new XMLHttpRequest
xhr.open "GET", "/doSearch?q=#{encodeURI(keyword)}", true
xhr.onreadystatechange = ->
if xhr.readyState is 4
if xhr.status is 200
response = JSON.parse xhr.responseText
results =
google: response.google.map (result) ->
new GoogleSearchResult result
twitter: response.twitter.map (result) ->
new TwitterSearchResult result
callback results
xhr.send null
Lớp CombinedSearch có một phương thức duy nhất là search có chữ ký giống như phương thức
search của MockSearch. Nó nhận một từ khóa và một hàm gọi lại. Bên trong hàm này:
Sử dụng XMLHttpRequest, một "người bạn cũ" quen thuộc của bất kỳ nhà phát triển web
nào, để tạo một yêu cầu HTTP đến máy chủ bằng cách sử dụng đường dẫn /doSearch và
từ khóa đã được chuyển đến hàm này.
Khi bạn nhận được một phản hồi, hãy phân tích cú pháp của nó bằng cách sử dụng
JSON.parse.
Tạo một đối tượng các kết quả với các trường google và twitter. Tạo ra chúng bằng
cách sử dụng các lớp GoogleSearchResult và TwitterSearchResult trong Phần 3.
Chỉ cần chuyển các kết quả trở lại hàm callback.
Bây giờ, bạn chỉ cần sử dụng lớp doSearch này trong phương thức MockSearch. Liệt kê 8 cho
thấy cách sử dụng lớp CombinedSearch.
Liệt kê 8. Sử dụng lớp CombinedSearch
@doSearch = ->
$ = (id) -> document.getElementById(id)
kw = $("searchQuery").value
appender = (id, data) ->
data.forEach (x) ->
$(id).innerHTML += "#{x.toHtml()}"
ms = new CombinedSearch
ms.search kw, (results) ->
appender("gr", results.google)
appender("tr", results.twitter)
Nếu bạn so sánh Liệt kê 8 với doSearch trong Phần 3, bạn sẽ không thấy nhiều sự khác biệt.
Điều duy nhất có khác là ở dòng thứ bảy. Thay vì tạo một cá thể là MockSearch thì bạn tạo một
cá thể là CombinedSearch. Những thứ khác đều như nhau. Bạn nhận được từ khóa từ trang web,
gọi tìm kiếm và sau đó gắn thêm các kết quả bằng cách gọi phương thức toHtml của từng đối
tượng SearchResult. Hình 1 cho thấy ứng dụng web với các kết quả tìm kiếm "sống" đến từ
máy chủ.
Hình 1. Chạy ứng dụng web ví dụ
Để nhận được các thay đổi bạn đã làm đối với mã phía máy khách, bạn cần biên dịch lại nó bằng
coffee -c search.coffee. Để chạy ứng dụng, hãy sử dụng coffee search-server.coffee.
Sau đó, bạn có thể mở một trình duyệt tới địa chỉ và thử một số truy vấn
khác nhau.
Về đầu trang
Kết luận
Trong bài này, bạn đã hoàn thành ứng dụng web bằng cách xây dựng thành phần phía máy chủ
để bổ sung cho mã phía máy khách trong Phần 3. Giờ đây, sau khi hoàn thành phần cuối của loạt
bài này, bạn đã có một ứng dụng hoàn chỉnh—tất cả được viết bằng CoffeeScript. Bạn đã sử
dụng rất nhiều tính năng của Node.js đã cho phép bạn sử dụng CoffeeScript như một công nghệ
phía máy chủ.
Một hạn chế đáng trách của Node.js là nó không chặn các hàm callback khi gọi từ tầng này sang
tầng khác. Điều này có thể khiến cho việc tư duy sắp xếp/phân loại khó khăn và với cú pháp dài
dòng của JavaScript thậm chí còn khiến mọi việc phức tạp hơn. CoffeeScript không thay đổi nhu
cầu sử dụng của tất cả các hàm callback, nhưng với cú pháp rõ ràng của nó đã giúp dễ viết và dễ
hiểu những đoạn mã như thế này hơn một chút.