2013/04/23

WebSocket on Tornado

Tornado 一直是我愛用的 python web framework,這幾年工作上一直在寫 event-drivent socket programming,所以被它 non-blocking 的特性給深深吸引,如果每件 block 的工作都能用 asynchronous 的方式來解決,那程式執行的效率一定超好!


延續『WebSocket 雙向即時通訊 - 初探』,今天就以 Tornado 來寫個簡單的 WebSocket 範例。當 client 連到 server 後,會傳送 hello ,而 server 回應一樣的訊息,傳送到第五次,server 就把 client 斷掉。

Server (ws_server.py)
import tornado.ioloop
import tornado.web
from tornado import websocket


class WSHandler(websocket.WebSocketHandler):
    count = 0
    def open(self):
        print "Client connected"

    def on_message(self, msg):
        print "[%d] Got message [%s] from client" % (self.count, msg)
        if self.count == 4:
            self.close()
            return
        # Write back message
        self.write_message("[%d] Echo: %s" % (self.count, msg))
        self.count += 1

    def on_close(self):
        print "Client disconnected"


application = tornado.web.Application([
    (r"/", WSHandler),
])

if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()



Client (ws_client.py)
from tornado.websocket import websocket_connect
from tornado.ioloop import IOLoop


class ws_client():
    conn = None # This is WebSocketClientConnection
    def __init__(self):
        websocket_connect('ws://localhost:8888/', callback=self.conn_cb)
        
    def conn_cb(self, future):
        self.conn = future.result()
        self.conn.write_message("hello")
        self.conn.read_message(self.read_cb)
        
    def read_cb(self, future):
        msg = future.result()
        if msg is None:
            print "Server disconnected"
            IOLoop.instance().stop()
        else:
            print msg
            self.conn.write_message("hello")
            self.conn.read_message(self.read_cb)

if __name__ == '__main__':
    ws_client()
    IOLoop.instance().start()

2013/04/21

人生 就該有限度的冒險

一件事六成把握就去做吧
要相信自己的應變能力
最差的時候 你還有求生存的自我保護機制

一件事不要尋求全部人的認同
如果想迎合全部的人
那肯定會失去初衷 失去原始的自我

追求完美的境界 永無止境
有時候 留一成餘地給別人
反而可以創造更多機會

沒有什麼是絕對的
要說可以做到十成
我想只有你堅定的信心

人生 就該有限度的冒險

2013/04/18

對於今日電視新聞的看法

新聞有毒,勇敢戒斷你會更快樂
                                --- Inside 硬塞的網路趨勢觀察


這半年沒看過幾次電視新聞 但不因而跟社會脫節
網路 書籍 所帶來的更多 不管是 及時性 時事 知識
選擇性也更廣 不只有藍台 綠台

網路新聞雖然也是記者寫的 但有更多評判的機會
很容易可以看到正反兩面

通常新聞報導都是不好的居多 而且越糟糕的的報越久
畢竟 這件事情只是一小部份 社會上還是有很多好的地方
很容易過度恐慌 造成大家對社會的觀感不好

像之前三餐都配人頭案 很倒胃口
每天看這些 心情會好才怪

畫面的呈現蔚為一種趨勢
記者還要充當演員 還原現場演一次
不如看寶傑的關鍵時刻娛樂性更甚
人對於畫面的理解較好
相對 思考 評斷能力也降低了
照單全收 很好洗腦


究竟 新聞的目的在於?
原始概念應該接近
客觀的呈現當下的情況 由收看的人思考而後斷
媒體只是扮演傳遞的角色

furthermore
讓社會進步 而不是圖利政黨財團
監督社會 揚善伐惡 但評斷的人 素質待加強
畢竟 結論是直接深植到大眾

播放的內容該跟大眾利益有關
腥羶色 奇人異事 是新聞 但我相信有更重要的東西可以報
世界觀 整體趨勢 試著把層次拉的更高


2013/04/17

來去基隆走走吧

愛上了平日逛街、平日旅遊的感覺,除了人比較少之外,可以和正在工作的人形成一種對比,心情特別不一樣 :P

今天請假在家休息,下午一個衝動就想去基隆走走,從汐止過去還蠻快的,沿新台五路走20分鐘就到了。基隆的路真不是普通亂的,除了很多單行道,還有一些路彎彎曲曲的,感覺很容易就逆向,尤其在港邊,差點誤闖貨運專用道。

查了一下比較 popular 的景點有
  • 基隆廟口夜市
  • 和平島公園
  • 白米甕砲台
  • 外木山漁港

港邊
 


和平島公園
 


走著走著 沙灘上突然浮現一些文字...


  


發現外星生物與外星地表
 

夕陽餘暉
 


天黑前趕快前往白米甕砲台
原來這邊的炮台早撤了 不過這是個制高點 瞭望港邊還不錯
如果晚上來 港邊燈火應該更漂亮
 

核彈基地?


基隆廟口 奠濟宮
 

來到廟口當然別忘了伴手禮呀
李鵠餅店的綠豆椪
連珍的芋泥球~超好吃

2013/04/13

WebSocket 雙向即時通訊 - 初探

Web 已經走向 Application 的概念,其中即時性(real-time)這個特點扮演很重要的角色

早期 Web service 要做到即時通訊不外乎兩種
  • Polling
    每n秒問一次 server,n 越小即時性越好,但相對 server loading 就越大
  • Long polling
    需要 Server support,延遲 response,確切來說是需要的時候再 response


但這兩種方式都需要額外的 overhead
  • HTTP 協定限制,一個 request 配一個 response,然後結束 connection,每次重新連線都需要 tcp 3-way handshake
  • Request 只能從 client 主動發出,若要做到 server push,client 得先建立一條連線讓 server 需要時 send back
  • 每次連線都要帶上 HTTP header,對頻寬也是一種浪費
  • Connection 斷掉再建立有個空窗期


而 WebSocket 正是改善這些缺點
  • 全雙工通訊(full-duplex communication),連線一旦建立,server/client 隨時都可以發送資料給對方
  • 除了第一次 handshake 外,過程中傳送資料的 header 很小
  • 避免對同一個網站建立多條 connection


WebSocket 的 implementation


WebSocket 對各種瀏覽器版本的支援度


WebSocket Protocol/Packet 分析


其實在 HTTP 1.0 與 1.1 也有 persistent connection 的實作
  • HTTP 1.0 persistent connection:又稱 HTTP keep-alive 或 HTTP connection reuse,可以在同個 connection 下發送多次 request/response 
  • HTTP 1.1 提出了 pipelining:允許一次發送多個 request 給 server,但 server response 時也必需按照順序
但這機制卻沒有被廣泛的應用?是因為早期的 web server 存在 C10K problem 嗎?還是 server 支援度的問題?加上 proxy 與 browser 有限制 client 到 web server 的 current connection 只能建立兩條,避免浪費 server 資源

詳細可以參考 HTTP 連線管理 | ihower { blogging }

2013/04/12

SQL Injection Prevention - Postgresql

這是一個 C 版本的 postgresql database 存取
#include <libpq-fe.h>

#define UPDATE_COMMAND   \
        "UPDATE employee SET password='%s' WHERE username='%s';"

int db_update(char *username, char *password)
{
  PGconn            *conn;
  PGresult          *res;
  ExecStatusType    status;
  char              query[1024];

  // connect to server

  /*
   * Set parameter
   */
  snprintf(query, sizeof(query), UPDATE_COMMAND, password, username);

  /*
   * Execute the query.
   */
  res = PQexec(conn, query);
  if (res) {
    status = PQresultStatus(res);
    if (status == PGRES_COMMAND_OK)) {
      printf("Update successfully\n");
    } else {
      printf("error message=[%s]\n", PQresultErrorMessage(res));
    }
  }
}
初學者容易犯的錯誤就是把 user input 直接跟 SQL query 串接在一起,如果 username, password 存在不安全的字眼,很有可能會造成 SQL injection,切記永遠不要相信 user 的資料,該做的檢查、防呆都不能少。


改成參數形式就可以避免 :)
Database client library 都會有提供類似的用法
#include <libpq-fe.h>

#define UPDATE_COMMAND   \
        "UPDATE employee SET password=$1 WHERE username=$2;"
#define PREPARED_STMT    "my_update"
#define NPARAMS          2

int db_update(char *username, char *password)
{
  PGconn            *conn;
  PGresult          *res;
  ExecStatusType    status;
  char              *params[NPARAMS];

  // connect to server

  /*
   * Set parameter
   */
  params[0] = password;
  params[1] = username;

  res = PQprepare(conn, PREPARED_STMT, UPDATE_COMMAND, NPARAMS, NULL);
  if (PQresultStatus(res) != PGRES_COMMAND_OK) {
    printf("PQprepare() failed [%s]", PQresultErrorMessage(res));
    return -1;
  }

  /*
   * Execute the query.
   */
  res = PQexecPrepared(conn, PREPARED_STMT, NPARAMS, 
                       (const char **)params, NULL, NULL, 0);
  if (res) {
    status = PQresultStatus(res);
    if (status == PGRES_COMMAND_OK)) {
      printf("Update successfully\n");
    } else {
      printf("error message=[%s]\n", PQresultErrorMessage(res));
    }
  }
}
關於 PQprepare()、PQexecPrepared() 的使用方式可以參考官方文件

2013/04/11

Basic Authentication for Apache Virtual Host

最近裝了 phpmemcacheadmin 這套 monitor tool 來用,但它本身沒有使用者驗證的機制,雖然是內部使用,但擁有這麼大的權限應該要控管一下。
在不動到 source 的前提,想到了可以在 Apache web server 端加上 basic authentication:


Apache virtual host 設定檔 vhost.conf

<VirtualHost 10.0.0.1:80>
  ServerName example.com
  DocumentRoot /home/user/www
  DirectoryIndex index.php
  <Directory /home/user/www>
    Deny from all
    # Allow from 10.0.2.1 # 允許從這個 IP 來的不用認證
    AuthUserFile ~/.htpasswd
    AuthName authorization
    AuthType Basic
    Satisfy Any
    require valid-user
  </Directory>
</VirtualHost>
重啓 Apache 就生效了


用 htpasswd 建立帳號/密碼設定檔

$ htpasswd ~/.htpasswd brian
New password:
Re-type new password:
Adding password for user brian

關於 htpasswd 的使用方式請參考 htpasswd - Manage user files for basic authentication