热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

gzip解压strpython_HTTP响应gzip+chunked分段压缩流的解压缩问题

一.问题阐述之前遇到这么一个问题:用原生Socket进行HTTP请求的时候,添加了请求头Accept-Encoding:gzip这个请求头表示的含义就是:返回的数据中
0022935949e8a439742012934157bf5c.png

一.问题阐述

之前遇到这么一个问题:

用 原生 Socket 进行 HTTP 请求的时候,添加了请求头

Accept-Encoding: gzip

这个请求头表示的含义就是:返回的数据中会对响应体进行压缩,响应头不进行压缩(HTTP/1.1版)

如果服务器支持这种格式的压缩,那么返回的数据会如下这种格式

// 响应头不会压缩
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Encoding: gzip
Content-Type: text/html;charset=UTF-8
Date: Wed, 20 Feb 2019 06:19:04 GMT// 响应体会进压缩
xxxxxxxxxx

服务器压缩的方式可能如下

public static byte[] compress(String str, String encoding) {if (str == null || str.length() == 0) {return null;}ByteArrayOutputStream out = new ByteArrayOutputStream();GZIPOutputStream gzip;try {gzip = new GZIPOutputStream(out);gzip.write(str.getBytes(encoding));//将字符串转为字节数组,对字节数组进行压缩gzip.close();} catch (IOException e) {}return out.toByteArray();//返回压缩后的字节流}

正常情况下,如果请求头包含 gzip,响应时这种方式返回,那么在客户端接收到这种压缩的字节流,只有用同样的压缩流进行解压处理就可以得到数据,并且通常响应头都会包含如下的相应头

Content-Encoding: gzip
Content-Length: 13131

这表示返回的响应体是 gzip 格式的,并且字节流长度为 13131

一般情况是这样


但是在这样一种情况,如果返回的数据很大,或者数据量不确定(如一些动态网页),这个时候服务器就会选择对数据进行分段,并用一个16进制的数进行划分,表示一段的长度,如

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Encoding: gzip
Content-Type: text/html;charset=UTF-8
Transfer-Encoding: chunked // 分段的数据就会返回这个响应头
Date: Wed, 20 Feb 2019 06:19:04 GMTa3 // 16进制
xxxxx
5d9f
xxxxx
0 // 以 0 为结尾

这就使得响应头包含 gzip 和 chunked 的数据是一段经过分段的压缩流,因此也就不能简单地使用 GZIPInputStream 对数据进行处理

二.解决方法

对返回的字节流进行一个代理处理

public class SegmentInputStream extends InputStream {private InputStream mInputStream; //需要处理的字节流private HashMap mHeaders; //响应头private boolean mChunked; //分段的标识private boolean mIsData; private boolean mEnd; //读取到末尾的标志 即读取到长度为 0private long mReadLength &#61; 0L;//当前读取到的长度private long mSegmentLength &#61; -1L; //分段时&#xff0c;每一段的长度public final boolean DEBUG &#61; true;public SegmentInputStream(InputStream inputStream) throws IOException {mInputStream &#61; inputStream;mHeaders &#61; new HashMap<>();mChunked &#61; false;mIsData &#61; false;mEnd &#61; false;parseHeaders(); //在构造函数的时候就先将响应头解析&#xff0c;因为其没有压缩}public HashMap getHeaders() {return mHeaders;}//重写read 方法&#xff0c;每次读的时候跳过分段的16 进制数字&#64;Overridepublic int read() throws IOException {return !mChunked ? mInputStream.read() : readChunked();}private int readChunked() throws IOException {if (mEnd) {return -1;}int byteCode;if (mIsData) {byteCode &#61; mInputStream.read();mReadLength&#43;&#43;;if (mReadLength &#61;&#61; mSegmentLength) {mIsData &#61; false;mReadLength &#61; 0L;mSegmentLength &#61; -1L;}} // <<数据的部分读取完毕else {int endTag &#61; 0;//回车字符标识 一个 /n/r 就是一个回车byte[] buffer &#61; new byte[1];ArrayList bytes &#61; new ArrayList<>();while ((byteCode &#61; mInputStream.read()) !&#61; -1) {buffer[0] &#61; (byte) byteCode;// 因为read(x,x,x)// 最后会调用read 所以是一个递归&#xff0c;会栈溢出if (buffer[0] !&#61; &#39;r&#39; && buffer[0] !&#61; &#39;n&#39;) {bytes.add(buffer[0]);endTag &#61; 0;} else {/* (buffer[0] &#61;&#61; &#39;n&#39; || buffer[0] &#61;&#61; &#39;r&#39;)*/endTag&#43;&#43;;if (endTag &#61;&#61; 2 && bytes.size() !&#61; 0) {//四个字符就是有两个回车符&#xff0c;响应头就终止byte[] resultByte &#61; new byte[bytes.size()];for (int i &#61; 0; i bytes &#61; new ArrayList<>();while (read(buffer, 0, 1) !&#61; -1) { //bytes.add(buffer[0]);if (buffer[0] &#61;&#61; &#39;n&#39; || buffer[0] &#61;&#61; &#39;r&#39;) {enterCount&#43;&#43;;if (enterCount &#61;&#61; 4) { //四个字符就是有两个回车符&#xff0c;响应头就终止break;}} else {enterCount &#61; 0;}}byte[] resultByte &#61; new byte[bytes.size()];for (int i &#61; 0; i




推荐阅读
author-avatar
zongnaxxl240
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有