五、Request

1.      Request

由于python函数所有变量都没有显示类型声明,特别是函数的输入参数,输出参数,因此在阅读代码时会造成比较大的困扰,比如大部分处理函数都有request输入参数,不同模块的的request对于的类型不同,比如在socketserver.py模块,request就是一个_socketobject实体,在WSGIHandler里面是WSGIRequest(HTTPRequest)实体。

Django中对到来的http请求数据进行解析和缓存的数据流程如下图所示。

 

在上图中,WSGIServer在process_request()成员函数中调用self.get_request()返回[request, clientAddr],这里的request仅仅是一个_socketobject类实体,[request, clientAddr]作为入参,初始化WSGIRequestHandler,在WSGIRequestHandler模块,首先根据reqeust(socket),创建输入输出缓冲区rfile和wfile,然后对http头部进行一些基本的解析操作,解析的结果保存到self.command,self.version,self.path等成员变量。解析主要通过下面几个调用实现:

self.raw_requestline = self.rfile.readline(65537)
self.parse_request()
self.get_environ()

       其中get_environ()将解析到的头部信息,通过字典的形式保存起来并作为ServerHandler的输入参数之一,这样ServerHandler初始化后的实体base_env获得了这些环境变量信息(头部信息)。ServerHandler共有三个变量[os_environ, base_env, environ]来保存环境变量,其中os_environ保存系统的参数,如程序运行的主机参数等,base_env即ServerHandler初始化时由上层(WSGIRequestHandler)传递过来的HTTP头部请求参数,environ在[os_environ, base_env]基础上添加部分WSGI参数构成。

    ServerHandler.run(application)à application(self.environ, self.start_response)

       通过上述调用,environ直接传递给WSGIHandler,而在WSGIHandler模块里面,直接将environ再次传递给了WSGIRequest实体。

         request = WSGIHandler.request_class(environ)

1.1       HTTP body解析

对于POST操作,通常需要在HTTP body里携带用户信息,这些用户信息是何时去读取?何时去解析?最终保存到哪里呢?保存的格式又是怎么样的呢?

Django对HTTP body的解析是分为两步走策略,第一步,设置环境变量,设置回调函数等,为读取body做准备;第二步,在对这些body信息进行引用时,才会去调用设置的回调函数来读取和解析body参数。这中机制能够做到开销最小化——只有在需要对body信息进行引用时才进行读取和解析。

第一步、设置回调函数,设置环境变量

1、class WSGIRequest(http.HttpRequest)定义的最后两行语句独立于所有其他成员函数,因此这两行命令在加载(import)WSGIRequest时,或者在加载(import)引用WSGIRequest的类时进行执行。
POST = property(_get_post, _set_post)
FILES = property(_get_files)

事实上,因为WSGIHandler类引用了WSGIRequest类,因此在执行如下命令时,会执行上面两条命令:

from django.core.handlers.wsgi import WSGIHandler

而这条命令通常会在初始化django应用时即得到执行。显然,这种命令应该是静态命令,与程序执行前后的上下文无关,事实上,也只是对部分接口进行装饰,这样,后续,对

request.POST.get()/request.POST.set()操作时,实际上调用的是request. _get_post()/request. _set_post()的操作。request.FILES.get()也是如此。

2、对于到来的HTTP请求,Django在初始化WSGIRequest(HTTPRquest)实体时,对头部信息进行简单解析和继承(因为在前面的WSGIRequestHandler阶段已经进行了一定程度的绩溪),在解析到content_length后,初始化一个stream实体,为后续读取body

self._stream = LimitedStream(self.environ['wsgi.input'], content_length)

同样,LimitedStream()定义了自己的read(),readline()接口。

第二步、引用触发读取和解析过程。

在设置回调函数完毕之后,后续模块(中间件模块,handler模块等)如果需要对http body进行引用,就会触发读取和解析过程。例如,在csrfmidlerware中间件中如下调用会触发读取和解析操作:

request_csrf_token = request.POST.get('csrfmiddlewaretoken', '')

request.POST.get重定向到WSGIRequest:_get_post()-->

   if not hasattr(self, '_post'):  /*如果前面已经对body读取和解析了,会填充该成员变量*/

      self._load_post_and_files()       /*读取http body 并解析*/

   return self._post         /*前面已经对body读取和解析了,直接返回 */

 

HttpRequest: _load_post_and_files()-->

  if self.content_type == 'multipart/form-data':

self._post, self._files = self.parse_file_upload(self.META, data)

  elif self.content_type == 'application/x-www-form-urlencoded':

self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict()

  else:

self._post, self._files = QueryDict(encoding=self._encoding), MultiValueDict()

可见,Django只对multipart/form-data和application/x-www-form-urlencoded格式的http-body进行了解析,并将解析的结果以QueryDict的格式保存在self._post中,其余格式也保存在self._post中,但是并没有解析。如果需要添加django对json解析,在此次添加也不失为一种好策略。

另外,需要注意QueryDict的输入参数之一self.body,它完成对body数据的读取,并以字符的形式保存一个副本。

HttpRequest: body()-->

  if self._read_started:

raise RawPostDataException()

/*读取http body,此时的self._stream 指向第一步设置的LimitedStream 实体*/

  self._body = self.read()àreturn self._stream.read(*args, **kwargs)

 

  self._stream = BytesIO(self._body)  /*读取完毕之后恢复stream为指向self._body ,便于后续处理*/

  return self._body

posted on 2016-09-30 22:22  arduino  阅读(544)  评论(0编辑  收藏  举报

导航