如何用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获取脚本所在目录的正确方法
Apr 15 Python
零基础写python爬虫之使用urllib2组件抓取网页内容
Nov 04 Python
利用Celery实现Django博客PV统计功能详解
May 08 Python
实例讲解Python爬取网页数据
Jul 08 Python
Python实现简易过滤删除数字的方法小结
Jan 09 Python
详解Python Matplot中文显示完美解决方案
Mar 07 Python
Pandas 解决dataframe的一列进行向下顺移问题
Dec 27 Python
Django 批量插入数据的实现方法
Jan 12 Python
python def 定义函数,调用函数方式
Jun 02 Python
pycharm 复制代码出现空格的解决方式
Jan 15 Python
一篇文章带你搞懂Python类的相关知识
May 20 Python
Django实现drf搜索过滤和排序过滤
Jun 21 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
如何开发一个虚拟域名系统
2006/10/09 PHP
执行、获取远程代码返回:file_get_contents 超时处理的问题详解
2013/06/25 PHP
codeigniter教程之上传视频并使用ffmpeg转flv示例
2014/02/13 PHP
dedecms函数分享之获取某一栏目所有子栏目
2014/05/19 PHP
详解Yii2 rules 的验证规则
2016/12/02 PHP
JavaScript 学习初步 入门教程
2010/03/25 Javascript
JavaScript的类型转换(字符转数字 数字转字符)
2010/08/30 Javascript
疯狂Jquery第一天(Jquery学习笔记)
2012/05/11 Javascript
jquery删除数据记录时的弹出提示效果
2014/05/06 Javascript
jquery拖拽排序简单实现方法(效果增强版)
2016/02/16 Javascript
微信小程序 Record API详解及实例代码
2016/09/30 Javascript
Vue中引入样式文件的方法
2017/08/18 Javascript
使用vue-resource进行数据交互的实例
2017/09/02 Javascript
利用Javascript实现一套自定义事件机制
2017/12/14 Javascript
微信小程序scroll-view仿拼多多横向滑动滚动条
2020/04/21 Javascript
Vue-不允许嵌套式的渲染方法
2018/09/13 Javascript
基于vue和react的spa进行按需加载的实现方法
2018/09/29 Javascript
Vue.extend实现挂载到实例上的方法
2019/05/01 Javascript
微信小程序聊天功能的示例代码
2020/01/13 Javascript
vue css 引入asstes中的图片无法显示的四种解决方法
2020/03/16 Javascript
如何在JavaScript中正确处理变量
2020/12/25 Javascript
[02:38]2018年度DOTA2最佳劣单位选手-完美盛典
2018/12/17 DOTA
python3实现公众号每日定时发送日报和图片
2018/02/24 Python
Python 字符串与二进制串的相互转换示例
2018/07/23 Python
Python实现时间序列可视化的方法
2019/08/06 Python
详解HTML5中表单验证的8种方法介绍
2016/12/19 HTML / CSS
Nobody Denim官网:购买高级女士牛仔裤
2021/03/15 全球购物
物理教育专业毕业生推荐信
2013/11/03 职场文书
业务经理岗位职责
2013/11/11 职场文书
旅游安全协议书
2014/04/21 职场文书
精神文明单位申报材料
2014/05/02 职场文书
整顿机关作风心得体会
2014/09/10 职场文书
2014年小学少先队工作总结
2014/12/18 职场文书
博士论文答辩开场白
2015/06/01 职场文书
vue-router中hash模式与history模式的区别
2021/06/23 Vue.js
Log4j.properties配置及其使用
2021/08/02 Java/Android