如何用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 smtplib发送带附件邮件小程序
May 22 Python
Python操作rabbitMQ的示例代码
Mar 19 Python
Python3+Appium实现多台移动设备操作的方法
Jul 05 Python
使用python分析统计自己微信朋友的信息
Jul 19 Python
解决python多行注释引发缩进错误的问题
Aug 23 Python
Python 实现大整数乘法算法的示例代码
Sep 17 Python
python 实现快速生成连续、随机字母列表
Nov 28 Python
Python底层封装实现方法详解
Jan 22 Python
利用Python实现字幕挂载(把字幕文件与视频合并)思路详解
Oct 21 Python
Python 图片处理库exifread详解
Feb 25 Python
Pytorch 实现变量类型转换
May 17 Python
Python实现批量将文件复制到新的目录中再修改名称
Apr 12 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
php获取ip的三个属性区别介绍(HTTP_X_FORWARDED_FOR,HTTP_VIA,REMOTE_ADDR)
2012/09/23 PHP
解析linux下安装memcacheq(mcq)全过程笔记
2013/06/27 PHP
WordPress中编写自定义存储字段的相关PHP函数解析
2015/12/25 PHP
PHP实现在数据库百万条数据中随机获取20条记录的方法
2017/04/19 PHP
PHP弱类型语言中类型判断操作实例详解
2017/08/10 PHP
js小技巧--自动隐藏红叉叉
2007/08/13 Javascript
JavaScript 未结束的字符串常量常见解决方法
2010/01/24 Javascript
基于jquery完美拖拽,可返回拖动轨迹
2012/03/29 Javascript
深入浅析JavaScript中对事件的三种监听方式
2015/09/29 Javascript
angularjs 源码解析之scope
2016/08/22 Javascript
jquery判断iPhone、Android设备类型
2016/09/14 Javascript
React学习笔记之条件渲染(一)
2017/07/02 Javascript
基于Vue过渡状态实例讲解
2017/09/14 Javascript
Nodejs处理异常操作示例
2018/12/25 NodeJs
6种JavaScript继承方式及优缺点(小结)
2020/02/06 Javascript
用Python编写一个简单的俄罗斯方块游戏的教程
2015/04/03 Python
python实现微信接口(itchat)详细介绍
2017/10/23 Python
python正则实现计算器功能
2017/12/14 Python
对python插入数据库和生成插入sql的示例讲解
2018/11/14 Python
超简单使用Python换脸实例
2019/03/27 Python
Python3.5常见内置方法参数用法实例详解
2019/04/29 Python
python画图——实现在图上标注上具体数值的方法
2019/07/08 Python
Python3 sys.argv[ ]用法详解
2019/10/24 Python
Python爬虫使用代理IP的实现
2019/10/27 Python
Python argparse模块使用方法解析
2020/02/20 Python
关于keras.layers.Conv1D的kernel_size参数使用介绍
2020/05/22 Python
Spartoo葡萄牙鞋类网站:线上销售鞋履与时尚配饰
2017/01/11 全球购物
英国最大的宠物商店:Pets at Home
2019/04/17 全球购物
大学生写自荐信的技巧
2014/01/08 职场文书
最新茶叶店创业计划书
2014/01/14 职场文书
2014社区三八妇女节活动总结
2014/03/01 职场文书
视光学专业自荐信
2014/06/24 职场文书
购房协议书范本
2014/10/02 职场文书
财务工作检讨书
2014/10/29 职场文书
2014幼儿园中班工作总结
2014/11/10 职场文书