2016年7月31日 星期日

Node.js筆記:Node.js with Socket.io

WebSocket:

為一個從HTML5中開始的一個協定, 瀏覽器和伺服器之間只需要一個handshaking動作就形成了一條快速通道. 兩者之間可以直接互相傳送資料.
這種方式可以避免無謂的浪費伺服器頻寬和資源(舊式的是一段時間後再發一次request.)

Socket.io

一般的http協定不支援長久的連接, 需要不斷地發送request給server更新資料. 而Socket.io可以達到 server 和 client 的雙向溝通, 只要一建立連線 server 可以一直傳訊息給 client.

ChatRoom Example

1.新增Chatroom資料夾並新增package.json
首先新增一個Chatroom的資料夾, 並執行以下指令安裝相關套件.

    {
      "name": "socket-chat-example",
      "version": "0.0.1",
      "description": "my first socket.io app",
      "dependencies": {}
    }

2.安裝express & Socket.io

    $npm install --save express
    $npm install --save socket.io

3.新增index.js
第一行: 建立一個express module的app function.
第二行: 建立 http 物件
第三行(app.get…): 處理 / (根目錄) 的 URL GET 要求

sendFile為傳輸檔案, 在這裡傳的是index.js所在資料夾底下的index.html檔
__dirname: 返回被執行的.js檔所在資料夾的絕對路徑
最後則是監聽PORT:3000

    var app = require('express')();
    var http = require('http').Server(app);

    app.get('/', function(req, res){
      res.sendFile(__dirname + '/index.html');
    });

    http.listen(3000, function(){
      console.log('listening on *:3000');
    });

4.建立Socket.io
首先導入Socket.io模組, 因 Socket.io 依賴 HTTP 所以這裡採用一併引入的方式.

socket.io 的核心函數:

emit :用來發射一個事件或者說觸發一個事件, 第一個參數為事件名,第二個參數為要發送的數據, 第三個參數為callback function.
on :用來監聽一個emit 發射的事件, 第一個參數為要監聽的事件名,第二個參數為一個callback function用來接收對方發來的數據.

如上述的介紹這裡的io.on就可以解釋為使用 on() 函數將特定的事件連接到指定的匿名函數(用來定義當伺服器接收到請求時,該做什麼事情,以及該如何回應), 藉此定義整個資料傳輸過程要如何運作。

而connection/connect事件則為socket.io默認的事件之一.
在這邊socket.io提供的interface是基於事件的,server端監聽”connectione”事件, 如果接收到client的連結請求, 就會進入callback function,而連上的socket就會開始監聽事件,事件的名稱就是’chat message’,當socket發出’chat message’事件時就會執行所寫的程式內容,以下面為例,當觸發事件後會針對io所有的client socket發出’chat message’事件以及其後發所帶的msg參數。

io.on為監聽跟client是否達成連線, 內圈的socket.on則是監聽client的事作而回覆對應的function, 通常這裡的事件也需要在client端建立同樣的事件名稱才行.

    var io = require('socket.io')(http);

    io.on('connection', function(socket){
        socket.on('chat message', function(msg){
          io.emit('chat message', msg);
        });
    }); 

5.撰寫index.html
在使用 Socket.io 時, 伺服器端與客戶端都必須要有它的 JS 檔案才有辦法運作, 而在上一步已經為server端裝上 Socket.io module了, 接下來就是為client端, 也就是在html中裝上模組.

裝法為在 HTML 頁面中加入以下這段程式碼.

    <script src="https://cdn.socket.io/socket.io.js"></script>

在Client端一樣要先註冊一個socket, 並建立監聽Server的’chat message’事件。
完整如下:

    <!doctype html>
    <html>
      <head>
        <title>Socket.IO chat</title>
      </head>
      <body>
        <ul id="messages"></ul>
        <form action="">
          <input id="m" autocomplete="off" /><button>Send</button>
        </form>
        <script src="/socket.io/socket.io.js"></script>
        <script src="http://code.jquery.com/jquery-1.12.4.js"></script>
        <script>
          var socket = io.connect();
          $('form').submit(function(){
            socket.emit('chat message', $('#m').val());
            $('#m').val('');
            return false;
          });
          socket.on('chat message', function(msg){
            $('#messages').append($('<li>').text(msg));
          });
        </script>
      </body>
    </html>

參考:
1. http://socket.io/get-started/chat/
2. https://blog.allenchou.cc/socket-io-nodejs/
3. http://socket.io/docs/server-api/

2016年7月3日 星期日

Node.js筆記:Blog system(3)

新增資料庫功能

先在專案中新增一個”lib”的資料夾, 並新建一個檔案”db.js”. 在這裡我們要使用mongoose來存取MongoDB.

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var Blog = new Schema({
    Username: String,
    Article: String,
    CreateDate: Date
});

var Comment = new Schema({
    Visitor: String,
    Comment: String,
    MessageID: Schema.Types.ObjectId,
    CreateDate: Date
});

mongoose.model( 'Blog', Blog );
mongoose.model( 'Comment', Comment );
mongoose.connect('mongodb://localhost/blog');

在上面我們新增的兩個model, 一個叫Blog, 一個是Comment. 最後一行則是連線到本機端的MongoDB伺服器.

接著修改apis.js初始化資料庫
主要是利用require把資料庫設定載入, 再把定義好的model也加入.

require('../lib/db');

var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var Blog = mongoose.model('Blog');
var Comment = mongoose.model('Comment');

...

支線任務:
1.brew update error.
問題簡述在此:https://github.com/Homebrew/legacy-homebrew/issues/49879
解決方式就是利用下面兩組command就可以讓brew正常運作.

    $sudo chown -R $(whoami):admin /usr/local
    $cd $(brew --prefix) && git fetch origin && git reset --hard origin/master

2.MongoDB使用.
安裝:

//Step 1 - Installation ( Don't follow this step if you have already installed MongoDB ):
brew update
brew install mongodb

//Step 2 - Run Mongo Daemon:
mkdir -p /data/db
sudo mongod

//Step 3 - Run Mongo Shell Interface:
mongo

新增功能

1.首頁列出所有文章
修改routes/index.js阿

利用find()找出資料庫中所有的資料, 並把資料存在blogs物件中, 最後把blogs物件回傳給頁面.

require('../lib/db');

var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var Blog = mongoose.model('Blog');

router.get('/', function(req, res, next) {
    res.locals.username = req.session.name ;
    res.locals.authenticated = req.session.logined;
    Blog.find(function (err, blogs, count) {
      res.render('index', {
        title : 'Blog System';
        blogs : blogs;
      });
    });
});

module.exports = router;

修改index.jade, 利用forloop把blogs物件的資料都列印在頁面上.

...
div(style='padding:50px;')
        h1= title

        for article in blogs
                div
                    div(style='float:left;margin-top:10px;')= article.Username
                    div(style='float:right;margin-top:10px;')= article.CreateDate
                    br
                    div(style='clear:both')= article.Article
                        a(href='/users/message/'+article.id, style='float:right;margin-top:105px;margin-right:20px;') Leave a message
                    hr(style='margin-top:110px;')

2.新增文章
views/users/add_article.jade

doctype html
html
    head
        title= Login
    body
        div(style='float:left;margin:10px;')
            a(href='/') Home

        div(style='float:right')
            if (username && authenticated)
                span #{username}
                a(href='/users/profile', style='margin:10px;') Profile
                a(href='/users/signout', style='margin:10px;') Sign Out
            else
                a(href='/users/signin', style='margin:10px;') Log In
                a(href='/users/register', style='margin:10px;') Register
                a(href='/users/forget', style='margin:10px;') Forgot password

        div(style='padding:50px;')
            form(name="add", action="/apis/add", method="post")
                h1 Add Article
                div
                    p
                        span Writer:
                        label(style='padding-right:50px') #{username}
                    p
                        textarea(cols="50", rows="25", style='resize : none;', name="Content")
                    div
                        span(style='padding-right:377px')
                        input(type="submit", name="submit", value="Save")

後半段為一個POST方法, 當按下送出按鈕後會執行 /apis/add 的網址.

修改users.js跟apis.js
下面先判斷使用者是否有登入, 如果有登入才能進入新增文章.

#/users/js

router.get('/add_article', function(req, res, next) {
  if((!req.session.name) || (!req.session.logined)) {
    res.redirect('/');
    return;
  }
  res.locals.username = req.session.name;
  res.locals.authenticated = req.session.logined;
  res.render('users/add_article');
});

...
#/apis.js

router.post('/add', function(req, res, next) {
  if (!req.session.name) {
      res.redirect('/');
      return;
  }
  new Blog({
      Username: req.session.name,
      Article: req.body.Content,
      CreateDate: Date.now()
  }).save( function( err ){
      if (err) {
          console.log('Fail to save to DB.');
          return;
      }
      console.log('Save to DB.');
  });
  res.redirect('/');
});

enter image description here

2016年7月1日 星期五

Node.js筆記:Blog system(2)

Express generation 專案架構

當用express generation指令後會產生一個node project的資料夾.
enter image description here

  • app.js: 開機檔案, 或是說入口網站
  • bin: 存放執行檔
  • node_modules: 存放package.json中安裝的模組, 當你在package.json中新增相依的模組並安裝後, 該模組會存放在這個資料夾下.
  • package.json: 儲存著專案的資訊以及模組相依(dependencies), 當在dependencies中新增相依的模組時, 執行npm install, npm會檢查目前的資料下的package.json, 並自動安裝所有指定的模組
  • public: 存放image, css, js等文件
  • routes: 存放路由文件
  • views: 存放視圖檔, 或是說範本檔案

配置Routing

規劃網站各個頁面的路由路徑. 首先再次修改app.js: 新增apis路由, 用來負責POST方法的處理.

    ...
    var routes = require('./routes/index');
    var users = require('./routes/users');
    var apis = require('./routes/apis');

    ...
    app.use('/', routes);
    app.use('/users', users);
    app.use('/apis', apis);

設定user路由

在我們的設定中, user路由負責處理所有來自/user網址的要求, 例如新增文章, 會員註冊等. user路由會視用戶的請求而回應不同的資料.

新增首頁

首先在views資料夾中建立一個users資料夾. 裡面會放置所有user路由會用到的頁面.

首頁

1.更新index.jade檔
中間的if判斷是會根據使用者是否登入而顯示不一定的連結給使用者.

    doctype html
html
    head
        title= 'Blog System'
    body
        div(style='float:left;margin:10px;')
            a(href='/') Home

        div(style='float:right')
            if (username && authenticated)
                a(href='/users/signout', style='margin:10px;') Sign Out
                a(href='/users/add_article', style='margin:10px;') Add Article
                a(href='/users/profile', style='margin:10px;') Profile
                span #{username}
            else
                a(href='/users/signin', style='margin:10px;') Log In
                a(href='/users/register', style='margin:10px;') Register
                a(href='/users/forget', style='margin:10px;') Forgot password

    div(style='padding:50px;')
        h1= title

2.修改routes/index.js
把session所存的變數所存的變數傳回頁面.

    var express = require('express');
    var router = express.Router();

    /* GET home page. */
    router.get('/', function(req, res, next) {
        res.locals.username = req.session.name ;
        res.locals.authenticated = req.session.logined;
        res.render( 'index', {title : 'Blog System'});
    });
    module.exports = router;

使用者註冊頁面

1.新增views/users/register.jade
這邊的重點是寫了一個form的POST表單來處理使用者設定帳密

doctype html
html
    head
        title= Register
    body
        div(style='float:left;margin:10px;')
            a(href='/') Home

        div(style='float:right')
                a(href='/users/signin', style='margin:10px;') Log In
                a(href='/users/forget', style='margin:10px;') Forgot password

        div(style='padding:50px;')
            form(name="login", action="/apis/login", method="post")
                h1 Register
                div
                    p
                        label(style='padding-right:50px') Username or e-mail
                        input(type="text", name="user", placeholder='username@example.com')
                    p
                        label(style='padding-right:110px')  password
                        input(type="password", name="passwd")
                p
                    span(style='padding-right:260px')
                    input(type="submit", name="submit", value="Sing Up")

2.修改routes/user.js
新增一個判斷式, 如果logined是true, 代表使用者已經是登入狀態, 就會直接回到首頁.
如果尚未登入則會顯示register.jade的頁面.

var express = require('express');
var router = express.Router();

router.get('/register', function(req, res, next) {
    if (req.session.logined) {
        res.redirect('/');
        return;
    }
    res.render('users/register');
});

    ....

3.修改routes/apis.js
新增login函數, 處理註冊頁面傳來的POST方法.
先利用if來判斷使用者是否有輸入帳密, 當其中少一個的話則返回註冊畫面.
當輸入正確就把帳密給儲存到session中. 並把login設定為true代表使用者登入了.

ar express = require('express');
var router = express.Router();

router.post('/login', function(req, res, next) {
    if ((!req.body.user) || (!req.body.passwd)) {
        res.redirect('register');
        return;
    }
    req.session.name = req.body.user;
    req.session.passwd = req.body.passwd;
    req.session.logined = true;
    res.redirect('/');
});

...

使用者登入畫面

1.使用者登入頁面跟註冊的register.jade比, 只有差在字改成”Login”.

doctype html
html
    head
        title= Login
    body
        div(style='float:left;margin:10px;')
            a(href='/') Home

        div(style='float:right')
                a(href='/users/register', style='margin:10px;') Register
                a(href='/users/forget', style='margin:10px;') Forgot password

        div(style='padding:50px;')
            form(name="login", action="/apis/login", method="post")
                h1 Login
                div
                    p
                        label(style='padding-right:50px') Username or e-mail
                        input(type="text", name="user", placeholder='username@example.com')
                    p
                        label(style='padding-right:110px')  password
                        input(type="password", name="passwd")
                p
                    span
                        a(href='/users/forget', style='padding-right:175px') Forgot password
                    input(type="submit", name="submit", value="login")

2.修改routes/user.js

...

router.get('/signin', function(req, res, next) {
    if (req.session.logined) {
        res.redirect('/');
        return;
    }
    res.render('users/signin');
});

使用者登出頁面

1.修改routes/user.js
當登出之後會回到首頁.

...
router.get('/signout', function(req, res, next) {
    req.session.logined = false;
    res.redirect('/');
    res.end();
});

忘記密碼頁面

1.views/users/forget/jade

doctype html
html
    head
        title= 'Forget Password'
    body
        div(style='float:left;margin:10px;')
            a(href='/') Home

        div(style='float:right')
                a(href='/users/register', style='margin:10px;') Register
                a(href='/users/signin', style='margin:10px;') Log In

        div(style='padding:50px;')
            form(name="login", action="/apis/login", method="post")
                h1 Forget Password
                div
                    p
                        label(style='padding-right:50px') Username or e-mail
                        input(type="text", name="user", placeholder='username@example.com')
                p
                    span(style='padding-right:210px')
                    input(type="submit", name="submit", value="Forget Password")

2.修改routes/user.js

...

router.get('/forget', function(req, res, next) {
    if (req.session.logined) {
        res.redirect('/');
        return;
    }
    res.render('users/forget');
});

2016年6月30日 星期四

Node.js筆記:Blog system(1)

練習利用Node.js實作一個Blog:

主要可以分成下列幾項下去實作.
1. 使用Express Web Framework建置一個網站
2. 使用cookie-based session建立會員機制
3. 新增Jade模板動態產生各個頁面
4. 新建自行設計的Restful API處理自訂路由
5. 使用mongoose控制MongoDB資料庫

  1. 使用Express Web Framework建置一個網站
    Express 4.0後提供了一個新的工具“express-generator”, 可以讓我們快速建立一個網站專案, 並幫我們初始化一些基礎設定.
    $npm install express
    $sudo npm install -g express-generator

enter image description here

    $express blog-system

執行express指令時, 後方參數為資料夾名稱.

enter image description here

express命令預設會把express和jade套件填入預先產生的package.json檔中, 所以我們能透過npm install指令把模組安裝完畢.

enter image description here

express 跟 Jade安裝好後就能透過Node.js啟動網站.

    $SET DEBUG=blog-system:* & npm start

打開瀏覽器輸入:http://localhost:3000就可以看到網站了.
enter image description here

npm init指令

Express產生的package.json只有基本的資訊, 這時可利用npm init指令更新專案的相關資訊.

enter image description here

當初入完自訂的資訊之後, 最後會把結果印出來(礙於畫面太長我只截取一小段表示), 如果確定資料正確就會存入package.json中.

安裝mongoose套件

在這邊我們要來修改package.json, 把mongoose套件寫到相依模組(dependencies), 這樣才能把文章寫入MongoDB或是從MongoDB中把文章讀取出來.

新增:
cookie-session是為了用來處理會員登入和登出時可以記得當下的status.

"*"表示版本不限!!!

    "cookie-session": "*",
    "mongoose": "*"

enter image description here

接著透過”npm install”來安裝新增的套件.
enter image description here

2016年6月28日 星期二

Node.js筆記:Express

Express Web Framework

官方定義:
Web Applications

Express is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications.

主要幫忙解決許多node.js http server 所需要的基本服務,讓開發http service 變得更為容易,不需要像之前需要透過層層模組(module)才有辦法開始編寫自己的程式。

使用Express之後, 它包裝了大多數繁複的HTTP低階操作, 使得我們的程式碼更為精簡.

安裝:
要不要-g(global)就看個人需求.

    npm install -g express

enter image description here

Why use Express?

  1. Express helps you respond to request with route support so that you may write responses to specific URLs.
  2. Supports multiple templating engines to simplify generating HTML.

Explanation of Routes

  • A router maps HTTP requests to a callback(針對從 Client 送來的 URL 做相對應的處理)
  • HTTP requests can be sent as GET/POST/PUT/DELETE …
  • URLs describe the location targeted.
  • To a request handler(callback)
    -> app.<HTTP請求類型>(url,handler{});
    app.get('/index', function(req, res){});

request: 連線要求的資訊和方法:
包含了瀏覽器傳來的各種信息,像是query ,body ,headers,都可以通過req對象訪問到。

response: 回應連線的資訊和方法
我們一般不從裡面取信息,而是通過它來定制我們向瀏覽器輸出的信息,比如 header 信息,比如想要向瀏覽器輸出的內容。這裡我們調用了它的 #send 方法,向瀏覽器輸出一個字符串。

app.get 可以帶入兩個參數,第一個是路徑名稱設定,第二個為Call back function),這裡面就如同前一篇介紹的 createServer 方法,裡面包含 request跟response 兩個物件。使用者就可以透過瀏覽器,輸入不同的url 切換到不同的頁面,顯示不同的結果。

Example:

var express = require('express');
var app = express();

app.get('/', function(req, res){
  res.send('This is GET method');
  res.end;
});

app.listen(process.env.PORT || 12345);

Express middleware

Express在處理一個連線要求時, 利用了middleware的設計, 讓開發者隨時可以自訂或擴充處理的流程. 也可以在適當的時機安插執行一些程式.

為了載入middleware要使用.use, 一個基本middleware長的樣子如下:

function logger(req, res, next){
  console.log(new Date(), req.method, req.url);
  next();
}

Middleware 會收到 3 個參數:
1. req 是 Request 物件,存放這此請求的所有資訊
2. res 是 Response 物件,用來回應該請求
3. next 用來控制流程

上面所寫的logger middleware會把每次request的時間, method還有url都列印在終端機上, 然後呼叫next()往下個middleware過去。Express.js的任務, 就是管理你所有middleware chain。

app.use 和 app.get

再來我們寫一個叫作hello的middleware:

function hello(req, res, next){
  res.write('Hello! \n');
  next();
}

將這個middleware加到我們的middlware chain中:

app.use(logger);
app.get('/hello', hello);
`app.use`表示 所有的request都會執行這個middleware
`app.get`表示 只有針對該URL 做 GET request的時候才會執行這個middleware

Reference:
1. http://expressjs.com
2. https://www.youtube.com/watch?v=czmulJ9NBP0
3. https://dca.gitbooks.io/nodejs-tw-wiki-book/content/book/node_express/node_express.html
4. https://stormpath.com/blog/how-to-write-middleware-for-express-apps
5. http://www.jollen.org/blog/2013/11/expressjs-middleware.html

2016年6月25日 星期六

Node.js筆記:Basic HTTP server

一個基本的網站伺服器流程:
1. 建立HTTP伺服器, 監聽指定的PORT
2. 等待連線
3. 取得新的客戶端連線要求
4. 回傳字串

Example:

var http = require('http');

var server = http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World!\n');
});

server.listen(12345);

console.log('Server rinning at http:127.0.0.1:88888/');

enter image description here

上述主要是透過Node內建的http模組, 然後使用http.createServer()去建立伺服器. 最後使用listen()方法去監聽指定的port.

當在cmd中執行上述的程式, 就可以在瀏覽器上輸入127.0.0.1:88888, 就能得到hellow world的回傳結果.

Port

Port:通過端口來區分出同一電腦內不同應用或者進程,從而實現一條物理網線(通過分組交換技術-比如internet)同時鏈接多個程序

端口號是一個16位的uint, 所以其範圍為1 to 65535 (對TCP來說, port 0 被保留,不能被使用. 對於UDP來說, source端的端口號是可選的, 為0時表示無端口).

server.listen(12345),程式在執行時,電腦接收到的12345端口的網路消息就會被發送給我們啟動的這個程式.

2016年6月23日 星期四

Django筆記:自定義過濾器

在這邊只需要記住一點“過濾器本身是一個函數”.

接著要新增一個filter來判斷食物辣或是不辣.

<tr>
    <td> {{ food.name }} </td>
    <td> {{ food.price }} </td>
    <td> {{ food.is_spicy|yes_no:"辣/不辣" }} </td>
    <td> {{ food.comment }} </td>
</tr>

1. 寫一個Filter函式

首先打開myfilters.py

def yes_no(bool_value, show_str):
    if bool_value:
        return show_str.partition('/')[0]
    else:
        return show_str.partition('/')[2]

這邊的函式名稱不需要跟我們定義的過濾器名稱一樣. 但是一樣的話通常會比較好管理.
bool_value是過濾器的第一個參數,他負責接收模板中pipe符號(|)左邊的值;而show_str是第二個參數,會用來接收過濾器中的額外參數, 在這邊指的是”辣/不辣”

2. 註冊該函式

from django import template
...
register = template.Library()
register.filter('yes_no', yes_no)

在這邊我們使用register的filter函式來幫我們註冊, filter函式第一個變數是”過濾器名稱“, 第二個參數是他對應的過濾器函式

3. 載入過濾器

最後就是在模板中載入他

{% load myfilters %}
...