如何用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类的多重继承问题深入分析
Nov 09 Python
python实现线程池的方法
Jun 30 Python
Python 调用Java实例详解
Jun 02 Python
Python 比较两个数组的元素的异同方法
Aug 17 Python
Python实现文件信息进行合并实例代码
Jan 17 Python
python实现读取大文件并逐行写入另外一个文件
Apr 19 Python
python3爬虫之设计签名小程序
Jun 19 Python
详解python运行三种方式
May 13 Python
Python中实现输入超时及如何通过变量获取变量名
Jan 18 Python
给Django Admin添加验证码和多次登录尝试限制的实现
Jul 26 Python
使用scrapy ImagesPipeline爬取图片资源的示例代码
Sep 28 Python
如何用 Python 制作 GitHub 消息助手
Feb 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
十天学会php之第五天
2006/10/09 PHP
php随机获取金山词霸每日一句的方法
2015/07/09 PHP
自定义min版smarty模板引擎MinSmarty.class.php文件及用法
2016/05/20 PHP
Django 标签筛选的实现代码(一对多、多对多)
2018/09/05 PHP
laravel 自定义常量的两种方案
2019/10/14 PHP
通过PHP的Wrapper无缝迁移原有项目到新服务的实现方法
2020/04/02 PHP
PHP替换Word中变量并导出PDF图片的实现方法
2020/11/26 PHP
php的lavarel框架中join和orWhere的用法
2020/12/28 PHP
javascript:以前写的xmlhttp池,代码
2008/05/18 Javascript
html中table数据排序的js代码
2011/08/09 Javascript
window.event快达到全浏览器支持了,以后使用就方便了
2011/11/30 Javascript
原生JS实现左右箭头选择日期实例代码
2017/03/14 Javascript
echarts同一页面中四个图表切换的js数据交互方法示例
2018/07/03 Javascript
[00:57]深扒TI7聊天轮盘语音出处5
2017/05/11 DOTA
Python实现在tkinter中使用matplotlib绘制图形的方法示例
2018/01/18 Python
Python实现的径向基(RBF)神经网络示例
2018/02/06 Python
Python通过属性手段实现只允许调用一次的示例讲解
2018/04/21 Python
django将图片上传数据库后在前端显式的方法
2018/05/25 Python
利用python GDAL库读写geotiff格式的遥感影像方法
2018/11/29 Python
Python+opencv 实现图片文字的分割的方法示例
2019/07/04 Python
python+Selenium自动化测试——输入,点击操作
2020/03/06 Python
Python虚拟环境库virtualenvwrapper安装及使用
2020/06/17 Python
python线程里哪种模块比较适合
2020/08/02 Python
阿根廷在线宠物商店:Puppis
2018/03/23 全球购物
澳大利亚二手奢侈品网站:Modsie
2019/09/23 全球购物
澳大利亚最受欢迎的超级商场每日优惠:Catch
2020/11/17 全球购物
eHarmony英国:全球领先的认真恋爱约会平台之一
2020/11/16 全球购物
下列程序在32位linux或unix中的结果是什么
2015/01/26 面试题
专业求职信撰写要诀
2014/02/18 职场文书
政风行风整改报告
2014/11/06 职场文书
出租车拒载检讨书
2015/01/28 职场文书
2015年清明节网上祭英烈留言寄语
2015/03/04 职场文书
心术观后感
2015/06/11 职场文书
2016教师六五普法学习心得体会
2016/01/21 职场文书
化工生产实习心得体会
2016/01/22 职场文书
python通过函数名调用函数的几种方法总结
2021/06/07 Python