Skip to content

Latest commit

 

History

History
1473 lines (1160 loc) · 61.3 KB

File metadata and controls

1473 lines (1160 loc) · 61.3 KB

九、使用 Web 服务器框架

Node.js 非常适合构建 web 服务器。正因为如此,许多开发人员构建了 web 服务器应用。其中一些已经成为开源框架,可供任何希望开发 Node.js web 服务器的开发人员使用。在本章中,你将看到利用这些框架来构建 Node.js web 服务器的例子。

首先,您将研究最流行的 Node.js 框架之一 Express。您将了解如何利用 Express 构建 web 应用。您还将看到如何利用 Express 附带的工具快速构建应用。Express 还提供了一种路由请求的简单方法,允许您创建逻辑路由,并使用框架构建 API。

除了 Express 之外,本章还将介绍其他几个框架,它们允许您创建基于 Node.js 的 web 应用。您将研究的下一个框架在实现表达上略有不同,但是您将看到创建 Node.js 应用的各种方法。其中包括以下内容:

  • 盖迪
  • 雅虎!莫吉托(鸡尾酒的一种)
  • 熨斗

9-1.快速入门

问题

您想要启动并运行 Express Node.js 应用框架。

解决办法

有几种方法可以帮助您开始使用 Express。在这个解决方案中,清单 9-1 ,您将使用 Express 构建一个 web 服务器来执行几个任务。

首先,您的服务器将使用 Express 中间件来记录向服务器发出的请求。您的服务器还将从脚本执行的目录中提供静态文件。这些静态文件将在提供时用 gzip 压缩。除了这些操作之外,您的 Express 服务器将能够执行简单的身份验证,并对静态页面无法提供服务的任何地址提供回退响应。

您还将看到如何获取和设置许多快速设置,以及如何启用和禁用它们。您还将创建一个方法,根据您是在开发模式还是在生产环境中提供内容,为您的应用设置不同的配置。要开始使用,您必须首先使用 npm 安装 Express framework。这可以通过运行命令$ npm install express 或全局执行$ npm install –g express 来完成;然后,您将能够开始使用 Express。

清单 9-1 。快速入门

/**
* Getting started with ExpressJS
*/

var express = require('express'),
        app = express();
// use middleware
app.use(express.logger());
app.use(express.compress());
app.use(express.static(__dirname));
app.use(express.basicAuth(function(username, password) {
        return username == 'shire' & password == 'baggins';
}));
// a simple route
app.get('/blah', function(req, res) {
        res.send(app.get('default'));
});
// a default handler
app.use(function(req, res) {
        res.send(app.get('default'));
});

// settings
console.log(app.get('env'));         // development
console.log(app.get('trust proxy')); // undefined
app.disable('trust proxy');
console.log(app.get('trust proxy')); // false
console.log(app.get('jsonp callback name'));
console.log(app.get('json replacer'));
console.log(app.get('json spaces'));
console.log(app.get('case sensitive routing'));
console.log(app.get('strict routing'));
console.log(app.get('view cache'));
console.log(app.get('view engine'));
console.log(app.get('views'));

// configurations
app.configure('development', function() {
        app.set('default', 'express development site');
});

app.configure('production', function() {
        app.set('default', 'express production site');
});
// app.engine('jade', require('jade').__express);
app.listen(8080);                    // same as http.server.listen

它是如何工作的

Express 是一个为 Node.js 设计的应用框架。它是一个高度灵活的框架,允许您根据自己的需要构建 Node.js 应用。在清单 9-1 的解决方案中,你创建了一个执行几项任务的 web 服务器。为了实现这一点,首先必须安装 Express Node.js 模块。这可以用$ npm install express在你正在做的项目的本地完成,也可以用$ npm install –g express在全局完成。

一旦您安装了 Express,您就可以将它合并到您的项目中。您必须包含框架—require('express'),然后您告诉 express 通过实例化 Express 对象来创建一个应用。这将在您的解决方案中创建一个对 app 变量的引用,这将提供对 Express API 的访问。

这个解决方案首先让你看到的是对app.use() 的一系列调用(见清单 9-2 )。这个函数来自于 Express 的主要依赖项之一——Connect,它是 Sencha Labs 为 Node.js 构建的一个应用中间件框架。下面的片段来自于app.use()的快速实现。这向你展示了。use()调用是从 Connect 扩展而来的。

清单 9-2 。express/lib/application.js 中的 app.use

app.use = function(route, fn){
  var app;
  // default route to '/'
  if ('string' != typeof route) fn = route, route = '/';

  // express app
  if (fn.handle && fn.set) app = fn;

  // restore .app property on req and res
  if (app) {
    app.route = route;
    fn = function(req, res, next) {
      var orig = req.app;
      app.handle(req, res, function(err){
        req.__proto__ = orig.request;
        res.__proto__ = orig.response;
        next(err);
      });
    };
  }

  connect.proto.use.call(this, route, fn);
  // mounted an app
  if (app) {
    app.parent = this;
    app.emit('mount', this);
  }

  return this;
};

从这段代码中可以看出,app.use()方法是通过一些定制逻辑运行的,其中包括确保方法签名是预期的,请求和响应被正确传递,所有这些都是在调用 Connect 的.use()方法之前。

这意味着,当您在解决方案中调用app.use(<function>)时,您要么使用特殊的中间件功能提供一个通用路由,要么设置一个显式路由。在解决方案中,您首先使用它来调用express.logger()

当添加到 Express 应用中时,express.logger()中间件用于记录服务器上的每个请求。在这个解决方案中,当您运行 Express 应用并导航到您的 Express 站点时,express.logger() 应用将记录类似如下的内容:

清单 9-3 。express.logger()在运行中

< 127.0.0.1 - - [Tue, 16 Jul 2013 00:26:22 GMT] "GET / HTTP/1.1" 401 - "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:22.0) Gecko/20100101 Firefox/22.0"
< 127.0.0.1 - - [Tue, 16 Jul 2013 00:26:27 GMT] "GET / HTTP/1.1" 200 24 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:22.0) Gecko/20100101 Firefox/22.0"

您使用的下一个中间件是express.compress() 。这个中间件将使用 gzip 压缩内容。例如,当添加了express.compress()中间件时,一个 1.3 KB(未压缩)的 JavaScript 文件大约需要 518 B。

express.compress()之后,您将中间件express.static()添加到您的应用中。Express 使用express.static指向您希望用作静态文件处理程序的目录。例如,您可以使用它来提供静态 JavaScript、HTML 和 CSS 文件。关于express.static() 的案例对于发现app.use()的用法很有意思。如解决方案所示,您只需使用app.use()的功能处理程序,并将静态中间件放在那里。但是,您希望提供的静态内容可能位于应用中一个不知名的目录中,或者您只是希望将静态内容的路径重命名为其他名称。只需为静态处理程序命名一条路由,就可以轻松做到这一点。例如,如果您的静态内容驻留在子目录'/content/shared/static/'中,并且您希望将其作为'/static'来提供,那么您的app.use()将更改如下:

//Original
app.use(express.static(__dirname + '/content/shared/static'));
//Altered route
App.use('/static',express.static(__dirname + '/content/shared/static'));

您连接到 Express 的下一个中间件是express.basicAuth()中间件。这个中间件将允许您以基本的方式实现身份验证,以授予对 Express 应用的访问权限。在这个解决方案中,您提供了一个回调,然后直接检查这个回调中由basicAuth()中间件提供的凭证。

解决方案中的最后一个例子app.use()是为您的应用设置通用路由的默认响应。这是为回调提供请求和响应的地方。在解决方案部分,您还可以看到app.get()的使用。

app.get() 的表达方法有两个主要作用。第一个角色是路由角色。这是app.HTTP_VERB,意味着您将处理任何 HTTP GET 请求。您将在后面的章节中看到更多关于app.get()的用法。第二个角色,正如您在默认路由的回调中看到的,是通过使用app.set()方法检索您为 Express 应用设置的设置。

方法用于改变应用中的设置。签名是app.set(<name>, <value>)。在这个解决方案中,您设置了几个变量。一个是“default”变量,它设置您希望从 web 服务器中的默认路由提供的文本。

有一些设置用作 Express 应用的环境变量。有些,比如'jsonp callback name',默认情况下被设置为' callback ',但是可以设置为您希望的 JSON with padding (JSONP)方法的任何值。其他的,比如'trust proxy',显示设置的状态。在示例中,您可以看到默认的“trust proxy”设置是未定义的。然后利用app.disable('trust proxy')方法将该值设置为 false。这也可以使用app.enable()设置为真。还有一个env变量,它是 Node.js 进程运行的环境。然后,您可以使用来配置希望在开发和生产环境中保持不同的选项。

这是 Express 提供的基本 API。您可以将它用作 web 服务器框架,如本例所示,也可以利用 Express 的命令行功能来生成一个应用,您将在下面几节中看到。

9-2.使用 Express 生成应用

问题

您希望利用 Express 的命令行界面来快速生成应用支架。

解决办法

Express 不仅附带了您在上一节中看到的 API,还允许您创建能够处理 Node.js 服务器中所需的许多样板方法的 web 服务器,而且它还可以用作命令行应用生成器。清单 9-4 展示了快速应用生成的几种方法。

清单 9-4 。快递申请代

> npm install -g express
> mkdir myapp
> cd myapp
> express -h

      Usage: express [options]
      Options:
        -h, --help          output usage information
        -V, --version       output the version number
        -s, --sessions      add session support
        -e, --ejs           add ejs engine support (defaults to jade)
        -J, --jshtml        add jshtml engine support (defaults to jade)
        -H, --hogan         add hogan.js engine support
        -c, --css <engine>  add stylesheet <engine> support (less|stylus) (defaults to plain css)
        -f, --force         force on non-empty directory
> express
destination is not empty, continue? y
create : .
create : ./package.json
create : ./app.js
create : ./public
create : ./routes
create : ./routes/index.js
create : ./routes/user.js
create : ./public/images
create : ./public/javascripts
create : ./views
create : ./views/layout.jade
create : ./public/stylesheets
create : ./public/stylesheets/style.css

install dependencies:
  $ cd . && npm install
run the app:
  $ node app
> express --sessions --ejs --css less --force

create : .
create : ./package.json
create : ./app.js
create : ./public
create : ./routes
create : ./routes/index.js
create : ./routes/user.js
create : ./public/images
create : ./public/javascripts
create : ./views
create : ./views/index.ejs
create : ./public/stylesheets
create : ./public/stylesheets/style.less

install dependencies:
  $ cd . && npm install
run the app:
  $ node app

它是如何工作的

这个命令行实用程序首先使用npm install –g express将 Express 模块安装到您的机器上。从这里,您现在可以访问 express 命令行实用程序。当您键入“express”时,您运行的 JavaScript 文件可以在源文件的 bin 文件中找到。

首先,您想知道在 Express 命令行工具中可以访问什么。您可以通过键入express –hexpress --help来完成此操作。这将打印可以伴随express命令的命令参数列表。前两个是帮助文本和您正在使用的 Express 版本的一般输出。

其他选项是通过解析命令行参数在应用中设置的,这些参数通过使用名为“commander”的模块传递并添加到名为“program”的对象中。

清单 9-5 。使用 commander 解析命令行参数

program
  .version(version)
  .option('-s, --sessions', 'add session support')
  .option('-e, --ejs', 'add ejs engine support (defaults to jade)')
  .option('-J, --jshtml', 'add jshtml engine support (defaults to jade)')
  .option('-H, --hogan', 'add hogan.js engine support')
  .option('-c, --css <engine>', 'add stylesheet <engine> support (less|stylus) (defaults to plain css)')
  .option('-f, --force', 'force on non-empty directory')
  .parse(process.argv);

接下来,您将创建一个 Express 应用,默认设置是不使用任何东西。这可以通过导航到您希望创建应用的目录并运行不带任何参数的express命令来完成。您还可以添加一个单独的参数来命名一个应用名express myapp,它将根据这个名称创建一个子目录,脚本将在这个目录中执行。

当 express 命令行应用执行时,除了如上所述解析参数之外,它还将立即调用一个函数,该函数将传入生成应用的路径。

清单 9-6 。生成应用

(function createApplication(path) {
  emptyDirectory(path, function(empty){
    if (empty || program.force) {
      createApplicationAt(path);
    } else {
      program.confirm('destination is not empty, continue? ', function(ok){
        if (ok) {
          process.stdin.destroy();
          createApplicationAt(path);
        } else {
          abort('aborting');
        }
      });
    }
  });
})(path);

这将检查您创建应用的目录是否为空。它通过利用 Node.js 和文件系统模块来实现这一点。

function emptyDirectory(path, fn) {
  fs.readdir(path, function(err, files){
    if (err && 'ENOENT' != err.code) throw err;
    fn(!files || !files.length);
  });
}

如果目录不为空,Express 将向您显示警告“目的地不为空,是否继续?”您只需输入“y”并继续。应用将根据您提供的参数生成应用结构并搭建您的应用。这是通过createApplicationAt()功能完成的。

该方法首先创建应用的根目录,然后创建应用所需的所有目录。您可以看到,这将利用您设置的标志来创建应用目录树。

mkdir(path, function(){
  mkdir(path + '/public');
  mkdir(path + '/public/javascripts');
  mkdir(path + '/public/images');
  mkdir(path + '/public/stylesheets', function(){
    switch (program.css) {
      case 'less':
        write(path + '/public/stylesheets/style.less', less);
        break;
      case 'stylus':
        write(path + '/public/stylesheets/style.styl', stylus);
        break;
      default:
        write(path + '/public/stylesheets/style.css', css);
    }
  });

  mkdir(path + '/routes', function(){
    write(path + '/routes/index.js', index);
    write(path + '/routes/user.js', users);
  });

  mkdir(path + '/views', function(){
    switch (program.template) {
      case 'ejs':
        write(path + '/views/index.ejs', ejsIndex);
        break;
      case 'jade':
        write(path + '/views/layout.jade', jadeLayout);
        write(path + '/views/index.jade', jadeIndex);
        break;
      case 'jshtml':
        write(path + '/views/layout.jshtml', jshtmlLayout);
        write(path + '/views/index.jshtml', jshtmlIndex);
        break;
      case 'hjs':
        write(path + '/views/index.hjs', hoganIndex);
        break;

    }
  });

在设置好目录结构之后,将会生成根应用 JavaScript 文件以及正确配置的package.json文件。这可以通过简单地替换基于您在命令行上传递给 Express generator 的设置而设置的令牌来实现。

// CSS Engine support
    switch (program.css) {
      case 'less':
        app = app.replace('{css}', eol + 'app.use(require(\'less-middleware\')({ src: __dirname + \'/public\' }));');
        break;
      case 'stylus':
        app = app.replace('{css}', eol + 'app.use(require(\'stylus\').middleware(__dirname + \'/public\'));');
        break;
      default:
        app = app.replace('{css}', ");
    }

    // Session support
    app = app.replace('{sess}', program.sessions
      ? eol + 'app.use(express.cookieParser(\'your secret here\'));' + eol + 'app.use(express.session());'
      : ");

    // Template support
    app = app.replace(':TEMPLATE', program.template);

    // package.json
    var pkg = {
        name: 'application-name'
      , version: '0.0.1'
      , private: true
      , scripts: { start: 'node app.js' }
      , dependencies: {
        express: version
      }
    }

    if (program.template) pkg.dependencies[program.template] = '*';
    // CSS Engine support
    switch (program.css) {
      case 'less':
        pkg.dependencies['less-middleware'] = '*';
        break;
      default:
        if (program.css) {
          pkg.dependencies[program.css] = '*';
        }
    }

    write(path + '/package.json', JSON.stringify(pkg, null, 2));
    write(path + '/app.js', app);

现在,您已经有了一个通过应用的命令行界面工具构建的正常运行的 Express 应用。您可以看到,Express 附带的默认模板呈现利用了一个名为 Jade 的框架。在下一节中,您将看到如何利用这个工具来为您的 Express 应用创建最小且干净的 HTML 模板。

9-3.用 Jade 渲染 HTML

问题

您已经使用命令$ express生成了一个快速应用。默认情况下,这个应用将利用 Jade HTML 模板框架,因此您需要能够理解并在 Node.js 应用中利用这个框架。

解决办法

Jade 是专门为 Node.js 创建的模板语言,默认情况下它与 Express 一起使用,所以对它有所了解是很重要的。它是作为一个极简的模板引擎构建的;它可以用来从一个非常简洁的模板构建 HTML。

在此解决方案中,您将检查并构建使用 Express 生成的默认模板。这些位于/视图中。

清单 9-7 。/views/layout.jade

doctype 5
html
  head
    title= title
    link(rel='stylesheet', href='/stylesheets/style.css')
    //if lt IE 8
      script(src='/old_ie.js')
  body
    block content

清单 9-8 。/views/index.jade

extends layout

block content
  h1= title
  p.a_class Welcome to #{title}
  #an_id this is a div which has an ID
  label A range slider:
    input(type='range')
  #ckbx
    label A checkbox:
      input(type='checkbox', checked)
  ul
  li.odd: a(href='#', title='one') one
  li.even: a(href='#', title='two') two
  li.odd: a(href='#', title='three') three

  case flag
    when 0: #zero there is no flag
    when 1: #one there is a single flag
    default: #other other

  - if (items.length)
  ul
    - items.forEach(function(item){
      li= item
    - })

清单 9-9 。Routes/index.js

/*
 * GET home page.
 */

exports.index = function(req, res){
  res.render('index', { title: 'Express', flag: 0, items: ['a', 'b', 'c'] });
};

它是如何工作的

Jade 的工作原理是解析自己的语法,然后将其转换成相应的 HTML 输出。看看你的 layout.jade 文件。这个文件声明了一个 doctype,特别是 HTML5 doctype。这可以通过键入“doctype 5”或“!!!5'.这些是文档类型的简写符号。虽然对于简洁的 HTML5 文档类型来说,这不是一个太极端的快捷方式,但是从清单 9-10 中可以看出,如果您在 Express 应用中使用一个过渡文档类型,这将变得非常有用。

清单 9-10 。Express.js 的可用文档类型

var doctypes = exports.doctypes = {
  '5': '<!DOCTYPE html>',
  'default': '<!DOCTYPE html>',
  'xml': '<?xml version="1.0" encoding="utf-8" ?>',
  'transitional': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
  'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
  'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
  '1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
  'basic': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',
  'mobile': '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
};

接下来,您会看到 HTML 元素是以速记的方式编写的,这允许高度可读的标记。创建元素,内部内容由两个空格或一个制表符嵌套,以指示它们相对于父元素。您可以看到,嵌套在 head 元素下面的 title 元素明显体现了这一点。元素中的属性(如 script 元素中的“src ”)在括号中设置。注释从单行 C 风格注释转换为 HTML 注释,因此您可以看到添加条件参数来测试旧版本的 Internet Explorer 并不复杂。一旦完成了这个 HTML 块的所有 Jade 呈现,直到元素,您可以看到这个相对简短的标记被翻译成下面的 HTML:

<!DOCTYPE html><html>
<head>
        <title>Express</title>
        <link rel="stylesheet" href="/stylesheets/style.css">
        <!--[if lt IE 8]>
                <script src=/zomg_old_ie.js”></script>
                <![endif]-->
</head>
<body>
</body>
</html>

在检查 index.jade 文件时,您会看到许多与 layout.jade 文件相同的内容。您可以看到,您已经用“扩展布局”命名了要扩展的 Jade 文件。您还有一个命名块“块内容”,它与布局文件上的相应块相匹配。

这个文件中还展示了 Jade 的模板呈现功能的一个示例。您有一个“h1= title”代码段。该代码片段将在您的代码中创建一个 H1 元素,但也会从 routes/index.js 文件中的索引呈现代码中获取对象,并将“标题”添加到 HTML 标记中。您创建的模板对象的其他部分是“flag”属性和“items”数组。这些项目也被解析成 HTML。如您所见,您在代码中的“case”语句中使用的标志。这允许条件标记。您还可以看到,您能够遍历项目数组,并为每个项目呈现一个列表项目。当进行这种迭代时,您必须在您用来迭代的代码前面加上一个连字符。然后你可以在这个连字符后面写 JavaScript 来生成你想要的布局。当您构建大型列表时,这可以极大地改进标记的生成,并在开发周期中节省时间。另一种遍历这个数组的方法是写“items 中的每一项”,然后写“li= item”(下面缩进)。

Jade 也有向 HTML 标签添加类和 id 的简写方法。您可以看到p.a_class将在哪里生成一个

标签,并将类“

a_class”添加到该标签中。类也可以链接在一起,允许任意数量的类名绑定到一个标签上。向标签添加 ID 也同样简单。只需在文本字符串前加上#就可以了,该文本字符串将成为标签的 ID。但是,您也可以创建一个带有 ID 的标记,而不命名该标记。只需添加'#an_id,您就可以在标记中生成一个<div id='an_id'>标记,而无需键入额外的三个字符来命名 div。

如果您使用 Express 和 Jade 创建 Node.js 应用,您就可以使用这个强大的模板引擎创建干净的标记。

9-4.使用 Express 路由

问题

您已经使用 Express 创建了一个应用。您的应用中的 URL 结构变得越来越复杂,因此您需要一个强大的路由机制来将这些 URL 连接到适当的处理程序。

解决办法

Express 提供了一种在 Node.js 应用中调用正确路由的简单方法。您可以在 Express 应用的路由处理程序中将路由作为外部模块或内联回调来处理。清单 9-11 给出了这两种路由行为的例子。这建立在第 9-2 节的服务器文件上。您可以看到已经添加的路由在这个列表中以粗体突出显示。

清单 9-11 。快速路由

/**
 * Module dependencies.
 */

var express = require('express')
  , routes = require('./routes')
  , user = require('./routes/user')
  , http = require('http')
  , path = require('path');

var app = express();
// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

// development only
if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}

app.get('/', routes.index);
app.get('/user/:id', function(req, res) {
        res.send('you supplied param ' + req.params.id);
});
app.get('/users', user.list);
app.post('/user/:id', function(req, res) {
        console.log('update user' + req.params.id);
        res.send('successfully updated');
});

http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

它是如何工作的

Express 通过利用动词是 HTTP 动词的app.VERB范例来处理路由。这意味着你可以为特定类型的 HTTP 请求路由一个应用,当你在 9-6 节用 Express 构建一个 API 时,你会看到更多。

app.VERB方法的行为都类似于你在 9-1 节看到的中间件路由。它们各自接收一个路由,该路由指向请求的访问 URL,相对于应用根。它们还接收一个回调函数。如果您希望以特定的方式处理特定路由的所有请求,不管提供的 HTTP 动词是什么,您可以使用app.all()函数来实现。

在此解决方案中,有多种路线可供使用。首先,您会看到“/”的根路径有一个路由。这指向一个路由模块和驻留在那里的索引方法。这个索引方法有一个请求和响应对象,然后通过发送一个响应来处理它们,在本例中,响应是使用 Jade 呈现的,但不一定是 HTTP 响应可以发送的任何内容。

您还可以看到路由请求可以提供通用参数。这在路由“users/:id”中很明显。这里的':id '是您将传递给路由的参数。这不限于单一参数;事实上,您可以从清单 9-12 中的源代码片段中看到,任何数量的参数都可以添加到一个路由中。

清单 9-12 。找到一条匹配的路由,然后将剩余的路由解析为参数的一部分

Route.prototype.match = function(path){
  var keys = this.keys
    , params = this.params = []
    , m = this.regexp.exec(path);

  if (!m) return false;
  for (var i = 1, len = m.length; i < len; ++i) {
    var key = keys[i - 1];

    var val = 'string' == typeof m[i]
      ? decodeURIComponent(m[i])
      : m[i];

    if (key) {
      params[key.name] = val;
    } else {
      params.push(val);
    }
  }

  return true;
};

这个逻辑依赖于给定路径的正则表达式,正如您在 app.js 文件中定义的那样。对于' users/:id '路由,构建了这个正则表达式,它将成为下面显示的表达式。

清单 9-13 。“user/:id”路由的正则表达式

/^\\/user\\/(?:([^\\/]+?))\\/?$/

如果您提供了一个 ID,这将创建['/user/1 ',' 1']的正则表达式匹配。如果您要向这个路由添加一个额外的参数(例如' user/:role/:id '或类似的东西),那么正则表达式将会如下所示。

清单 9-14 。“/user/:role/:id”的正则表达式

/^\\/user\\/(?:([^\\/]+?))\\/(?:([^\\/]+?))\\/?$/

这个路由产生['/user/eng/1 ',' eng ',' 1']的正则表达式匹配。然后,这些匹配被添加到路由的 params 数组中。

您可以使用 Express 来建立布线和参数化布线。在下一节中,您将大致了解如何在 Node.js 应用中处理失败的请求。

9-5.处理应用中失败的请求

问题

您已经在 Node.js 中构建了一个 Express 应用,甚至还指定了路由和请求处理。但是,您需要能够在应用中正确处理失败的请求。

解决办法

Express 是一个健壮的框架,允许开发人员以自己的方式处理应用开发过程的许多方面。当您在构建快速路线或通过向 Node.js 应用添加中间件来扩充设置时,这一点很明显。处理失败的请求也是 Express 允许的事情,但它不会对此过于固执己见。

这个失败的请求处理显示在列表 9-15 中。这些处理程序必须遵循一定的模式,但是可以在需要的地方适合您的应用。

清单 9-15 。Express 中失败的请求处理

/**
* Getting started with ExpressJS
*/

var express = require('express'),
        app = express();
// use middleware
app.use(express.logger());
app.use(express.compress());
app.use(express.static(__dirname));
app.use(express.basicAuth(function(username, password) {
        return username == 'shire' & password == 'baggins';
}));
// a simple route
app.get('/blah', function(req, res, next) {
        next(new Error('failing route'));
        res.send(app.get('blah'));
});

// a default handler
app.use(function(req, res) {
        res.send(app.get('default'));
});

app.use(function(err, req, res, next){
 console.error(err.stack);
 res.send(500, 'Oh no! Something failed');
});

// configurations
app.configure('development', function() {
        app.set('default', 'express development site');
        app.set('blah', 'blah blah blah');
});

app.configure('production', function() {
        app.set('default', 'express production site');
});
// app.engine('jade', require('jade').__express);
app.listen(8080); // same as http.server.listen

它是如何工作的

你用与你在第 9-1 节中构建的初始例子相似的方式构建了这个解决方案。清单 9-15 中突出显示了两个重要部分。

首先,为了模拟失败的路由,您在路由'/blah '上构建一个错误。这会调用next()路由处理程序,并向其传递一个新的Error()对象。这允许失败的请求路由工作,因为如果没有这个处理程序,站点将会崩溃。

失败的请求路由是通过使用app.use()函数建立的。然后使用包含错误参数的默认回调。当调用next()函数出错时,Express 将利用这个错误处理程序,并将其作为失败的请求处理程序。通过这种方式,您可以优雅地处理 Node.js 服务器上的错误。

9-6.用 ExpressJS 设计 RESTful API

问题

在设计 API 时,很多时候你希望利用 HTTP 并为你的应用创建一个表述性状态转移(REST)架构。

解决办法

在前面的章节中,您已经了解了如何在 Express 中构建有用的路线。因为 Express API 允许路由以特定的 HTTP 动词为目标,所以它非常适合类似 REST 的 API。

在这个解决方案中,您将创建一个简单的 REST API 来与由产品组成的数据模型进行交互。在本例中,这些产品由一个简单的 JavaScript 对象表示,但是它们可以很容易地成为一个对象集并从数据存储中检索。该示例如清单 9-16 所示。

清单 9-16 。休息界面

/**
 * Module dependencies.
 */

var express = require('express')
  , routes = require('./routes')
  , http = require('http')
  , path = require('path');

var app = express();
// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

// development only
if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}
var products = [
        { id: 0, name: 'watch', description: 'Tell time with this amazing watch', price: 30.00 },
        { id: 1, name: 'sandals', description: 'Walk in comfort with these sandals', price: 10.00 },
        { id: 2, name: 'sunglasses', description: 'Protect your eyes in style', price: 25.00 }
];

app.get('/', routes.index);
// curl -X GET http://localhost:3000/products
app.get('/products', function(req, res) {
        res.json(products);
});
// curl -X GET http://localhost:3000/products/2
app.get('/products/:id', function(req, res) {
        if (req.params.id > (products.length - 1) || req.params.id < 0) {
                res.statusCode = 404;
                res.end('Not Found');
        }
        res.json(products[req.params.id]);
});
// curl -X POST -d "name=flops&description=sandals&price=12.00" http://localhost:3000/products
app.post('/products', function(req, res) {
        if (typeof req.body.name === 'undefined') {
                res.statusCode = 400;
                res.end('a product name is required');
        }
        products.push(req.body);
        res.send(req.body);
});
// curl -X PUT -d "name=flipflops&description=sandals&price=12.00" http://localhost:3000/products/3
app.put('/products/:id', function(req, res) {
        if (req.params.id > (products.length -1) || req.params.id < 0) {
                res.statusCode = 404;
                res.end('No product found for that ID');
        }
        products[req.params.id] = req.body;
        res.send(req.body);
});
// curl -X DELETE http://localhost:3000/products/2
app.delete('/products/:id', function(req, res) {
        if (req.params.id > (products.length - 1) || req.params.id < 0) {
                req.statusCode = 404;
                res.end('No product found for that ID');
        }
        products.splice(req.params.id, 1);
        res.json(products);
});

http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

它是如何工作的

REST 是一种应用架构模式,可以依赖 HTTP 请求来执行操作。这些操作通常采用服务器上常见的创建、读取、更新和删除方法的形式。

典型的 REST 接口至少会使用 HTTP GET 和 POST 方法 在服务器上执行这些动作。然而,在这个解决方案中,您实际上使用了 GET、PUT、POST 和 DELETE HTTP 动词来创建您的 API。您用来简单地从资源中检索数据的 GET 方法。POST 方法用于创建数据,而 PUT 方法用于更新现有数据。DELETE 方法将从数据存储中移除数据。

您从创建产品的数据存储开始,这是代码中包含三个条目的对象数组。一旦你的服务器开始运行,你就可以通过发送 HTTP GET 到http://localhost:3000/products .来获取这些数据

$ CURL -X GET http://localhost:3000/products
[
  {
    "name": "watch",
    "description": "Tell time with this amazing watch",
    "price": 30
  },
  {
    "name": "sandals",
    "description": "Walk in comfort with these sandals",
    "price": 10
  },
  {
    "name": "sunglasses",
    "description": "Protect your eyes in style",
    "price": 25
  }
]

现在您已经创建并获取了数据,您可以向产品数据存储中插入新记录了。在 REST API 中,您只需向产品路线发送一个 POST 请求。如果成功,并且它必须至少包括产品的名称,它将返回您刚刚添加的项目:

$ CURL -X POST -d "name=flops&description=sandal%20things&price=12.00"http://localhost:3000/products
{
  "name": "flops",
  "description": "sandal things",
  "price": "12.00"
}

您现在已经创建了一个新记录,但是您意识到您给产品起的名字不正确。要更新产品的名称,您需要向服务器发送一个 PUT 请求,但是您还必须知道您希望将它发送到的:id。在您的例子中,您知道这是数组中的第四项,所以:id 变成了“3”为了更新名称,您现在可以发送请求,该请求将使用数据存储中的更新值进行响应:

$ curl -X PUT -d "name=flip%20flops&description=sandals&price=12.00" http://localhost:3000/products/3
{
  "name": "flip flops",
  "description": "sandals",
  "price": "12.00"
}

在创建和更新这个新记录后,您意识到产品“凉鞋”在您的产品系列中不再是必需的。因此,您可以通过向该项发送 DELETE HTTP 请求 来删除它,这将把它从数据存储中完全删除。这也将返回删除此项目后现在可用的产品列表:

$ curl -X DELETE http://localhost:3000/products/1
[
  {
    "name": "watch",
    "description": "Tell time with this amazing watch",
    "price": 30
  },
  {
    "name": "sunglasses",
    "description": "Protect your eyes in style",
    "price": 25
  },
  {
    "name": "flip flops",
    "description": "sandals",
    "price": "12.00"
  }
]

9-7.和盖迪一起行动

问题

您希望利用 Node.js 的 Geddy 应用框架来构建您的产品。

解决办法

要开始使用 Geddy,首先需要通过 npm 安装该模块。如果你全局安装它,如清单 9-17 所示,你将可以从你机器上的任何目录访问应用生成器。

清单 9-17 。安装 Geddy

$ npm install –g geddy

清单 9-18 。在指定的目录中生成应用

$ geddy gen app geddyapp
$ cd geddyapp

清单 9-19 。运行应用

$ geddy

清单 9-20 。为应用生成一个新模型并运行应用

$ geddy gen scaffold products name:default description price
$ geddy

您可以通过在浏览器中导航到http://localhost:4000来查看正在运行的应用。

它是如何工作的

Geddy 通过安装一个有用的命令行工具来工作,该工具将允许您快速有效地生成应用和构建新功能。一旦安装了这个命令行工具,您就可以通过键入命令$ geddy gen app <appname>来构建您的第一个应用。这将在appname目录中为您生成一个新的应用。目录结构将如下所示:

appname
        -app
                -controllers
                -helpers
                -models
                -views
        -config
        -lib
        -log
        -node_modules
        -public
                -css
                -img
                -js
        -test
                -controllers
                -models

您可以看到,这搭建出了一个工作应用,它在应用中有默认的模型、视图、控制器和助手,允许在默认情况下测试您的应用。为了运行应用,您使用了命令$ geddy,这将启动服务器运行。使用包含在 config 目录中的 production.js 或 development.js 文件配置服务器。

清单 9-21 。配置文件

var config = {
  detailedErrors: true
, debug: true
, hostname: null
, port: 4000
, model: {
    defaultAdapter: 'memory'
  }
, sessions: {
    store: 'memory'
  , key: 'sid'
  , expiry: 14 * 24 * 60 * 60
  }
};

module.exports = config;

这为 HTTP 服务器的标准特性(如端口和主机名)创建了几个配置选项,但是它添加了一些特性,如数据模型适配器,您会看到这些特性默认为内存中的。Geddy 非常健壮,因为除了内存选项之外,它还提供了一组通用的适配器用于存储。这些选项包括 PostgreSQL、MongoDB 和 Riak。一旦应用开始运行,您将会在您的控制台中看到对服务器的任何请求。

[Sat, 20 Jul 2013 19:47:42 GMT]  127.0.0.1 - - [Sat Jul 20 2013 15:47:42 GMT-0400 (Eastern Daylight Time)] "GET / 1.1" 200 2645 "-" "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1568.2 Safari/537.36"

接下来,您在应用中为产品模型生成了一个支架。这是通过命令$ geddy gen scaffold products name:default description price完成的。这将在每个app/modelsapp/views/app/controllers目录中生成相应的 JavaScript 或 HTML 视图文件。您会注意到,您用属性“default”设置了产品模型的“name”参数。这将它设置为应用中的必填字段,当您查看产品模型文件时,这一点变得很明显。

清单 9-22 。产品模型文件

var Product = function () {

  this.defineProperties({
    name: {type: 'string', required: true},
    description: {type: 'string'},
    price: {type: 'string'}
  });

};

exports.Product = Product;

Geddy 搭建了一个完整的创建、读取、更新和删除(CRUD)控制器。通过这个简单的命令,您现在可以控制应用可能需要的所有 CRUD 操作。控制器将指导应用存储、删除或重定向您的视图到您试图到达的模型的任何部分。

清单 9-23 。产品控制员

var Products = function () {
  this.respondsWith = ['html', 'json', 'xml', 'js', 'txt'];
  this.index = function (req, resp, params) {
    var self = this;

    geddy.model.Product.all(function(err, products) {
      self.respond({params: params, products: products});
    });
  };

  this.add = function (req, resp, params) {
    this.respond({params: params});
  };

  this.create = function (req, resp, params) {
    var self = this
      , product = geddy.model.Product.create(params);
    if (!product.isValid()) {
      this.flash.error(product.errors);
      this.redirect({action: 'add'});
    }
    else {
      product.save(function(err, data) {
        if (err) {
          self.flash.error(err);
          self.redirect({action: 'add'});
        }
        else {
          self.redirect({controller: self.name});
        }
      });
    }
  };

  this.show = function (req, resp, params) {
    var self = this;

    geddy.model.Product.first(params.id, function(err, product) {
      if (!product) {
        var err = new Error();
        err.statusCode = 404;
        self.error(err);
      }
      else {
        self.respond({params: params, product: product.toObj()});
      }
    });
  };

  this.edit = function (req, resp, params) {
    var self = this;

    geddy.model.Product.first(params.id, function(err, product) {
      if (!product) {
        var err = new Error();
        err.statusCode = 400;
        self.error(err);
      }
      else {
        self.respond({params: params, product: product});
      }
    });
  };

  this.update = function (req, resp, params) {
    var self = this;

    geddy.model.Product.first(params.id, function(err, product) {
      product.updateProperties(params);
      if (!product.isValid()) {
        this.flash.error(product.errors);
        this.redirect({action: 'edit'});
      }
      else {
        product.save(function(err, data) {
          if (err) {
            self.flash.error(err);
            self.redirect({action: 'edit'});
          }
          else {
            self.redirect({controller: self.name});
          }
        });
      }
    });
  };

  this.destroy = function (req, resp, params) {
    var self = this;

    geddy.model.Product.remove(params.id, function(err) {
      if (err) {
        self.flash.error(err);
        self.redirect({action: 'edit'});
      }
      else {
        self.redirect({controller: self.name});
      }
    });
  };

};

exports.Products = Products;

控制器执行 CRUD 操作,在本例中是在内存中,但是有了可用的适配器,您可以轻松地存储到 PostgreSQL 或 MongoDB 中以实现持久性。有几个对 self.redirect 的调用。这个函数将你的应用重定向到控制器中描述的视图。默认情况下,这些视图是用 EmbeddedJS (EJS)模板创建的,但是在生成应用时,您可以通过将它作为命令行参数传递来利用 Jade。

EmbeddedJS 基本上就是听起来那样;您可以在视图模板中直接嵌入 JavaScript,这将控制页面的最终布局。嵌入的 JavaScript 简单地放在标记中的'< % % >'标记内,当视图呈现时,它将被剥离、解析和执行。如果您检查添加产品的模板,您会看到清单 9-24 中显示的内容,以及嵌入子模板的能力。

清单 9-24 。EJS 模板:add.html.ejs

<div class="hero-unit">
  <form id="product-form" class="form-horizontal" action="/products" method="POST">
    <fieldset>
      <legend>Create a new Product</legend>
      <% if(params.errors) { %>
      <div class="control-group">
        <ul>
        <% for(var err in params.errors) { %>
          <li><%= params.errors[err]; %></li>
        <% } %>
        </ul>
      </div>
      <% } %>

      <%- partial('form', {product: {}}) %>
      <div class="form-actions">
        <%- contentTag('input', 'Add', {type: 'submit', class: 'btn btn-primary'}) %>
      </div>
    </fieldset>
  </form>
</div>

清单 9-25 。分部子模板 .html.ejs

<div class="control-group">
  <label for="name" class="control-label">name</label>
  <div class="controls">
    <%- contentTag('input', product.name, {type:'text', class:'span6', name:'name'}) %>
  </div>
</div>
<div class="control-group">
  <label for="description" class="control-label">description</label>
  <div class="controls">
    <%- contentTag('input', product.description, {type:'text', class:'span6', name:'description'}) %>
  </div>
</div>
<div class="control-group">
  <label for="price" class="control-label">price</label>
  <div class="controls">
    <%- contentTag('input', product.price, {type:'text', class:'span6', name:'price'}) %>
  </div>
</div>

这里的 EJS 模板利用一个函数从产品模型中创建内容。例如,为了创建将成为产品名称的输入字段,EJS 看起来像<%- contentTag('input', product.name, {type:'text', class:'span6', name:'name'}) %>,它使用模型项的名称,并在表单被提交时将它分配回该模型。

除了在应用中自动生成这些 CRUD 方法的脚手架之外,您还可以使用$ geddy gen resource <resourcename>命令。resource命令不太固执己见,因为它不为模型上的每个操作创建特定的视图,控制器更一般化,如下所示:

var Products = function () {
  this.respondsWith = ['html', 'json', 'xml', 'js', 'txt'];
  this.index = function (req, resp, params) {
    this.respond({params: params});
  };

  this.add = function (req, resp, params) {
    this.respond({params: params});
  };

  this.create = function (req, resp, params) {
    // Save the resource, then display index page
    this.redirect({controller: this.name});
  };

  this.show = function (req, resp, params) {
    this.respond({params: params});
  };

  this.edit = function (req, resp, params) {
    this.respond({params: params});
  };

  this.update = function (req, resp, params) {
    // Save the resource, then display the item page
    this.redirect({controller: this.name, id: params.id});
  };

  this.destroy = function (req, resp, params) {
    this.respond({params: params});
  };

};

exports.Products = Products;

使用资源生成器可能更适合您的应用,因为它不那么固执己见,并且您可以轻松地添加您自己的特定于应用的逻辑,而无需使用scaffold命令中的 CRUD 样板文件。

最后,正如您在使用 Geddy 的测试中已经看到的,Geddy 提供了一个灵活的路由。该路由位于 config/router.js 文件中。它可以匹配路由,类似于 9-4 节中的快速路由。这意味着,如果您有一个特定的产品,并且您知道该 ID,并且想要在该路由上执行特定的操作,您可以将它添加到 router.js 文件中作为router.match('products/:id', 'GET').to(products.handleId);这将把特定的 ID 路由到products.handleId控制器方法。您不需要在router.match查询中指定 HTTP 动词,事实上,您可以将它写成router.get('products/:id').to(products.handleId);,它会以同样的方式执行。在此解决方案中,您利用了完全基于资源的路由,因此您的路由将显示如下:

var router = new geddy.RegExpRouter();
router.get('/').to('Main.index');
router.resource('users');
router.resource('products');
exports.router = router;

您可以使用 Geddy 快速启动并运行应用。这个框架对于启动一个简单的 CRUD 应用来说非常快,并且仍然允许您的 Node.js 应用的更有创造性的实现。

9-8.使用雅虎!莫吉托(鸡尾酒的一种)

问题

您希望通过使用 Yahoo!Mojito 框架。

解决办法

雅虎!Mojito 是另一个 Node.js 应用开发框架。由雅虎创建!,它允许您“使用 Node.js 构建在客户机和服务器上运行的高性能、独立于设备的 HTML5 应用。”

开始使用 Yahoo!Mojito 你首先必须安装命令行界面。这是通过国家预防机制完成的。从那时起,你可以利用命令行界面来构建你的应用,如清单 9-26 所示。

清单 9-26 。安装雅虎!Mojito 和创建应用

$ npm install –g mojito-cli
$ mojito create app mojitoapp
$cd mojitoapp
$ mojito create mojit testmojit
$ mojito test
$ mojito start
http://localhost:8666/@testmojit/index

它是如何工作的

当您通过 npm 安装 Mojito 时,您是在全局范围内这样做的,以获得对整个机器的命令行界面的访问。然后通过键入$ mojito create app mojitoapp创建一个应用。这将创建一个名为“mojitoapp”的目录,其结构如下。

mojitoapp
  - artifacts
  - assets
  - mojits
  - node_modules
  application.json
  package.json
  routes.json
  server.js

server.js 文件控制 Mojito 应用,并在您运行mojito start时启动。在这个文件中,您可以看到似乎是 Node.js HTTP 服务器的 Mojito 版本。

/*jslint anon:true, sloppy:true, nomen:true*/
process.chdir(__dirname);

/*
 * Create the MojitoServer instance we'll interact with. Options can be passed
 * using an object with the desired key/value pairs.
 */
var Mojito = require('mojito');
var app = Mojito.createServer();

// ---------------------------------------------------------------------------
// Different hosting environments require different approaches to starting the
// server. Adjust below to match the requirements of your hosting environment.
// ---------------------------------------------------------------------------

module.exports = app.listen();

从这一点上来说,你创造了一个“魔咒”“mojit”是一个 Mojito 术语,表示名称“模块”和“小部件”的混搭。这意味着当你创建一个 mojit 时,你可以把它看作是构建一个模块。默认情况下,Mojito 应用中的 mojit 可以通过路径http://localhost:8666/@mojitname/index访问。当您在应用中导航到这个位置时,您将看到默认的 mojit 页面,它是 Mojito 模型-视图-控制器(MVC)框架的一部分。

Mojito 中的 MVC 架构以所谓的“动作上下文”为中心,你会在代码中的大多数地方看到“ac”。当您查看 controller.server.js 文件的源代码时,每个 mojit 的操作上下文变得很明显。这个文件向应用注册 mojit,并控制模型视图的行为。

/*jslint anon:true, sloppy:true, nomen:true*/
YUI.add('testmojit', function(Y, NAME) {

/**
 * The testmojit module.
 *
 * @module testmojit
 */

    /**
     * Constructor for the Controller class.
     *
     * @class Controller
     * @constructor
     */
    Y.namespace('mojito.controllers')[NAME] = {

        /**
         * Method corresponding to the 'index' action.
         *
         * @param ac {Object} The ActionContext that provides access
         *        to the Mojito API.
         */
        index: function(ac) {
            ac.models.get('testmojitModel').getData(function(err, data) {
                if (err) {
                    ac.error(err);
                    return;
                }
                ac.assets.addCss('./index.css');
                ac.done({
                    status: 'Mojito is working.',
                    data: data
                });
            });
        }

    };

}, '0.0.1', {requires: ['mojito', 'mojito-assets-addon', 'mojito-models-addon', 'testmojitModel']});

在这个 mojit 的索引处理程序中提供了Y.namespace回调中的动作上下文。它将使用合适的型号ac.models.get('testmojitModel')。。。然后添加资产并通过ac.done()处理器发送数据。

模型服务器位于每个 mojit 的模型目录中,您可以看到它遵循类似的 Yahoo!用户界面(YUI ) 模式生成模型并通过Y.namespace将其添加到应用中。这也是您不仅用一个配置初始化模型,而且然后添加诸如getData之类的方法的地方。

/*jslint anon:true, sloppy:true, nomen:true*/
YUI.add('testmojitModel', function(Y, NAME) {

/**
 * The testmojitModel module.
 *
 * @module testmojit
 */

    /**
     * Constructor for the testmojitModel class.
     *
     * @class testmojitModel
     * @constructor
     */
    Y.namespace('mojito.models')[NAME] = {
        init: function(config) {
            this.config = config;
        },

        /**
         * Method that will be invoked by the mojit controller to obtain data.
         *
         * @param callback {function(err,data)} The callback function to call when the
         *        data has been retrieved.
         */
        getData: function(callback) {
            callback(null, { some: 'data', even: 'more data' });
        }

    };

}, '0.0.1', {requires: []});

在您了解这些视图如何与 Mojito 一起工作之前,您应该首先理解您并不局限于将您的 URL 作为默认的.../@mojitname/index路径。这些 URL 在你的应用中是没问题的,但是如果用户需要记住这些 URL,那就不是很好的体验了。有一种方法可以让这些 URL 看起来更干净、更友好。

首先,您需要通过将名称添加到应用根目录中的 application.json 文件来命名您的新端点。在这个文件中,您将一个规范命名为“test”,并将其指向您之前创建的“testmojit”类型。然后,您需要在 routes.json 文件中命名这个新路由。这是通过命名“test index”并告诉“/”路径上的 HTTP GET 方法解析。testmojit mojit 的索引处理程序。这些文件显示在以下示例中。

清单 9-27 。应用. json

[
    {
        "settings": [ "master" ],
        "appPort": "8666",
        "specs": {
            "test": {
                "type": "testmojit"
            }

        }
    },
    {
        "settings": [ "environment:development" ],
        "staticHandling": {
            "forceUpdate": true
        }
    }
]

清单 9-28 。Routes.json

[{
    "settings": [ "master" ],
    "test index": {
        "verbs": ["get"],
        "path": "/",
        "call": "test.index"
    }
//^^ convert http://localhost:8666/@testmojit/index to http://localhost:8666/
}]

$ mojito start
http://localhost:8666/

现在,您已经在您想要的 URL 上提供了您的 mojit,您可以修改这些模板了。Mojito 的模板默认使用手柄模板语言。Handlebars 允许您使用简单的表达式从模型中插入对象,就像您在 testmojit 模板中看到的那样。

清单 9-29 。用车把做模板

<div id=" {{mojit_view_id}} ">
    <dl>
        <dt>status</dt>
        <dd id="dd_status"> {{status}} </dd>
        <dt>data</dt>
        <dd id="dd_data">
            <b>some:</b> {{#data}}{{some}}{{/data}}
            <span>event:</b> {{#data}}{{even}}{{/data}}
        </dd>
    </dl>
</div>

使用 Yahoo!构建 Node.js 应用 Mojito 应用框架可以支持多功能的 MVC 应用。

9-9.构建熨斗应用

问题

您希望通过利用 Flatiron 应用框架来构建 Node.js 服务器。

解决办法

要开始使用 Flatiron,就像你在本章中看到的许多其他框架一样,你需要安装这个框架,这样做可以全局地允许对命令行界面的通用访问。命令行界面,正如你在清单 9-30 中看到的,允许你快速搭建一个 Flatiron 应用。

清单 9-30 。安装熨斗和生成应用

$ npm install  -g flatiron

$ flatiron create flatironapp

一旦创建了应用,就可以从生成的目录$ cd flatironapp中安装依赖项。然后,为了使用您的 Flatiron 应用,您只需启动 app.js 文件。

$ npm install
$ node app.js # starts your app on localhost:3000

它是如何工作的

当您第一次用 Flatiron 生成一个应用时,您利用了创建该应用的cli/create.js文件。这将提示您输入作者和您要创建的应用的描述。

$ flatiron create flatironapp
info:    Creating application flatironapp
info:    Using http scaffold.
prompt: author:  cgack
prompt: description:  test application
test application
prompt: homepage:
info:    Creating directory config
info:    Creating directory lib
info:    Creating directory test
info:    Writing package.json
info:    Writing file app.js
info:    Writing file config/config.json
info:    Application flatiron is now ready

提供这些信息后,您现在已经生成了 app.js 文件、配置文件和 package.json 文件。package.json 文件包含运行应用所需的依赖项。这意味着您需要做的就是导航到您的应用目录并安装 npm 的依赖项。这将使您能够访问主 Flatiron 应用,默认情况下,它将使用 HTTP 插件 flatiron.plugins.http。

var flatiron = require('flatiron'),
    path = require('path'),
    app = flatiron.app;

app.config.file({ file: path.join(__dirname, 'config', 'config.json') });
app.use(flatiron.plugins.http);
app.router.get('/', function () {
  this.res.json({ 'hello': 'world' })
});

app.start(3000);

这个应用内置了一个路由,它使用语法app.router.VERB('/path', callback);来提供路由和这些路由的处理程序。在 Flatiron 应用中,您的视图没有默认的模板语言,但是文档建议您可以使用语言“Plates–NPM install Plates–save-dev”来创建简单的、与模板语言无关的模板。这些模板只在唯一的 HTML ID 属性之间使用绑定,看起来与这里显示的类似。

var flatiron = require('flatiron'),
    path = require('path'),
    plates = require('plates'),
    app = flatiron.app;

app.config.file({ file: path.join(__dirname, 'config', 'config.json') });
app.use(flatiron.plugins.http);
app.router.get('/', function () {
  this.res.json({ 'hello': 'world' })
});

app.router.get('/test', function() {
        var html = '<div id="bind"></div>';
        var data = { "bind": "Bound data" };

        var output = plates.bind(html, data);
        this.res.end(output);
});

app.start(3000);

Flatiron 具有类似于 Express 框架的开放性和 API 语法。它可以很快上手并运行,您应该会发现它是灵活的和可扩展的。像你在本章中看到的许多框架一样,它提供了一种构建 Node.js web 服务器应用的方式,这种方式允许快速生成和灵活的可伸缩性。