手机浏览 RSS 2.0 订阅 膘叔的简单人生 , 腾讯云RDS购买 | 超便宜的Vultr , 注册 | 登陆
浏览模式: 标准 | 列表Tag:ajax

客户端和服务端的编码“陷阱”

前几天的问题仍然在思索,这些网上搜索下来的文章,都将成为我的资料库中的参考资料。

原文:http://www.v-sky.com/blog/?p=215

  为什么页面出现乱码?为什么数据库里出现乱码?为什么这些乱码的出现几率飘忽不定了?诸如此类的乱码问题困扰了很多WEB开发人员。假如不将这 背后的细节扫扫清楚,那么我们的确不知道什么时候乱码又出现了,如果你确实没有时间关心这些细节,那么你可以直接看文章最后的总结。

  我们所遇到的乱码多数情况是发生在有中文字符的时候,这是由于计算机各种编码的标准不同而造成的,首先我们有必要了解一下计算机编码的发展 史,ASCII码、GB2312、GBK、GB18030、UNICODE、UTF-8、UTF-16、ISO-8859-1…,简而言之它们都是为了满 足不同时期,不同对象的需求,以自己的一套标准尽可能多地表示所需要的符号信息,如果你需要了解得更深入,可以google一下相关资料。(PS:计算机 领域的很多技术发展都是很有意思的)正是因为有这么多不同标准的编码存在,稍不留神我们就走入了编码“陷阱”,如何避免这些“陷阱”了?

  一、注意文件编码和字符编码

  用记事本建立一个新的文件,默认是ASCII码,我们另存为其他编码类型,例如unicode,如果用UtraEdit或者Emeditor编 辑,你会发现编码类型的选择范围更大。试想一下如果在编码A类型的文件中存在了编码B的字符会有什么现象了?多数情况下会出现乱码。例如我们在一个文件编 码为GB2312的网页文件中写入了UTF-8的字符,那么这个网页浏览的时候就出现了乱码,而我们在浏览器中再自己设定一下编码为UTF-8,页面就恢 复正常了。当然也有例外情况,如果这两种编码是扩展关系,其中的部分字符编码是相同的,这个时候当然不会出现乱码了,例如GBK就兼容了BG2312的所 有字符。(PS:这里有个有趣的现象,新建一个记事本,然后输入“联通”两个字,保存再打开,你会发现它变成了乱码。具体原因你可以google一下,简 单说来是记事本错误地识别了字符编码。)

  第一条原则:保持文件编码和字符编码的一致性。(PS:尤其是在客户端使用编辑器更改文件的编码类型时更要注意,有些编辑器只改变了文件编码,而没有将内部的字符编码同样做转换。)

  二、 指定网页编码

  目前,主流浏览器都遵循RFC标准,他们会优先考虑服务端对Header中的content-type属性的设置,无论你是在server层做的全局设置,还是你的服务端脚本临时设置,你都需要清楚地知道网页否指定了你预定的编码。

  如果服务端对Header没有做处理,那么浏览器会识别Meta信息中的content-type,例如,这个网页的编码就指定了是UTF-8,如果其中存在GB的字符,那么乱码就出现了。

第二条原则:清楚地为网页指定我们预定的编码,最便捷的方式是在服务端指定。

  三、 留意Form这个数据入口

  Form的数据提交看起来是一个很简单的过程,事实上在这个背后浏览器给我们做了很多工作:(详细可参考W3C网站上的TR文档
1. 检测这个Form是否标准,包括name属性是否存在,disabled是否可用等等一系列信息,同时获取到各种表单的当前值
2. 对于一个检测通过的Form,要把它内部的表单元素的值按照他们在文档中的出现顺序,以name/value的形式记录为一组数据集。
3. 根据Form的enctype属性指定的content-type对获取到的一组数据集做URL编码
4. 最后根据action 和method属性来向服务端发送这组数据集

  这里我们需要特别留意的是第三步,这里做URL编码的数据集本身是什么字符编码了?通常情况下它会遵照网页 本身的编码,这就是为何我在第二原则里建议你要明确指定你选定的网页编码。假如你的网页是GBK编码,那么表单提交到服务端的数据就是GBK的编码,如果 网页是UTF-8,那么提交过去的就是UTF-8的。这里就有一个问题,如果客户端提交的是UTF-8,而我们服务端(包括数据库)都统一使用的是GBK 的编码,那会出现什么问题了?答案是:那会很棘手,服务端不得不对这个UTF-8的数据做编码转换,转换为了GBK才能入库,否则你的数据库里存入的就是 UTF-8的编码,乱码又出现了。

事实上这里还有个例外的情况,在W3C的标准中Form是有一个ACCEPT-CHARSET属性的,目的单独指定FORM中的编码,这个比文档的编 码设置优先级要高。我个人认为开发中用到的可能性不大,一个指定了编码A的文档,为何要发送编码B的字符呢?如果有真有这个需要,那么在服务端做转换就可 以了,没有必要在客户端弄出两种不同的编码,更何况IE这个强硬的家伙又是背离标准的。

A character encoding is a method of converting a sequence of bytes into a sequence of characters.
If ACCEPT-CHARSET is not specified, the form will be submitted in the character encoding specified for the document. If the form includes characters that fall outside the character set specified for the document, Microsoft Internet Explorer will attempt to determine an appropriate character set. If an appropriate character set cannot be determined, then the characters will be encoded as HTML numeric character references. For more information on character sets and numerical character references, see HTML Character Sets. (参考:MSNDhttp://msdn2.microsoft.com/en-us/library/ms533061.aspx)

  因此,我们还是“忘记”这个属性吧,没有它,我们的FORM一样可以运转得很好。(也正是因为浏览器默认会给FORM做很多不错的工作,所以我们看到的很多富文本编辑器在提交编辑内容时,都会先把这个内容写入到一个表单的value中,让浏览器自动给它编码。)

  第三个原则:清楚地认识Form中的数据的编码是依照网页本身编码的,在提交到服务端之前,这组数据是做了格式化处理,然后做了URL编码。目前服务端不会智能到自动识别FORM发送过来的编码类型,更不会智能到给你自动转换为服务端统一使用的编码。

  四、 特别留意AJAX这个数据入口

  AJAX的流行让XMLHTTPRequest这个“数据入口”使用更频繁了,但遗憾的是XMLHTTPRequest对象本身没有像浏览器对 Form的处理那么勤劳,同时它的无刷新特性让我们可以动态将服务端返回给我们的数据显示在当前已经被渲染过的页面内,相对于FORM这个数据入 口,XMLHTTPRequest还存在一个“数据出口”的问题,因此这里的乱码现象更加频繁。
1、“数据入口”
一般情况下,XMLHTTPRequest发送的数据来源有两种:页面源码和XMLHTTPRequest所在的Javascript的文件源码。为何会将它们分开了?这里我们来假设几种不同情况:

 • JS获取用户在表单元素中的输入数据,然后设置XMLHTTPRequest

    在上文中已经提到了Form表单中数据的编码问题,用户输入的字符编码是和网页编码一致的,因此这里JS获取的数据编码和网页编码是一致的。

  • JS源码中预设的数据

    某些情况下,我们会将一些预定参数在程序中写死(这里就不要讨论程序扩展性了),那么这种情况的编码就得取决于文件编辑时编辑器输出字符 的编码了。这就是我们的一个准则中建议的,你需要保持文件编码和字符编码的统一。JS本身是基于UNICODE编码的,所以这是我推荐文件用UTF-8编 码的理由之一。

    现在我们已经知道了XMLHTTPRequest发送的数据的编码,接着我们来看发送数据会出现什么问题。
XMLHTTPRequest不同于表单数据的发送,它不会给我们的数据做序列化,不会自动给我们做URL编码,也不能在客户端指定发送的编码类 型。(PS:虽然setRequestHeader方法可以设置发送请求Header中的charset,不过我经过测试和对Header信息的监视,发 现这个设置从Header中看是有效的,但服务端接收到的数据始终是UTF-8的,查了些资料没有找到原因在哪儿,暂且认为XMLHTTPRequest 只能以UTF-8格式发送数据吧。但值得“庆幸”的是无论客户端的网页是什么编码,这里发送的UTF-8编码的数据都是正确转换过的,否则让我们自己在客 户端写转换功能那就相当棘手了)为了保证数据send到服务端不出问题,我们只有自己动手做XMLHTTPRequest没有给我们完成的工作了。
• 指定请求Header中的content-type

  XMLHTTPRequest.setRequestHeader('Content-Type','application/x-www-form-urlencoded');

  • 将序列化的数据进行URL编码
这里可以使用encodeURI和encodeURIComponent 
这样以来,那么服务端就可以正确接收到我们POST过去的数据集了,这里需要注意,服务端得到的数据集是UTF-8格式的,是否做编码转换,那就是视你具体情况而定了。

  2、“数据出口”
无论XMLHTTPRequest GET的是一个静态页面,还是一个动态页面,XMLHTTPRequest都不关心,它只关心 responseText得到数据的编码,默认的编码是UTF-8,而这个编码是可以通过服务端直接设置的,这个在上文的指定网页编码中已经提到了。
因此当你需要在页面显示responseText的内容时,你需要很清楚地知道页面的编码和responseText的编码,如果他们不统一,那乱码的麻烦就有出来了,不过现在你知道问题在哪儿了。

  第四个原则:在使用AJAX时,尤其要注意数据流动中造成的编码类型改变。AJAX发送的是UTF-8编码,接收的编码默认是UTF-8,但允许服务端重新指定。

  总结:避免乱码的最简洁方法就是系统中所有文本文件都统一使用最通用的编码,(目前在WEB应用上当然是 UTF-8了)并在页面中指定charset,同时在服务端设置header中的charset,防止客户端浏览器的设置造成编码不一致。但如果你目前的 系统已经存在了编码不统一情况,那么你就需要根据具体情况找到做编码转换代价最小的地方,或许上面的分析能帮你解决问题。

  以上分析我的测试环境是IE6 、IE7、Firefox,有兴趣的话也可以动手测测,欢迎交流,如果有什么不当之处希望指出。

Tags: ajax, 乱码, 细节

Comet研究

说实话,在看到这个Comet被人提起来的时候,我真的不知道是什么意思。翻开金山词霸,结果求伯君同学告诉我,这是彗星的意思。

彗星?怎么着也不能理解。。。于是乎G了一下,发现原来Comet并不是一个新技术,而是和AJAX一样也是一个炒冷饭的东东。

目前用的最多的使用Comet的大概是两种方法,一种是iframe,一种就是ajax。

用ajax的就没有什么好讲了,无非就是setIntval();定一个时间循环往复,往复循环的用ajax读回数据。

用iframe呢。则是利用现在的一些JS类库(当然不用也可以)生成一个Iframe 的Element,在这个页面里打开一个PHP或者其他的动态网页,再由那个网页不停的生成生成再生成,然后通过iFrame的window.parent.xxx.innerHtml = 'this...value...';通过这种方法将iframe里得到的内容生成到主页面上。

细看看这两种,哪种不是炒冷饭?只是冠以一个好听的名字而己,自从2001年,网络聊天室开始有无刷新聊天时,iframe取得数据返回主页面这种方法就早己经存在。AJAX虽然是最近几年的新酒,但何尝不是原来的那些方法的集合?只是以前可能用的是setIntval定时用window.location.reload();来进行页面刷新,现在是用AJAX取回数据再通过innerHtml来更新DIV区域而己。

自此,对Comet有点失望。毕竟对这些都只是一些简单的封装,没有什么新的东西。

 

Tags: iframe, ajax, comet