Wednesday, April 25, 2007

Seven JavaScript Techniques You Should Be Using Today

By Dustin Diaz, Published on April 23, 2007

Whether or not your JavaScript is maintainable often seems to be relative to the task you’re trying to accomplish. You can follow best practices, or not, and sometimes you’ll be fine either way. Nevertheless, in this article, we’ll demonstrate seven techniques you can add to your daily code writing habits to make you a better programmer.

A closure establishes a space in which any variables defined within that space are not accessible from outside that space. This also includes functions themselves, and in fact, it is only functions (changed as of JavaScript 1.7) that can provide block scope and create closures for you.

One thing to keep in mind as you go through these examples is that there is definitely more than one way to accomplish these tasks—the goal here is to shed a little light on how things can be done in a smarter way. The benefits of each method should be self-evident, but, in the end, the purpose is to learn a concise way of identifying common mistakes made by programmers and JavaScript library authors, add to your list of JavaScript ninja skills, and learn the flexibility of the language.
One: Branch when possible

When performance matters, it’s often advisable to branch out your functions in a way that ensures processor-intensive or memory-hungry tasks won’t be frequently repeated. One of the most common scenarios where this situation can arise is handling browser differences. Let’s take a look at the following two examples, and see how we can speed up XHR handling and event listener assignment.

For this first code sample we’ll build an asyncRequest function which puts branching into practice.


var asyncRequest = function() {
function handleReadyState(o, callback) {
var poll = window.setInterval(function() {
if(o && o.readyState == 4) {
window.clearInterval(poll);
if ( callback ){
callback(o);
}
}
},
50);
}
var http;
try {
http = new XMLHttpRequest();
}
catch(e) {
var msxml = [
'MSXML2.XMLHTTP.3.0',
'MSXML2.XMLHTTP',
'Microsoft.XMLHTTP'
];
for ( var i=0, len = msxml.length; i < len; ++i ) {
try {
http = new ActiveXObject(msxml[i]);
break;
}
catch(e) {}
}
}
return function(method, uri, callback, postData) {
http.open(method, uri, true);
handleReadyState(http, callback);
http.send(postData || null);
return http;
};
}();

Take special note how—through the use of closures—we were able to cleverly branch out our core functionality before a function is returned to the asyncRequest variable. The advantage we gain is that our code will not have to check if the native XMLHttpRequest object is available upon every request, nor will we have to obnoxiously loop through three separate IE cases to check if various versions of the ActiveX compatible component are available. Also, take note how the closure is used to encapsulate our logic (see point Six below) so that we don’t pollute the global namespace with variables that only pertain to getting this particular task done.

Let’s look at another example—one that Dean Edwards touched on about a year ago when he shared a brief tip on how to speed up object detection and apply the same technique of branching we used in our asyncRequest function to the problem of attaching event listeners.

Take note here that this version of addListener will correct the scope of this in your callbacks, as well as send the appropriate event back as the first argument for A-grade browsers. The resulting function, incidentally, will be faster than any entry to the addEvent recoding contest, simply because it puts branching into practice.

var addListener = function() {
if ( window.addEventListener ) {
return function(el, type, fn) {
el.addEventListener(type, fn, false);
};
} else if ( window.attachEvent ) {
return function(el, type, fn) {
var f = function() {
fn.call(el, window.event);
};
el.attachEvent('on'+type, f);
};
} else {
return function(el, type, fn) {
element['on'+type] = fn;
}
}
}();

Two: Make Flags

Making flags is yet another great way to speed up object detection. If you have various functions checking for the same kind of object, then simply create a flag. For instance, if you’re checking to see if you have a browser capable of performing common DOM tasks, it may be wise to set a flag to represent that fact:

var w3 = !!(document.getElementById && document.createElement);

Or, if you’re a browser sniffer, an easy way to check if your visitor is running on Internet Explorer is to check for the ActiveX Object:

var ie = !!window.ActiveX;

The not operators (!!) simply perform a Boolean conversion. The first operator changes the type of the object on the right to a Boolean, and then the second will just reverse whatever the first returned. Nifty trick, eh?

One thing you should pay particular attention to is the scope at which these flags are declared. If your working domain is a small environment such as a blog, with only a few JavaScript bells and whistles, then you should be safe declaring them globally. But if this becomes a larger application (or you just want to keep your code clean), you can namespace your flags into a flags object:

var FLAGS = {
w3: function() {
return !!(document.getElementById && document.createElement);
}(),
ie: function() {
return !!window.ActiveX;
}()
};

The purpose of declaring your flags once is so that you’re not redefining them locally in various functions throughout your application, thus adding logic and code duplication that isn’t necessary.
Three: Make bridges

Be nice to your APIs! A bridge is a design pattern used in software engineering that is created to “decouple an abstraction from its implementation so that the two can vary independently” (Source: Wikipedia). Bridges are your friend when it comes to event-driven programming. If you’re just entering the world of API development—whether it’s a web service API, or a simple getSomething, setSomething API—make bridges to keep them clean.

One of the most practical cases for a bridge is for event listener callbacks. Let’s say you have an API function called getUserNameById. And, naturally, you want this information fetched upon a click event. And of course, the id of the element you click on contains that information. Well, here’s the bad way:

addListener(element, 'click', getUserNameById);
function getUserNameById(e) {
var id = this.id;
// do stuff with 'id'
}

As you can see, we’ve now created a nasty API that depends on a browser implementation that naturally passes back an event object as the first argument, and we only have the scope of callback to work with to grab the id from the this object. Good luck running this from the command line, or running a unit test against it! Instead, why don’t we try the following: Start with the API function first:

function getUserNameById(id) {
// do stuff with 'id'
}

Hey, that looks a little more practical! Now we can program to an interface and not an implementation (sound familiar, pattern junkies?). With that in mind, now we can create a bridge to connect the two functions:

addListener(element, 'click', getUserNameByIdBridge);
function getUserNameByIdBridge (e) {
getUserNameById(this.id);
}

Four: Try Event Delegation

Try is the key word here, since it isn’t practical for all cases, but it’s definitely worth…trying. I first learned of this through chatting with some of the Yahoo! folks and seeing it in action when the Menu widget was re-architected; blogger and Yahoo! developer, Christian Heilmann, has also discussed the topic in his Event Delegation article.

Event delegation is a simple way to cut back on event attachment. It works by adding a listener to a container element, and then retrieving the target that fired the event, rather than attaching several listeners to the children and accessing the element object through the this object. To give a simple example: If you have a ul (unordered list) with fifty list items as its children, and you want to handle the click event of those lis, it is more efficient to just assign one click event to the entire ul and then handle the click’s target. Here’s an example:


  • foo

  • bar

  • baz

  • thunk


var element = document.getElementById('example');
addListener(element, 'click', handleClick);
function handleClick(e) {
var element = e.target || e.srcElement;
// do stuff with 'element'
}

In this example, when a user clicks on an li element, it is essentially the same as clicking on the ul element. We can track down the source target by inspecting the event object that was invisibly passed to the callback method—in this case e (e is a common variable naming convention used for event). e contains a property called target (which is the target element), or in Internet Explorer, the property is called srcElement. We simply use a logical OR operator to determine if the target property can be found; if not, we default to srcElement for IE.
Five: Include methods with your getElementsByWhatever

One of the most notable omissions in nearly all JavaScript library APIs is the lack of an ability to add callbacks to element collector functions. Whether it’s a getElementsByClassName, getElementsByAttribute, or even a CSS querying engine, think about the logic behind what you want to do with these collections in the end; it just makes sense to allow the ability to have callbacks.

Step back for one moment and think about what goes on behind the scenes every time you want to get an element collection: You usually want to do something with it. And if your getElementsByWhatever function only allows you to get an array of elements and return them, don’t you think it’s a bit of a waste to iterate through that entire collection again when you’re ready to attach a function to the elements?

Let’s start off with a standard selector API that allows you to get elements in the DOM based on a CSS selector:

function getElementsBySelector(selector) {
var elements = [];
for ( ... ) {
if ( ... ) {
elements.push(el);
}
}
return elements;
}

Now that we have an array of matching elements, we want to attach some functionality to them:

var collection = getElementsBySelector('#example p a');
for ( var i = collection.length - 1; i >=0; --i ) {
// do stuff with collection[i]
}

By the end of this process, we’ve run two loops for what can be done in one. With that in mind, we can change our function to register the function callback as part of the initial loop through the matching elements:

function getElementsBySelector(selector, callback) {
var elements = [];
for ( ... ) {
if ( ... ) {
elements.push(el);
callback(el);
}
}
return elements;
}

getElementsBySelector('#example p a', function(el) {
// do stuff with 'el'
});

Six: Encapsulate your code

This is an oldie, but still rarely practiced. When you’re implementing JavaScript that starts to become large and unmanageable (trust me, it happens to us all), the most reliable safeguard to protect your code and let it play nicely with the other JavaScript kids is to simply use a closure. Instead of having a page full of globals:

var a = 'foo';
var b = function() {
// do stuff...
};
var c = {
thunk: function() {

},
baz: [false, true, false]
};

We can add a closure:

(function() {
var a = 'foo';
var b = function() {
// do stuff...
};
var c = {
thunk: function() {

},
baz: [false, true, false]
};
})();

The reason this safeguards your code is that closures give your code a closing scope that does not allow predators (or other messy engineers) to mess with it. A closure establishes a space in which any variables defined within that space are not accessible from outside that space. This also includes functions themselves, and in fact, it is only functions (changed as of JavaScript 1.7) that can provide block scope and create closures for you. Try the following example to see how the scope chain travels from the innermost function, outward.

// global land has access to ONLY a, but cannot execute code within it
function a() {
// has access to a, and b, but cannot execute code within b, c, and d
function b() {
// has access to a, b, and c, but cannot execute code within c, and d
function c() {
// has access to a, b, c, and d but cannot execute code within d
function d() {
// has access to all, and the ability to execute all
}
}
}
}

Seven: Reinvent the wheel

Last, but not least, reinvent the wheel. Okay, this isn’t a clever technique or anything to do with code—rather, it is a note of encouragement to never feel ashamed to try something radical when writing JavaScript.

Often, when I sit down to prototype out code or build a widget, I’m almost positive someone has already accomplished what I’m about to attempt. But nevertheless, it’s worth my effort to see if I can do it better. Not every wheel is round. Nor does every round wheel have twenty-inch shiny spinners. Imagination with such a highly flexible language can take you a long way.

Tuesday, April 24, 2007

PHP历史回顾

PHP/FI

PHP 继承自一个老的工程,名叫 PHP/FI。PHP/FI 在 1995 年由 Rasmus Lerdorf 创建,最初只是一套简单的 Perl 脚本,用来跟踪访问他主页的人们的信息。它给这一套脚本取名为“Personal Home Page Tools”。随着更多功能需求的增加,Rasmus 写了一个更大的 C 语言的实现,它可以访问数据库,可以让用户开发简单的动态 Web 程序。Rasmus 发布了 PHP/FI 的源代码,以便每个人都可以使用它,同时大家也可以修正它的 Bug 并且改进它的源代码。

  PHP/FI,一个专为个人主页/表单提供解释程序的程序,已经包含了今天 PHP 的一些基本功能。它有着 Perl 样式的变量,自动解释表单变量,并可以嵌入 HTML。语法本身与 Perl 很相似,但是它很有限,很简单,还稍微有些不协调。

  到1997年,PHP/FI 2.0,也就是它的 C 语言实现的第二版在全世界已经有几千个用户(估计)和大约 50,000 个域名安装,大约是 Internet 所有域名的 1%。但是那时只有几个人在为该工程撰写少量当代码,它仍然只是一个人的工程。

  PHP/FI 2.0 在经历了数个 beta 版本的发布后于 1997 年 11 月发布了官方正式版本。不久,PHP 3.0 的第一个 alpha 版本的发布,PHP 从此走向了成功。

PHP 3

  PHP 3.0 是类似于当今 PHP 语法结构的第一个版本。Andi Gutmans 和 Zeev Suraski 在为一所大学的项目中开发电子商务程序时发现 PHP/FI 2.0 功能明显不足,于是他们重写了代码。这就是 PHP 3.0。经过Andi,Rasmus 和 Zeev 一系列的努力,考虑到 PHP/FI 已存在的用户群,他们决定联合发布 PHP 3.0 作为 PHP/FI 2.0 的官方后继版本。而 PHP/FI 2.0 的进一步开发几乎终止了。

  PHP 3.0 的一个最强大的功能是它的可扩展性。除了给最终用户提供数据库、协议和 API 的基础结构,它的可扩展性还吸引了大量的开发人员加入并提交新的模块。后来证实,这是 PHP 3.0 取得巨大成功的关键。PHP 3.0 中的其它关键功能包括面向对象的支持和更强大和协调的语法结构。

  这个全新的语言伴随着一个新的名称发布。它从 PHP/FI 2.0 的名称中移去了暗含“本语言只限于个人使用”的部分。它被命名为简单的缩写“PHP”。这是一种递归的缩写,它的全称是——PHP: Hypertext Preprocessor。

  1998 年末,PHP 的安装人数几近 10,000,有大约 100,000 个网站报告他们使用了 PHP。在 PHP 3.0 的顶峰,Internet 上 10% 的 Web 服务器上都安装了它。

  约九个月的公开测试后,官方于1998年6月正式发布 PHP 3.0。

PHP 4

  1998 年的冬天,PHP 3.0 官方发布不久,Andi Gutmans 和 Zeev Suraski 开始重新编写 PHP 代码。设计目标是增强复杂程序运行时的性能和 PHP 自身代码的模块性。PHP 3.0 的新功能和广泛的第三方数据库、API的支持使得这样程序的编写成为可能,但是 PHP 3.0 没有高效处理如此复杂程序的能力。

  新的被称为“Zend Engine”(这是 Zeev 和 Andi 的缩写)的引擎,成功的实现了设计目标,并在 1999 年中期首次引入 PHP。基于该引擎并结合了更多新功能的 PHP 4.0,在 PHP 3.0 发布两年后,于2000年5月发布了官方正式版本。除了更高的性能以外,PHP 4.0 还包含了其它一些关键功能,比如:支持更多的 Web 服务器;HTTP Sessions 支持;输出缓存(output buffering);更安全的处理用户输入的方法;一些新的语言结构。

什么是系统平均负载 Load average

原文链接
在Linux系统中,uptime、w、top等命令都会有系统平均负载load average的输出,那么什么是系统平均负载呢?
系统平均负载被定义为在特定时间间隔内运行队列中的平均进程树。如果一个进程满足以下条件则其就会位于运行队列中:
  - 它没有在等待I/O操作的结果
  - 它没有主动进入等待状态(也就是没有调用'wait')
  - 没有被停止(例如:等待终止)
  例如:
  [root@opendigest root]# uptime
  7:51pm up 2 days, 5:43, 2 users, load average: 8.13, 5.90, 4.94
  命令输出的最后内容表示在过去的1、5、15分钟内运行队列中的平均进程数量。
  一般来说只要每个CPU的当前活动进程数不大于3那么系统的性能就是良好的,如果每个CPU的任务数大于5,那么就表示这台机器的性能有严重问题。对于上面的例子来说,假设系统有两个CPU,那么其每个CPU的当前任务数为:8.13/2=4.065。这表示该系统的性能是可以接受的。

二、Load average的算法
上面的输出数据是每隔5秒钟检查一次活跃的进程数,然后根据这个数值算出来的。如果这个数除以CPU的数目,结果高于5的时候就表明系统在超负荷运转了。其算法(摘自Linux 2.4的内核代码)如下:

文件: include/linux/sched.h:
#define FSHIFT 11 /* nr of bits of precision */
#define FIXED_1 (1<#define LOAD_FREQ (5*HZ) /* 5 sec intervals */
#define EXP_1 1884 /* 1/exp(5sec/1min) as fixed-point, 2048/pow(exp(1), 5.0/60) */
#define EXP_5 2014 /* 1/exp(5sec/5min), 2048/pow(exp(1), 5.0/300) */
#define EXP_15 2037 /* 1/exp(5sec/15min), 2048/pow(exp(1), 5.0/900) */

#define CALC_LOAD(load,exp,n) \
load *= exp; \
load += n*(FIXED_1-exp); \
load >>= FSHIFT;

/**********************************************************/

文件: kernel/timer.c:
unsigned long avenrun[3];

static inline void calc_load(unsigned long ticks)
{
unsigned long active_tasks; /* fixed-point */
static int count = LOAD_FREQ;

count -= ticks;
if (count < 0) {
count += LOAD_FREQ;
active_tasks = count_active_tasks();
CALC_LOAD(avenrun[0], EXP_1, active_tasks);
CALC_LOAD(avenrun[1], EXP_5, active_tasks);
CALC_LOAD(avenrun[2], EXP_15, active_tasks);
}
}

/**********************************************************/

文件: fs/proc/proc_misc.c:

#define LOAD_INT(x) ((x) >> FSHIFT)
#define LOAD_FRAC(x) LOAD_INT(((x) & (FIXED_1-1)) * 100)

static int loadavg_read_proc(char *page, char **start, off_t off,
int count, int *eof, void *data)
{
int a, b, c;
int len;

a = avenrun[0] + (FIXED_1/200);
b = avenrun[1] + (FIXED_1/200);
c = avenrun[2] + (FIXED_1/200);
len = sprintf(page,"%d.%02d %d.%02d %d.%02d %ld/%d %d\n",
LOAD_INT(a), LOAD_FRAC(a),
LOAD_INT(b), LOAD_FRAC(b),
LOAD_INT(c), LOAD_FRAC(c),
nr_running(), nr_threads, last_pid);
return proc_calc_metrics(page, start, off, count, eof, len);
}
三、/proc/loadavg 各项数据的含义
/proc文件系统是一个虚拟的文件系统,不占用磁盘空间,它反映了当前操作系统在内存中的运行情况,查看/proc下的文件可以聊寄到系统的运行状态。查看系统平均负载使用“cat /proc/loadavg”命令,输出结果如下:
0.27 0.36 0.37 4/83 4828/
前三个数字大家都知道,是1、5、15分钟内的平均进程数(有人认为是系统负荷的百分比,其实不然,有些时候可以看到200甚至更多)。后面两个呢,一个的分子是正在运行的进程数,分母是进程总数;另一个是最近运行的进程ID号。

四、查看系统平均负载的常用命令

1、cat /proc/loadavg

2、uptime
名称: uptime
使用权限: 所有使用者
使用方式: uptime [-V]
说明: uptime 提供使用者下面的资讯,不需其他参数:
现在的时间 系统开机运转到现在经过的时间 连线的使用者数量 最近一分钟,五分钟和十五分钟的系统负载
参数: -V 显示版本资讯。
范例: uptime
其结果为:
10:41am up 5 days, 10 min, 1 users, load average: 0.00, 0.00, 1.99


3、w
功能说明:显示目前登入系统的用户信息。
语  法:w [-fhlsuV][用户名称]
补充说明:执行这项指令可得知目前登入系统的用户有那些人,以及他们正在执行的程序。单独执行w
指令会显示所有的用户,您也可指定用户名称,仅显示某位用户的相关信息。
参  数:
-f  开启或关闭显示用户从何处登入系统。
-h  不显示各栏位的标题信息列。
-l  使用详细格式列表,此为预设值。
-s  使用简洁格式列表,不显示用户登入时间,终端机阶段作业和程序所耗费的CPU时间。
-u  忽略执行程序的名称,以及该程序耗费CPU时间的信息。
-V  显示版本信息。

4、top
功能说明:显示,管理执行中的程序。
语  法:top [bciqsS][d <间隔秒数>][n <执行次数>]
补充说明:执行top指令可显示目前正在系统中执行的程序,并通过它所提供的互动式界面,用热键加以管理。
参  数:
 b  使用批处理模式。
 c  列出程序时,显示每个程序的完整指令,包括指令名称,路径和参数等相关信息。
 d<间隔秒数>  设置top监控程序执行状况的间隔时间,单位以秒计算。
 i  执行top指令时,忽略闲置或是已成为Zombie的程序。
 n<执行次数>  设置监控信息的更新次数。
 q  持续监控程序执行的状况。
 s  使用保密模式,消除互动模式下的潜在危机。
 S  使用累计模式,其效果类似ps指令的"-S"参数。

5、tload
功能说明:显示系统负载状况。
语  法:tload [-V][-d <间隔秒数>][-s <刻度大小>][终端机编号]
补充说明:tload指令使用ASCII字符简单地以文字模式显示系统负载状态。假设不给予终端机编号,则会在执行tload指令的终端机显示负载情形。
参  数:
 -d<间隔秒数>  设置tload检测系统负载的间隔时间,单位以秒计算。
 -s<刻度大小>  设置图表的垂直刻度大小,单位以列计算。
 -V  显示版本信息。

什么是逻辑卷管理器及利用Disk Druid设置LVM和命令行设置LVM(LVM-logical volume manager)

利用Disk Druid设置LVM
IBM通用线程: 学习 Linux LVM
LVM是一种把硬盘空间分配成逻辑卷的方法。相比硬盘分区,逻辑卷更容易改变容量。

利用LVM,硬盘或者硬盘集被分配成一个或者多个物理卷。一个物理卷只能分配到一块硬盘,不能跨硬盘分配。

物理卷被整合成逻辑卷组,不过有个例外就是/boot/分区。/boot/分区不能处在逻辑卷组上,因为引导装载器不能读逻辑卷组。如果根分区(/)在一个逻辑卷上,应该创建一个单独的/boot/分区,让它不属于任何卷组。

因为单个物理卷不能跨硬盘,为了能够跨多个硬盘,应该在每个硬盘上创建一个或者多个物理卷。

逻辑卷组下面是逻辑卷。逻辑卷被指定了挂载点,比如/home和/,也指定了文件系统,比如ext3。当“分区”满了的时候,空闲空间被从逻辑卷组中加到这个逻辑卷,来增加这个分区的容量。如果系统增加了一块硬盘,可以把它加到逻辑卷组,这样作为分区的逻辑卷容量就可以扩大。

另一方面,如果系统采用ext3文件系统分区,硬盘被划分成几个固定尺寸的分区。如果一个分区满了,很难扩大该分区的容量。甚至如果一个分区被移到另外一块硬盘上,原来的硬盘上的空间就要重新分配成不同的分区,或者不被使用。

LVM支持需要被编译进内核。常见的Linux发行版的内核缺省已经编译进LVM支持了。


Filesystem 1K-blocks Used Available Use% Mounted on
/dev/mapper/VolGroup00-LogVol00
68378168 37420660 27484100 58% /
/dev/sda1 101086 12525 83342 14% /boot
none 1037372 0 1037372 0% /dev/shm


命令行操作LVM
本文作者 szxsztszk ,其著作权归 szxsztszk 所有。

LVM: (logical volume manager)逻辑卷管理
原理:
把一个或多个分区分配到卷组,在卷组中被分成多个逻辑卷.方便改变大小和快速镜像.

步骤:
fdisk
partprobe 刷新修改后的分区表。
一.初始化数据库
vgscan
二 .建立物理卷
pvcreate /dev/sda9 类似格式化.
pvdisplay 查看创建好的物理卷
三..分配物理卷到卷组
vgcreate myvg2 /dev/sda9 /dev/sda10
vgdisplay 查看 (键入VP,安TAB键即可看到相关命令)
vgcreate -s 8M myvg2/dev/sda9 /dev/sda10
备注: 卷组块默认是4M,可以用"-s 4M的倍数"来定制块.

四.创建逻辑卷 |->逻辑卷名子 |->属于的卷组
lvcreate -L 48M -n data myvg2
|->逻辑卷大小(卷块的倍数,切记)
lvdisplay 查看分区

mkfs.ext3 /dev/myvg2/data 建立文件系统
五.逻辑卷的管理
改变逻辑卷大小(变化的空间是块的倍数方可)
lvextend -l +5 /dev/myvg2/data 扩展块
lvextend -L +50M /dev/myvg2/data 扩展M

lvreduce -l -5 /dev/myvg2/data 减少块
lvreduce -L -50M /dev/myvg2/data 减少M

lvresize -L -100M /dev/myvg2/data 用块改变大小
lvresize -L +100M /dev/myvg2/data 用M改变大小

ext2online /dev/myvg2/data 让lv1容量更改立即生效
添加新的物理卷到卷组:
vgextend myvg2 /dev/sda4

移除物理卷从卷组:
vgreduce myvg2 /dev/sda4

移除物理卷:
pvmove /dev/sda4

Reference: http://www.cnblogs.com/lee/archive/2008/06/09/1216410.html
http://www.diybl.com/course/6_system/linux/Linuxjs/200876/130633.html

CentOS History

Linux是GNU/Linux的缩写,通常指各种Linux发行版的通称。
常见的Linux厂家主要有Redhat/Novell等。

Redhat有两大Linux产品系列,其一是免费的Fedora Core系列,主要用于桌面版本,提供了较多新特性的支持。另外一个产品系列是收费的Enterprise系列,这个系列分成:AS/ES/WS等分支。

Advanced Server,缩写即AS。AS在标准Linux内核的基础上,做了性能上的增强,并提高了可靠性,集成了众多常见服务器的驱动程序。可轻松识别IBM/DELL/HP等常见机架式服务器的磁盘阵列卡等设备。

AS主要版本2.x/3.x/4.x,也就是我们所说的AS3/AS4,每一个版本还有若干个升级,例如最早推出的AS4后,遇到了一些更新。此时就会发布AS4 Update1,以后还会陆续有AS4 Update2/Update3等出现,简称AS4u1/AS4u2/AS4u3等。这和微软的发布形式也是非常类似的,微软的Windows NT4 从SP1出到SP6,Windows2000从SP1出到SP4。。。。

AS这些Update版本所包含的主要程序包版本都有一定差别,最好不要混用,否则很容易出现问题。Prima和Plesk的安装包,对于各种发行版都有了针对性的设计,在下载页面上,通常都会标识出来,支持哪些版本和哪些update的系统。

ES,是AS的精简版本。他与常见的AS系列的区别是,AS支持到4路以上CPU,而ES只能支持两路CPU。AS和ES在大多数程序包上并无区别,
只在内核等少数软件包上有差异。AS和ES的售价差别比较大,通常ES用在随服务器一同购买的OEM版本中,例如购买DELL服务器,搭配的Linux会是ES系列。如果要搭配AS系列,则需要多花数千元。

WS,是ES的进一步简化版,主要针对企业内部的桌面办公市场,国内较少采用。

Redhat的Fedora Core Linux和Enterprise Linux,都需要遵循GNU协议,即需要发布自己的源代码。所以,对于免费的Fedora Core Linux,从Redhat网站上可以直接下载ISO刻盘,还能下载到SRPM的ISO,即程序包源码光盘。对于收费的Enterprise Linux系列,是一款商业产品,所以网站上不能下载到ISO文件,需要购买正式授权方可。由于Enterprise Linux也需要遵循GNU协议,故必须发布源代码。
所以在Redhat的网站上,可以获得AS/ES/WS系列的SRPM源码ISO文件。这些文件可以被自由的下载,修改代码,重新编译使用。

一个名为Community Enterprise Operating System的项目诞生了。他的缩写既是CentOS。CentOS社区将Redhat的网站上的所有源代码下载下来,进行重新编译。重新编译后,由于AS/ES/WS是商业产品,必须将所有Redhat的Logo和标识改成自己的CentOS标识。比如将AS4原版的SRPM源码编译后,就成为了CentOS 4.0。
AS4Update1的源码编译后,就成为了CentOS4.1。
AS4Update2的源码编译后,就成为了CentOS4.2。
同理,CentOS的3.x/4.x都对应着相应的版本。

所以我们说,CentOS就是Redhat的AS/ES/WS的免费版本。使用CentOS,可以获得和AS/ES相同的性能和感受。CentOS除了提供标准的编号1~4或者1~5的若干张ISO以外,还提供了最小化1CD的Server光盘。用Server光盘安装好的系统,就是一个最小化的Linux内核加上常用的httpd/mysql等包,不包含Xwindows桌面等对于服务器无用的软件。

Prima、Plesk、Virtuozzo和都可以安装在CentOS上。

Monday, April 23, 2007

Cronolog 设置定期轮循日志

http://cronolog.org/download/index.html
假设安装目录为:/usr/local/cronolog

cat access_log |/usr/local/cronolog/sbin/cronolog -p 12hours /home/test/%Y-%m-%d.log
无提示,正确在/home/test/目录下生成Log文件
cat access_log |/usr/local/cronolog/sbin/cronolog -p 13hours /home/test/%Y-%m-%d.log
提示:/usr/local/cronolog/sbin/cronolog: invalid explicit period specification ((null))
这个需要看一下Cronolog文档说明,如下:
-p PERIOD
--period=PERIOD
specifies the period explicitly as an optional digit string followed by one of units: seconds, minutes,
hours, days, weeks or months. The count cannot be greater than the number of units in the
next larger unit, i.e. you cannot specify "120 minutes", and for seconds, minutes and hours the
count must be a factor of the next higher unit, i.e you can specify 1, 2, 3, 4, 5, 6, 10, 15, 20 or 30
minutes but not say 7 minutes.

假设需要每4小时导出一次Log,则在http.conf里设置
CustomLog "|/usr/local/cronolog/sbin/cronolog -p 4hours /home/test/%Y-%m-%d-%H.log" common
注释原来的CustomLog那一行

Apache2.2 日志滚动和管道日志

http://lamp.linux.gov.cn/Apache/ApacheMenu/logs.html

即使一个并不繁忙的服务器,其日志文件的信息量也会很大,一般每10000个请求,访问日志就会增加1MB或更多。这就有必要定期滚动日志文件。由于Apache会保持日志文件的打开,并持续写入信息,因此服务器运行期间不能执行滚动操作。移动或者删除日志文件以后,必须重新启动服务器才能让它打开新的日志文件。

用优雅的(graceful)方法重新启动,可以使服务器启用新的日志文件,而不丢失原来尚未写入的信息。为此,有必要等待一段时间,让服务器完成正在处理的请求,并将记录写入到原来的日志文件。以下是一个典型的日志滚动和为节省存储空间而压缩旧日志的例子:

mv access_log access_log.old
mv error_log error_log.old
apachectl graceful
sleep 600
gzip access_log.old error_log.old

另一种执行滚动的方法是使用下一节阐述的管道日志。

管道日志
Apache httpd可以通过管道将访问记录和出错信息传递给另一个进程,而不是写入一个文件,由于无须对主服务器进行编程,这个功能显著地增强了日志的灵活性。只要用管道操作符"|"后面跟一个可执行文件名,就可以使这个程序从标准输入设备获得事件记录。Apache在启动时,会同时启动这个管道日志进程,并且在运行过程中,如果这个进程崩溃了,会重新启动这个进程(所以我们称这个技术为"可靠管道日志")。

管道日志进程由其父进程Apache httpd产生,并继承其权限,这意味着管道进程通常是作为root运行的,所以保持这个程序简单而安全极为重要。

管道日志的一种重要用途是,允许日志滚动而无须重新启动服务器。为此,服务器提供了一个简单的程序rotatelogs 。每24小时滚动一次日志的例子如下:

CustomLog "|/usr/local/apache/bin/rotatelogs /var/log/access_log 86400" common

注意:引号用于界定整个管道命令行。虽然这是针对访问日志的,但是其用法对于其他日志也一样。

在其他站点,有一个类似但更灵活的日志滚动程序叫cronolog 。

如果有较简单的离线处理日志的方案,就不应该使用条件日志和管道日志,即使它们非常强大。

Apache2.2条件日志

http://lamp.linux.gov.cn/Apache/ApacheMenu/logs.html

许多时候,根据与请求特征相关的环境变量来有选择地记录某些客户端请求会带来便利。首先,需要使用SetEnvIf指令来设置特定的环境变量以标识符合某种特定条件的请求,然后用CustomLog指令的 env= 子句,根据这些环境变量来决定记录或排除特定的请求。例如:

# 不记录本机发出的请求
SetEnvIf Remote_Addr "127\.0\.0\.1" dontlog
# 不记录对robots.txt文件的请求
SetEnvIf Request_URI "^/robots\.txt$" dontlog
# 记录其他请求
CustomLog logs/access_log common env=!dontlog

再如,将使用英语的请求记录到一个日志,而记录非英语的请求到另一个日志:

SetEnvIf Accept-Language "en" english
CustomLog logs/english_log common env=english
CustomLog logs/non_english_log common env=!english

虽然上述已经展示了条件日志记录的强大和灵活,但这不是控制日志内容的唯一手段,还可以用日志后继处理程序来剔除你不关心的内容,从而使日志更加有用。

多服务器的日志合并统计——apache日志的cronolog轮循

作者:车东 发表于:2003-04-12 11:04 最后更新于:2007-04-13 22:04
版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明。
http://www.chedong.com/tech/rotate_merge_log.html
内容摘要:你完全不必耐心地看完下面的所有内容,因为结论无非以下2点:
1 用 cronolog 干净,安全地轮循apache日志
2 用 sort -m 合并排序多个日志

根据个人的使用经历:
1 先介绍apache日志的合并方法;
2 然后根据由此引出的问题说明日志轮循的必要性和解决方法,介绍如何通过cronolog对apache日志进行轮循;
中间有很多在设计日志合并过程中一些相关工具的使用技巧和一些尝试的失败经历……
我相信解决以上问题的路径不止这一条途径,以下方案肯定不是最简便或者说成本最低的,希望能和大家有更多的交流。


多服务器日志合并统计的必要性

越来越多大型的WEB服务使用DNS轮循来实现负载均衡:使用多个同样角色的服务器做前台的WEB服务,这大大方便了服务的分布规划和扩展性,但多个服务器的分布使得日志的分析统计也变得有些麻烦。如果使用webalizer等日志分析工具对每台机器分别做日志统计:
1 会对数据的汇总带来很多麻烦,比如:统计的总访问量需要将SERVER1 SERVER2...上指定月份的数字相加。
2 会大大影响统计结果中唯一访客数unique visits,唯一站点数unique sites的等指标的统计,因为这几个指标并非几台机器的代数相加。

统一日志统计所带来的好处是显而易见的,但如何把所有机器的统计合并到一个统计结果里呢?
首先也许会想:多个服务器能不能将日志记录到同一个远程文件里呢?我们不考虑使用远程文件系统记录日志的问题,因为带来的麻烦远比你获得的方便多的多……
因此,要统计的多个服务器的日志还是:分别记录=>并通过一定方式定期同步到后台=>合并=>后用日志分析工具来进行分析。

首先,要说明为什么要合并日志:因为webalizer没有将同一天的多个日志合并的功能
先后运行
webalizer log1
webalizer log2
webalizer log3
这样最后的结果是:只有log3的结果。

能不能将log1<<log2<<log3简单叠加呢?
因为一个日志的分析工具不是将日志一次全部读取后进行分析,而且流式的读取日志并按一定时间间隔,保存阶段性的统计结果。因此时间跨度过大(比如2条日志间隔超过5分钟),一些日志统计工具的算法就会将前面的结果“忘掉”。因此, log1<<log2<<log3直接文件连接的统计结果还是:只有log3的统计结果。

多台服务日志合并问题:把多个日志中的记录按时间排序后合并成一个文件

典型的多个日志文件的时间字段是这样的:
log1 log2 log3
00:15:00 00:14:00 00:11:00
00:16:00 00:15:00 00:12:00
00:17:00 00:18:00 00:13:00
00:18:00 00:19:00 00:14:00
14:18:00 11:19:00 10:14:00
15:18:00 17:19:00 11:14:00
23:18:00 23:19:00 23:14:00

日志合并必须是按时间将多个日志的交叉合并。合并后的日志应该是:
00:15:00 来自log1
00:15:00 来自log2
00:16:00 来自log1
00:17:00 来自log3
00:18:00 来自log2
00:19:00 来自log1
....

如何合并多个日志文件?
下面以标准的clf格式日志(apache)为例:
apche的日志格式是这样的:
%h %l %u %t \"%r\" %>s %b
具体的例子:
111.222.111.222 - - [03/Apr/2002:10:30:17 +0800] "GET /index.html HTTP/1.1" 200 419

最简单的想法是将日志一一读出来,然后按日志中的时间字段排序
cat log1 log2 log3 |sort -k 4 -t " "
注释:
-t " ": 日志字段分割符号是空格
-k 4: 按第4个字段排序,也就是:[03/Apr/2002:10:30:17 +0800] 这个字段
-o log_all: 输出到log_all这个文件中

但这样的效率比较低,要知道。如果一个服务已经需要使用负载均衡,其服务的单机日志条数往往都超过了千万级,大小在几百M,这样要同时对多个几百M的日志进行排序,机器的负载可想而之……
其实有一个优化的途径,要知道:即使单个日志本身已经是一个“已经按照时间排好序“的文件了,而sort对于这种文件的排序合并提供了一个优化合并算法:使用 -m merge合并选项,
因此:合并这样格式的3个日志文件log1 log2 log3并输出到log_all中比较好方法是:
sort -m -t " " -k 4 -o log_all log1 log2 log3
注释:
-m: 使用 merge优化算法

注意:合并后的日志输出最好压缩以后再发给webalizer处理
有的系统能处理2G的文件,有的不能。有的程序能处理大于2G的文件,有的不能。尽量避免大于2G的文件,除非确认所有参与处理的程序和操作系统都能处理这样的文件。所以输出后的文件如果大于2G,最好将日志gzip后再发给webalizer处理:大于2G的文件分析过程中文件系统出错的可能性比较大,并且gzip后也能大大降低分析期间的I/O操作。

日志的按时间排序合并就是这样实现的。

日志的轮循机制

让我们关心一下数据源问题:webalizer其实是一个按月统计的工具,支持增量统计:因此对于大型的服务,我可以按天将apache的日志合并后送给 webalizer统计。WEB日志是如何按天(比如每天子夜00:00:00)截断呢?
如果你每天使用crontab:每天0点准时将日志备份成access_log_yesterday
mv /path/to/apache/log/access_log /path/to/apache/log/access_log_yesterday
的话:你还需要:马上运行一下:apache restart 否则:apache会因为的日志文件句柄丢失不知道将日志记录到哪里去了。这样归档每天子夜重启apache服务会受到影响。
比较简便不影响服务的方法是:先复制,后清空
cp /path/to/apache/log/access_log /path/to/apache/log/access_log_yesterday
echo >/path/to/apache/log/access_log

严肃的分析员会这样做发现一个问题:
但cp不可能严格保证严格的0点截断。加入复制过程用了6秒,截断的access_log_yesterday日志中会出现复制过程到00:00:06期间的日志。对于单个日志统计这些每天多出来几百行日志是没有问题的。但对于多个日志在跨月的1天会有一个合并的排序问题:
[31/Mar/2002:59:59:59 +0800]
[31/Mar/2002:23:59:59 +0800]
[01/Apr/2002:00:00:00 +0800]
[01/Apr/2002:00:00:00 +0800]

要知道[01/Apr/2002:00:00:00 这个字段是不可以进行“跨天排序”的。因为日期中使用了dd/mm/yyyy,月份还是英文名,如果按照字母排序,很有可能是这样的结果:排序导致了日志的错误
[01/Apr/2002:00:00:00 +0800]
[01/Apr/2002:00:00:00 +0800]
[01/Apr/2002:00:00:00 +0800]
[01/Apr/2002:00:00:00 +0800]
[01/Apr/2002:00:00:00 +0800]
[01/Apr/2002:00:00:00 +0800]
[01/Apr/2002:00:00:00 +0800]
[31/Mar/2002:59:59:59 +0800]
[31/Mar/2002:59:59:59 +0800]
[31/Mar/2002:23:59:59 +0800]
[31/Mar/2002:59:59:59 +0800]
[31/Mar/2002:23:59:59 +0800]

这些跨天过程中的非正常数据对于webalizer等分析工具来说简直就好像是吃了一个臭虫一样,运行的结果是:它可能会把前一个月所有的数据都丢失!因此这样的数据会有很多风险出现在处理上月最后一天的数据的过程中。

问题的解决有几个思路:
1 事后处理:
。所以一个事后的处理的方法是:用grep命令在每月第1天将日志跨月的日志去掉,比如:
grep -v "01/Apr" access_log_04_01 > access_log_new

修改SORT后的日志:所有跨天的数据去掉。也许对日志的事后处理是一个途径,虽然sort命令中有对日期排序的特殊选项 -M(注意是:大写M),可以让指定字段按照英文月份排序而非字母顺序,但对于apache日志来说,用SORT命令切分出月份字段很麻烦。(我尝试过用 "/"做分割符,并且使用“月份” “年:时间”这两个字段排序)。虽然用一些PERL的脚本肯定可以实现,但最终我还是放弃了。这不符合系统管理员的设计原则:通用性。并且你需要一直问自己:有没有更简单的方法呢?
还有就是将日志格式改成用TIMESTAMP(象SQUID的日志就没有这个问题,它的日志本身就是使用TIMESTAMP做时间时间戳的),但我无法保证所有的日志工具都能识别你在日期这个字段使用了特别的格式。

2 优化数据源:
最好的办法还是优化数据源。将数据源保证按天轮循,同一天的日志中的数据都在同一天内。这样以后你无论使用什么工具(商业的,免费的)来分析日志,都不会因为日志复杂的预处理机制受到影响。

首先可能会想到的是控制截取日志的时间:比如严格从0点开始截取日志,但在子夜前1分钟还是后一分钟开始截取是没有区别的,你仍然无法控制一个日志中有跨 2天记录的问题,而且你也无法预测日志归档过程使用的时间。
因此必须要好好考虑一下使用日志轮循工具的问题,这些日志轮循工具要符合:
1 不中断WEB服务:不能停apache=>移动日志=>重启apache
2 保证同一天日志能够按天轮循:每天一个日志00:00:00-23:59:59
3 不受apache重启的影响:如果apache每次重启都会生成一个新的日志是不符合要求的
4 安装配置简单

首先考虑了apache/bin目录下自带的一个轮循工具:rotatelogs 这个工具基本是用来按时间或按大小控制日志的,无法控制何时截断和如何按天归档。
然后考虑logrotate后台服务:logrotate是一个专门对各种系统日志(syslogd,mail)进行轮循的后台服务,比如SYSTEM LOG,但其配置比较复杂,放弃,实际上它也是对相应服务进程发出一个-HUP重启命令来实现日志的截断归档的。

在apache的FAQ中,推荐了经过近2年发展已经比较成熟的一个工具cronolog:安装很简单:configure=>make=> make install

他的一个配置的例子会让你了解它有多么适合日志按天轮循:对httpd.conf做一个很小的修改就能实现:
TransferLog "|/usr/sbin/cronolog /web/logs/%Y/%m/%d/access.log"
ErrorLog "|/usr/sbin/cronolog /web/logs/%Y/%m/%d/errors.log"

然后:日志将写入
/web/logs/2002/12/31/access.log
/web/logs/2002/12/31/errors.log
午夜过后:日志将写入
/web/logs/2003/01/01/access.log
/web/logs/2003/01/01/errors.log
而2003 2003/01 和 2003/01/01 如果不存在的话,将自动创建

所以,只要你不在0点调整系统时间之类的话,日志应该是完全按天存放的(00:00:00-23:59:59),后面日志分析中: [31/Mar/2002:15:44:59这个字段就和日期无关了,只和时间有关。

测试:考虑到系统硬盘容量,决定按星期轮循日志
apache配置中加入:
#%w weekday
TransferLog "|/usr/sbin/cronolog /path/to/apache/logs/%w/access_log"

重启apache后,除了原来的CustomLog /path/to/apche/logs/access_log继续增长外,系统log目录下新建立了 3/目录(测试是在周3),过了一会儿,我忽然发现2个日志的增长速度居然不一样!
分别tail了2个日志才发现:
我设置CustomLog使用的是combined格式,就是包含(扩展信息的),而TransferLog使用的是缺省日志格式,看了apache的手册才知道,TransferLog是用配置文件中离它自己最近的一个格式作为日志格式的。我的httpd.conf里写的是:
LogFormat ..... combined
LogFormat ... common
...
CustomLog ... combined
TransferLog ...

所以TrasferLog日志用的是缺省格式,手册里说要让TRANSFER日志使用指定的格式需要:
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
TransferLog "|/usr/local/sbin/cronolog /path/to/apache/logs/%w/access_log"

重启,OK,日志格式一样了。
这样的设置结果其实是同时在logs目录下分别记录2个日志access_log和%w/access_log,能不能只记录%w/下的日志那?
查apache手册,更简单的方法:直接让CustomLog输出到cronolog归档日志,并且还能指定格式。
CustomLog "|/usr/local/sbin/cronolog /path/to/apache/logs/%w/access_log" combined

最后是一个日志同步的问题。

任务:每天凌晨找到前1天的日志,另存一个文件准备发送到服务器上。
比如我要保留前1周的日志:每天复制前1天的日志到指定目录,等待日志服务器来抓取:
/bin/cp -f /path/to/apache/logs/`date -v-1d +%w`/access_log /path/for/backup/logs/access_log_yesterday

在FREEBSD上使用以下命令
date -v-1d +%w
注释:
-v-1d: 前1天,而在GNU/Linux上这个选项应该是date -d yesterday
+%w: weekday,由于使用的都是标准时间函数库,所有工具中的WEEKDAY定义都是一样的 0-6 => 周日-周六

注意:
写到CRONTAB里的时候"%"前面需要加一个"\"转义:每天0点5分进行一次日志归档,
另外一个问题就是在cront中需要用:rm -f {} ; 而不是rm -f {}\;
5 0 * * * /bin/cp /path/to/logs/`date -v-1d +\%w`/access_log /path/to/for_sync/logs/access_yesterday
37 10 * * * /usr/bin/find /home/apache/logs/ -name access_log -mtime +1 -exec /bin/rm -f {} ;

首次开始cronolog日志统计是周3,一周以后日志又将轮循回3/access_log
但这次日志是追加到3/access_log还是重新创建一个文件呢?>>access_log or >access_log?
我测试的结果是日志将被追加:
[01/Apr/2002:23:59:59 +0800]
[01/Apr/2002:23:59:59 +0800]
[08/Apr/2002:00:00:00 +0800]
[08/Apr/2002:00:00:00 +0800]

肯定是不希望每次日志还带着上周的数据的并重复统计一次的(虽然对结果没影响),而且这样%w/下的日志不是也越来越多了吗?
解决方法1 把每天的cp改成mv
解决方法2 每天复制完成后:删除6天以前的access_log日志
find /path/to/apache/logs -name access_log -mtime +6 -exec rm -f {}\;
多保留几天的日志还是有必要的:万一日志分析服务器坏了一天呢?

以下是把apache安装在/home/apache下每天统计的一个脚本文件:
#!/bin/sh

#backup old log
/bin/cp -f /home/apache/logs/`date -d yesterday +%w`/access_log /home/apache/logs/access_log_yesterday

#remove old log
/usr/bin/find /home/apache/logs -name access_log -mtime +6 -exec rm -f {}\;

#analysis with webalizer
/usr/local/sbin/webalizer

总结:
1 用 cronolog 干净,安全地轮循日志
2 用 sort -m 排序合并多个日志


参考资料:

日志分析统计工具:
http://directory.google.com/Top/Computers/Software/Internet/Site_Management/Log_Analysis/

Apche的日志设置:
http://httpd.apache.org/docs/mod/mod_log_config.html

Apache的日志轮循:
http://httpd.apache.org/docs/misc/FAQ.html#rotate

Cronolog
http://www.cronolog.org

Webalizer
http://www.mrunix.net/webalizer/
Webalzer的Windows版
http://www.medasys-lille.com/webalizer/

AWStats的使用简介
http://www.chedong.com/tech/awstats.html

Sunday, April 22, 2007

一个服务器同时运行多个不同版本的Rails应用[转台湾thegiive文章]

http://lightyror.thegiive.net/2007/04/rails-version.html,以下為其原文內容。
目前使用 Rails 的網站,Version 分成好幾派。
0.X
1.0
1.1
1.2

Rails 0.X 的就是幾年前就開始在使用 Rails 的始祖,據我所知國內也是有網站還再用 0.X ,他們之所以死撐不換的原因只有一個,就是怕升級上去原本的 code 根本不相容。1.0 的時候我沒趕上,那可能要問一些長輩才知道 1.0 的模樣是怎麼樣子。

我進入 Rails 圈子剛好是 1.1 的剛剛出的時代,那時候 RJS 剛剛出來,大家叫好又叫座。我也因此全新投入了 Rails,1.1 的穩定度很高,速度也不賴。 Rails .12 是今年才出的,REST 加上 has_many :through 實在非常的吸引人,不過有 benchmark 表示 Rails 1.2 在速度上輸給 Rails 1.1,不過也沒輸太多。所以綜觀起來,要使用 Rails 1.1 或是 1.2 是要看你的需求而定的,沒有一定要使用那個版本的建議。

我現在手邊的 Project 都是用 1.1 ,而且絕大多數已經上線在跑的,短期間不太可能轉換到 1.2 。但是我一定會花很多時間在了解 1.2 的情況。也就是說,我希望能夠在我的機器上面裝 1.1 跟 1.2,有沒有辦法達成呢?

當然有,這很基本!!!
首先,你的 gem 已經安裝了你所要求的 Rails Version,像是我希望在我的機器上面可以自由使用 1.1 或是 1.2 的 Rails,所以我的 gem 安裝情況就是

rails (1.2.3, 1.1.6)
Web-application framework with template engine, control-flow layer,
and ORM.


問題來了,要怎麼一次安裝兩個以上的 version 呢?假設你的 Rails 已經安裝了 1.1.6 ,可是又要安裝 1.2.3 ,就這樣打吧。
sudo gem install -v=1.2.3 rails
當然,如果你想安裝的是最新的 Rails Release,那麼這樣也是可以的
sudo gem i rails
如此就可以在同一台機器上面安裝不同 version 的 rails 。

確定了你的 gem 已經安裝了多個 version,我們必須了較 freeze code 的概念,很多時候我們的某個 Project 的 code 是在某個版本的 rails 開發的。當rails 出了新的 release,原本run 好好的 code 就可能出現一堆 error,所以我們必須要在這個 Project 也包入 Rails 這個 version 的 code。使用方式如下

rake rails:freeze:edge TAG=rel_1-1-6
rake rails:freeze:edge TAG=rel_1-2-3

顧名思義,你可以再 TAG 下面指定你要的 Rails Version,這個指令會把 Rails 放入 vender/rails/ 底下,以後伺服器執行前,他都會去這個資料夾尋找,如果有 vender/rails/ 的資料夾,他就不會使用系統預設的 Rails version ,而是使用已經包在這個 Rails 資料夾的 version。

當然,如果我們將已經包好的 Rails Version 解除,重新使用系統預設的 Rails Version,就這樣打即可

rake rails:unfreeze

他做的事情其實就只是 rm -fr vender/rails/ 資料夾而已。

最後一點,如果你不確定你的 Rails Package 到底使用那個 Version 的 Rails ,你可以打入

ruby script/about

他會跟你講的一清二楚的。

Representational State Transfer[REST]


http://en.wikipedia.org/wiki/Representational_State_TransferFrom Wikipedia, the free encyclopedia

Representational State Transfer (REST) is a style of software architecture for distributed hypermedia systems such as the World Wide Web. The term originated in a doctoral dissertation about the web written in 2000 by Roy Fielding, one of the principal authors of the HTTP protocol specification, and has quickly passed into widespread use in the networking community.

“ Representational State Transfer is intended to evoke an image of how a well-designed Web application behaves: a network of web pages (a virtual state-machine), where the user progresses through an application by selecting links (state transitions), resulting in the next page (representing the next state of the application) being transferred to the user and rendered for their use. ”
— Dr. Roy Fielding, Architectural Styles and the Design of Network-based Software Architectures

REST strictly refers to a collection of architectural principles (described below). The term is also often used in a loose sense to describe any simple interface that transmits domain-specific data over HTTP without an additional messaging layer such as SOAP or session tracking via HTTP cookies. These two meanings can conflict as well as overlap. It is possible to design any large software system in accordance with Fielding's REST architectural style without using the HTTP protocol and without interacting with the world wide web. It is also possible to design simple XML+HTTP interfaces that do not conform to REST principles, and instead follow a Remote Procedure Call (RPC) model. The two different uses of the term REST cause some confusion in technical discussions.

Systems that follow Fielding's REST principles are often referred to as RESTful; REST's most zealous advocates call themselves RESTafarians.

Contents
1 RESTful Example: The World Wide Web
2 Principles
3 REST's Central Principle: Resources
4 Claimed Benefits
5 REST versus RPC
5.1 Example
5.2 Uniform Interfaces in REST and RPC
6 Public implementations
7 Outside of the Web
8 References
9 See also
10 External links



RESTful Example: The World Wide Web
The Web is the key example of existing RESTful design. Much of it conforms or can be made to conform to REST principles. The Web consists of the HyperText Transfer Protocol (HTTP), content types including the HyperText Markup Language (HTML), and other Internet technologies such as the Domain Name Service (DNS).

HTML can include javascript and applets to support code-on-demand, and has implicit support for hyperlinks.

HTTP has a uniform interface for accessing resources which consists of URIs, methods, status codes, headers, and content distinguished by mime type.

The most important HTTP methods are PUT, GET, POST and DELETE. These are often compared with the CREATE, READ, UPDATE, DELETE (CRUD) operations associated with database technologies. An analogy can also be made to cut and paste: GET is analogous to READ or COPY, PUT to CREATE or PASTE OVER, POST to UPDATE or PASTE AFTER, and DELETE to DELETE or CUT. The CRUD verbs are designed to operate on atomic data within the context of a database transaction. REST is designed around the atomic transfer of more complex state, such as can be seen in paste of a structured document from one application to another.

HTTP separates the notions of a web server and a web browser. This allows the implementation of each to vary from the other based on the client/server principle. When used RESTfully, HTTP is Stateless. Each message contains all the information necessary to understand the request when combined with state at the resource. As a result, neither the client nor the server needs to remember any communication-state between messages. Any state retained by the server must be modelled as a resource.

The statelessness constraint can be violated in HTTP using cookies to maintain sessions. Fielding notes the risks of privacy leaks and security complications that often arise through the use of cookies, and the confusions and bugs that can result from interactions between cookies and the "back" button in a browser.

HTTP provides mechanisms to control caching, and permits a conversation between web browser and web cache to occur using the same mechanisms as between web browser and web server. No layer can "see" beyond the conversation it is immediately involved with.


Principles
REST's proponents argue that the Web enjoyed the scalability and growth that it has had as a direct result of a few key design principles:

Application state and functionality are divided into resources
Every resource is uniquely addressable using a universal syntax for use in hypermedia links
All resources share a uniform interface for the transfer of state between client and resource, consisting of
A constrained set of well-defined operations
A constrained set of content types, optionally supporting code-on-demand
A protocol that is:
Client/Server
Stateless
Cacheable
Layered
REST's client-server separation of concerns simplifies component implementation, reduces the complexity of connector semantics, improves the effectiveness of performance tuning, and increases the scalability of pure server components. Layered system constraints allow intermediaries--proxies, gateways, and firewalls--to be introduced at various points in the communication without changing the interfaces between components, thus allowing them to assist in communication translation or improve performance via large-scale, shared caching.

REST enables intermediate processing by constraining messages to be self-descriptive: interaction is stateless between requests, standard methods and media types are used to indicate semantics and exchange information, and responses explicitly indicate cacheability.[citation needed]

—Roy Fielding


REST's Central Principle: Resources
An important concept in REST is the existence of resources (sources of specific information), each of which can be referred to using a global identifier (a URI). In order to manipulate these resources, components of the network (clients and servers) communicate via a standardized interface (e.g. HTTP) and exchange representations of these resources (the actual documents conveying the information). For example, a resource that is a circle may accept and return a representation that specifies a centre point and radius, formatted in SVG, but may also accept and return a representation that specifies any three distinct points along the curve as a comma-separated list.

Any number of connectors (e.g., clients, servers, caches, tunnels, etc.) can mediate the request, but each does so without "seeing past" its own request (referred to as "layering", another constraint of REST and a common principle in many other parts of information and networking architecture). Thus an application can interact with a resource by knowing two things: the identifier of the resource, and the action required – it does not need to know whether there are caches, proxies, gateways, firewalls, tunnels, or anything else between it and the server actually holding the information. The application does, however, need to understand the format of the information (representation) returned, which is typically an HTML or XML document of some kind, although it may be an image or any other content.


Claimed Benefits
REST advocates claim that REST:

Provides improved response times and server loading characteristics due to support for caching
Improves server scalability by reducing the need to maintain communication state. This means that different servers can be used to handle initial and subsequent requests
Requires less client-side software to be written than other approaches, because a single browser can access any application and any resource
Depends less on vendor software than mechanisms that layer additional messaging frameworks on top of HTTP
Provides equivalent functionality when compared to alternative approaches to communication
Does not require a separate resource discovery mechanism, due to the use of hyperlinks in content
Provides better long-term compatibility and evolvability characteristics than RPC. This is due to:
The capability of document types such as HTML to evolve without breaking backwards- or forwards- compatibility, and
The ability of resources to add support for new content types as they are defined without dropping or reducing support for older content types.
REST detractors note the lack of tool support and the scarcity of truly RESTful applications deployed on the web of today. Some claim that REST is applicable to GET, but unproven for other state transfer operations such as PUT. Fielding points out in his thesis that the REST architecture was designed specifically for massive scale hypermedia distribution, and not as a one size fits all architectural style. Indeed what characterizes REST is the constraints that it imposes on a REST based system. POST is often considered the only necessary client-to-server state transfer operation, and is treated as a mechanism to tunnel arbitrary method invocations across HTTP.

Some REST systems have been deployed and gained tools support such as WebDAV which uses not only GET and POST, but also established HTTP headers like HEAD, DELETE, PUT as well as WebDAV-specific HTTP methods: PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, and UNLOCK.

One common stumbling block in the dialog on Claimed Benefits is focusing too much on web browser support for REST. Gateways, caching servers, proxies, and other REST connectors are the critical components for system design and REST.


REST versus RPC
REST: Resources - Commands are defined in simple terms: resources to be retrieved, stored / get, set - difficult to do many joins

RPC: Commands - Commands are defined in methods with varying complexity: depending on "standard" - easier (?) to hide complex things behind a method

REST: Nouns - Exchanging resources and concepts

RPC: Verbs - Exchanging methods





REST Triangle of nouns, verbs, and content types.A RESTful web application requires a different design approach from an RPC (Remote procedure call) application. An RPC application is exposed as one or more network objects, each with an often unique set of functions that can be invoked. Before a client communicates with the application it must have knowledge of the object identity in order to locate it and must also have knowledge of the object type in order to communicate with it.

RESTful design constrains the aspects of a resource that define its interface (the verbs and content types). This leads to the definition of fewer types on the network than an RPC-based application but more resource identifiers (nouns). REST design seeks to define a set of resources that clients can interact with uniformly, and to provide hyperlinks between resources that clients can navigate without requiring knowledge of the whole resource set. Server-provided forms can also be used in a RESTful environment to describe how clients should construct a URL in order to navigate to a particular resource.


Example
An RPC application might define operations such as the following:

getUser()
addUser()
removeUser()
updateUser()
getLocation()
addLocation()
removeLocation()
updateLocation()
listUsers()
listLocations()
findLocation()
findUser()
Client code to access this application may look something like this:

exampleAppObject = new ExampleApp("example.com:1234")
exampleAppObject.getUser()
With REST, on the other hand, the emphasis is on the diversity of resources, or nouns; for example, a REST application might define the following resources

http://example.com/users/
http://example.com/users/{user} (one for each user)
http://example.com/findUserForm
http://example.com/locations/
http://example.com/locations/{location} (one for each location)
http://example.com/findLocationForm
Client code to access this application may look something like this:

userResource = new Resource("http://example.com/users/001")
userResource.delete()
Each resource has its own identifier noun. Clients start at a single resource such as the user resource that represents themselves, and navigate to location resources and other user resources. Clients work with each resource through standard operations, such as GET to download a copy of the resource's representation, PUT to paste a changed copy over the top of the original, or DELETE to remove the data or state associated with the resource. POST is sometimes used interchangeably with PUT, but can also be seen as a "paste after" rather than a "paste over" request. POST is generally used for actions with side-effects, such as requesting the creation of a purchase order, or adding some data to a collection. Note how each object has its own URL and can easily be cached, copied, and bookmarked.


Uniform Interfaces in REST and RPC
The uniform interface allows clients to access data from a range of resources without special code to deal with each one, so long as it is actually uniform. The content returned from a user resource could be the globally standard and RESTful HTML, a less RESTful industry standard representation such as UserML, or an unRESTful application-specific data format. Which content is returned can be negotiated at request time. The content could even be a combination of these representations: HTML can be marked up with Microformats that have general or industry-specific appeal, and these microformats can be extended with application-specific information.

Uniform interfaces reduce the cost of client software by ensuring it is only written once, rather than once per application it has to deal with. Both REST and RPC designs may try to maximise the uniformity of the interface they expose by conforming to industry or global standards. In the RPC model these standards are primarily in the form of standard type definitions and standard choreography. In REST it is primarily the choice of standard content types and verbs that controls uniformity.


Public implementations
It is possible to claim an enormous number of RESTful applications on the Web (just about everything accessible through an HTTP GET request or updateable through HTTP POST). Taken more narrowly, in its sense as an alternative to both Web Services generally and the RPC style specifically, REST can be found in a number of places on the public Web:

The 'blogosphere' — the universe of weblogs — is mostly REST-based, since it involves downloading XML files (in RSS, or Atom format) that contain lists of links to other resources;
The Atom Publishing Protocol for publishing to blogs is considered the canonical RESTful protocol;
Ruby on Rails 1.2 offers a REST model.
Various websites and web applications offer REST developer interfaces to data.
The size of the last category is very small (i.e., those that offer the Atom Publishing Protocol plus a handful of others).

There are examples of website interfaces that label themselves 'REST', but are, in fact, using HTTP to tunnel function calls or which offer a 'POX/HTTP', (Plain Old XML over HTTP) endpoint. These interfaces do not intentionally respect REST's architectural constraints. Some have been called Accidentally RESTful, by a REST expert. The 'accident' of RESTfulness occurs primarily when standalone GETs are used, i.e. when navigating through many GETs in hypermedia style is not supported, and when those GETs simply retrieve data and do not change it.


Outside of the Web
Just as much of the web can be seen as RESTful or nearly-RESTful, a number of existing protocols and architectures have RESTful characteristics. Software that may interact with a number of different kinds of objects or devices can do so by virtue of a uniform, agreed interface. Many of these uniform interfaces follow document-oriented REST patterns rather than object-oriented patterns [should expand on and thus clarify this distinction]:

Modbus is a protocol that allows memory ranges within PLCs to be addressed. Ranges can be written and read effectively as PUT and GET operations.
Java Beans and other systems that perform property-based editing follow the PUT and GET model of the REST architectural style. Rather than write object-specific editor code, the code is written once and can interact with various object types. Resources in this model are defined by the combination of an object identifier and a property name.
Document-oriented SOA has several RESTful characteristics
Desktop cut and paste is closely related to REST principles. Cursor positions and highlighted selections in the source and target applications define application state or resources. They are operated on by the small set of verbs CUT, COPY and PASTE. Once cut or copied, data is retained in a number of forms so that, when pasted, the richest format understood by both applications is transferred.

References
Roy T. Fielding, Richard N. Taylor, "Principled design of the modern Web architecture", ACM Transactions on Internet Technology (TOIT), Volume 2, Issue 2 (May 2002), ACM Press, ISSN:1533-5399
Roy T. Fielding, "Architectural Styles and the Design of Network-based Software Architectures", PhD thesis, UC Irvine, 2000, html
M zur Muehlen, JV Nickerson, Keith D Swenson, "Developing Web Services Choreography Standards-The Case of REST vs. SOAP", Decision Support Systems. Vol. 40, no. 1, pp. 9-29. July 2005, ISSN:0167-9236 PDF
R. Khare, R.N. Taylor, "Extending the Representational State Transfer (REST) architectural style for decentralized systems", 26th International Conference on Software Engineering, 2004. ICSE 2004. Proceedings. 23-28 May 2004, ISSN: 0270-5257, ISBN 0-7695-2163-0
Jay M. Tenenbaum, Rohit Khare, "Business Services Networks: delivering the promises of B2B", Proceedings of the IEEE EEE05 international workshop on Business services networks, 2005,
Jonathan Robie, "An XQuery Servlet for RESTful Data Services", Proceedings of XML 2006, Boston, Massachusetts, December 2006. html
Paul Prescod, "Second Generation Web Services", O'Reilly's XML.com website, February 06, 2002, html
Ian Jacobs, Norman Walsh, eds. "Architecture of the World Wide Web, Volume One", W3C, html

See also
Plain Old XML
Web service
List of Web service markup languages
Service-oriented architecture
Web operating system
RSSBus

External links
RESTwiki: a short summary of REST
Belgian Java User Group video presentation on REST - The Better Web Services Model by Stefan Tilkov
rest-discuss Yahoo! Group
Paul Prescod's REST Resources
"What is SOA and REST Web services ..."
Constructing or Traversing URIs? This article discusses what the "hypermedia as engine of application state" constraint means.
Building Web Services the REST Way
How I explained REST to my wife...
The REST dialogues
Restlet - Lightweight REST framework for Java
TRYNT - Creative Commons style REST Service Library
REST Search Engine A specialized search engine based on Google Co-op platform
REST for the Rest of Us
MindTouch Dream REST-based distributed application framework developed for Mono/.NET
Mixed reaction to JSR-311
Retrieved from "http://en.wikipedia.org/wiki/Representational_State_Transfer"
Categories: Wikipedia articles needing copy edit from January 2007 | All articles needing copy edit | Wikipedia articles needing style editing | Software architecture

Ruby Msn implement

http://zerobase.jp/blog/entry-213.html
#send_msg.rb


require 'msnm'

MSNMessenger = Net::InstantMessaging::MSNMessenger

class SendMsgHandler < MSNMessenger::SessionHandler; end

class SendMsgHandlerFactory # Factory
def create( msnm, session )
SendMsgHandler.new( msnm, session )
end
end

USERID = 'test@msn.com'
PASSWD = 'testpassword'

msnm = MSNMessenger.new( USERID, PASSWD, SendMsgHandlerFactory.new)
msnm.ns.synchronize
msnm.ns.online
rs = msnm.call('account@msn.com')
rs.queue_message('test send msg! successfully! ')
msnm.logout


#msnm.rb

=begin


= msnm.rb version 0.3.0 2001/11/18 - 2005/01/28

Copyright (c) 2001,2002,2005 ZEROBASE, Inc. http://zerobase.jp/

This program is free software. You can re-distribute and/or
modify this program under the same terms as Ruby itself,
Ruby Distribute License or GNU General Public License.


== What is This Module?

This module provides the framework for instant messaging(IM).


== How to Use This Module?

See template methods in Net::InstantMessaging::MSNMessenger::SessionHandler.
You do not need to implement a subclass of SessionHandler.
You need to implement template methods in your class and
register an object of its class as an event handler object.

=end



require 'socket'
require 'thread'
require 'md5'
require 'forwardable'
require 'net/https'


module Net


module InstantMessaging


class MSNMessenger


CRLF = "\r\n"


extend Forwardable


def_delegators( '@ns', 'online' )
def_delegators( '@ns', 'offline' )
def_delegators( '@ns', 'user_nick' )
def_delegators( '@ns', 'set_nick' )
def_delegators( '@ns', 'synchronize' )
def_delegators( '@ns', 'list' )
def_delegators( '@ns', 'allow_mode' )
def_delegators( '@ns', 'privacy_mode' )
def_delegators( '@ns', 'add_user' )
def_delegators( '@ns', 'remove_user' )


# quit service
def logout
@session_handlers.each { |sh| sh.session_out }
@ns.offline
@ns.logout
end


# listening... (wait for service)
def listen
@main_thread = Thread.current
Thread.stop
end


# stop listening
def wakeup
@main_thread.wakeup # quit listening
end


# call user and start new session
def call( *target_users )
sess = @ns.new_switchboard
ans_users = sess.call( *target_users )
sh = @session_handler_factory.create( self, sess )
@session_handlers.push sh
if ans_users.size > 0
sh
else
sh.session_out
nil
end
end


# called from other
# (event handler method)
def on_ringing( sess, session_id, ss_cookie, calling_id, calling_nick )
handler = @session_handler_factory.create( self, sess )
@session_handlers.push handler
Thread.start { sess.answer session_id, ss_cookie }
handler
end


# added to other's contact list
# (event handler method)
def on_added( cl_type, cl_serial, cl_userid, cl_nick )
if cl_type == :LST_REVERSE || cl_type == :LST_ALLOW
Thread.start { @ns.add_user :LST_ALLOW, cl_userid, cl_nick }
Thread.start { @ns.add_user :LST_FORWARD, cl_userid, cl_nick }
end
end


def wall( msg, hdr = nil )
@session_handlers.each { |sh| sh.queue_message( msg, hdr ) }
end


# a SessionHandler object unregister itself by calling this method
# (callback method)
def on_session_out( sess )
@session_handlers.delete sess
end


def initialize( user_id, passwd, session_handler_factory )
@user_id = user_id
@session_handler_factory = session_handler_factory
@ds = DispatchServer.new( user_id )
notification_handler = self
@ns = @ds.login( passwd, notification_handler )
@session_handlers = []
@main_thread = nil
end


attr_reader :ds, :ns



class SessionHandler # Strategy

TIMEOUT = 5

# <template methods>
#
# private
#
# def handle_message( peer_id, peer_nick, msg_header, msg_body )
#
# public (event handlers)
#
# def on_ans
# def on_join( peer_id, peer_nick )
# def on_bye( peer_id )
# def on_out
#
# (not implemented yet)
# def on_ack
# def on_nack
#
# </template methods>


public


def session_out
@msg_recv_queue.push nil
@recv_t.join
@send_t.join
@msnm.on_session_out( self )
@sess.disconnect
end


def on_out
session_out
end


def on_bye( peer_id )
if @sess.session_users.size.zero?
session_out
end
end


def queue_message( msg_body, msg_header = nil )
sleep(1) # modified by David
@msg_send_queue.push [ msg_body, msg_header ]
end


def on_message( peer_id, peer_nick, msg )
@msg_recv_queue.push [ peer_id, peer_nick, msg ]
end


private


def send_loop
while senddata = @msg_send_queue.pop
@tick = Time.now
content, header = senddata
@sess.send_message2( content, header )
end
end


def recv_loop
while recvdata = @msg_recv_queue.pop
@tick = Time.now
peer_id, peer_nick, msg = recvdata
msg_header, msg_body = msg.split( /\r\n\r\n/um, 2 )
handle_message( peer_id, peer_nick, msg_header, msg_body )
end
@msg_send_queue.push nil
end


def initialize( msnm, sess )
@msnm = msnm
@sess = sess
@sess.set_event_handler self
@msg_recv_queue = SizedQueue.new(1000)
@msg_send_queue = SizedQueue.new(1000)
@send_t = Thread.start { send_loop }
@recv_t = Thread.start { recv_loop }
@tick = Time.now
@killer = Thread.start do
while true
sleep 1
if Time.now - @tick > TIMEOUT
session_out
Thread.exit
end
end
end
end


end # class SessionHandler



class User


def to_str
format '%s <%s>', @nick, user_id
end


alias :to_s :to_str


def initialize( user_id, nick )
@user_id = user_id
@nick = nick
end


attr_accessor :user_id, :nick


end # class User



class ProtocolHandler


# constants


XLN_STATUS = {
:XLN_ONLINE => 'NLN', # Online
:XLN_OFFLINE => 'FLN', # Offline
:XLN_LOGIN => 'ILN', # Login(online)
:XLN_HIDDEN => 'HDN', # Hidden/Inisible
:XLN_BUSY => 'BSY', # Busy
:XLN_IDLE => 'IDL', # Idle
:XLN_BACK => 'BRD', # Be Right Back
:XLN_AWAY => 'AWY', # Away From Computer
:XLN_PHONE => 'PHN', # On The Phone
:XLN_LUNCH => 'LUN', # Out To Lunch
}


LST_TYPE = {
:LST_FORWARD => 'FL', # Forward List
:LST_REVERSE => 'RL', # Reverse List
:LST_ALLOW => 'AL', # Allow List
:LST_BLOCK => 'BL', # Block List
}


ACK_MODE = {
:ACK_UNACKNOWLEDGE => 'U',
:ACK_NEGATIVE => 'N',
:ACK_ACKNOWLEDGE => 'A',
}


GTC_MODE = {
:GTC_NOASK => 'N',
:GTC_ASK => 'A',
}


BLP_MODE = {
:BLP_ALLOW => 'AL',
:BLP_BLOCK => 'BL',
}


def url_escape(str) # from cgi-lib.rb
return nil if str.nil?
str.gsub(/[^a-zA-Z0-9_\-.]/n){ sprintf("%%%02X", $&.unpack("C")[0]) }
end

def url_unescape(str) # from cgi-lib.rb
return nil if str.nil?
str.gsub(/\+/, ' ').gsub(/%([0-9a-fA-F]{2})/){ [$1.hex].pack("c") }
end



# transaction/session queue


def pop_transaction_queue( trid )
queue = @transaction_queues[trid]
queue.pop if queue
end


def delete_transaction_queue( trid )
queue = @transaction_queues[trid]
if queue.nil?
raise InvalidTransactionID,
format( 'invalid TransactionID - %d', trid )
end
@transaction_queues.delete( trid )
end


def pop_session_queue( peer_id )
@session_queues[peer_id].pop
end


def delete_session_queue( peer_id )
queue = @session_queues[peer_id]
if queue.nil?
raise InvalidSessionID,
format( 'invalid PeerID - %s', peer_id )
end
@session_queues.delete( peer_id )
end



def wait_transaction( trid )
ret = pop_transaction_queue( trid )
delete_transaction_queue( trid )
ret
end


def wait_session( peer_id )
ret = pop_session_queue( peer_id )
delete_session_queue( peer_id )
ret
end


# protocol command


def ver
send_command( 'VER %d %s'+CRLF, PROTOCOL_VER.join(' ') )
end


def cvr
locale = '0x0411'
ostype = 'win'
osver = '6.00'
osarch = 'i386'
cliname = 'MSNMSGR'
cliver = '6.2.0137'
send_command( 'CVR %s %s %s %s %s %s %s MSMSGS %s'+CRLF,
locale, ostype, osver, osarch, cliname, cliver, @user_id )
end


def inf
send_command( 'INF %d'+CRLF )
end


def usr_i
send_command( 'USR %d TWN I %s'+CRLF, @user_id )
end


def usr_s( ticket )
send_command( 'USR %d TWN S %s'+CRLF, ticket )
end


def rea( user_id, nickname )
return if nickname.nil? | nickname.empty?
nn = url_escape( nickname )
send_command( 'REA %d %s %s'+CRLF, user_id, nn )
end


def syn( serial )
send_command( 'SYN %d %d'+CRLF, serial )
end


def gtc( mode )
send_command( 'GTC %d %s'+CRLF, GTC_MODE[mode] )
end


def blp( mode )
send_command( 'BLP %d %s'+CRLF, BLP_MODE[mode] )
end


def lst( list_type )
send_command( 'LST %d %s'+CRLF, LST_TYPE[list_type] )
end


def add( list_type, peer_id, peer_nick )
send_command( 'ADD %d %s %s %s'+CRLF, LST_TYPE[list_type],
peer_id, url_escape( peer_nick ) )
end


def rem( list_type, peer_id )
send_command( 'REM %d %s %s'+CRLF, LST_TYPE[list_type], peer_id )
end


def chg( status )
send_command( 'CHG %d %s'+CRLF, XLN_STATUS[status] )
end


def xfr_sb
send_command( 'XFR %d SB'+CRLF )
end


def usr_sb( cookie )
send_command( 'USR %d %s %s'+CRLF, @user_id, cookie )
end


def cal( peer_id )
send_command( 'CAL %d %s'+CRLF, peer_id )
end


def ans( session_id, ss_cookie )
send_command( 'ANS %d %s %s %d'+CRLF, @user_id, ss_cookie, session_id )
end


def msg( content, header = nil, ack_mode = :ACK_UNACKNOWLEDGE )
mesg = 'MIME-Version: 1.0' + CRLF
if header
mesg += header.to_s + CRLF
else
mesg += 'Content-Type: text/plain; charset=UTF-8' + CRLF
end
if content
mesg += CRLF + content
end
send_command( 'MSG %d %s %d'+CRLF+'%s',
ACK_MODE[ack_mode], mesg.size, mesg )
end


def out
sock_write 'OUT'+CRLF
end


# control


def disconnect
out
if Thread.current == @sock_parser_thread
terminate
else
@sock_parser_thread.join
end
end


def terminate
if self.instance_variables.include?( '@notification_server' )
@notification_server.delete_switchboard( self )
end
@sock.close unless @sock.closed?
end


def self.logfile=( logfile )
@@logfile = logfile
end


def set_event_handler( obj )
@eh_m.synchronize { @event_handler = obj }
end


attr_reader :user_id
attr_reader :user_nick


private


@@logfile = nil


PROTOCOL_VER = [ 'MSNP9', 'CVR0' ]
MSN_MESSENGER_SERVER = 'messenger.hotmail.com'
MSN_MESSENGER_PORT = '1863'


def initialize( user_id, host, port = MSN_MESSENGER_PORT )

@user_id = user_id
@user_nick = nil

@host = host
@port = port
@sock = TCPSocket.open( @host, @port )

@transaction_id = 0
# Transaction ID : initial value = 1 (incremented)

@default_transaction_queue = SizedQueue.new(1000)
@transaction_queues = Hash.new(@default_transaction_queue)
# @transaction_queues[@transaction_id] = SizedQueue.new(1000)

@default_session_queue = SizedQueue.new(1000)
@session_queues = Hash.new(@default_session_queue)

@event_handler = nil
@eh_m = Mutex.new

@sock_parser_thread = Thread.start { parse_sock_input }

end


def new_transaction_queue( new_trid = nil )
new_trid ||= (@transaction_id += 1)
@transaction_queues[new_trid] = SizedQueue.new(1000)
new_trid
end


def push_transaction_queue( trid, val )
queue = @transaction_queues[trid]
queue.push val if queue
end


def push_session_queue( peer_id, val )
@session_queues[peer_id].push val
end


def send_command(format_string, *params)
trid = new_transaction_queue
sock_write sprintf( format_string, trid, *params )
trid
end


# regular expression for parsing


RE_VER = /^VER (\d+) (.+)#{CRLF}/u
RE_INF = /^INF (\d+) (.+)#{CRLF}/u
RE_CVR = /^CVR (\d+) (.+)#{CRLF}/u
RE_XFR_NS = /^XFR (\d+) NS ([^:]+)(?::(\d+))? 0 (.+)#{CRLF}/u
RE_USR_NS_S = /^USR (\d+) (.+) S (.+)#{CRLF}/u
RE_USR_NS_OK = /^USR (\d+) OK (.+) (.+)#{CRLF}/u
RE_CHG = /^CHG (\d+) (.+)#{CRLF}/u
RE_XFR_SB = /^XFR (\d+) SB ([^:]+)(?::(\d+))? CKI (.+)#{CRLF}/u
RE_USR_SB = /^USR (\d+) OK (.+) (.+)#{CRLF}/u
RE_CAL = /^CAL (\d+) (.+) (\d+)#{CRLF}/u
RE_RNG = /^RNG (\d+) ([^:]+)(?::(\d+))? CKI (.+) (.+) (.+)#{CRLF}/u
RE_IRO = /^IRO (\d+) (\d+) (\d+) (.+) (.+)#{CRLF}/u
RE_ANS = /^ANS (\d+) OK#{CRLF}/u
RE_JOI = /^JOI (.+) (.+)#{CRLF}/u
RE_BYE = /^BYE (.+)#{CRLF}/u
RE_OUT = /^OUT(?: (.+))?#{CRLF}/u
RE_CHL = /^CHL 0 (.+)#{CRLF}/
RE_MSG = /^MSG (.+) (.+) (\d+)#{CRLF}/u
RE_ACK = /^(ACK|NAK) (\d+)#{CRLF}/u
RE_REA = /^REA (\d+) (\d+) (.+) (.+)#{CRLF}/u
RE_SYN = /^SYN (\d+) (\d+)#{CRLF}/u
RE_GTC = /^GTC (\d+) (\d+) (A|N)#{CRLF}/u
RE_BLP = /^BLP (\d+) (\d+) (AL|BL)#{CRLF}/u
RE_XLN = /^(.LN) (\d+) ([A-Z]{3})(?: (.+) (.+))?.*#{CRLF}/u
RE_LST = /^LST (\d+) (.+) (\d+) (\d+) (\d+)(?: ([^\s]+) (.+))?#{CRLF}/u
RE_ADD = /^ADD (\d+) (.+) (\d+) (.+) (.+)#{CRLF}/u
RE_REM = /^REM (\d+) (.+) (\d+) (.+)#{CRLF}/u
RE_ERR = /^(\d{3})(?:\s+(\d+)?(?:\s+(.+)?)?)?#{CRLF}/u


def parse_sock_input # DO NOT CALL DIRECTLY (called from #initialize)

while res = sock_gets

case res

when RE_VER

trid = $1.to_i
dialects = $2
push_transaction_queue trid, dialects

when RE_INF

trid = $1.to_i
security_package = $2
push_transaction_queue trid, security_package

when RE_CVR

trid = $1.to_i
ignore = $2
push_transaction_queue trid, ignore

when RE_XFR_NS

trid = $1.to_i
ns_host = $2
ns_port = $3
push_transaction_queue trid, [ ns_host, ns_port ]

when RE_USR_NS_S

trid = $1.to_i
security_package = $2
challenge_str = $3
push_transaction_queue trid, [ security_package, challenge_str ]

when RE_USR_NS_OK

trid = $1.to_i
user_id = $2
@user_nick = url_unescape( $3 )
push_transaction_queue trid, [ user_id, @user_nick ]

when RE_CHG

trid = $1.to_i
status = $2
push_transaction_queue trid, status

when RE_XFR_SB

trid = $1.to_i
ss_host = $2
ss_port = $3
ss_cookie = $4
push_transaction_queue trid, [ ss_host, ss_port, ss_cookie ]

when RE_CAL

trid = $1.to_i
status = $2
session_id = $3.to_i
push_transaction_queue trid, [ status, session_id ]

when RE_RNG

session_id = $1.to_i
ss_host = $2
ss_port = $3
ss_cookie = $4
calling_id = $5
calling_nick = url_unescape( $6 )

ss = add_switchboard( ss_host, ss_port, ss_cookie )
if @event_handler.respond_to? :on_ringing
@event_handler.on_ringing( ss, session_id, ss_cookie, calling_id, calling_nick )
end

when RE_IRO

trid = $1.to_i
cl_entrynum = $2.to_i
cl_size = $3.to_i
cl_userid = $4
cl_nick = url_unescape( $5 )
push_transaction_queue trid, [ cl_entrynum, cl_size,
cl_userid, cl_nick ]

when RE_ANS

trid = $1.to_i
push_transaction_queue trid, nil

if @event_handler.respond_to? :on_ans
@event_handler.on_ans
end

when RE_JOI

peer_id = $1
peer_nick = url_unescape( $2 )
push_session_queue peer_id, [ :SS_JOI, peer_id, peer_nick ]

user_join peer_id, peer_nick

if @event_handler.respond_to? :on_join
@event_handler.on_join peer_id, peer_nick
end

when RE_BYE

peer_id = $1
push_session_queue peer_id, [ :SS_BYE ]

user_bye peer_id

if @event_handler.respond_to? :on_bye
@event_handler.on_bye peer_id
end

when RE_OUT

status = $1
push_transaction_queue nil, status

if @event_handler.respond_to? :on_out
@event_handler.on_out
end

terminate

when RE_CHL

ch_str = $1
ch_res = MD5.new( ch_str + 'Q1P7W2E4J9R8U3S5' ).hexdigest # challenge response
send_command( 'QRY %d msmsgs@msnmsgr.com 32'+CRLF+'%s', ch_res )

when RE_MSG

peer_id = $1
peer_nick = url_unescape( $2 )
msg_len = $3.to_i
msg = sock_read msg_len
push_session_queue peer_id, [ :SS_MSG, peer_id, peer_nick, msg ]

if @event_handler.respond_to? :on_message
@event_handler.on_message peer_id, peer_nick, msg
end

when RE_ACK

status = $1
trid = $2.to_i
push_transaction_queue trid, status

when RE_REA

trid = $1.to_i
serial = $2.to_i
userid = $3
nick = url_unescape( $4 )
push_transaction_queue trid, [ userid, nick ]

when RE_SYN

trid = $1.to_i
serial = $2.to_i
push_transaction_queue trid, serial

when RE_GTC

trid = $1.to_i
serial = $2.to_i
status = GTC_MODE.index($3)
push_transaction_queue trid, [ serial, status ]

when RE_BLP

trid = $1.to_i
serial = $2.to_i
status = BLP_MODE.index($3)
push_transaction_queue trid, [ serial, status ]

when RE_XLN

ntfn_type = $1
trid = $2.to_i
ntfn_stat = $3
peer_id = $4
peer_nick = url_unescape( $5 )
push_transaction_queue( trid,
[ ntfn_type, ntfn_stat, peer_id, peer_nick ] )

if @event_handler.respond_to? :on_status
@event_handler.on_status ntfn_stat, peer_id, peer_nick
end

when RE_LST

trid = $1.to_i
cl_type = LST_TYPE.index($2)
cl_serial = $3.to_i
cl_entrynum = $4.to_i
cl_size = $5.to_i
cl_userid = $6
cl_nick = url_unescape( $7 )
if cl_userid.nil?
push_transaction_queue trid, nil
else
push_transaction_queue( trid,
[ cl_type, cl_serial,
cl_entrynum, cl_size,
cl_userid, cl_nick ] )
end

when RE_ADD

trid = $1.to_i
cl_type = LST_TYPE.index($2)
cl_serial = $3.to_i
cl_userid = $4
cl_nick = url_unescape( $5 )
push_transaction_queue( trid, [ cl_type, cl_serial,
cl_userid, cl_nick ] )

if @event_handler.respond_to? :on_added
@event_handler.on_added( cl_type, cl_serial, cl_userid, cl_nick )
end

when RE_REM

trid = $1.to_i
cl_type = LST_TYPE.index($2)
cl_serial = $3.to_i
cl_userid = $4
push_transaction_queue( trid, [ cl_type, cl_serial, cl_userid ] )

if @event_handler.respond_to? :on_removed
@event_handler.on_removed( cl_type, cl_serial, cl_userid )
end

when RE_ERR
err_code = $1.to_i
trid = $2.to_i
err_desc = $3

err = MSNPError.new( err_code, err_desc )
push_transaction_queue( trid, err )

else

push_transaction_queue nil, res

end

end

# sock_gets == nil => EOF

terminate

end


# socket read/write


def sock_write( str )
begin
if $DEBUG
STDOUT.puts ">>> " + Time.now.strftime('%Y/%m/%d:%H:%M:%S ') +
self.class.name.split('::')[-1] +
format( "#<%s>\n%s\n", self.object_id, ( str || "(nil)" ) )
STDOUT.flush
end
@sock.write str
rescue
end
end


def sock_gets
str = nil
begin
str = @sock.gets
if $DEBUG
STDOUT.puts "<<< " + Time.now.strftime('%Y/%m/%d:%H:%M:%S ') +
self.class.name.split('::')[-1] +
format( "#<%s>\n%s\n", self.object_id, ( str || "(nil)" ) )
STDOUT.flush
end
rescue
end
str
end


def sock_read( len )
str = nil
begin
str = @sock.read( len )
if $DEBUG
STDOUT.puts "<<< " + Time.now.strftime('%Y/%m/%d:%H:%M:%S ') +
self.class.name.split('::')[-1] +
format( "#<%s>\n%s\n", self.object_id, ( str || "(nil)" ) )
STDOUT.flush
end
rescue
end
str
end


class InvalidTransactionID < StandardError
end

class InvalidSessionID < StandardError
end

class MSNPError
def initialize( code, desc )
@code = code
@desc = desc
end
attr_reader :code, :desc
def to_s
"code: <#{code}> desc: <#{desc}>"
end
def to_str
to_s
end
end

end # class ProtocolHandler



class NotificationServer < ProtocolHandler


def login( passwd )
wait_transaction( ver )
wait_transaction( cvr )
sec_pkg, ch_str = wait_transaction( usr_i )
# HTTP: Passport Nexus
https = Net::HTTP.new('nexus.passport.com',443)
https.use_ssl = true
resp = https.get('/rdr/pprdr.asp')
m = resp['passporturls'].match('DALogin=(.*?),')
loginurl = 'https://'+m[1]
uri = URI.split(loginurl)
# HTTP: Login Server
https = Net::HTTP.new(uri[2],443)
https.use_ssl = true
auth_str = 'Passport1.4 OrgVerb=GET,OrgURL=http%3A%2F%2Fmessenger%2Emsn%2Ecom,sign-in='+url_escape(@user_id)+',pwd='+passwd+','+ch_str
resp = https.get(uri[5], {'Authorization'=>auth_str, 'Host'=>uri[2]})
if resp['location']
uri = URI.split(resp['location'])
https = Net::HTTP.new(uri[2],443)
https.use_ssl = true
resp = https.get(uri[5]+'?'+uri[7], {'Authorization'=>auth_str,'Host'=>uri[2]})
end
m = resp['authentication-info'].match(/'(t=.*?)'/)
ticket = m[1]
# MSNP: login
wait_transaction( usr_s(ticket) )
end


def logout
@switchboard_sessions.each do |ss|
ss.disconnect
end
disconnect
end


def online
delete_transaction_queue( chg( :XLN_ONLINE ) )
end


def offline
delete_transaction_queue( chg( :XLN_OFFLINE ) )
end


def list( list_type = :LST_FORWARD )

contactlist = {}
trid = lst( list_type )

while cl_item = pop_transaction_queue( trid )

cl_type, cl_serial, cl_entrynum,
cl_size, cl_userid, cl_nick = cl_item

contactlist[cl_userid] = cl_nick

break if cl_size == cl_entrynum

end

delete_transaction_queue( trid )
contactlist

end


def add_user( list_type, peer_id, peer_nick )
wait_transaction( add( list_type, peer_id, peer_nick ) )
end


def remove_user( list_type, peer_id )
wait_transaction( rem( list_type, peer_id ) )
end


def set_nick( userid, nickname )
wait_transaction( rea( userid, nickname ) )
end


def synchronize( serial = 0 )
syn(serial)
end


def allow_mode( mode )
wait_transaction( gtc( mode ) )
end


def privacy_mode( mode )
wait_transaction( blp( mode ) )
end


def new_switchboard
ss_host, ss_port, ss_cookie = wait_transaction( xfr_sb )
ss = add_switchboard( ss_host, ss_port, ss_cookie )
ss.auth( ss_cookie )
ss
end


def delete_switchboard( ss )
@ss_m.synchronize { @switchboard_sessions.delete ss }
end


private


def add_switchboard( ss_host, ss_port, ss_cookie )
ss = SwitchboardServer.new( @user_id, ss_host, ss_port, self )
ss.set_event_handler @event_handler
@ss_m.synchronize { @switchboard_sessions.push ss }
ss
end


def initialize( user_id, host, port )
super
@switchboard_sessions = []
# @switchboard_sessions.push switchboard
@ss_m = Mutex.new
end


end # class NotificationServer



class DispatchServer < NotificationServer


def login( passwd, event_handler )
wait_transaction( ver )
wait_transaction( cvr )
ret = wait_transaction( usr_i )
ns_host, ns_port = ret
disconnect
ns = NotificationServer.new( @user_id, ns_host, ns_port )
ns.set_event_handler( event_handler )
ns.login( passwd )
return ns
end


private


DS_HOST = MSN_MESSENGER_SERVER # DispatchServer address
DS_PORT = MSN_MESSENGER_PORT # DispatchServer port


def initialize( user_id, host = DS_HOST, port = DS_PORT )
super
end


end # class DispatchServer



class SwitchboardServer < ProtocolHandler


def auth( cookie )
user_id, nick = wait_transaction( usr_sb(cookie) )
end


def answer( session_id, ss_cookie )

@su_m.synchronize {

@session_users = {}
trid = ans( session_id, ss_cookie )

while cl_item = pop_transaction_queue( trid )
cl_entrynum, cl_size, cl_userid, cl_nick = cl_item
@session_users[cl_userid] = cl_nick
end

delete_transaction_queue( trid )
@session_users

}

disconnect if session_users.size == 0

end


def call( peers )

cal_trids = {}
ans_users = []

peers.each do |peer_id|
trid = cal( peer_id )
cal_trids[ trid ] = peer_id
end

cal_trids.each do |trid,peer_id|
ret = wait_transaction( trid )
ans_users.push( peer_id ) unless ret.kind_of?( MSNPError )
end

return ans_users

end


def send_message( content, header = nil )
delete_transaction_queue( msg( content, header ) )
end


def send_message2( content, header = nil )
wait_transaction( msg( content, header, :ACK_ACKNOWLEDGE ) )
end


def session_users
@su_m.synchronize { @session_users.clone }
end


private


def user_join( peer_id, peer_nick )
@su_m.synchronize { @session_users[peer_id] = peer_nick }
end


def user_bye( peer_id )
@su_m.synchronize { @session_users.delete(peer_id) }
disconnect if session_users.size == 0
end


def initialize( user_id, host, port, notification_server )
super( user_id, host, port )
@notification_server = notification_server
@session_users = {}
# @session_users.push[user_id] = user_nick
@su_m = Mutex.new
end


end # class SwitchboardServer


end # class MSNMessenger



end # module InstantMessaging


end # module Net

JMETER 命令行运行及JTL的LOG查看

Jmeter full list of command-line options

Invoking JMeter as "jmeter -?" will print a list of all the command-line options. These are shown below.


-h, --help
print usage information and exit
-v, --version
print the version information and exit
-p, --propfile {argument}
the jmeter property file to use
-q, --addprop {argument}
additional property file(s)
-t, --testfile {argument}
the jmeter test(.jmx) file to run
-l, --logfile {argument}
the file to log samples to
-n, --nongui
run JMeter in nongui mode
-s, --server
run the JMeter server
-H, --proxyHost {argument}
Set a proxy server for JMeter to use
-P, --proxyPort {argument}
Set proxy server port for JMeter to use
-u, --username {argument}
Set username for proxy server that JMeter is to use
-a, --password {argument}
Set password for proxy server that JMeter is to use
-J, --jmeterproperty {argument}={value}
Define additional JMeter properties
-D, --systemproperty {argument}={value}
Define additional System properties
-S, --systemPropertyFile {filename}
a property file to be added as System properties
-L, --loglevel {argument}={value}
Define loglevel: [category=]level
e.g. jorphan=INFO or jmeter.util=DEBUG
-r, --runremote
Start remote servers from non-gui mode
-d, --homedir {argument}
the jmeter home directory to use

C:\jmeter\bin>jmeter -n -t test.jmx -l test.jtl
Created the tree successfully
Starting the test
Tidying up ...
... end of run

上面的jmx文件是用Jmeter控制台生成的。详见官方文档:http://wiki.apache.org/jakarta-jmeter/UserManual/BuildWebTest
命令行运行后生成jtl的LOG文件之后需要在Jmeter控制台里任意添加一个 Listener ,然后点击“Browse” 按钮,选择要查看的 jtl 文件。不过中文看到的"HTTP请求"这里的中文字乱码了。