如何用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中使用item()方法遍历字典的例子
Aug 26 Python
以一段代码为实例快速入门Python2.7
Mar 31 Python
python实现按行切分文本文件的方法
Apr 18 Python
Python黑魔法Descriptor描述符的实例解析
Jun 02 Python
python实现用户管理系统
Jan 10 Python
python实现AES加密和解密
Mar 27 Python
Django重设Admin密码过程解析
Feb 10 Python
解决Python spyder显示不全df列和行的问题
Apr 20 Python
浅谈django channels 路由误导
May 28 Python
浅谈Keras的Sequential与PyTorch的Sequential的区别
Jun 17 Python
Python  word实现读取及导出代码解析
Jul 09 Python
python 解决Windows平台上路径有空格的问题
Nov 10 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
PHP 通过Socket收发十六进制数据的实现代码
2013/08/16 PHP
PHP中array_keys和array_unique函数源码的分析
2016/02/26 PHP
PHP Web表单生成器案例分析
2020/06/02 PHP
网页前台通过js非法字符过滤代码(骂人的话等等)
2010/05/26 Javascript
jQuery.extend 函数详解
2012/02/03 Javascript
鼠标滚轮改变图片大小的示例代码
2013/11/20 Javascript
js对象内部访问this修饰的成员函数示例
2014/04/27 Javascript
js获取checkbox值的方法
2015/01/28 Javascript
jQuery实现Div拖动+键盘控制综合效果的方法
2015/03/10 Javascript
js 截取或者替换字符串中的数字实现方法
2016/06/13 Javascript
基于JavaScript代码实现自动生成表格
2016/06/15 Javascript
Vue 过渡(动画)transition组件案例详解
2017/01/22 Javascript
jQuery基于ajax方式实现用户名存在性检查功能示例
2017/02/10 Javascript
有关JS中的0,null,undefined,[],{},'''''''',false之间的关系
2017/02/14 Javascript
BootStrap表单控件之文本域textarea
2017/05/23 Javascript
使用vue和datatables进行表格的服务器端分页实例代码
2017/06/07 Javascript
react-native使用react-navigation进行页面跳转导航的示例
2017/09/07 Javascript
傻瓜式vuex语法糖kiss-vuex整理
2018/12/21 Javascript
JavaScript实现省市联动效果
2019/11/22 Javascript
AI小程序之语音听写来了,十分钟掌握百度大脑语音听写全攻略
2020/03/13 Javascript
浅谈Pandas 排序之后索引的问题
2018/06/07 Python
Python寻找两个有序数组的中位数实例详解
2018/12/05 Python
使用Python计算玩彩票赢钱概率
2019/06/26 Python
Python Selenium 之数据驱动测试的实现
2019/08/01 Python
解决Python数据可视化中文部分显示方块问题
2020/05/16 Python
营销部内勤岗位职责
2014/04/30 职场文书
企业文化标语口号
2014/06/09 职场文书
政府采购方案
2014/06/12 职场文书
管理标语大全
2014/06/24 职场文书
英语教育专业毕业生求职信
2014/08/28 职场文书
签字仪式主持词
2015/07/03 职场文书
2016年国培研修日志
2015/11/13 职场文书
装修安全责任协议书
2016/03/22 职场文书
python - asyncio异步编程
2021/04/06 Python
一篇文章带你了解Python和Java的正则表达式对比
2021/09/15 Python
Python中super().__init__()测试以及理解
2021/12/06 Python