<?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; REQUEST</title>
	<atom:link href="http://blog.xiaket.org/tag/request/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处理HTTP请求的过程</title>
		<link>http://blog.xiaket.org/2009/12/25/django-httpquest-procession/</link>
		<comments>http://blog.xiaket.org/2009/12/25/django-httpquest-procession/#comments</comments>
		<pubDate>Fri, 25 Dec 2009 08:07:28 +0000</pubDate>
		<dc:creator>xiaket</dc:creator>
				<category><![CDATA[Django开发]]></category>
		<category><![CDATA[Python开发]]></category>
		<category><![CDATA[REQUEST]]></category>
		<category><![CDATA[WSGI]]></category>

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

HTTP请求的处理是由wsgi或mod_python脚本处理的. 在我的机器上使用的WSGI, 这个脚本里面配置好Django运行环境后, 初始化了一个WSGI句柄的实例:



<span class="readmore"><a href="http://blog.xiaket.org/2009/12/25/django-httpquest-procession/" title="Django处理HTTP请求的过程">阅读全文——共4456字</a></span>]]></description>
			<content:encoded><![CDATA[<h3>WSGIHandler</h3>
<p>HTTP请求的处理是由wsgi或mod_python脚本处理的. 在我的机器上使用的WSGI, 这个脚本里面配置好Django运行环境后, 初始化了一个WSGI句柄的实例:</p>
<pre class="brush: python;">
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
</pre>
<p>这个句柄将处理HTTP请求, 我们跟进去看这个WSGI句柄的初始化. WSGIHandler这个类没有__init__方法, 因此使用的是base.BaseHandler的初始化方法:</p>
<pre class="brush: python;">
# django/core/handlers/base.py
class BaseHandler(object):
    # Changes that are always applied to a response (in this order).
    response_fixes = [
        http.fix_location_header,
        http.conditional_content_removal,
        http.fix_IE_for_attach,
        http.fix_IE_for_vary,
    ]

    def __init__(self):
        self._request_middleware = self._view_middleware = self._response_middleware = self._exception_middleware = None
</pre>
<p>好吧, 这个里面貌似也没干啥&#8230; 我们继续看WSGIHandler的__call__方法. 这个方法会被调用上面的wsgi脚本中的语句调用.</p>
<p>根据WSGI的文档:</p>
<pre>
The Web framework/app is represented to the server as a Python callable. It can be
a class, an object, or a function. The arguments to __init__, __call__, or the function
must be as above: an environ object and a start_response callable.
</pre>
<p>参考看下WSGIHandler的__call__方法, 很符合这个要求, 也是两个外部参数.</p>
<pre class="brush: python;">
# django/core/handlers/wsgi.py
class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest
    def __call__(self, environ, start_response):
        &quot;&quot;&quot; some code omitted here. &quot;&quot;&quot;
        signals.request_started.send(sender=self.__class__)
        try:
            try:
                request = self.request_class(environ)
            except UnicodeDecodeError:
                response = http.HttpResponseBadRequest()
            else:
                response = self.get_response(request)

            &quot;&quot;&quot; some more code omitted. &quot;&quot;&quot;
        finally:
            signals.request_finished.send(sender=self.__class__)

        try:
            status_text = STATUS_CODE_TEXT[response.status_code]
        except KeyError:
            status_text = 'UNKNOWN STATUS CODE'
        status = '%s %s' % (response.status_code, status_text)
        response_headers = [(str(k), str(v)) for k, v in response.items()]
        for c in response.cookies.values():
            response_headers.append(('Set-Cookie', str(c.output(header=''))))
        start_response(status, response_headers)
        return response
</pre>
<p>这个方法一开始还做了些和中间件相关的事情, 这儿就省略了. 我们从信号这一段开始, 在处理完中间件相关的逻辑后, 此处放出一个request_started的信号, 我们可以利用这个信号来处理一些对每个HTTP请求都要处理的逻辑. 接下来我们开始处理请求. 这儿用了一个try-except-else语句块. 首先我们尝试将environ作为参数来生成一个WSGIRequest类, 如果这一步出了问题, 我们返回400, 否则我们用get_response(request)来获得一个HttpRequest对象. 接下来Django放出了一个request_finished信号, 然后返回这个HTTP请求. 我们继续看处理请求的这部分.</p>
<h3>WSGIRequest</h3>
<p>前面的代码中, 我们已经在WSGIHandler中将其request_class属性设为一个WSGIRequest的实例, 并用一个装有环境变量的字典environ初始化了一个WSGIRequest. 我们接下来看Django是怎么处理这个WSGIRequest的.</p>
<p>首先看这个类的初始化. 相关代码如下:</p>
<pre class="brush: python;">
class WSGIRequest(http.HttpRequest):
    def __init__(self, environ):
        script_name = base.get_script_name(environ)
        path_info = force_unicode(environ.get('PATH_INFO', u'/'))
        if not path_info or path_info == script_name:
            &quot;&quot;&quot; some comments omitted here. &quot;&quot;&quot;
            path_info = u'/'
        self.environ = environ
        self.path_info = path_info
        self.path = '%s%s' % (script_name, path_info)
        self.META = environ
        self.META['PATH_INFO'] = path_info
        self.META['SCRIPT_NAME'] = script_name
        self.method = environ['REQUEST_METHOD'].upper()
        self._post_parse_error = False
</pre>
<p>这段初始化基本上也是比较平淡的, 主要做了两件事, 一个是设置了request.META这个字典, 另外设置了request.method. 如果这一段成功了, 那么我们将用get_response来处理这个WSGIRequest.</p>
<h3>get_response</h3>
<p>这个函数的定义在django/core/handlers/base.py中, 是BaseHandler的一个方法, 代码如下:</p>
<pre class="brush: python;">
class BaseHandler(object):
    # Changes that are always applied to a response (in this order).
    def get_response(self, request):
        &quot;Returns an HttpResponse object for the given HttpRequest&quot;
        &quot;&quot;&quot; Some code omitted here. &quot;&quot;&quot;
        # Get urlconf from request object, if available.  Otherwise use default.
        urlconf = getattr(request, &quot;urlconf&quot;, settings.ROOT_URLCONF)
        resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
        try:
            callback, callback_args, callback_kwargs = resolver.resolve(
                    request.path_info)
            &quot;&quot;&quot; Some code omitted here. &quot;&quot;&quot;
            # Actually, this line was in a try-except thing...   by xiaket.
            response = callback(request, *callback_args, **callback_kwargs)
            &quot;&quot;&quot; Some code omitted here. &quot;&quot;&quot;
            return response
        except http.Http404, e:
            if settings.DEBUG:
                from django.views import debug
                return debug.technical_404_response(request, e)
            else:
                try:
                    callback, param_dict = resolver.resolve404()
                    return callback(request, **param_dict)
                except:
                    try:
                        return self.handle_uncaught_exception(request, resolver, sys.exc_info())
                    finally:
                        receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
        &quot;&quot;&quot; Some code omitted here. &quot;&quot;&quot;
</pre>
<p>原来的代码很长, 仔细分析, 除掉了一些我不太关心的异常处理后就变成了现在这个样子. 这个函数接受一个HttpRequest实例, 返回一个HttpRequest实例. 函数一开始就来匹配URL. 第7行这句很强大, 因为它提供了很多可能性. 例如, 你可以通过中间件来对于request中的IP做判断, 然后通过设置urlconf来决定使用什么样的URL匹配方式. 当然, 这个时候已经经过了中间件, request.user已经设置好了, 你也可以根据用户的不同来提供不同的urlconf. 从settings.ROOT_URLCONF中拿到了URL配置后, 我们尝试匹配URL, 然后在第11行拿第10行匹配得到的适当的view函数来处理这个URL. 到视图函数以后就没有太多好说了. 中间那层我们略过, 返回了response之后也没啥了, 到这儿来就交给WSGI了. Django处理HTTP请求的逻辑也就结束了.</p>
<p>挖个坑, 实际上Django有很多HTTP请求的细节是在中间件里面完成的, 这篇文章完全没有提及这一点, 我有时间再仔细分析下中间件的作用, 为强大的中间件们正名~</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.xiaket.org/2009/12/25/django-httpquest-procession/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>
