2012年11月30日 星期五

苦惱訂單系統沒有額外LOG記錄,Stingray幫你通通記下來

當LoadBalancer不再只有流量重導與負載平衡時候,您也可以透過LB來做一些特殊的事情...
這邊展示從HTTP協定著手,將LB所負載之WEB上的Log做一個額外的紀錄...

[系統URL或API URI範例]
[POST] https://your.ip.address:port/cloudstore/simonsu/order/OD-01346

Create Body:(當post上面url時候,實際是送這邊的資料出去)
{"order_id":"OD-01346","username":"simonsu","price":"1000"}
Response Body:(當post完成時候,系統回傳資訊)
{"status":"OK"}

這邊透過一個Request Rule與一個Response Rule來組成這個服務,
其中Request部分記錄使用者傳入參數
而Response部分記錄系統回傳完成訂購之資訊
回傳部分,可以透過某些檢查機制來確認是否要進行Log動作...

[RULE_CREATEORDER_LOG_REQ]
$rawurl = http.getRawURL(); # ex: /cloudstore/simonsu/order/OD-01346
$method = http.getMethod(); # ex: POST
$post = http.getBody(); # ex:
{"order_id":"OD-01346","username":"simonsu","price":"1000"}
$arr = string.split($rawurl,'/'); $ want to get user
$user = '';
if ( array.length($arr) >= 2 ) {
  $user = $arr[1];
}
if ( string.startsWith( $rawurl,"/cloudstore") &&
    string.regexmatch($method, 'POST') ) {
  log.info('>>>>>>>YES GOT CREATE ACTION!!!!');
  # pass the parameter to response rule
  connection.data.set("post", $post);
  connection.data.set("user", $user);
}


[RULE_CREATEORDER_LOG_RESP]
$user = connection.data.get("user");
$post = connection.data.get("post");
$resp = http.getResponseBody();
# Query the mapi2 for record the post and the response
# All things in url can process from node.js using req.params.[name]
# All thinsg in POST column can process from node.js using req.body
$new_url = "http://log.server.ip.address:port/logOrder/".$user."/".string.urlencode( $post );
if($resp != '{"status":"Failed"}'){ $result = http.request.post($new_url, $resp,"Content-Type: application/json");
log.info('>>>create order result='.$result);
}

接收Log的程式,也相當簡單,這邊展示使用Node.js的接值方式
至於儲存部分,則依據自己實作啦∼

[Node.js Server (Using ExpressJS)]
# app.js
app.get('/logOrder/:user/:post', route.createOrderLog);
# route/index.js
exports.createOrderLog = function(req, res){
//post data取值,直接接回body取值
 var order = req.body;
 res.writeHead(200, {'Content-Type': 'application/json'});
//url列的參數取值,使用req.params.[name]取值
 order.user=req.params.user;
 order.post=JSON.parse(req.params.post);

  //TODO: 使用您prefer的儲存方式存檔
 console.log('Get create order request...., %s', JSON.stringify(order));
//回應狀態
 res.end(JSON.stringify(order));
}

已上面的範例,做後可以看到console的Log

[LOG]
Get create order request...., {"status":"OK","user":"simonsu","post":{"order_id":"OD-01346","username":"simonsu","price":"1000"}}

TrafficScript的基本文字運算


官方的教學文章中提到許多文字操作的的範例,相當具有參考價值,這邊擷取精華部分呈現:

使用RE的範例:
$id ="user=Joe, password=Secret";
if(string.regexmatch( $id,"^user=(.*), password=(.*)$")){
   log.info("Got UserID: ".$1.", Password: ".$2 );
}

文字大寫寫轉換:
$string ="AbCdEfG"; $upper =string.toUpper( $string );# returns "ABCDEFG" $lower =string.toLower( $string );# returns "abcdefg" HTML文件標籤與特殊字元encode: $xss ="<script>alert('Hello!');</script>"; 結果:"&lt;script&gt;alert('Hello!');&lt;/script&gt;" $safe =string.htmlencode( $xss );

文字中跳脫字元轉換:
$str =string.escape("Hello World!\r\n"); 結果:"Hello%20World!%0D%0A"
使用HTTP BASIC authentication方式加密帳密: $enc =string.base64encode("user:passwd"); http.setHeader("Authorization","Basic ".$enc );
HEX加解密: string.hexEncode() string.hexDecode(). AES cipher加解密: $encrypted =string.encrypt( $plaintext, $passphrase ); $plaintext =string.decrypt( $encrypted, $passphrase );

TrafficScript的URL運算

文字的操作在程式語言上一直是不可或缺的技巧,TrafficScript中也提供基本的文字操作
這邊簡單介紹url切割取出中間欄位的方式

取出URL,並透過"/"切割字元,然後從array中取出第二個元素:
$rawurl
= http.getRawURL(); # ex: /cloudstore/simonsu/product1/AB1234
$arr = string.split($rawurl,'/'); $ want to get user (simonsu)
$user = '';
if ( array.length($arr) >= 2 ) {
  $user = $arr[1];
}

另外,網址是開始於什麼字元、結束於什麼字元、包含什麼字元,也是常用的方式:
if ( string.startsWith( $rawurl,"/cloudstore") && 
string.endsWith( $rawurl,"AB1234") && 
string.contains( $rawurl,"simonsu") &&
    string.regexmatch($method, 'POST') ) {
# do your jobs...
}


Stingray Rule的http操作

http這個物件在Stingray中有許多有用的操作,這邊列出幾個常用的:

  • http.getRawURL(); 取出使用者連到LB的網址,值的部份不包含IP、PORT與協定,僅從Context開始之後 ex:/Wiki/en/somepage.jsp
  • http.getMethod(); 取出連線時候的http method,可以作為判斷RESTful操作方法之使用 ex: GET or POST
  • http.getBody(); 取回連線時候送進LB的內文,一般用在Request Rule中 ex: {"title":"hello","content":"test 123"}
  • http.getResponseBody(); 取回使用者要求之回應內容,一般用在Response Rule中 ex: <html>....</html>


Stingray Rule中的一進一出

Stingray的rule中有分所謂的Request Rule與Response Rule,而真正的作用是在針對User Request跟Server Response兩件事情增加操作的規則。下面展示兩個Rule中怎麼彼此交換資料來達到某些特定的操作目的:

這邊假設管理人員需要針對伺服器的連線時間過長(在此為5秒)的這個Event增加操作,操作中會去觸發一個HTTP的連線,連線中就可以由開發人員撰寫相關程式來達到通知、記錄、甚至Cloud Scale Out的目的...

此邏輯的設計包含:

  1. Request Rule(RULE_SET_RESP_TIME_FLAG)做進入時間記錄
  2. Response Rule(RULE_SET_RESP_TIME)做回應時間記錄,並操作HTTP連線動作


Rule的原始碼如下:

RULE_SET_RESP_TIME_FLAG.rule
$tm = sys.time(); connection.data.set("start", $tm);


RULE_SET_RESP_TIME.rule

# Response time in (integer) seconds above
$THRESHOLD =5;
# which requests are logged.
$start = connection.data.get("start");
$now = sys.time();
$diff =($now - $start);

if( $diff >= $THRESHOLD ){
   $uri = http.getRawURL();
   $node = connection.getNode();
   log.info ("SLOW REQUEST (". $diff ."s) ". $node .":". $uri );
   # Doing scale out...
   http.request.get("http://your.server.ip.address:300/scaleout" );
}