如何用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 23 Python
Python优化技巧之利用ctypes提高执行速度
Sep 11 Python
Python正则表达式完全指南
May 25 Python
python 表达式和语句及for、while循环练习实例
Jul 07 Python
python爬取各类文档方法归类汇总
Mar 22 Python
pygame游戏之旅 按钮上添加文字的方法
Nov 21 Python
Python检查ping终端的方法
Jan 26 Python
Python3离线安装Requests模块问题
Oct 13 Python
Python 炫技操作之合并字典的七种方法
Apr 10 Python
django和flask哪个值得研究学习
Jul 31 Python
python smtplib发送多个email联系人的实现
Oct 09 Python
pytorch训练神经网络爆内存的解决方案
May 22 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中将ip地址转成十进制数的两种实用方法
2013/08/15 PHP
php中使用GD库做验证码
2016/03/31 PHP
PHP重定向与伪静态区别
2017/02/19 PHP
PHP设计模式之模板方法模式实例浅析
2018/12/20 PHP
jQuery对象和Javascript对象之间转换的实例代码
2013/03/20 Javascript
JS滚轮事件onmousewheel使用介绍
2013/11/01 Javascript
javascript中利用柯里化函数实现bind方法
2016/04/29 Javascript
vue 2.0路由之路由嵌套示例详解
2017/05/08 Javascript
jQuery+CSS实现的table表格行列转置功能示例
2018/01/08 jQuery
vue2.0 axios跨域并渲染的问题解决方法
2018/03/08 Javascript
Vue.js添加组件操作示例
2018/06/13 Javascript
Angularjs 根据一个select的值去设置另一个select的值方法
2018/08/13 Javascript
JS开发自己的类库实例分析
2019/08/28 Javascript
利用layer实现表单完美验证的方法
2019/09/26 Javascript
JS sort方法基于数组对象属性值排序
2020/07/10 Javascript
[53:03]Optic vs TNC 2018国际邀请赛小组赛BO2 第一场 8.17
2018/08/18 DOTA
如何搜索查找并解决Django相关的问题
2014/06/30 Python
Python中解析JSON并同时进行自定义编码处理实例
2015/02/08 Python
Python中使用装饰器时需要注意的一些问题
2015/05/11 Python
Python基于pygame实现图片代替鼠标移动效果
2015/11/11 Python
Python实现解析Bit Torrent种子文件内容的方法
2017/08/29 Python
numpy.delete删除一列或多列的方法
2018/04/03 Python
浅谈Pandas 排序之后索引的问题
2018/06/07 Python
Django实现文件上传下载功能
2019/10/06 Python
python中从for循环延申到推导式的具体使用
2019/11/29 Python
HTML5实时语音通话聊天MP3压缩传输3KB每秒
2019/08/28 HTML / CSS
新加坡交友网站:be2新加坡
2019/04/10 全球购物
惠而浦美国官网:Whirlpool.com
2021/01/19 全球购物
shell程序如何生命变量?shell变量是弱变量吗?
2014/11/10 面试题
运动会广播稿150字(9篇)
2014/09/20 职场文书
2015年秋学期师德师风建设工作总结
2015/10/23 职场文书
大学三好学生主要事迹范文
2015/11/03 职场文书
关于Javascript闭包与应用的详解
2021/04/22 Javascript
Python爬虫基础初探selenium
2021/05/31 Python
SQL模糊查询报:ORA-00909:参数个数无效问题的解决
2021/06/21 Oracle
Nginx配置https的实现
2021/11/27 Servers