[Node]重要外部模塊

1. Express

Express(Node的MVC框架)是使用最廣泛的Node模塊,它吸取了Ruby的Sinatra框架的精髓,並提供了許多功能。

Express使用路由定義的頁面處理器來工作。路由可以是一個簡單的路徑,也可以比較複雜,例如:

var express = require('express');
var app = express.createServer();
app.get('/', function(reg, res) {
  res.send('hello world');
});
app.listen(9000);

app.get()爲特定路由創建了響應函數。與一般http服務器不同,Express不是爲一般的請求提供監聽器,而是針對特定的HTTP動作提供監聽器。所以get()只會響應GET請求,put()請處理PUT請求。Express增加了send()方法,我們不需要手動提供HTTP頭或是調用end()方法,send()會處理好相關操作。

路由可以包含一個簡單的字符串或一個正則表達式,並且可以包含變量聲明、通配符及可選關鍵字標記,例如:

var express = require('express');
var app = express.createServer();
app.get('/:id?', function(req, res) {
  if (req.params.id) {
    res.send(req.params.id);
  } else {
    res.send('oh hai');
  }
});
app.listen(9000);

在Express路由中,使用冒號":"來標記想要使用的變量,那麼在URL中傳遞的字符串就會被捕獲並保存在該變量中。Express中的所有路由最終都會轉變成正則表達式來處理,並進行分詞操作以便於應用代碼的使用。

Express路由將"/"視作一個標記,而同時又會把請求末尾的"/"當做可選項,所以路由"/:id?"會匹配localhost,localhost/,localhost/tom,localhost/tom/,但不包括localhost/tom/tom。

路由中也可以使用通配符,比如"*"會匹配所有的內容,直到下一個標記出現(非貪心的正則匹配)。與其他正則語言不同的是,"*"表示的不是零個以上的字符,它表示的是一個以上字符。

路由是按順序執行的,當多個路由同時匹配上提供的URL時,只有第一個匹配的路由會執行相關的動作。如果想從URL中提取變量,需要用正則的語法進行處理,例如:

var express = require('express');
var app = express.createServer();
app.get(/\/(\d+)/, function(req, res) {
  res.send(req.params[0]);
});
app.listen(9000);

也可以在路由的正則匹配時指定變量的命名,例如:

var express = require('express');
var app = express.createServer();
app.get('/:id(\\d+)', function(req, res) {
  res.send(req.params[0]);
});
app.listen(9000);

有時會希望同一個URL在不同的情景下匹配多個路由,路由定義的順序會決定哪個路由被選中使用,但也有辦法可以把控制權傳給下一個路由,例如:

app.get('/users:id', function(req, res, next) {
  var id = req.params.id;
  if (!checkPermission(id)) {
    next();
  }
});

next參數會通知路由中間件去調用下一個路由,這個參數總是會傳遞給回調函數,只不過上例中第一次爲它命名並使用它。這還能與app.all()方法很好地配合,它表示所有的HTTP動作都進行處理,例如:

var express = require('express');
var app = express.createServer();
var users = [{name: 'tj'}, {name: 'tom'}];
app.all('/user/:id/:op?', function(req, res, next) {
  req.user = user[req.params.id];
  if (req.user) {
    next();
  } else {
    next(new Error('Cannot find user'));
  }
});
app.get('/user/:id', function(req, res) {
  res.send('Viewing ' + req.user.name);
});
app.get('/user/:id/edit', function(req, res) {
  res.send('Editing ' + req.user.name);
});
app.put('/user/:id', function(req, res) {
  res.send('Updateing ' + req.user.name);
});
app.get('*', function(req, res) {
  res.send('Danger, Will Robinson!', 404);
});
app.listen(9000);

Express中以Ruby on Rails的風格來提供RESTful的架構的,可以讓表單進行各種操作:PUT(替換數據)、POST(創建數據)、DELETE(刪除數據)和GET(獲取數據),例如:

var express = require('express');
var app = express.createServer();
app.user(express.limit('lmb'));
app.user(express.bodyParser());
app.user(express.methodOverride());
app.get('/', function(req, res) {
  res.send('<form method="post" action="/"> ' +
           '<input type="hidden" name="_method" value="put" />' +
           'Your Name: <input type="text" name="username" />' +
           '<input type="submit" />' + '</form>'
});
app.put('/', function(req, res) {
  res.send('Welcome ' + req.body.username);
});
app.listen(9000);

bodyParse()方法會解析從Web瀏覽器發送來的請求正文,並把表單變量轉換成Express使用的對象。methodOverride()方法允許表單提交隱藏的_method變量,並把GET方法替換掉,然後調用相應的RESTful方法類型。express.limit()方法指定Express把請求正文的大小限制在1MB以內。

以上例子中包含了看起來無關的函數app.use(),這個函數調用了Connect庫,並提供了許多有用的工具,使得添加功能很容易。因此在此需要介紹中間件以及它爲什麼對開發Express程序如此重要。

中間件指的是鏈接兩個程序的一個軟件,並且通常是更高級的程序間或更寬廣的網絡間的軟件。Connect庫提供了Express使用的中間件功能,Express中從Connect繼承下來的,同時獲得了http和connect的功能。任何添加到Connect的模塊都會自動被Express所使用。

Express的路由功能會在處理環節使用內部的中間件,可以通過重載來添加額外的功能,例如:

var express = require('express');
var app = express.createServer(
  express.cookieParser(),
  express.session({secret: 'secret key'})
);
var roleFactory = function(role) {
  return function(req, res, next) {
    if (req.session.role && req.session.role.indexOf(role) != -1) {
      next();
    } else {
      res.send('You are not authenticated.');
    }
  }
}
app.get('/', roleFactory('admin'), function(req, res) {
  res.send('Welcome to Exrpess!');
});
app.get('/auth', function(req, res) {
  req.session.role = 'admin';
  res.send('You have been authenticated.');
});
app.listen(9000);


2. Socket.IO

Socket.IO是一個小巧的擴展庫,功能像Node的核心庫net,可以通過Socket.IO在瀏覽器客戶端與Node服務器之間採用高效的底層socket機制來回發送消息。它的另一個優點是,可以在瀏覽器與服務器之間共享代碼,例如:

var http = require('http');
var io = require('socket.io');
server = http.createServer();
server.on('request', function(req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World');
});
server.listen(80);
socket = io.listen(server);
socket.on('connection', function(client) {
  console.log('Client connected');
});

Socket.IO並不關心HTTP服務器做什麼,它只是把自帶的事件監聽器包裝在發送到服務器的所有請求上,該監聽器會查找從Socket.IO客戶端發送過來的請求,並以塘沽 處理。

因爲socket是持久性連接,所以不需要像HTTP服務器那樣處理req和res對象,可以在瀏覽器裏放置一些代碼來與服務器交互,例如:

<!DOCTYPE html>
<html>
<body>
<script src="/socket.io/socket.io.js"></script>
<script>
  var socket = io.connect('http://localhost:80');
  socket.on('message', function(data) {console.log(data)});
</script>
</body>
</html>

可以使用命名空間把Socket.IO的監聽器區分到頻道中,避免導致意外衝突,例如:

var http = require('http');
var io = require('socket.io');
server = http.createServer();
server.on('request', function(req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World');
});
server.listen(80);
socket = io.listen(server);
socket.of('/space1').on('connection', function(client) {
  console.log('Client connected to space1');
});
socket.of('/space2').on('connection', function(client) {
  console.log('Client connected to space2');
});

如果同時使用Express和Socket.IO,將會在使用統一的語言編寫整個軟件結構方面獲得巨大的便利,如下演示一個Socket.IO綁定到Express應用上的客戶端代碼:

<!DOCTYPE html>
<html>
  <body>
    <script src="/socket.io/socket.io.js"></script>
    <script>
      var socket = io.connect('http://localhost:80');
      socket.on('news', function(data) {
        document.write('<h1>' + data.title + '</h1>');
        document.write('<p>' + data.contents + '</p>');
        if (data.allowResponse) {
          socket.emit('scoop', {
            contents: 'New data received by client.'
          });
        }
      });
    </script>
  </body>
</html>

對應的服務器端代碼如下:

var app = require('express').createServer();
var io = require('socket.io').listen(app);
app.listen(80);
app.get('/', function(req, res) {
  res.sendfile(__dirname + '/socket_express.html');
});
io.socket.on('connection', function(socket) {
  socket.emit('news', {
    title: 'Welcom to World News',
    contents: 'This news flash was sent from Node.js!',
    allowResponse: true;
  });
  socket.on('scoop', function(data) {
    socket.emit('news', {
      title: 'Circular Emissions Worked',
      contents: 'Received this content: ' + data.contents
    });
  });
});



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章