如何用Django处理gzip数据流


Posted in Python onJanuary 29, 2021

最近在工作中遇到一个需求,就是要开一个接口来接收供应商推送的数据。项目采用的python的django框架,我是想也没想,就直接一梭哈,写出了如下代码:

class XXDataPushView(APIView):
  """
  接收xx数据推送
  """
		# ...
  @white_list_required
  def post(self, request, **kwargs):
    req_data = request.data or {}
				# ...

但随后,发现每日数据并没有任何变化,质问供应商是否没有做推送,在忽悠我们。然后对方给的答复是,他们推送的是gzip压缩的数据流,接收端需要主动进行解压。此前从没有处理过这种压缩的数据,对方具体如何做的推送对我来说也是一个黑盒。

因此,我要求对方给一个推送的简单示例,没想到对方不讲武德,仍过来一段没法单独运行的java代码:

private byte[] compress(JSONObject body) {
  try {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    GZIPOutputStream gzip = new GZIPOutputStream(out);
    gzip.write(body.toString().getBytes());
    gzip.close();
    return out.toByteArray();
  } catch (Exception e) {
    logger.error("Compress data failed with error: " + e.getMessage()).commit();
  }
  return JSON.toJSONString(body).getBytes();
}

public void post(JSONObject body, String url, FutureCallback<HttpResponse> callback) {
  RequestBuilder requestBuilder = RequestBuilder.post(url);
  requestBuilder.addHeader("Content-Type", "application/json; charset=UTF-8");
  requestBuilder.addHeader("Content-Encoding", "gzip");

  byte[] compressData = compress(body);

  int timeout = (int) Math.max(((float)compressData.length) / 5000000, 5000);

  RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
  requestConfigBuilder.setSocketTimeout(timeout).setConnectTimeout(timeout);

  requestBuilder.setEntity(new ByteArrayEntity(compressData));

  requestBuilder.setConfig(requestConfigBuilder.build());

  excuteRequest(requestBuilder, callback);
}

private void excuteRequest(RequestBuilder requestBuilder, FutureCallback<HttpResponse> callback) {
  HttpUriRequest request = requestBuilder.build();
  httpClient.execute(request, new FutureCallback<HttpResponse>() {
    @Override
    public void completed(HttpResponse httpResponse) {
      try {
        int responseCode = httpResponse.getStatusLine().getStatusCode();
        if (callback != null) {
          if (responseCode == 200) {
            callback.completed(httpResponse);
          } else {
            callback.failed(new Exception("Status code is not 200"));
          }
        }
      } catch (Exception e) {
        logger.error("Get error on " + requestBuilder.getMethod() + " " + requestBuilder.getUri() + ": " + e.getMessage()).commit();
        if (callback != null) {
          callback.failed(e);
        }
      }

      EntityUtils.consumeQuietly(httpResponse.getEntity());
    }

    @Override
    public void failed(Exception e) {
      logger.error("Get error on " + requestBuilder.getMethod() + " " + requestBuilder.getUri() + ": " + e.getMessage()).commit();
      if (callback != null) {
        callback.failed(e);
      }
    }

    @Override
    public void cancelled() {
      logger.error("Request cancelled on " + requestBuilder.getMethod() + " " + requestBuilder.getUri()).commit();
      if (callback != null) {
        callback.cancelled();
      }
    }
  });
}

从上述代码可以看出,对方将json数据压缩为了gzip数据流stream。于是搜索django的文档,只有这段关于gzip处理的装饰器描述:

django.views.decorators.gzip 里的装饰器控制基于每个视图的内容压缩。

  • gzip_page()

如果浏览器允许 gzip 压缩,那么这个装饰器将压缩内容。它相应的设置了 Vary 头部,这样缓存将基于 Accept-Encoding 头进行存储。

但是,这个装饰器只是压缩请求响应至浏览器的内容,我们目前的需求是解压缩接收的数据。这不是我们想要的。

幸运的是,在flask中有一个扩展叫flask-inflate,安装了此扩展会自动对请求来的数据做解压操作。查看该扩展的具体代码处理:

# flask_inflate.py
import gzip
from flask import request

GZIP_CONTENT_ENCODING = 'gzip'


class Inflate(object):
  def __init__(self, app=None):
    if app is not None:
      self.init_app(app)

  @staticmethod
  def init_app(app):
    app.before_request(_inflate_gzipped_content)


def inflate(func):
  """
  A decorator to inflate content of a single view function
  """
  def wrapper(*args, **kwargs):
    _inflate_gzipped_content()
    return func(*args, **kwargs)

  return wrapper


def _inflate_gzipped_content():
  content_encoding = getattr(request, 'content_encoding', None)

  if content_encoding != GZIP_CONTENT_ENCODING:
    return

  # We don't want to read the whole stream at this point.
  # Setting request.environ['wsgi.input'] to the gzipped stream is also not an option because
  # when the request is not chunked, flask's get_data will return a limited stream containing the gzip stream
  # and will limit the gzip stream to the compressed length. This is not good, as we want to read the
  # uncompressed stream, which is obviously longer.
  request.stream = gzip.GzipFile(fileobj=request.stream)

上述代码的核心是:

request.stream = gzip.GzipFile(fileobj=request.stream)

于是,在django中可以如下处理:

class XXDataPushView(APIView):
  """
  接收xx数据推送
  """
		# ...
  @white_list_required
  def post(self, request, **kwargs):
    content_encoding = request.META.get("HTTP_CONTENT_ENCODING", "")
    if content_encoding != "gzip":
      req_data = request.data or {}
    else:
      gzip_f = gzip.GzipFile(fileobj=request.stream)
      data = gzip_f.read().decode(encoding="utf-8")
      req_data = json.loads(data)
    # ... handle req_data

ok, 问题完美解决。还可以用如下方式测试请求:

import gzip
import requests
import json

data = {}

data = json.dumps(data).encode("utf-8")
data = gzip.compress(data)

resp = requests.post("http://localhost:8760/push_data/",data=data,headers={"Content-Encoding": "gzip", "Content-Type":"application/json;charset=utf-8"})

print(resp.json())

以上就是如何用Django处理gzip数据流的详细内容,更多关于Django处理gzip数据流的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
Python实现抓取页面上链接的简单爬虫分享
Jan 21 Python
python3爬虫获取html内容及各属性值的方法
Dec 17 Python
浅谈Python 多进程默认不能共享全局变量的问题
Jan 11 Python
Python实现的对本地host127.0.0.1主机进行扫描端口功能示例
Feb 15 Python
python三大神器之fabric使用教程
Jun 10 Python
django数据关系一对多、多对多模型、自关联的建立
Jul 24 Python
Pytorch实现各种2d卷积示例
Dec 30 Python
Django bulk_create()、update()与数据库事务的效率对比分析
May 15 Python
Python模拟伯努利试验和二项分布代码实例
May 27 Python
Python基于Twilio及腾讯云实现国际国内短信接口
Jun 18 Python
深入探讨opencv图像矫正算法实战
May 21 Python
Python OpenGL基本配置方式
May 20 Python
Spy++的使用方法及下载教程
Jan 29 #Python
Python实现随机爬山算法
Jan 29 #Python
用pushplus+python监控亚马逊到货动态推送微信
Jan 29 #Python
用python监控服务器的cpu,磁盘空间,内存,超过邮件报警
Jan 29 #Python
python热力图实现简单方法
Jan 29 #Python
Ubuntu20.04环境安装tensorflow2的方法步骤
Jan 29 #Python
python3定位并识别图片验证码实现自动登录功能
Jan 29 #Python
You might like
Flash空降上海 化身大魔王接受挑战
2020/03/02 星际争霸
用Flash图形化数据(一)
2006/10/09 PHP
php下将XML转换为数组
2010/01/01 PHP
新手学习PHP的一些基础知识分享
2011/07/27 PHP
php截取字符串并保留完整xml标签的函数代码
2013/02/06 PHP
php实现图片添加描边字和马赛克的方法
2014/12/10 PHP
浅谈PHP中关于foreach使用引用变量的坑
2016/11/14 PHP
关于ThinkPhp 框架表单验证及ajax验证问题
2017/07/19 PHP
Laravel 5.5 的自定义验证对象/类示例代码详解
2017/08/29 PHP
laravel5环境隐藏index.php后缀(apache)的方法
2019/10/12 PHP
DWZ刷新dialog解决方法
2013/03/03 Javascript
浅谈JavaScript中Date(日期对象),Math对象
2015/02/05 Javascript
JavaScript中String.prototype用法实例
2015/05/20 Javascript
JavaScript实现可拖拽的拖动层Div实例
2015/08/05 Javascript
web 前端常用组件之Layer弹出层组件
2016/09/22 Javascript
基于JS实现checkbox全选功能实例代码
2016/10/31 Javascript
分享bootstrap学习笔记心得(组件及其属性)
2017/01/11 Javascript
AngularJS 中的数据源的循环输出
2017/10/12 Javascript
vue使用技巧及vue项目中遇到的问题
2018/06/04 Javascript
微信小程序实现传递多个参数与事件处理
2019/08/12 Javascript
js获取浏览器地址(获取第1个斜杠后的内容)
2019/09/03 Javascript
[45:56]Ti4正赛第一天 VG vs NEWBEE 3
2014/07/19 DOTA
python中使用urllib2伪造HTTP报头的2个方法
2014/07/07 Python
Python 使用SMTP发送邮件的代码小结
2016/09/21 Python
python中logging库的使用总结
2017/10/18 Python
打包python 加icon 去掉cmd黑窗口方法
2019/06/24 Python
python json load json 数据后出现乱序的解决方案
2020/02/27 Python
Python 解决火狐浏览器不弹出下载框直接下载的问题
2020/03/09 Python
python简单实现最大似然估计&amp;scipy库的使用详解
2020/04/15 Python
浅谈html5之sse服务器发送事件EventSource介绍
2017/08/28 HTML / CSS
Aveda美国官网:天然护发产品、洗发水、护发素和沙龙
2016/12/09 全球购物
我能否用void** 指针作为参数, 使函数按引用接受一般指针
2013/02/16 面试题
SQL面试题
2013/04/30 面试题
2014年社区学雷锋活动总结
2014/03/09 职场文书
宣传活动总结范文
2014/07/01 职场文书
2015年度保密工作总结
2015/04/24 职场文书