码迷,mamicode.com
首页 > 其他好文 > 详细

[CoffeeScript]咖啡,向服务器端进军!

时间:2015-03-10 14:00:35      阅读:145      评论:0      收藏:0      [点我收藏+]

标签:coffeescript

??

简介

CoffeeScript 是构建在 JavaScript 基础之上的一种全新编程语言,提供了能够吸引 Python 或 Ruby 爱好者的整洁的语法。此外还提供了受 Haskell 和 Lisp 等语言启发得出的许多函数式编程特性。

在本 系列文章 的 第 1 部分 中,我们了解了使用 CoffeeScript 的优势。此外还设置了开发环境,运行了脚本。在 第 2 部分 中,我们在尝试解决数学问题的过程中尝试了许多 CoffeeScript 特性,探索了 CoffeeScript 编程语言。在 第 3 部分 中,为一个 Web 应用程序编写了客户端代码。

在最后的这篇文章中,您将编写服务器端组件,并完成应用程序 — 所有一切都是使用 CoffeeScript 完成的。

下载 本文中使用的源代码。

技术分享

调用所有 Web 服务

第 3 部分 中的 Web 应用程序使用一个关键字执行了 Google 和 Twitter 搜索。对于应用程序的客户端,您模拟了来自服务器的结果。为了实际实现此类功能,您需要应用程序的服务器端调用 Google 和 Twitter 提供的 Web 服务。两家公司均提供了非常简单的搜索服务。您只需对搜索服务发出 HTTP GET 请求即可。清单 1 展示了发出 HTTP GET 请求的一般函数。

清单 1. 获取 Web 资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
                 
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}"

 

require 语句是脚本的第一条语句,在本系列的 第 1 部分 中已经对此进行了简单的介绍。这是一种 Node.js 模块导入语法,或者至少应该说是这种语法的 CoffeeScript 版本。“原生” 版本应该是 var http = require("http");。在这篇文章中,您将使用多个 Node.js 核心模块。(这些模块的工作原理不在本文讨论范围之内。)如果您安装了 Node.js,那么就应该能使用本文中使用的所有模块(请参见 第 1 部分)。对于 清单 1 中的示例,您使用的是 http 模块,它为发出和接收 HTTP 请求提供了一些非常有用的类和函数。

清单 1 随后定义了一个 fetchPage 函数,可以接受以下四个参数:

1. 资源的 host 名称。

2. 资源的 port

3. 资源的 path

4. 一个 callback 函数。

Node.js 中任何类型的 I/O 函数在本质上都是异步的,因此在完成时需要通过一个 callback 函数进行调用。fetchPage 函数接受一个 callback 函数作为第四个参数。随后使用前三个参数,通过 http 模块的 get 函数发出一条 HTTP GET 请求。

fetchPage 函数也获取一个 callback 函数,将有一个 ClientResponse 实例传递给后一个函数。ClientResponse 是 http 模块中定义的一个对象,它实现了 ReadableStream 接口(Node.js 中的核心接口)。这是一个异步接口,接受两个事件:data 和 end。其惟一的函数用于为这些事件注册回调。在从您发出 HTTP GET 请求的资源接收到数据时,将发生数据事件。

资源将一次性返回所有数据,但更常见的做法是分块发送数据。接收到各块时,数据事件将被触发,回调将被调用。您创建了一个名为 contents 的变量;每次接收到另一个块时,都会将其附加到 contents。接收了所有数据之后,即触发 end 事件。现在,您获得了全部数据,因此可以将 contents 传递给传入 fetchPage 函数的 callback 函数。定义了这个多用途函数之后,下面我们将为 Google 和 Twitter 搜索 API 创建一些专用函数,如 清单 2 所示。

清单 2. Google 与 Twitter 搜索函数

1
2
3
4
5
6
7
8
9
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

 

清单 2 中定义了两个函数:

1. googleSearch,用于获取一个 keyword 和一个 callback 函数。它将固定主机,并使用 CoffeeScript 的字符串插值创建路径,随后使用 fetchPage

2. twitterSearch,该函数与 googleSearch 极为相似,但使用了不同的主机和路径值。

对于两个路径值,您都要使用字符串插值和 JavaScript 提供的便捷的 encodeURI 函数来处理任何空格或其他特殊字符。现在您已经拥有了这些搜索函数,下面即可为合并搜索场景创建特殊函数。

合并异步函数

您可以通过多种方法在 Google 和 Twitter 上执行合并搜索。您可以调用 googleSearch,随后在 callback 中调用 twitterSearch,或者相反。然而,Node.js 的异步/回调架构使您能够更优雅、更高效地完成任务。清单 3 展示了合并搜索。

清单 3. 同时搜索 Google 和 Twitter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)

 

combinedSearch 函数有一项现在已经广为人知的特征:接受一个关键字和一个回调。随后它为合并搜索结果创建一个数据结构,名为datadata 对象拥有一个 google 字段和一个 twitter 字段,两者均初始化为空字符串。下一步是调用 googleSearch 函数。在回调中,您将使用标准 JSON.parse 函数解析来自 Google 的结果。Google 返回的 JSON 文本将解析为 JavaScript 对象。这种它来设置data.google 字段的值。调用 googleSearch 之后,再调用 twitterSearch。其 callback 函数与 googleSearch 的回调函数极为相似。

有必要理解,在两个回调中,您都要检查是否有来自另一个回调的数据。您无法确知哪个回调先完成。因此需要查看是否有来自 Google 和 Twitter 的数据。确认之后,即可调用之前传入 combinedSearch 函数的 callback 函数。您现在得到了一个同时搜索 Google 和 Twitter 并提供合并结果的函数。下一个任务就是将这样的结果公开到您在本系列的 第 3 部分

CoffeeScript Web 服务器

至此,您已经得到了:

1. 一个能够发送关键字、显示搜索结果的网页。

2.一个能够接受关键字并生成 Google 和 Twitter 搜索结果的函数。

怎样将这一切关联起来?您可以将该服务器称为 Web 服务器、应用服务器,甚至是中间件。无论怎样称呼,在 CoffeeScript 为它编写代码都非常容易。

Web 服务器需要满足两个目的。显然,它需要接受合并搜索的请求。此外还需要提供您在 第 3 部分 中创建的静态资源。您要创建的是一个 Web 应用程序,因此必须密切注意同源策略。搜索调用必须发往生成网页的相同位置。我们首先来处理静态资源。清单 4 展示了一个处理静态资源的函数。

清单 4. 处理静态资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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()

 

serveStatic 函数处理 Web 应用程序中对静态资源的请求。请注意,您还需要使用两个 Node.js 模块:

1. path 是一个处理文件路径的实用工具库。

2. 文件系统或 fs 提供了 Node.js 中的所有文件 I/O,大体上就是基于标准 POSIX 函数的一个包装器。

serveStatic 函数接受两个参数:

1. uri 实际上是 Web 浏览器所请求的静态文件的相对路径。

2. ServerResponse 对象,是 http模块中定义的另外一种类型。它的功能之一就是使您能够写入 HTTP GET 向资源请求的数据。

在 serveStatic 中,使用 process.cwd 将文件的相对路径转为绝对路径。process 对象是一个全局对象,指示正在运行 Node.js 的系统进程。它的 cwd 方法提供了当前工作目录。使用 path 模块,整合当前工作目录和您需要的文件的相对目录,结果将得到一个绝对路径。有了绝对路径,您就可以再次使用 path 模块,检查文件是否存在。检查一个文件是否存在时,需要涉及到 I/O,因此这是一个异步函数。为其传递 fileName 和回调函数。回调函数将提供一个布尔值,使您了解文件是否存在。如果不存在,那么您就就需要写出一条 HTTP 404 “文件未找到”消息。

如果文件确实存在,那么就需要使用 fs 模块和它的 readFile 方法(异步方法)来读取文件的内容。该方法将获取 fileName、一个类型和一个回调函数。回调函数获取两个参数:

1. 一个表明在从文件系统中读取资源时遇到的任何问题的错误参数。如果存在问题,系统会向客户端返回一个 HTTP 500 错误消息。

2. 如果没有问题,则会显示 HTTP 200 OK 消息,并将文件的内容回发给客户端。

此函数能对静态文件进行相对较为简单的处理。下一部分将讨论您希望动态响应一个搜索请求的更为困难的场景。

动态响应与服务器

示例 Web 服务器主要处理对静态资源的请求和动态搜索请求。我们的战略是使用特定 URL 来处理搜索请求,随后将其他请求分载到serveStatic 函数。为搜索请求使用 /doSearch 的相对 URL。清单 5 展示了 Web 服务器代码。

清单 5. CoffeeScript Web 服务器

1
2
3
4
5
6
7
8
9
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 http://127.0.0.1:8080"

 

这个脚本同样从载入一个 Node.js 模块开始。url 模块是解析 URL 时的一个有用的库。下一步是使用 清单 1 中加载的 http 模块创建 Web 服务器。使用该模块的 createServer 方法,该方法将获取一个回调函数,每次对 Web 服务器发出一条请求时,都会调用这个回调函数。该回调函数接受两个参数:一个 ServerRequest 实例和一个 ServerResponse 实例。两种类型都是在 http 模块中定义的。在回调函数中,使用 url 模块的 parse 方法,解析对服务器发出的请求的 URL。这将为您提供一个 URL 对象,您可以使用它的pathname 属性获取相对路径。如果 pathname 是 /doSearch,则应调用 doSearch 函数(详见下文讨论)。否则就应该调用 清单 5 中的serveStatic 函数。清单 6 展示了 doSearch 的工作方式。

清单 6. 处理搜索请求

1
2
3
4
5
6
7
8
9
10
11
                
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

 

doSearch 函数将解析 URL 的查询字符串,可以在 uri 对象的查询属性中找到这个字符串。根据 “&” 字符拆分字符串。随后根据等号字符拆分各子字符串,获得查询字符串中的名称值对。将各名称值对存储在 params 对象中。获取 "q" 参数,以便获得您希望搜索的关键字。将此传递给 清单 3 中的 combinedSearch 函数。您必须为其传递一个回调函数。示例回调函数直接写出一条 HTTP 200 OK 消息,并使用标准函数 JSON.stringify 将结果转为字符串。

这就是服务器所需的一切。在下一节中,我们将介绍如何将这样的服务器代码与本系列 第 3 部分 中的客户端代码挂接起来。

调用搜索服务器

在 第 3 部分 中,您编写了一个使用模拟数据提供搜索结果的 MockSearch 类。现在,您将定义一个新类,调用搜索服务器来执行真正的搜索。清单 7 显示了新的搜索类。

清单 7. 实际搜索类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

 

CombinedSearch 类拥有单独一个方法,即 search 方法,它与 MockSearch 的 search 方法具有相同的特征。也就是接受一个关键字和一个回调函数。在函数内:

1. 使用 XMLHttpRequest(所有 Web 开发人员的 “老朋友”),通过传递到函数中的 /doSearch 路径和关键字向服务器发出 HTTP 请求。

2. 获得响应之后,使用 JSON.parse 来解析它。

3. 创建一个包含 google 和 twitter 字段的结果对象。使用 第 3 部分 中的 GoogleSearchResult 和 TwitterSearchResult 类来创建这些字段。

4. 将结果传递回 callback 函数。

现在,您需要的只是在 Web 页面的 doSearch 方法中使用这些类,而不是在 MockSearch 中使用。清单 8 展示了如何使用CombinedSearch 类。

清单 8. 使用 CombinedSearch 类

1
2
3
4
5
6
7
8
9
10
@doSearch = ->
   $ = (id) -> document.getElementById(id)
   kw = $("searchQuery").value
   appender = (id, data) ->
       data.forEach (x) ->
           $(id).innerHTML += "<p>#{x.toHtml()}</p>"
   ms = new CombinedSearch
   ms.search kw, (results) ->
       appender("gr", results.google)
       appender("tr", results.twitter)

 

将 清单 8 与 第 3 部分 中的 doSearch 对比,您不会发现很多的差异。惟一不同的就是第七行。这里实例化的不再是 MockSearch 实例,而是一个 CombinedSearch 实例。其他所有部分都是完全相同的。您从网页获取关键字,调用搜索,随后通过调用各SearchResult 对象的 toHtml 方法来附加结果。图 1 展示了包含来自服务器的 “实时” 搜索结果的 Web 应用程序。

图 1. 运行示例 Web 应用程序

技术分享

为了实现客户端代码的更改,您需要使用 coffee -c search.coffee 进行重新编译。如需运行应用程序,请使用 coffee search-server.coffee。随后即可打开浏览器,转到 http://127.0.0.1:8080,并尝试执行各种查询。

在这篇文章中,您完成了 Web 应用程序,构建了服务器端组件来补充 第 3 部分 中的客户端代码。现在,在本 系列 结束时,您获得了一个完全在 CoffeeScript 中编写的完整应用程序。您使用了 Node.js 中的许多特性,这使您能够将 CoffeeScript 用作服务器端技术。

人们对 Node.js 的普遍异议就是其非阻塞式风格会导致多层回调函数。这可能导致您难以理清头绪,而 JavaScript 繁冗的语法进一步加大了复杂度。CoffeeScript 并未改变使用所有这些回调的需求,但其优雅的语法确实使您能够更加轻松地编写和理解此类代码

[CoffeeScript]咖啡,向服务器端进军!

标签:coffeescript

原文地址:http://blog.csdn.net/erlib/article/details/44174273

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!