保持健康

前一段时间体检,其中一项空腹血糖值达到了6.13mmol/L,超过了3.9-6.1的正常范围,医学上称之为空腹血糖受损,是指空腹血糖在≥6.1mmol/L-<7.0mmol/L之间,即既高于正常标准又低于糖尿病诊断标准之间的一种亚健康状态,空腹血糖受损还不是疾病,但已经有血糖调节异常,它是一种危险状态,很可能发展成糖尿病。另外我的血脂指标也不好,体重指数BMI值偏高,总的来说,健康已经出现了一些问题,给我敲响了警钟,虽然这些都是大家公认的城市病、富贵病。

由于工作性质,每天大量的时间都是坐着,很少活动,下班比较晚,回去也就懒得动了,加上饮食上高油脂、高热量、高糖份,健康不出问题才怪,其实近3年,每周我都要进行一次体育锻炼,是在健身房里跑步,但看来这个运动量远远不够,我现在除了每周固定的运动之外,已经拒绝高脂肪、高糖份的食品,餐饮结构以蔬果为主,配合一定的谷物主食,同时坚持每天晚上少吃,并在吃过饭后在小区内步行20分钟以上,希望通过坚持,把体重也减下来,相信在下次体检时能恢复正常。

前几天研究Java导出SPSS格式数据,在网上找了半天,找到了2个三方库,都叫SPSS Writer,一个在http://spss.pmstation.com,另一个在http://sourceforge.net/projects/spss-writer/,这两个包生成的.sav文件,都会产生一个Demo的列,原来pmstation的这个SPSS Writer是要收费的,估计买了License就不会生成Demo列,pmstation的这个SPSS Writer的这个包还有个问题,就是不支持中文,我用UTF-8的编码也不支持(发邮件给pmstation询问,也没有给我回复),sourceforge的到是支持中文,但是这个包没有源码也没有任何文档,总的来说这两个包都不理想,难道没有比较好的Java导出SPSS格式的三方包吗?

今日调查《关于阿里巴巴战略投资新浪微博》,欢迎各位参与,结果下期公布。

欢迎订阅我的微信公共帐号:“Laoer杂谈”,搜索微信号:laoertalk,或扫描页面右侧的二维码
新浪微博:@laoer
Twitter:@laoer

Oracle收购SUN之后

北京时间4月20日晚一条IT界的爆炸性消息传来–《甲骨文将以74亿美元收购Sun》,这个消息确实让人非常意外,因为在之前传出IBM和Sun之间的收购谈判破裂的消息,是Oracle突然接手?应该不是,估计SUN早已有卖掉的计划,只是看谁出价合理吧。

对于Java开发阵营来说,这条消息未必是坏事,毕竟Oracle手里已经有了BEA,在Java企业级应用里就是领头羊,而SUN本身就没有在Java身上赚到钱,相信应该可以更好的整合Java的企业级应用,另外Oracle本身已经使用了非常多的Java技术,两个公司之前也有比较多的合作。

这笔收购让人最担心的还是Mysql,去年SUN收购Mysql,就让人感觉颇为另类,现在Oracle收购SUN,真不知道Oracle会怎样处理Mysql,任其自然发展,还是直接消灭,我想做为广大支持开源的开发者,都不希望Mysql倒掉,更何况当今大多数的Web2.0公司都在使用Mysql,不乏像Facebook这样的大容量用户,如果Mysql没有了,“LAMP”里的M会变成什么?PostgreSQL?还是其他?

商人逐利,这是自古以来的道理,并购的背后,一定有商业利益的存在,收购方希望整合资源、扩大市场、增加利润,被收购方希望套现,一个愿打一个愿挨,无可厚非,但他们究竟会如何面对最终客户,我们只能拭目以待了。

Tomcat中request.getContextPath()值引发的问题

今天是生日,一大早上社区就发现了一个问题,是一个网友报的错误(http://bbs.laoer.com/main-read-15-ff808081205a54e001205d2565901041.html),我一看还真是,连接地址有错误,成了http://post.bbscs?action=re&bid=15&parentID=……,前面的服务器地址没有了,程序没有修改过,为什么会出现这样的问题呢,我看了一下代码,在BBSCSUtil.java里有个getActionMappingURL(String action, HttpServletRequest request)方法,其中要取request.getContextPath(),当应用根路径下运行的时候,request.getContextPath()的值为“/”,所以getActionMappingURL方法返回的值前面多了一个“/”,在Tomcat5.5中对于//uri的连接解析似乎不正确,而在用Resin时候却没有问题,看来在request.getContextPath()的问题上还是要多注意。

Java RSA研究

前几天我做了PHP RSA的测试,想想Java来做RSA应该更简单,就在网上搜了一下Java RSA,发现资料是有一些,但却不是很完整,也不是很严谨,都是转来转去,看来还是自己要测试一下,两个文件RSAUtil.java和EncryptException.java,注意,JDK里没有RSA的provider,所以我们要用第三方的provider,在http://www.bouncycastle.org/下载最新的包,加入工程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
import javax.crypto.Cipher;
import java.security.*;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.io.*;
import java.math.BigInteger;
 
public class RSAUtil {
 
    /**
     *   * 生成密钥对
     *   * @return KeyPair
     *   * @throws EncryptException
     */
    public static KeyPair generateKeyPair() throws EncryptException {
        try {
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA",
                    new org.bouncycastle.jce.provider.BouncyCastleProvider());
            final int KEY_SIZE = 1024;//没什么好说的了,这个值关系到块加密的大小,可以更改,但是不要太大,否则效率会低
            keyPairGen.initialize(KEY_SIZE, new SecureRandom());
            KeyPair keyPair = keyPairGen.genKeyPair();
            return keyPair;
        } catch (Exception e) {
            throw new EncryptException(e.getMessage());
        }
    }
 
    /**
     *   * 生成公钥
     *   * @param modulus
     *   * @param publicExponent
     *   * @return RSAPublicKey
     *   * @throws EncryptException
     */
    public static RSAPublicKey generateRSAPublicKey(byte[] modulus, byte[] publicExponent) throws EncryptException {
        KeyFactory keyFac = null;
        try {
            keyFac = KeyFactory.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider());
        } catch (NoSuchAlgorithmException ex) {
            throw new EncryptException(ex.getMessage());
        }
 
        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(publicExponent));
        try {
            return (RSAPublicKey) keyFac.generatePublic(pubKeySpec);
        } catch (InvalidKeySpecException ex) {
            throw new EncryptException(ex.getMessage());
        }
    }
 
    /**
     *   * 生成私钥
     *   * @param modulus
     *   * @param privateExponent
     *   * @return RSAPrivateKey
     *   * @throws EncryptException
     */
    public static RSAPrivateKey generateRSAPrivateKey(byte[] modulus, byte[] privateExponent) throws EncryptException {
        KeyFactory keyFac = null;
        try {
            keyFac = KeyFactory.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider());
        } catch (NoSuchAlgorithmException ex) {
            throw new EncryptException(ex.getMessage());
        }
 
        RSAPrivateKeySpec priKeySpec = new RSAPrivateKeySpec(new BigInteger(modulus), new BigInteger(privateExponent));
        try {
            return (RSAPrivateKey) keyFac.generatePrivate(priKeySpec);
        } catch (InvalidKeySpecException ex) {
            throw new EncryptException(ex.getMessage());
        }
    }
 
    /**
     *   * 加密
     *   * @param key 加密的密钥
     *   * @param data 待加密的明文数据
     *   * @return 加密后的数据
     *   * @throws EncryptException
     */
    public static byte[] encrypt(Key key, byte[] data) throws EncryptException {
        try {
            Cipher cipher = Cipher.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider());
            cipher.init(Cipher.ENCRYPT_MODE, key);
            int blockSize = cipher.getBlockSize();//获得加密块大小,如:加密前数据为128个byte,而key_size=1024 加密块大小为127 byte,加密后为128个byte;因此共有2个加密块,第一个127 byte第二个为1个byte
            int outputSize = cipher.getOutputSize(data.length);//获得加密块加密后块大小
            int leavedSize = data.length % blockSize;
            int blocksSize = leavedSize != 0 ? data.length / blockSize + 1 : data.length / blockSize;
            byte[] raw = new byte[outputSize * blocksSize];
            int i = 0;
            while (data.length - i * blockSize > 0) {
                if (data.length - i * blockSize > blockSize)
                    cipher.doFinal(data, i * blockSize, blockSize, raw, i * outputSize);
                else
                    cipher.doFinal(data, i * blockSize, data.length - i * blockSize, raw, i * outputSize);
//这里面doUpdate方法不可用,查看源代码后发现每次doUpdate后并没有什么实际动作除了把byte[]放到ByteArrayOutputStream中,而最后doFinal的时候才将所有的byte[]进行加密,可是到了此时加密块大小很可能已经超出了OutputSize所以只好用dofinal方法。
 
                i++;
            }
            return raw;
        } catch (Exception e) {
            throw new EncryptException(e.getMessage());
        }
    }
 
 
 
    /**
     *   * 解密
     *   * @param key 解密的密钥
     *   * @param raw 已经加密的数据
     *   * @return 解密后的明文
     *   * @throws EncryptException
     */
    public static byte[] decrypt(Key key, byte[] raw) throws EncryptException {
        try {
            Cipher cipher = Cipher.getInstance("RSA", new org.bouncycastle.jce.provider.BouncyCastleProvider());
            cipher.init(cipher.DECRYPT_MODE, key);
            int blockSize = cipher.getBlockSize();
            ByteArrayOutputStream bout = new ByteArrayOutputStream(64);
            int j = 0;
 
            while (raw.length - j * blockSize > 0) {
                bout.write(cipher.doFinal(raw, j * blockSize, blockSize));
                j++;
            }
            return bout.toByteArray();
        } catch (Exception e) {
            throw new EncryptException(e.getMessage());
        }
    }
 
    public static String byte2hex(byte[] b) {
        String hs = "";
        String stmp = "";
        for (int i = 0; i < b.length; i++) {
            stmp = Integer.toHexString(b[i] & 0xFF);
            if (stmp.length() == 1) {
                hs += "0" + stmp;
            } else {
                hs += stmp;
            }
        }
        //return hs;
        return hs.toUpperCase();
    }
 
 
    public static byte[] hex2byte(String hex) throws IllegalArgumentException {
        if (hex.length() % 2 != 0) {
            throw new IllegalArgumentException();
        }
        char[] arr = hex.toCharArray();
        byte[] b = new byte[hex.length() / 2];
        for (int i = 0, j = 0, l = hex.length(); i < l; i++, j++) {
            String swap = "" + arr[i++] + arr[i];
            int byteint = Integer.parseInt(swap, 16) & 0xFF;
            b[j] = new Integer(byteint).byteValue();
        }
        return b;
    }
 
    /**
     *   *
     *   * @param args
     *   * @throws Exception
     */
    public static void main(String[] args) throws Exception {
 
        byte[] orgData = "test".getBytes();
        KeyPair keyPair = RSAUtil.generateKeyPair();
        RSAPublicKey pubKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey priKey = (RSAPrivateKey) keyPair.getPrivate();
 
        byte[] pubModBytes = pubKey.getModulus().toByteArray();
        System.out.println("PubKey Modulus:"+new BigInteger(pubModBytes).toString(16));
        byte[] pubPubExpBytes = pubKey.getPublicExponent().toByteArray();
        System.out.println("publicExponent:"+new BigInteger(pubPubExpBytes).toString(16));
        byte[] priModBytes = priKey.getModulus().toByteArray();
        byte[] priPriExpBytes = priKey.getPrivateExponent().toByteArray();
        RSAPublicKey recoveryPubKey = RSAUtil.generateRSAPublicKey(pubModBytes, pubPubExpBytes);
        RSAPrivateKey recoveryPriKey = RSAUtil.generateRSAPrivateKey(priModBytes, priPriExpBytes);
 
        byte[] raw = RSAUtil.encrypt(priKey, orgData);
        System.out.println("Encrypt:"+byte2hex(raw));
        byte[] data = RSAUtil.decrypt(recoveryPubKey, raw);
        System.out.println("Decrypt:"+new String(data));
 
    }
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class EncryptException extends Exception {
 
    public EncryptException() {
        super();
    }
 
    public EncryptException(String message) {
        super(message);
    }
 
    public EncryptException(String message, Throwable cause) {
        super(message, cause);
    }
 
    public EncryptException(Throwable cause) {
        super(cause);
    }
 
}

我们看一下运行结果:

1
2
3
4
PubKey Modulus:a907de8b5789b1df66c8a4ea90f99c9b00bbad520d487a7e218cd1ee2a1cafcaff2dd03a70cc61d8ccdfe0557b9132dd163a5a6c287d94790fc8b573a154ba0cd799e2cc73fc44c03083274760664125cafc33c647c44df300968665b6dbc9e553c59f8180de0ded3ae2163aab499c1ec0688ed7468fb816cdf05db501cbba19
publicExponent:10001
Encrypt:71521BBA91D871AAA8FF99D7E9D6E44DAE6218FAFDC07CE7C34ABACC1357854BF8F5D94F17FD106346B0916CC81A85B7031F9421810F1E5A568EE408E6DFDB219201CB1DE2AF259A516F3B930130D6AD4FFCB26072EB2BD9CFC11CA727B181A640311BC3023B2D1E54EBC52454C669389C24D0F7AD316B0B24257D53384E1446
Decrypt:test

使用已有的Modulus加密的话

1
2
3
4
5
6
7
8
String modulus = "D192471B8699640F931FE6F4FACC3E990B894F894CEA5BEE0DCBD7A4B76752F7345CF9B5F1271001B724F7A0ABF0A6E911E309536F4BE4749E92DCC531B8E36B95969D206649C9DD2371B413A8DFD9B92569660B1499A5CD310B86A8FDE24988E456897A416D2E7B0B649F0714F322C57EF92563B21A448D1072FF3806C34C75";
byte[] b_modulus = new BigInteger(modulus,16).toByteArray();
String publicExponent = "10001";
byte[] b_publicExponent = new BigInteger(publicExponent,16).toByteArray();
String text = "test";
RSAPublicKey recoveryPubKey = RSAUtil.generateRSAPublicKey(b_modulus, b_publicExponent);        
byte[] ss = RSAUtil.encrypt(recoveryPubKey,text.getBytes());
System.out.println(byte2hex(ss));

IDEA中使用Tomcat不能启动的问题

今天用IDEA做个Java的Web工程,想运行一下,在IDEA配置好Tomcat(8180端口),部署上去,运行,竟然报Address localhost:8180 is already in use,我检查了一下本地没有在8180上的服务,奇怪了,前一段用IDEA的时候没有这样的问题,我尝试将Tomcat换到其他的端口,依旧报错,还是Google一下吧,找到了http://www.notionzone.com/2008/11/19/intellij-idea-eclipse-tomcat-deploy-58.html,原来是NOD32的问题,我就是最近才换到NOD32的,将NOD32中“启用HTTP检查”关闭就好了。

从邮件服务商取得用户通讯录的方法

我们在使用facebook开心网这些SNS网站的时候,里面有一项功能,就是通过邮件邀请好友,它可以导出你在Hotmail、Gmail、Yahoo、网易、新浪等Web Mail里的通讯录,然后给这些好友发送邀请信,这种病毒式营销推广方式起到了非常好的效果,facebook、开心网这些网站得以迅速壮大,这项功能也成了SNS网站的标配,从技术角度,我们就来探讨一下实现这项功能的原理和方法。

先说Gmail、Hotmail,Google和MS还是非常强大的,Google提供了Account Authentication API,它允许第三方的网站通过它的认证服务后,通过Google的Service取得用户的数据,包括通讯录等,MS也有Windows Live ID服务,不过我对微软的东西研究的不深,MSDN上找到的Windows Live ID SDK的资料,http://msdn.microsoft.com/en-us/library/bb404787.aspx

接下来说说其他邮件系统的取得方式,主要的原理就是通过模拟用户行为,登陆Web Mail系统,取得用户通讯录的信息,很多Web Mail都提供通讯录CSV格式的下载服务,我们取得这个CSV文件就可以了,网易的邮件系统似乎已经不提供CSV文件的下载,那只能通过通讯录页面的HTML分析,来取得信息,所以相对复杂一些,而Sohu的通讯录虽然前端采用了Ajax,但后端取得通讯返回的格式是Json,应该是最方便。

接下来说模拟用户行为,其实就是通过在自己服务器端向Web Mail发送相对的HTTP请求,而模拟出用户登陆的行为,在Linux下,我们用cURL这个组件,在编译PHP的时候把cURL编译进去,使PHP支持cURL,cURL非常好用,也非常强大,在libcurl里有大多数开发语言的支持,如果使用JAVA的话,还可以使用Apache的HttpClient

我们要模拟出用户登陆Web Mail的过程,我们就要对通常情况下,登陆Web Mail的HTTP请求过程有所了解,HttpWatch是一个非常好的工具,在做为插件安装倒IE和FireFox上,在IE或是FireFox里就可以看到所有的HTTP请求的信息了。

httpwatch

我们看看网易163的邮件的登陆过程

00:00:00.000    跳转提示
+ 0.000        0.231    1543    2352    POST    200    text/html; charset=UTF-8    http://reg.163.com/login.jsp?type=1&url=http://entry.mail.163.com/coremail/fcg/ntesdoor2?lightweight%3D1%26verifycookie%3D1%26language%3D-1%26style%3D35
0.231    1543    2352    1 request
00:00:00.462        0.177    0    0    GET    (History Cache)    image/x-icon    http://reg.163.com/favicon.ico
00:00:00.469    网易电子邮箱 – 简约3.0Beta
+ 0.000        0.118    1703    604    GET    302    Redirect to http://g4a30.mail.163.com/jy3/main.jsp?sid=gAJQIqzzbBwvsaVoDhzztmnXQvBRiMuz    http://entry.mail.163.com/coremail/fcg/ntesdoor2?lightweight=1&verifycookie=1&language=-1&style=35&username=xxx
+ 0.245        0.366    1664    6788    GET    200    text/html;charset=GBK    http://g4a30.mail.163.com/jy3/main.jsp?sid=gAJQIqzzbBwvsaVoDhzztmnXQvBRiMuz
+ 0.422        0.158    0    0    GET    (Cache)    text/css    http://mimg.126.net/p/jy3style/lib/0903090940/jscss/globle.css
+ 0.659        0.087    0    0    GET    (Cache)    application/x-javascript    http://mimg.126.net/p/jy3style/js/0903090940/tools.js
+ 0.816        0.067    0    0    GET    (Cache)    text/css    http://mimg.126.net/p/jy3style/lib/0903090940/cmcss/skin_blue.css
+ 0.998        0.578    1586    5521    GET    200    text/html;charset=GBK    http://g4a30.mail.163.com/jy3/top.jsp?sid=gAJQIqzzbBwvsaVoDhzztmnXQvBRiMuz
+ 1.015        0.624    1589    24844    GET    200    text/html;charset=GBK    http://g4a30.mail.163.com/jy3/folder.jsp?sid=gAJQIqzzbBwvsaVoDhzztmnXQvBRiMuz
+ 1.025        0.316    1590    10490    GET    200    text/html;charset=GBK    http://g4a30.mail.163.com/jy3/welcome.jsp?sid=gAJQIqzzbBwvsaVoDhzztmnXQvBRiMuz
+ 1.034        3.174    1592    2043    GET    200    text/html;charset=GBK    http://g4a30.mail.163.com/jy3/justfresh.jsp?sid=gAJQIqzzbBwvsaVoDhzztmnXQvBRiMuz
+ 1.280        0.439    0    0    GET    (Cache)    text/css    http://mimg.126.net/p/jy3style/lib/0903090940/jscss/globle.css
+ 1.632        0.407    0    0    GET    (Cache)    text/css    http://mimg.126.net/p/jy3style/lib/0903090940/jscss/globle.css
+ 1.647        0.432    0    0    GET    (Cache)    text/css    http://mimg.126.net/p/jy3style/lib/0903090940/jscss/welcome2.css
+ 1.876        0.235    0    0    GET    (Cache)    text/css    http://mimg.126.net/p/jy3style/lib/0903090940/jscss/globle.css
+ 2.179        0.117    0    0    GET    (Cache)    application/x-javascript    http://mimg.126.net/p/jy3style/js/0903090940/tools.js
+ 2.263        0.167    0    0    GET    (Cache)    application/x-javascript    http://mimg.126.net/p/jy3style/js/0903090940/tools.js
+ 2.354        0.207    0    0    GET    (Cache)    application/x-javascript    http://mimg.126.net/p/jy3style/js/0903090940/tools.js
+ 2.488        0.209    0    0    GET    (Cache)    text/css    http://mimg.126.net/p/jy3style/lib/0903090940/cmcss/skin_blue.css
+ 2.614        0.152    0    0    GET    (Cache)    text/css    http://mimg.126.net/p/jy3style/lib/0903090940/cmcss/skin_blue.css
+ 2.619        0.192    0    0    GET    (Cache)    application/x-javascript    http://mimg.126.net/p/jy3style/js/0903090940/welcome.js
+ 2.740        0.325    0    0    GET    (Cache)    text/css    http://mimg.126.net/p/jy3style/lib/0903090940/cmcss/skin_blue.css
4.208    9724    50290    20 requests
00:00:00.878        0.124    0    0    GET    (History Cache)    image/x-icon    http://www.163.com/favicon.ico
00:00:03.572    网易电子邮箱 – 简约3.0Beta
+ 0.000        0.541    0    0    GET    (Cache)    text/html; charset=GB2312    http://mimg.163.com/tianqi/city_simple/58367_090311.html
+ 0.531        0.715    1424    250    GET    304    text/css    http://mimg.163.com/jy3style/lib/jscss/ifr_weather2.css
+ 1.614        0.245    0    0    GET    (Cache)    image/gif    http://mimg.163.com/jy3style/lib/images/weather2.gif
1.859    1424    250    3 requests

首先,将用户名和密码POST到http://reg.163.com/login.jsp?type=1&url=http://entry.mail.163.com/coremail/fcg/ntesdoor2?lightweight%3D1%26verifycookie%3D1%26language%3D-1%26style%3D35,reg.163.com是网易通行证,后面的url是用户登陆网易通行证后要转向的地址,在这里我们看到就是http://entry.mail.163.com/coremail/fcg/ntesdoor2,这就是网易邮件的入口,在我们模拟登陆的时候,可以不传这个url过去,至于用户名和密码表单的name,看mail.163.com的HTML代码就知道了。相关错误处理,在POST到http://reg.163.com/login.jsp这个地址之后,返回的信息要取得的,因为如果用户输错了用户名或是密码,还是应该要提示用户的,而且在错误的情况下,也不用往下进行了,reg.163.com返回的错误页面里有“您的密码不正确”或是“用户名不正确”这样的信息,以此就可以判断用户是否成功登陆了。

之后我们要模拟用户访问http://entry.mail.163.com/coremail/fcg/ntesdoor2?lightweight=1&verifycookie=1&language=-1&style=35&username=xxx,就是登陆邮件的过程,注意,这里是个GET方法,用POST方法是不行的,访问entry.mail.163.com这个URL之后,会返回一个302码,就是要Redirect转向,到http://g4a30.mail.163.com/jy3/main.jsp?sid=gAJQIqzzbBwvsaVoDhzztmnXQvBRiMuz这个地址,这也就是大家看到的邮件登陆进去的首页了,注意这个sid要抓出来,应该是登陆的一个token,还有http://g4a30.mail.163.com这个地址,网易邮件是个集群,有非常多的服务器,你们每次上去的服务器是随随机的,用你的sid访问其他服务器也是没问题的,比如http://g1a98.mail.163.com/jy3/main.jsp?sid=gAJQIqzzbBwvsaVoDhzztmnXQvBRiMuz,所以这个服务器地址你可以写死。

解析来我们要看看网易邮件通讯录的URL,在UI看到是http://g4a30.mail.163.com/jy3/address/addrlist.jsp?sid=gAJQIqzzbBwvsaVoDhzztmnXQvBRiMuz&gid=all,我们模拟用户访问这个URL,然后抓取这个页面的内容HTML,分析一下,就可以了,在PHP里有个很好的HTML Parser工具PHP Simple HTML DOM Parser,可以像jQuery一样取得页面元素,这样就很方便的取得页面信息了。

其他一些Web Mail的登陆过程分析也是类似的,如果是取得CSV文件就更简单了,通过取到的通讯录内容,就可以在SNS网站上邀请了。

在这里我向国内这些大的门户网站或是Mail服务商提个建议,现在的互联网是一个开放化、平台化的趋势,网站应该开放自己的资源,可以让第三方的的应用更方便的访问,固步自封是不能发展的,试想如果QQ开放他的好友平台,引入第三方应用,那它将轻松超过Facebook,成为世界第一大SNS社区。

关于Struts2里使用EL或JSTL

最近在公司里做一些Java Web开发的培训,同时对已经做的一些工程做一些ReView,现在的工程里,工程师直接使用JSTL取得Action里的属性,这个用法我以前到真的没有用过,因为在我印象中,Struts2的这些Action属性,应该是在ValueStack中,而在某些情况下,从ValueStack取值是件挺麻烦的事情,在做天乙社区8时,我就参考Struts2的标记库,自己扩展标记库,从而取得ValueStack里的值,而JSTL应该是从Page、Request、Session和Application里顺序取值,莫非Struts2将ValueStack里的值也放入了Request?同时我们直接用EL标签也直接取出了Action的属性值,莫非真的放入了Request?但是打开Struts2的Debug,发现Request里并没有值。

带着这个疑问,我Google了一下,很快找到答案:

提问:在Struts2中,如何使用JSTL来读取Action中的变量?

这是一个历史悠久的问题。因为事实上,很多朋友(包括我在内)是不使用Struts2自身的标签库,而是使用JSTL的,可能因为JSTL标签库比较少,简单易用的原因吧。

我们知道,JSTL默认是从page,request,session,application这四个Scope逐次查找相应的EL表达式所对应 的对象的值。那么如果要使用JSTL来读取Action中的变量,就需要把Action中的变量,放到request域中才行。所以,早在 Webwork2.1.X的年代,我们会编写一个拦截器来做这个事情的。大致的原理是:在Action执行完返回之前,依次读取Action中的所有的变 量,并依次调用request.setAttribute()来进行设置。具体的整合方式,请参考以下这篇文档:http://wiki.opensymphony.com/display/WW/Using+WebWork+and+XWork+with+JSP+2.0+and+JSTL+1.1

不过随着时代的发展,上面的这种方式,已经不再被推荐使用了。(虽然如此,我们依然可以学习它的一个解决问题的思路)目前来说,自从 Webwork2.2以后,包括Struts2,都使用另外一种整合方式:对HttpServletRequest进行装饰。让我们来看一下源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
 public class StrutsRequestWrapper extends HttpServletRequestWrapper {
 
     /**
      * The constructor
      * @param req The request
      */
     public StrutsRequestWrapper(HttpServletRequest req) {
         super(req);
     }
 
     /**
      * Gets the object, looking in the value stack if not found
      *
      * @param s The attribute key
      */
     public Object getAttribute(String s) {
         if (s != null && s.startsWith("javax.servlet")) {
             // don't bother with the standard javax.servlet attributes, we can short-circuit this
             // see WW-953 and the forums post linked in that issue for more info
             return super.getAttribute(s);
         }
 
         ActionContext ctx = ActionContext.getContext();
         Object attribute = super.getAttribute(s);
 
         boolean alreadyIn = false;
         Boolean b = (Boolean) ctx.get("__requestWrapper.getAttribute");
         if (b != null) {
             alreadyIn = b.booleanValue();
         }
 
         // note: we don't let # come through or else a request for
         // #attr.foo or #request.foo could cause an endless loop
         if (!alreadyIn && attribute == null && s.indexOf("#") == -1) {
             try {
                 // If not found, then try the ValueStack
                 ctx.put("__requestWrapper.getAttribute", Boolean.TRUE);
                 ValueStack stack = ctx.getValueStack();
                 if (stack != null) {
                     attribute = stack.findValue(s);
                 }
             } finally {
                 ctx.put("__requestWrapper.getAttribute", Boolean.FALSE);
             }
         }
         return attribute;
    }
}

看到了嘛?这个类会在Struts2初始化的时候,替换HttpServletRequest,运行于整个Struts2的运行过程中,当我们试 图调用request.getAttribute()的时候,就会执行上面的这个方法。(这是一个典型的装饰器模式)在执行上面的方法时,会首先调用 HttpServletRequest中原本的request.getAttribute(),如果没有找到,它会继续到ValueStack中去查找, 而action在ValueStack中,所以action中的变量通过OGNL表达式,就能找到对应的值了。

在这里,在el表达式广泛使用的今天,JSTL1.1以后,也支持直接使用el表达式。注意与直接使用struts2的tag的区别,这里需要使用el的表示符号:${}

例如:${user.name}, <c:out value=”${department.name}” />

原来是这样,学无止境啊,很多东西需要仔细去研究。

3DES加密

DES加密算法是一种标准算法,应该可以通过不同程序语言实现,我在异构系统里,可能需要用不同的语言加密解密,整理不同语言的DES算法还是有必要的,先前我发过一个Java的3DES算法代码,我再发一遍

import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;

public class DES {

public static int _DES = 1;
public static int _DESede = 2;
public static int _Blowfish = 3;

private Cipher p_Cipher;
private SecretKey p_Key;
private String p_Algorithm;

private void selectAlgorithm(int al) {
switch (al) {
default:
case 1:
this.p_Algorithm = “DES”;
break;
case 2:
this.p_Algorithm = “DESede”;
break;
case 3:
this.p_Algorithm = “Blowfish”;
break;
}
}

public DES(int algorithm) throws Exception {
this.selectAlgorithm(algorithm);
Security.addProvider(new com.sun.crypto.provider.SunJCE());
this.p_Cipher = Cipher.getInstance(this.p_Algorithm);
}

public byte[] getKey() {
return this.checkKey().getEncoded();
}

private SecretKey checkKey() {
try {
if (this.p_Key == null) {
KeyGenerator keygen = KeyGenerator.getInstance(this.p_Algorithm);
/*
SecureRandom sr = new SecureRandom(key.getBytes());
keygen.init(168, sr);*/
this.p_Key = keygen.generateKey();
}
}
catch (Exception nsae) {}
return this.p_Key;
}

public void setKey(byte[] enckey) {
this.p_Key = new SecretKeySpec(enckey, this.p_Algorithm);
}

public byte[] encode(byte[] data) throws Exception {
this.p_Cipher.init(Cipher.ENCRYPT_MODE, this.checkKey());
return this.p_Cipher.doFinal(data);
}

public byte[] decode(byte[] encdata, byte[] enckey) throws Exception {
this.setKey(enckey);
this.p_Cipher.init(Cipher.DECRYPT_MODE, this.p_Key);
return this.p_Cipher.doFinal(encdata);
}

public String byte2hex(byte[] b) {
String hs = “”;
String stmp = “”;
for (int i = 0; i < b.length; i++) {
stmp = Integer.toHexString(b[i] & 0xFF);
if (stmp.length() == 1) {
hs += “0” + stmp;
}
else {
hs += stmp;
}
}
return hs.toUpperCase();
}

public byte[] hex2byte(String hex) throws IllegalArgumentException {
if (hex.length() % 2 != 0) {
throw new IllegalArgumentException();
}
char[] arr = hex.toCharArray();
byte[] b = new byte[hex.length() / 2];
for (int i = 0, j = 0, l = hex.length(); i < l; i++, j++) {
String swap = “” + arr[i++] + arr[i];
int byteint = Integer.parseInt(swap, 16) & 0xFF;
b[j] = new Integer(byteint).byteValue();
}
return b;
}

public static void main(String[] args) throws Exception {

String info = “要加密的字串”;
System.out.println(“region string:” + info);
byte[] key; //密钥文件(byte)
DES des = new DES(DES._DESede); // 声明DES
key = des.getKey(); //获取随机生成的密钥
System.out.println(“encrypted key(byte):” + new String(key));
String hexkey = des.byte2hex(key); //生成十六进制密钥
System.out.println(“encrypted key(hex):” + hexkey);
byte[] enc = des.encode(info.getBytes()); //生成加密文件(byte)
System.out.println(“encrypted string(byte):” + new String(enc));

String hexenc = des.byte2hex(enc); //生成十六进制加密文件
System.out.println(“encrypted string(hex):” + hexenc);

byte[] dec = des.decode(enc, des.hex2byte(hexkey)); //解密文件,其中转换十六进制密钥为byte
System.out.println(“decrypted string:” + new String(dec)); //生成解密文件字符串, 与info相同
}

}

最近我在看PHP的时候,研究了一下PHP对于3DES的加密代码

<?php

function fmt3DESEncode($s,$base64key){

$key = base64_decode($base64key);

$td = mcrypt_module_open(MCRYPT_3DES, ”, MCRYPT_MODE_ECB, ”);

$iv_size = mcrypt_enc_get_iv_size($td);

$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);

mcrypt_generic_init($td, $key,$iv);

$encrypted_data = mcrypt_generic($td, $s);

mcrypt_generic_deinit($td);

mcrypt_module_close($td);

return $encrypted_data;

}

function PaddingPKCS7($data) {

$block_size = mcrypt_get_block_size(‘tripledes’, ‘ecb’);

$padding_char = $block_size – (strlen($data) % $block_size);

$data .= str_repeat(chr($padding_char),$padding_char);

return $data;

}

function fmt3DESDecode($s,$base64key) {

$key = base64_decode($base64key);

$td = mcrypt_module_open(MCRYPT_3DES, ”, MCRYPT_MODE_ECB, ”);

$iv_size = mcrypt_enc_get_iv_size($td);

$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);

mcrypt_generic_init($td, $key,$iv);

$p_t = mdecrypt_generic($td, $s);

mcrypt_generic_deinit($td);

mcrypt_module_close($td);

return $p_t;

}

function unPaddingPKCS7($data) {

$pad = ord($data{strlen($data)-1});

if ($pad > strlen($data)) {

return false;

}

if (strspn($data, chr($pad), strlen($data) – $pad) != $pad) {

return false;

}

$srcdata = substr($data, 0, -1 * $pad);

return $srcdata;

}

$S = “laoer”;

$str = BASE64_Encode(fmt3DESEncode(PaddingPKCS7($S),”XXXXXXXXX-BASE64-密钥”));

echo $str;

echo”<br/>”;

$str = fmt3DESDecode(base64_decode($str), “XXXXXXXXX-BASE64-密钥”);

echo unPaddingPKCS7($str);

?>

有几点注意,在Java里3DES默认ENCRYPT_MODE是ECB,并采用PKCS7补码,关于着两个概念,我不是专业人士,也解释不清楚,需要研究加密解密了,所以在PHP里也进行了相应的处理,还有就是Java代码里密钥和加密结果是用16进制编码了,PHP代码里用了Base64,大家用的时候相应改一下就可以了。

现在可以测试一下,JAVA和PHP的加密解密结果是否一致!

补:.NET下3DES的算法也没有问题,我不做这方面,但以前同事做过,都测试通过。

Struts2小结

天乙社区8.0(http://www.laoer.com)已经完全用Struts2实现,在使用Struts的过程中也遇到了一些问题,现在总结一下。

1、在web.xml中EncodingFilter的位置应该在Struts2的FilterDispatcher之前,道理很简单,要先调整字符集,再进入Action。

2、如果使用Urlrewrite,要指定filter-mapping的dispatcher方式,如下
<filter-mapping>
<filter-name>Struts2</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>

3、在做上传文件的时候,要在web.xml中增加ActionContextCleanUp这个filter,如果不增加,会发生第一次上传取不到文件的情况
<filter>
<filter-name>struts-cleanup</filter-name>
<filter-class>org.apache.struts2.dispatcher.ActionContextCleanUp</filter-class>
</filter>
<filter-mapping>
<filter-name>struts-cleanup</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
按照Struts2的API,filter的顺序是
struts-cleanup filter
SiteMesh filter
FilterDispatcher

4、在Apache+Resin的情况下,要在WEB-INF下增加resin-web.xml,该文件只针对Resin有效,作用是指定后缀与 Resin的Servlet引擎匹配,要不然从Apache转发过去的请求到Resin后会出现404的情况,resin-web.xml举例如下:
<web-app xmlns=”http://caucho.com/ns/resin”>
<servlet-mapping url-pattern=’*.bbscs’ servlet-name=’plugin_match’/>
</web-app>

5、在使用<s:url/>标签的时候,会出现将get或post数值带入url参数的情况,如果不需要这些参数,可以在struts.properties文件中设置
struts.url.includeParams=none
或是在<s:url/>标记中将includeParams属性设为none
另外还有两个值
all,是把get和post中的参数加入到url参数中
get,是只把get中的参数加入到url参数中

6、与webwork基本相同,Struts2提供了几种ui.theme,有xhtml、css_xhtml、simple等等,在 struts.properties中可以设置使用何种theme,这一点很关键,不同的theme,struts的tag会生成不同的html代码,而且在某些情况下这些theme不能满足页面要求,则需要自己进行扩展了,这些theme都是由freemarker写的,仿照这写就可以。

7、单个checkbox的标记库好像只能返回boolean的值,如果在数据库中设计为int型,则需要做一些转换,这一点我觉得不如Struts1.x的方便。

8、总体来说Struts2的标记库使用上比Struts1.x的方便,页面整体也比较简洁,Struts2采用stack的方式存取数据,与Struts1相比各有千秋吧。

Struts2主要延续自webwork,以前使用webwork的朋友转过来并不困难,Struts2的几个核心的部分,比如拦截器、Result Configuration、OGNL stack等等还是需要仔细的体会,深入了解,才能做出优秀的系统。

(这篇文章在JavaEye上发表过)

Tomcat AJP Connector参数调整

最近公司的一个面向C端客户的网站应用频繁的Down掉,昨天早上重起Tomcat后,快到下午下班时有不响应了,我们采用的是Apache+Tomcat,Apache和Tomcat之间采用的是AJP,在应用Down机的时候发现直接访问Tomcat的端口没有问题,而Apache的性能应该没有问题,其参数我也调整过,而且我们的访问量并不算很大,服务器的Idel也一直处于空闲状态,由此判断,估计是Apache到Tomcat的连接出了什么问题,晚上回家我查了查Tomcat的文档,找到AJP这部分配置(http://tomcat.apache.org/tomcat-5.5-doc/config/ajp.html),里面Standard Implementation部分有maxThreads、maxSpareThreads、minSpareThreads这几个参数,我想应该是AJP Connector原有的参数设置的太小或是不合理造成的,相应修改这几个参数,重起Tomcat看看情况如何。