您的位置:首页 > 产品设计 > UI/UE

django 使用中间件 访问request里面的内容

2017-11-21 16:47 453 查看
博主要做一个记录请求api日志的事情。

声明:本文指定Django版本为1.8,其它版本实现方式自己网上搜!

方法一 Nginx记录请求和响应 我最初用Nginx实现过能记录请求的信息(已经实现,记录的日志写在文件上),但是要Nginx需要其它Linux软件才能实现记录响应信息!如果你的机器不能联网,没有yum等,或者我特么就是讨厌手动安装软件在Linux机器上! please skip!

方法二 项目中有人提出利用装饰器在每个view上面利用装饰器模式来记录日志 。第一:耦合,耦合,耦合!我觉得是高度耦合!虽然只是import 这个方法,然后加上@funciton!第二:这个装饰器记录不了response的信息!所以也请你skip !

方法三 使用的是Django中的中间件(类似Java中serverlet中的拦截器)!

发现在中间件中的request与Django view中的request都是出自django.core.handlers.wsgi.WSGIRequest这个对象,瞬间就觉得棒!

能记录IP、用户等信息但是却不能访问请求中的body。原因是:you cannot access body after reading from request’s data stream

然后查看官网 发现:

Accessing request.POST or request.REQUEST inside middleware from process_request or process_view will prevent any view running after the middleware from being able to modify the upload handlers for the request, and should normally be avoided.

我们看看报错来自何处:

Django源码

@property
def body(self):
if not hasattr(self, '_body'):
if self._read_started:
raise RawPostDataException("You cannot access body after reading from request's data stream")

# Limit the maximum request data size that will be handled in-memory.
if (settings.DATA_UPLOAD_MAX_MEMORY_SIZE is not None and
int(self.META.get('CONTENT_LENGTH') or 0) > settings.DATA_UPLOAD_MAX_MEMORY_SIZE):
raise RequestDataTooBig('Request body exceeded settings.DATA_UPLOAD_MAX_MEMORY_SIZE.')

try:
self._body = self.read()
except IOError as e:
six.reraise(UnreadablePostError, UnreadablePostError(*e.args), sys.exc_info()[2])
self._stream = BytesIO(self._body)
return self._body


看这个意思说,别读过了,然后报错!why?

难道不行了吗?错!

你把这个值复制给另外一个变量就可以了:

re_request_body = getattr(request,'_body',request.body)
print re_request_body


你不能访问request.body,但是你可以访问复制过后的变量!还有,这个方法不适用在process_response方法中,我在process_request中是可以的!

Stack Overflow上有一个网友提到django restframework,我猜想,restframework它是继承Django的django.core.handlers.wsgi.WSGIRequest对象,他可以访问body,我想也是可以找到其它方法直接访问reqeust.body这个变量的,但是看了restframework源码,我一脸懵逼,能力有限,看不懂!最后,我附上国外友人提到的记录:

https://stackoverflow.com/questions/22740310/how-to-update-django-httprequest-body-in-middleware



最最后,我附上我能实现的代码:

import datetime
import random

class  middleware_record_req_rsp(object):

def __init__(self):
nowTime = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
randomNum = random.randint(0,1000)
uniqueNum = str(nowTime) + str(randomNum)
self.flag = uniqueNum

def process_request(self,request):
if request.method == 'POST':
x_forward_for = request.META.get('HTTP_X_FORWARDED_FOR')
request_ip = x_forward_for.split(',')[0] if x_forward_for else request.META.get('REMOTE_ADDR')
request_user = request.META.get('USER')
re_request_body = getattr(request,'_body',request.body)
flag = self.flag
return None

def process_response(self,request,response):
if request.method == 'POST':
flag = self.flag
response_status_code = response.status_code
return response


最后看到一篇介绍中间件不错的好文章!

http://blog.csdn.net/wolaiye320/article/details/52035451

ps:相比较而言,浏览官网的信息才是最好的。但是能去GitHub,Stack Overflow上最好,尤其是Stack Overflow(最近公司的网日了

——————-分割线——————–

一个很有意思的问题出现了!我之前提到在中间件 process_response(request,response)中的request是拿不到request.body这个变量的,因为会报”you cannot access body after reading from request’s data stream”这种错误!我猜想,process_response(request,response)和process_request(request)中的request不一样吧!

结果,我测试了一下:

把process_request中的request传给初始化变量,然后再去比较process_response(request,response)中的request变量,

id(request) == id(self.request) request is self.re_quest

发现是一样的。这就奇怪了!为什么我能在前者拿到body,在后面却拿不到值!

—————分割线——————

那么,在response的时候,是否可以修改response中的传给前端的值呢?官网如下解释:



it could alter the given response。Stack Overflow上有人问过:https://stackoverflow.com/questions/44112528/editing-response-contect-in-django-middleware

里面如下:

Response is already rendered in the middleware stage so you can't just change response.data, you need to rerender it or change rendered content directly.

class RequestLogMiddleWare(object):
def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
response = self.get_response(request)
if isinstance(response, Response):
response.data['detail'] = 'I have been edited'
# you need to change private attribute `_is_render`
# to call render second time
response._is_rendered = False
response.render()
return response
The second approach is just change content directly, but in that case built in rest framework browser API will not work because template will not render properly.

import json

class RequestLogMiddleWare(object):
def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
response = self.get_response(request)
if isinstance(response, Response):
response.data['detail'] = 'I have been edited'
response.content = json.dumps(response.data)
return response


然后我就是改掉content这个值,就可以实现更改了!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  django