<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>年华转瞬 &#187; 编码</title>
	<atom:link href="http://blog.xiaket.org/tag/%e7%bc%96%e7%a0%81/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.xiaket.org</link>
	<description>xiaket 的网志</description>
	<lastBuildDate>Sat, 21 Aug 2010 02:31:27 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>基于Django/jQuery的多文件上传实现 — 第五天</title>
		<link>http://blog.xiaket.org/2010/01/17/django-jquery-file-upload-day5/</link>
		<comments>http://blog.xiaket.org/2010/01/17/django-jquery-file-upload-day5/#comments</comments>
		<pubDate>Sat, 16 Jan 2010 16:50:27 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Django开发]]></category>
		<category><![CDATA[Web开发]]></category>
		<category><![CDATA[Uploadify]]></category>
		<category><![CDATA[编码]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=271</guid>
		<description><![CDATA[隔了几天, 接着做这个项目.

首先是上传, 前几天js, 视图, 文件句柄这块的代码比较乱, 今天首先整理了下. 保证代码中只有最简单的逻辑, 包括文件上传前js提示上传了什么文件, 视图函数里不保存这个文件, 而是直接返回字符串.

今天碰到的第一个问题是上传后js里面的onComplete没有执行. 我首先猜测是视图函数处理有错误. 看Apache的log发现返回的状态值是200, 这证明swf已经post过来数据而且拿到正确的返回值了. 进一步的debug发现视图函数的逻辑已经全部正确地处理完了. 我猜测是由于我的视图函数太简单, 没有读取和处理上传的数据, 导致swf客户端有问题, 于是用python的logging模块记录了request的具体信息. 用Django文件处理的样例函数保存了上传的数据. 测试发现上传的数据能够成功地保存, 因此服务器端是没有问题的. 这个应该是比较容易理解的, 因为无论服务器端是否处理上传的数据, 这个数据在swf客户端的眼里已经上传到服务器了. 那么我们接下来应该做的事情就比较简单了: 处理客户端js脚本的函数调用, 看到底是什么地方出了问题.

<span class="readmore"><a href="http://blog.xiaket.org/2010/01/17/django-jquery-file-upload-day5/" title="基于Django/jQuery的多文件上传实现 — 第五天">阅读全文——共1455字</a></span>]]></description>
			<content:encoded><![CDATA[<p>隔了几天, 接着做这个项目.</p>
<p>首先是上传, 前几天js, 视图, 文件句柄这块的代码比较乱, 今天首先整理了下. 保证代码中只有最简单的逻辑, 包括文件上传前js提示上传了什么文件, 视图函数里不保存这个文件, 而是直接返回字符串.</p>
<p>今天碰到的第一个问题是上传后js里面的onComplete没有执行. 我首先猜测是视图函数处理有错误. 看Apache的log发现返回的状态值是200, 这证明swf已经post过来数据而且拿到正确的返回值了. 进一步的debug发现视图函数的逻辑已经全部正确地处理完了. 我猜测是由于我的视图函数太简单, 没有读取和处理上传的数据, 导致swf客户端有问题, 于是用python的logging模块记录了request的具体信息. 用Django文件处理的样例函数保存了上传的数据. 测试发现上传的数据能够成功地保存, 因此服务器端是没有问题的. 这个应该是比较容易理解的, 因为无论服务器端是否处理上传的数据, 这个数据在swf客户端的眼里已经上传到服务器了. 那么我们接下来应该做的事情就比较简单了: 处理客户端js脚本的函数调用, 看到底是什么地方出了问题.</p>
<p>要debug, 首先将这个函数的内部逻辑减到最少, 我写了一个简单的函数, 里面只有一个alert语句, 然后在uploadify初始化的调用中, 写下:</p>
<pre class="brush: jscript;">
        'onSelect'       : function (event, ID, fileObj){ onSelect(ID, fileObj);},
        //'onComplete'     : function (event, ID, fileObj, response, data){ onComplete(queueID, fileObj,          response, data);},
        'onComplete'     : function (event){ simplealert();},
</pre>
<p>点击, 执行上传, 发现alert语句能够正确执行.</p>
<p>我特意在视图函数里面加了一个sleep. 测试发现这个alert语句是在视图函数处理完(sleep完)之后才执行的, 因此, 目前我们的代码都还是按照我们的思路在运行的.</p>
<p>接下来的debug让我发现我在前面犯了一个很低级的错误, 我上次为了简单将有些函数调用里的queueID简写成了ID, 而今天在我写onComplete这个函数的时候又使用了旧的queueID这个名字. 真囧. 修改好以后onComplete就能在正常的视图函数返回后显示服务器端返回的内容了.</p>
<p>接下来测试是否能正常保存一个中文文件. 我前面是把post过来的内容直接存到一个固定的文件里面去. 而现在我尝试根据客户端传过来的文件创建一个新文件. 当上传一个英文名的文件时, 系统运行是正常的, 当上传一个中文名的文件时, 却有问题, 日志记录下的错误是UnicodeEncodeError, 又遇到编码问题了.</p>
<p>现在我的目的很简单, 就是上传的文件能够全部放到/home/apache/bookshop/media/temp/这个文件夹下. 几番尝试后, 发现适当地encode所涉及到的字符串就能解决中文文件上传的编码问题了:</p>
<pre class="brush: python;">
    name = file.name.encode(&quot;utf8&quot;)
    dir = u'/home/apache/bookshop/media/temp/'.encode(&quot;utf8&quot;)
    filename = dir + name
    dest = open(filename, 'wb+')
</pre>
<p>下次该考虑进度条相关的逻辑了~  今天已经晚了, 就这样吧~</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2010/01/17/django-jquery-file-upload-day5/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>讨论一个纠结的Django编码问题</title>
		<link>http://blog.xiaket.org/2009/12/22/django-encoding-problem/</link>
		<comments>http://blog.xiaket.org/2009/12/22/django-encoding-problem/#comments</comments>
		<pubDate>Tue, 22 Dec 2009 13:54:08 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Django开发]]></category>
		<category><![CDATA[GBK]]></category>
		<category><![CDATA[GET]]></category>
		<category><![CDATA[REQUEST]]></category>
		<category><![CDATA[WSGI]]></category>
		<category><![CDATA[编码]]></category>

		<guid isPermaLink="false">http://blog.xiaket.org/?p=199</guid>
		<description><![CDATA[简介

本文简单记录了我在项目中遇到的一个Django编码问题. 通过Django源码分析, 我们找到了问题的根源, 并提出了解决问题的方法.

问题总结: 用WSGI方式使用Django, 请求的URL是GBK编码, 而且在视图函数中使用了request.REQUEST字典的话, 可能会有编码问题.

<span class="readmore"><a href="http://blog.xiaket.org/2009/12/22/django-encoding-problem/" title="讨论一个纠结的Django编码问题">阅读全文——共4454字</a></span>]]></description>
			<content:encoded><![CDATA[<h3>简介</h3>
<p>本文简单记录了我在项目中遇到的一个Django编码问题. 通过Django源码分析, 我们找到了问题的根源, 并提出了解决问题的方法.</p>
<p>问题总结: 用WSGI方式使用Django, 请求的URL是GBK编码, 而且在视图函数中使用了request.REQUEST字典的话, 可能会有编码问题.</p>
<h3>问题背景</h3>
<p>问题的背景是这样的, 在我接手的报警项目中, 报警服务器提交到服务器的报警信息内容是GBK编码的, 例如下面的URL是一个典型的报警请求:</p>
<pre>

http://192.168.24.133/msggw.php?from_ip=1.2.3.4&#038;project_name=blah&#038;msg=blah+427%28%D0%A1%C3%B7%C9%B3%29+1.2.3.4+has+new+debug+log%2Ccheck%21%21%21%21%0ATue+Dec+22+08%3A01%3A18+2009%2822172096%29%28539088508%29%09shutdownOS%280%29
</pre>
<p>为了处理GBK编码, 我们要在视图函数里面设置request的编码, 例如:</p>
<pre class="brush: python;">
def receiving_message(request):
    request.encoding = &quot;gbk&quot;
</pre>
<p>这样, 在这个语句之后, 我们理论上应该不会再遇到编码问题. 但是不幸的是, 我还是发现有编码问题, 问题似乎来自我在视图函数的后半部分使用了request.REQUEST这个字典.</p>
<h3>代码背后的故事</h3>
<p>今天, 我在确定这个问题来自Django处理request后, 仔细读了下相关的源码. 在Django产生一个request对象到在视图函数里面使用request.GET/request.POST的具体细节包括:</p>
<h4>由WSGI脚本生成了一个request对象.</h4>
<p>相关代码在django/core/handlers/wsgi.py文件中, 生成的request对象是WSGIRequest的一个实例. 这个实例的初始化基本只是设置了request.META这个字典的内容. 接下来这个对象就被传给视图函数了.(不清楚中间具体还发生了什么事情, 哪天有机会再去仔细研究下&#8230;)</p>
<h4>在视图函数里面使用GET/POST字典</h4>
<p>这个过程看起来很简单, 实际上却比较复杂. 首先, 在WSGIRequest这个对象定义的结尾处, Django设置了使用GET/POST/REQUEST这三个字典的方法. 我们使用GET/POST字典实际上是通过执行这些内置的方法来实现的. 例如, 我们需要知道request.GET['msg']的内容, 此时, Django是通过_get_get方法来拿到GET字典的内容的:</p>
<pre class="brush: python;">
    def _get_get(self):
        if not hasattr(self, '_get'):
            # The WSGI spec says 'QUERY_STRING' may be absent.
            self._get = http.QueryDict(self.environ.get('QUERY_STRING', ''), encoding=self._encoding)
        return self._get
</pre>
<p>Django文档中说, Django在处理GET/POST时是很懒惰的, 如果没有明确要GET/POST中的内容, Django是不会浪费时间生成这两个字典的. 上面这个函数里面的if就是一个明确的证据. 如果没有_get属性, 则实例化一个QueryDict(代码在django/http/__init__.py中), 并将其作为_get的内容, 也即是GET的内容. 在实例化的过程中给出了两个参数, 一个是QUERY_STRING, 一个是_encoding. 如果你还没有在视图函数里面设置编码, 那么此时_encoding是默认的值(utf-8). 在QueryDict的__init__方法里面, 我们看到这样的语句:</p>
<pre class="brush: python;">
        self.encoding = encoding
        for key, value in parse_qsl((query_string or ''), True): # keep_blank_values=True
            self.appendlist(force_unicode(key, encoding, errors='replace'),
                            force_unicode(value, encoding, errors='replace'))
</pre>
<p>这一段代码负责生成GET字典的内容. 其中那个parse_qsl函数来自python默认的cgi模块, 作用是将QUERY_STRING分解为一个字典. 对于字典里面的每个值, Django使用force_unicode方法(代码在django/utils/encoding.py文件中)将其编码解码. 顺便说一句, 这个force_unicode函数会做一些诡异的事情, 例如将一个GBK字符串转码不成功且不论, 它会生成类似u&#8217;\u68a6\u5e7b:\u0421\xf7\u0273&#8242;的字符串, 很乱很杂交.</p>
<p>上面说了这么多, 总结成一句话: Django处理视图函数时, 直到你明确要求GET字典中的键值时, 这个字典才会被生成, 生成字典时按照编码的配置做了编码转码等工作.</p>
<h4>设置编码</h4>
<p>我们在文章的一开头就给出了设置Django处理QUERY_STRING的编码的方法, 即直接指定request的encoding. 这句话看起来简单, 实际上却在背后做了一些额外的工作, 这是因为在Django对HttpRequest对象(代码在django/http/__init__.py中)的定义中有这么一句:</p>
<pre class="brush: python;">
encoding = property(_get_encoding, _set_encoding)
</pre>
<p>即, 要取得编码的值, 实际上是调用了_get_encoding方法, 要设置编码的值, 实际上调用了_set_encoding方法. 我们关注的是后一种方法, 它的定义为:</p>
<pre class="brush: python;">
    def _set_encoding(self, val):
        &quot;&quot;&quot;
        Sets the encoding used for GET/POST accesses. If the GET or POST
        dictionary has already been created, it is removed and recreated on the
        next access (so that it is decoded correctly).
        &quot;&quot;&quot;
        self._encoding = val
        if hasattr(self, '_get'):
            del self._get
        if hasattr(self, '_post'):
            del self._post
</pre>
<p>很聪明很合乎逻辑的做法, 如果用户设置了编码, 那么我们把_get和_post都删除掉, 这样也就对应地删除了GET/POST这两个字典. 下次用户再想要拿这两个字典里面的内容时, 再用新的编码再生成一次GET/POST就是了.</p>
<h4>request.REQUEST字典</h4>
<p>这个字典是模仿php中的对应快捷方式而建立的, 实际上只是将GET和POST合并起来, 代码在django/core/handlers/wsgi.py的WSGIRequest定义中:</p>
<pre class="brush: python;">
    def _get_request(self):
        if not hasattr(self, '_request'):
            self._request = datastructures.MergeDict(self.POST, self.GET)
        return self._request
</pre>
<h3>哪段代码有问题?</h3>
<p>上面洋洋洒洒一大篇描述了Django处理GET/POST字典的原理, 我接下来要说的是这种处理过程中有哪些问题. 我们看看下面这段视图函数中的代码:</p>
<pre class="brush: python;">
def receiving_message(request):
    msg1 = request.REQUEST['msg']
    request.encoding = &quot;gbk&quot;
    msg2 = request.REQUEST['msg']
</pre>
<p>首先, 第一次拿到的msg1显然是乱码, 我们还没设置处理URL的编码, 因此URL没有被正确地解码. 但是不幸地是, 第二次拿到的msg2仍然是乱码. 这是因为设置编码的过程中仅仅删除了可能存在的GET和POST字典, 而没有删除REQUEST字典. 因此第二次我们从REQUEST字典里取东西时, 所拿到的结果仍然是按照不正确的编码解出来的结果.</p>
<p>这个问题出在哪儿? 虽然看起来应该认为上面的_set_encoding方法是有问题的, 应该额外加两行, 删除self._request才对. 不过问题不是那么简单的, HttpRequest对象里面根本没有request.REQUEST这个东东. 这个东东是在WSGIRequest中定义的. 我们应该在WSGIRequest的定义中override掉HttpRequest的默认_set_encoding方法才对.</p>
<p>再顺口提一句, modpython里面也没有override这个方法, 因此理论上也存在同样的问题.</p>
<h3>问题回顾</h3>
<p>上面说了很多, 但是为什么只有在很偶然的情况下才会出现编码问题呢? 在大多数情况下, request.REQUEST字典都没有生成. 因此在视图函数中, 设置编码后从request.REQUEST字典中拿东西出来是没有问题的. 但是在一定几率下(我只能这么说了&#8230;), 后面的视图函数代码中从request.REQUEST字典里拿到的数据可能是有编码问题的.</p>
<p>我在前面曾经推测这个问题的出现可能和request.REQUEST这个字典相关, 不过我在测试中发现, 即使这个字典没有出现, 也可能会出现编码问题. 哎哎, 无论如何, 这个是一个诡异的问题&#8230;</p>
<p>关于这个一定几率, 根据我的经验, 不是指某一段时间可以, 某一段时间不行; 而是某一次重启后可以, 某一次重启后不行&#8230; 也许这个问题在更深层次上和会话相关, 不过暂时还是不要这么钻牛角尖的好, 因为我们有一个比较放心的解决方案．</p>
<h3>解决方案</h3>
<p>比较放心也比较累赘的解决方案是在设置编码后, 将GET/POST/REQUEST都删掉:</p>
<pre class="brush: python;">
def receiving_message(request):
    request.encoding = &quot;gbk&quot;
    if hasattr(request, '_request'):
        del request._request
    if hasattr(request, '_get'):
        del request._get
    if hasattr(request, '_post'):
        del request._post
</pre>
<p>这样我想, 应该是不会再有问题了.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2009/12/22/django-encoding-problem/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>
