2006年7月29日

原文来自: HttpClient POST 的 UTF-8 编码问题

Apache HttpClient ( http://jakarta.apache.org/commons/httpclient/ ) 是一个纯 Java 的HTTP 协议的客户端编程工具包, 对 HTTP 协议的支持相当全面, 更多细节也可以参考IBM 网站上的这篇文章 HttpClient入门 ( http://www-128.ibm.com/developerworks/cn/opensource/os-httpclient/ ).

问题分析

不过在实际使用中, 还是发现按照最基本的方式调用 HttpClient 时, 并不支持 UTF-8 编码, 在网络上找过一些文章, 也不得要领, 于是查看了 commons-httpclient-3.0.1 的一些代码, 首先在 PostMethod 中找到了 generateRequestEntity() 方法:
    /**
     * Generates a request entity from the post parameters, if present.  Calls
     {@link EntityEnclosingMethod#generateRequestBody()} if parameters have not been set.
     
     @since 3.0
     */
    protected RequestEntity generateRequestEntity() {
        if (!this.params.isEmpty()) {
            // Use a ByteArrayRequestEntity instead of a StringRequestEntity.
            // This is to avoid potential encoding issues.  Form url encoded strings
            // are ASCII by definition but the content type may not be.  Treating the content
            // as bytes allows us to keep the current charset without worrying about how
            // this charset will effect the encoding of the form url encoded string.
            String content = EncodingUtil.formUrlEncode(getParameters(), getRequestCharSet());
            ByteArrayRequestEntity entity = new ByteArrayRequestEntity(
                EncodingUtil.getAsciiBytes(content),
                FORM_URL_ENCODED_CONTENT_TYPE
            );
            return entity;
        else {
            return super.generateRequestEntity();
        }
    }

原来使用 NameValuePair 加入的 HTTP 请求的参数最终都会转化为 RequestEntity 提交到 HTTP 服务器, 接着在 PostMethod 的父类 EntityEnclosingMethod 中找到了如下的代码:
    /**
     * Returns the request's charset.  The charset is parsed from the request entity's 
     * content type, unless the content type header has been set manually. 
     
     @see RequestEntity#getContentType()
     
     @since 3.0
     */
    public String getRequestCharSet() {
        if (getRequestHeader("Content-Type"== null) {
            // check the content type from request entity
            // We can't call getRequestEntity() since it will probably call
            // this method.
            if (this.requestEntity != null) {
                return getContentCharSet(
                    new Header("Content-Type", requestEntity.getContentType()));
            else {
                return super.getRequestCharSet();
            }
        else {
            return super.getRequestCharSet();
        }
    }


解决方案

从上面两段代码可以看出是 HttpClient 是如何依据 "Content-Type" 获得请求的编码(字符集), 而这个编码又是如何应用到提交内容的编码过程中去的. 按照这个原来, 其实我们只需要重载 getRequestCharSet() 方法, 返回我们需要的编码(字符集)名称, 就可以解决 UTF-8 或者其它非默认编码提交 POST 请求时的乱码问题了.

测试

首先在 Tomcat 的 ROOT WebApp 下部署一个页面 test.jsp, 作为测试页面, 主要代码片段如下:
<%@ page contentType="text/html;charset=UTF-8"%>
<%@ page session="false" %>
<%
request.setCharacterEncoding("UTF-8");
String val = request.getParameter("TEXT");
System.out.println(">>>> The result is " + val);
%>


接着写一个测试类, 主要代码如下:
    public static void main(String[] argsthrows Exception, IOException {
        String url = "http://localhost:8080/test.jsp";
        PostMethod postMethod = new UTF8PostMethod(url);
        //填入各个表单域的值
        NameValuePair[] data = {
                new NameValuePair("TEXT""中文"),
        };
        //将表单的值放入postMethod中
        postMethod.setRequestBody(data);
        //执行postMethod
        HttpClient httpClient = new HttpClient();
        httpClient.executeMethod(postMethod);
    }
    
    //Inner class for UTF-8 support
    public static class UTF8PostMethod extends PostMethod{
        public UTF8PostMethod(String url){
            super(url);
        }
        @Override
        public String getRequestCharSet() {
            //return super.getRequestCharSet();
            return "UTF-8";
        }
    }


运行这个测试程序, 在 Tomcat 的后台输出中可以正确打印出 ">>>> The result is 中文" .

代码下载

本文所提到的所有代码, 以及测试程序(可直接导入 eclipse)提供打包下载: att:HttpClient POST 的 UTF-8 编码问题.httpClientUTF8.tar.bz2

END


posted @ 2006-10-31 23:00 thinkbase.net 阅读(7638) | 评论 (8)编辑 收藏
 
原文来自:  使用 Apache 反向代理实现负载均衡及热备


初步设想

  • 早些时候在 JavaEye 上看到过一些使用 lighttpd 或者 apache 作前端, 通过负载均衡, 实现高性能的 Web 系统的讨论, 于是留意了一下这方面的技术;
  • 考虑到对不同的 App Server 而言, 实现 Session 复制的配置各不相同(通常是需要配置集群), 因此从通用的角度, 觉得使用 session sticky 方式实现的负载均衡比较方便;
  • 由于没有看到有资料说 lighttpd 能够实现 session sticky, 所以决定先使用 Apache 试试.
参考资料:

环境准备

  • 下载安装 Apache, 测试时使用的是 XAMPP ( http://www.apachefriends.org/en/xampp.html ) 的 Linux 版本 (xampp-linux-1.5.4.tar.gz), 按照安装说明, 解压到 /opt/lampp 目录下就可以使用了;
    • 启动 Apache: sudo /opt/lampp/lampp startapache
    • 重新加载 Apache: sudo /opt/lampp/lampp reloadapache (在 httpd.conf 文件被修改后可以不重启, 而是直接 reload 就可以了)
    • 停止服务: sudo /opt/lampp/lampp stop
  • 准备两个运行同样程序的 Web 服务器, 这里使用的是 Tomcat 5.5, 并使用一个 jsp 文件作为测试文件(相关源代码参见文章最后的附件);
    • 这两个 Tomcat 服务器需要将 HTTP 服务配置在不同的端口上, 同时由于测试时运行在同一台机器上, 其它端口也需要避免冲突;
  • 下载安装 JMeter ( jakarta-jmeter-2.2), 用于压力测试, 验证负载均衡的效果;

测试 jsp 文件的说明

测试用的 jsp 文件 (test.jsp) 具有如下功能:
  • 显示当前运行的服务器的 IP 地址及端口号, 这样从返回的页面就能够知道是运行在哪一个 Web 服务器上的了;
  • 统计每个客户端(不同的 session)向同一台服务器发出请求的次数, 通过这个计数可以验证是否实现了 session sticky;
  • 通过 clear 请求参数(即 .../test.jsp?clear=1)清除请求次数的计数结果, 以便进行下一次测试;
  • 模拟 JSESSIONID +jvmRoute 的机制, 自行实现了一个 STICK_PORT_TOKEN 的 Cookie, 直接使用不同服务器的 HTTP 端口号作为 route;
    • 说明1: 考虑到方案的通用性, 这里没有直接使用 JSESSIONID +jvmRoute 的机制;
    • 说明2: 虽然作为一个例子, 相关代码是写死在 jsp 文件中的, 但是这个机制可以很方便的用一个 Filter 统一实现;

Apache 的配置

编辑 Apache 的 httpd.conf 文件(如果使用 xampp-linux 的话, 应该在 /opt/lampp/etc 目录下), 在文件的最后加上如下内容:
###############################################################################
# Reverse Proxy and Load Balance ##############################################
###############################################################################
# 1)简单的反向代理
ProxyRequests Off
<Proxy *>
Order deny,allow
Allow from all
</Proxy>
ProxyPass /1 http://localhost:8080/test
#ProxyPassReverse /1 http://localhost:8080/test
ProxyPass /2 http://localhost:18080/test
#ProxyPassReverse /2 http://localhost:18080/test
# 2)非 stickysession 的 balance
ProxyPass /3 balancer://non-sticky-cluster nofailover=On
<Proxy balancer://non-sticky-cluster>
BalancerMember http://localhost:8080/test
BalancerMember http://localhost:18080/test smax=10
</Proxy>
# 3)stickysession 的 balance
ProxyPass /4 balancer://sticky-cluster stickysession=STICK_PORT_TOKEN nofailover=On
<Proxy balancer://sticky-cluster>
BalancerMember http://localhost:8080/test route=8080
BalancerMember http://localhost:18080/test route=18080 loadfactor=2
</Proxy>
这个配置分为3个部分, 包括了 1)简单的反向代理, 2)非 session sticky 的 load balance, 以及 3)session sticky 的 load balance 三种方式的配置(这里假设两个 Tomcat 服务器的 HTTP 服务被配置在 8080 和 18080 端口), 其中第 2) 和 3) 的配置中 "nofailover=On" 适合于没有 session 复制的情况下, 这种情况下, 如果其中一台 HTTP 服务器出错, 那么原来分配在这个出错机器上的浏览器客户端不会被自动转移到另外的服务器上, 必须重新启动浏览器才能将请求分配到另外一台服务器上去.

使用 JMeter 测试结果

使用 JMeter 对 "3)session sticky 的 load balance" 的效果进行测试, 通过压力测试的方式, 检查两台 Tomcat 服务器被分配到的请求数量, 相关的测试脚本参见文章最后的附件.

注意如果重复测试, 在下一次测试开始之前请对每个 Tomcat 服务器执行 .../test.jsp?clear=1 的请求, 清除上一次的计数结果.

从下图的测试结果可见: 50个线程中有21个被分配在 8080 端口的服务器上, 29个则被分配到 18080 端口的服务器; 另外, 所有的 session 请求次数都是 20 次, 说明 session sticky 达到了预期的效果.

附件


后记

如何禁用 XAMPP 自带的内容, 使之成为一个单纯的转发服务器:
  • 1)注释掉 /opt/lampp/etc/httpd.conf 中 "Include etc/extra/httpd-xampp.conf" 这一行;
  • 2)删除或者移走 /opt/lampp/htdocs 目录下的内容(但是此目录需要保留).
posted @ 2006-10-10 13:12 thinkbase.net 阅读(7006) | 评论 (7)编辑 收藏
 

(原文来自: 2006-07-28 介绍一个 RSA 加密小工具 bmrsa)

因为要做一个不对称加密/解密相关的应用(其实是想用在软件的注册控制方面), 本来的想法是去找找有没有 RSA 加密/解密的源代码的例子, 结果在 sourceforge 找到来 bmrsa(RSA Cryptographic Text Processor), 可以从 http://sourceforge.net/projects/bmrsa 访问.

项目 README 里的描述:

 This program is an exercise in prime number generation,
RSA key generation, RSA encryption and conversion between
decimal, hexadecimal, base64 and text. Take note that RSA
is generally not used to encrypt entire messages because it
is too slow. It is normally used to encrypt keys used in
other encryption algorithms or other relatively small
values. For more detailed documentation, execute bmrsa
from a command line without passing any arguments. You will
probably want to pipe the output through more like this

试用了一下, 总结该软件有如下特点:
  • 开源, GPL 协议;
  • 跨平台, 而且完全使用 C++ 编写, 下载包里包含了 Windows 和 Linux 下的可执行文件;
  • 命令行程序, 运行时使用 stdin 和 stdout, 应该说被其它程序调用还算方便;
  • 只能处理文本, 不支持中文, 因此对于中文文本或者二进制文件可能需要先 base64 一下.

我用的是 2003-03-16 16:00 的 bmrsa10.zip 这个包, 可以从项目主页找到下载, 也可以从下面的链接下载:
下面简单说明产生 公钥/私钥 和 加密/解密 的整个过程:
  • 首先将下载后的 bmrsa10.zip 解压缩到一个目录, 进入这个目录;
  • 如果是 linux 环境下, 为了后面输入命令方便, 可以执行 export PATH=.:$PATH , 将当前目录加入 PATH;
  • 首先生成一个 768 位的密钥文件 _keys.768-bit.txt:
bmrsa -mkh -g 48 -f _keys.768-bit.txt
  • 接走我们建立另外两个文本文件 _private-key.768-bit.txt_public-key.768-bit.txt, 分别对应私钥和公钥. 这两个文件都是对 _keys.768-bit.txt 中内容的裁减, 基本上类似这个样子:
    • _private-key.768-bit.txt
public mod=7ECF58B12DAB4557B9B39589D26CA444BDF96...
private key=3C4B0676352943057A3B6B0D54A8B0E56265B...
private p=ED66018402DEED19082ED5EA500B778DAFA7A0...
private q=88BF09780DC8C15C429AE72AC6F91B0795C4E68...
  • 和:
    • _public-key.768-bit.txt
public mod=7ECF58B12DAB4557B9B39589D26CA444BDF96...
public key=BA562B6FEF44681C8937C54FCB985B205DAF0A...
  • 在实际使用中, 我们可以把公钥公布出来, 而用私钥加密传递的信息, 接收到信息的人可以使用公钥对信息进行解密(另外也可以反过来使用公钥加密, 接收方使用私钥解密);
    • 具体到这个例子中, 也就是说我们可以把 _public-key.768-bit.txt 公开给需要接收我们的信息的人, 而 _private-key.768-bit.txt 必需妥善保管, 因为一旦别人拿到这个私钥, 那就意味着他可以冒充你发送消息了;
  • 下面我们建立一个文本文件 _demo.txt 作为加密的原文:
NAME=thinkbase
URL =http://www.thinkbase.net
IP =218.81.120.31
  • 我们使用密钥 _private-key.768-bit.txt_demo.txt 进行加密, 加密后的文件是 _demo.enc.txt
bmrsa -mkh -mit -moh -pr -f _private-key.768-bit.txt <_demo.txt >_demo.enc.txt
  • 接收方拿到加密后的文件 _demo.enc.txt 后, 可以使用公钥 _public-key.768-bit.txt 对信息进行解密:
bmrsa -mkh -mih -mot -pu -f _public-key.768-bit.txt <_demo.enc.txt

具体的运行界面参见下图:




在实验过程中使用的一些文件也可以下载, 供参考:
posted @ 2006-07-29 00:18 thinkbase.net 阅读(1983) | 评论 (0)编辑 收藏
 

(原文来自: 2006-06-18 尝试了一下用 swig 编写 JNI 程序)

总是觉得编写 JNI (Java Native Interface) 程序是件复杂的事情, 于是开始漫无目的地搜索是否有简单一点的方法, 倒是有些商业软件提供这样的功能, 比如 http://www.jniwrapper.com 就提供了通过 Java 直接调用 DLL 的功能(可惜是商业软件, 哈哈).

搜索中无意到了 swig 的网站( http://www.swig.org ), 看到一个用 swig 产生 Java 模块的例子(原来知道 swig 是因为 python 的缘故), 于是就照着例子自己尝试了一下(比例子稍微复杂一点, 另外我是用 mingw 上的 gcc 进行编译的).

源代码包括 3 个文件, Example.c, Example.i, net/thinkbase/test/Test.java:

/** Example.c ****************************************************************/
#include <time.h>

/**A variable*/
double  PI =  3.1415927 ;

/**Return n!*/
int  fact ( int  n ) {
     if  ( n <=  1 )
         return  1 ;
     else
         return  n*fact ( n- 1 ) ;
}

/**mod function*/
int  mod ( int  x,  int  y ) {
     return  ( x%y ) ;
}

/**Get time as String*/
char  * getTime (){
     time_t ltime;
     time ( &ltime ) ;
     return  ctime ( &ltime ) ;
}

/**to upper case*/
char  * toUpperCase ( char  * result ){
     char  * p = result;
     while ( '\0' !=*p ){
         char  c = *p;
         if  ( ( c >  'a' ) && ( c <  'x' ) ){
         *p = c- 32 ;
         }
         p++;
     }
     return  result;
}


/** Example.i ****************************************************************/
%module Example
% {
     /* Put header files here or function declarations like below */
     extern  double  PI;
     extern  int  fact ( int  n ) ;
     extern  int  mod ( int  x,  int  y ) ;
     extern  char  * getTime () ;
     extern  char  * toUpperCase ( char  * str ) ;
% }

extern  double  PI;
extern  int  fact ( int  n ) ;
extern  int  mod ( int  x,  int  y ) ;
extern  char  * getTime () ;
extern  char  * toUpperCase ( char  * str ) ;


/** net/thinkbase/test/Test.java *********************************************/
package  net.thinkbase.test;

import  net.thinkbase.test.swig.Example;

public class  Test  {
     public static  void  main ( String argv []) {
         System.loadLibrary ( "Example" ) ;
         System.out.println (
             "    Example.getPI()      = "  + Example.getPI ()) ;
         System.out.println (
             "    Example.fact(6)      = "  + Example.fact ( 6 )) ;

         System.loadLibrary ( "Example" ) ;
         System.out.println (
             "    Example.mod(100, 30) = "  + Example.mod ( 100 30 )) ;
         System.out.println (
             "    Example.getTime()    = "  + Example.getTime ()) ;
         System.out.println (
             "    Example.toUpperCase(\"Hello, world!\") = "  +
             Example.toUpperCase ( "Hello, world!" )) ;
     }
}



试验步骤

建立必要的目录:
  • mkdir "net/thinkbase/test/swig"
  • mkdir ".out"
使用 swig 建立相关接口:
  • swig -java -package net.thinkbase.test.swig -outdir net/thinkbase/test/swig Example.i
编译 c 文件得到 dll:
  • gcc -c Example.c -o .out/Example.o -I%JAVA_HOME%/include -I%JAVA_HOME%/include/win32
  • gcc -c Example_wrap.c -o .out/Example_wrap.o -I%JAVA_HOME%/include -I%JAVA_HOME%/include/win32
  • gcc -shared .out/Example.o .out/Example_wrap.o -mno-cygwin -Wl,--add-stdcall-alias -o .out/Example.dll
编译测试用的 Java 程序:
  • javac -sourcepath . net/thinkbase/test/Test.java -d .out
运行测试:
  • java -cp .out -Djava.library.path=.out net.thinkbase.test.Test
运行及测试结果:
源代码可以从这里下载:
posted @ 2006-07-29 00:16 thinkbase.net 阅读(4110) | 评论 (0)编辑 收藏