我们的架构是b/s的,后台用的是tomcat,和google的webservice打交道还用到一个gae做代理。
之前的博文我们说过在tomcat和浏览器之间的ajax交互时要用一种客户端encode两次加上一个辅助函数加工然后服务端decode两次的恶心解决方案。后来我们研究了一下tomcat,发现其实问题是这样的。
tomcat默认使用iso-8859-1来解释request的,所以我们用ff提交参数时用的是utf8,我们在浏览器里输入“大学”,其实是被ff encode过的(被转成%B5%33形式),传到服务器时tomcat又用iso-8859-1 decode了。所以我们想到解决方案如下
String keyword =URLEncoder.encode(request.getParameter("keyword"),"ISO-8859-1");
keyword=URLDecoder.decode(keyword, "UTF-8");
这样子我们反过来弄一下就ok了。
这里还要说一下,如果换成时ie的话,默认“大学”这个url参数会用gbk,如果还是用上面的那个,其实就会出现乱码。这里还要说glassfish貌似也是用的iso-8859-1
这里还要讲一个关于HTTPURLConnection的事情,我们在tomcat用HTTPURLConnection时,我在设置parameter的时候没有encode我的参数,我就这样传过去了,而在本地的jetty服务器的gae上接收到了我的字符串竟然是对的,看来HttpURLConnection会自动帮我们按照默认的charset(linux下是utf8)把字符串编码再传过去。然后再我们本地的gae上,可以直接写这样的code
String keyword = req.getParameter("keyword");
String center = req.getParameter("center");
String range = req.getParameter("range");
这个可能gae用的jetty服务器又自动帮我们decode了,这里虽然我们没有decode和encode,但是程序都自动帮我们完成了。但是必须满足客户端和服务器用的默认的编码是一样的。
这里讲一下什么是decode什么是encode,比如,我们对一个“作者”encode,这里会encode成 %E4%BD%9C%E8%80%85这样的,再encode一下会,encode成“%25E4%25BD%259C%25E8%2580%2585”这样,%会转成%25。 而decode呢?如果我们对“%25E4%25BD%259C%25E8%2580%2585” decode会变成“%E4%BD%9C%E8%80%85”,而对“%25E4%25BD%259C%25E8%2580%2585大学” decode会变成“%E4%BD%9C%E8%80%85大学”,也就是说decode可能只会对%开头的东西进行decode。而encode比较诡异,处理%的手段让人搞不懂。
话说回来,我做了几次实验,我发现URLConnection会自动帮助我们进行编码的,而且很智能,如果我们事先encode过,他就不会再帮我们encode。而本地gae会用默认的utf8方式编码。设想一个情况,我们用gbk encode参数然后再传到gae上,我们就需要在gae上先encode("UTF-8")在decode 成gbk。这里的道理是一样的。而我们可以用一个函数就是request.setCharsetEncoding("GBK")来直接来让服务器帮我们用GBK转,这样就不要先encode再decode了。
现在我们看age里面的代码,我们需要请求google的web service。这时,我们仍然是需要用HTTPURLConnection这个类,但是我们在没有encode参数的时候,发给google api时竟然说url是不合法的,一定要实现encode才可以。这就不懂了,为什么一定要我们手动encode呢,不是HTTPURLConnection自动帮我们encode了吗?我想这是不是同一个版本的URLCOnnection啊?我看了一下原来不加encode的错误堆栈信息,原来最后会调用一个什么httpclient的类,而这个类不是标准库里的,是外部的,也不知道这里面到底出了什么鬼!!!就连我step into进去HTTPURLConnection,里面的堆栈也是不一样的,诡异啊!!!
接着我们从webservice里获得了json字符串,里面也还是没有decode过的utf8,随即
String allString=URLDecoder.decode(all.toString(),"UTF-8");
最后还要返回给tomcat结果,此时如果我们不用以下这个字符编码限定会出问题
resp.setContentType("text/json");
resp.setCharacterEncoding("UTF-8");
PrintWriter writer=resp.getWriter();
我不知道这个是为什么!我看本地的age上默认是utf8啊,难道传过来的时候就不是utf8?
后来我又搞成这样子,在本地gae上这样写
OutputStream writer = resp.getOutputStream();
writer.write(jsonString.getBytes());
writer.close();
在tomcat上这样写
byte[] bytes=new byte[10000];
InputStream inputStream=connection.getInputStream();
inputStream.read(bytes);
String string=new String(bytes);
这样我们用纯字节进行传递就不可能会有错了!!!这里我没有指定到底用什么编码传递,这个时候会用默认的,我始linux的,所以java的默认编码用utf8。
我们把age部署到服务器,在服务器上出现了诡异,原来服务器上用的是一个us什么的默认编码格式。真正的age要想正常返回给我们字符串,需要加上这么一句话
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(),"UTF-8"));
StringBuilder all = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
all.append(line);
}
在我本地gae是不需要加第一句话中的UTF-8的。因为在服务器上的gae是us编码的,所以需要这么转,否则默认就转成us这种东西了。
这里就又有一个问题了,service传过来的是一个需要decode的东西,我在本地的gae上用
new BufferedReader(new InputStreamReader(connection.getInputStream(),"UTF-8"));
得到的字符串是需要再一次decode的,而在服务器上的gae是不需要再一次被decode的。
终其所述,无论本地还是服务器的age下的HTTPURLConnection和普通java的不一样,它不会帮我们encode;而服务器上gae的InputStreamReader和本地的gae和普通java也不一样,他会自动转码。