RestTemplate与Gzip压缩

Stella981
• 阅读 772

Gzip 是一种压缩算法,服务器经常通过这个算法来压缩响应体,再响应给客户端,从而减少数据体积,提高传输速度。客户端再通过Gzip解压缩,获取到原始的数据。因为需要压缩计算,所以会耗费额外的CPU资源。

Gzip 与 HttpHeader

对于压缩,这个行为来说,客户端与服务器都要经过协商。只有使用了同一种压缩算法,才能正确的解码出数据。http协议中定义了相关的header

Content-Encoding

是一个实体消息首部,用于对特定媒体类型的数据进行压缩。当这个首部出现的时候,它的值表示消息主体进行了何种方式的内容编码转换。这个消息首部用来告知客户端应该怎样解码才能获取在 Content-Type 中标示的媒体类型内容。

一般建议对数据尽可能地进行压缩,因此才有了这个消息首部的出现。不过对于特定类型的文件来说,比如jpeg图片文件,已经是进行过压缩的了。有时候再次进行额外的压缩无助于负载体积的减小,反而有可能会使其增大。

客户端和服务器都可以使用,表示body中的数据采用了什么编码(压缩算法)

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Encoding

Accept-Encoding

HTTP 请求头 Accept-Encoding 会将客户端能够理解的内容编码方式——通常是某种压缩算法——进行通知(给服务端)。通过内容协商的方式,服务端会选择一个客户端提议的方式,使用并在响应头 Content-Encoding 中通知客户端该选择。

即使客户端和服务器都支持相同的压缩算法,在 identity 指令可以被接受的情况下,服务器也可以选择对响应主体不进行压缩。导致这种情况出现的两种常见的情形是:

  • 要发送的数据已经经过压缩,再次进行压缩不会导致被传输的数据量更小。一些图像格式的文件会存在这种情况;
  • 服务器超载,无法承受压缩需求导致的计算开销。通常,如果服务器使用超过80%的计算能力,微软建议不要压缩。

只要 identity —— 表示不需要进行任何编码——没有被明确禁止使用(通过 identity;q=0 指令或是 *;q=0 而没有为 identity 明确指定权重值),则服务器禁止返回表示客户端错误的 406 Not Acceptable 响应。

一般是客户端使用,表示给服务器说明,客户端支持的压缩算法列表。服务从中选择一个对响应体进行压缩。

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Accept-Encoding

演示一个手动编/解码的Demo

服务端手动进行Gzip编码

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.zip.GZIPOutputStream;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class TestController {
    
    private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class);

    @GetMapping
    public void test(HttpServletRequest request, HttpServletResponse reponse) throws IOException {
        
        // 响应体
        String content = "昔日龌龊不足夸,今朝放荡思无涯。春风得意马蹄疾,一日看尽长安花。";
        
        String acceptEncooding = request.getHeader(HttpHeaders.ACCEPT_ENCODING);
        
        /**
         * 获取客户端支持的编码格式,程序可以根据这个header判断是否要对响应体进行编码
         */
        LOGGER.info(acceptEncooding);
        
        
        // 响应体使用 gzip 编码
        reponse.setHeader(HttpHeaders.CONTENT_ENCODING, "gzip");
        // 响应体类型是字符串
        reponse.setContentType(MediaType.TEXT_PLAIN_VALUE);
        // 编码是utf-8
        reponse.setCharacterEncoding(StandardCharsets.UTF_8.displayName());
        // Gzip压缩后响应
        reponse.getOutputStream().write(gZip(content.getBytes(StandardCharsets.UTF_8)));
        
    }

    /**
     * Gzip压缩数据
     * @param data
     * @return
     * @throws IOException
     */
    public static byte[] gZip(byte[] data) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream)) {
            gzipOutputStream.write(data);
            gzipOutputStream.finish();
            return byteArrayOutputStream.toByteArray();
        }
    }
}

客户端手动解码

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.zip.GZIPInputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

public class Main {

    public static final Logger LOGGER = LoggerFactory.getLogger(Main.class);

    public static void main(String[] args) throws Exception {
        
        RestTemplate restTemplate = new RestTemplate();
        
        
        HttpHeaders httpHeaders = new HttpHeaders();
        // Accept 表示客户端支持什么格式的响应体
        httpHeaders.set(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN_VALUE);
        // Accept-Encoding 头,表示客户端接收gzip格式的压缩
        httpHeaders.set(HttpHeaders.ACCEPT_ENCODING, "gzip");
        
        ResponseEntity<byte[]> responseEntity = restTemplate.exchange("http://localhost/test", HttpMethod.GET, new HttpEntity<>(httpHeaders), byte[].class);
        
        if (!responseEntity.getStatusCode().is2xxSuccessful()) {
            // TODO 非200响应
        }

        // 获取服务器响应体编码
        String contentEncoding = responseEntity.getHeaders().getFirst(HttpHeaders.CONTENT_ENCODING);
        
        if ("gzip".equals(contentEncoding)) { // gzip编码
            // gzip解压服务器的响应体
            byte[] data = unGZip(new ByteArrayInputStream(responseEntity.getBody()));
            
            LOGGER.info(new String(data, StandardCharsets.UTF_8));
        } else {
            // TODO 其他的编码
        }
    }

    /**
     * Gzip解压缩
     * @param inputStream
     * @return
     * @throws IOException
     */
    public static byte[] unGZip(InputStream inputStream) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        try (GZIPInputStream gzipInputStream = new GZIPInputStream(inputStream)) {
            byte[] buf = new byte[4096];
            int len = -1;
            while ((len = gzipInputStream.read(buf, 0, buf.length)) != -1) {
                byteArrayOutputStream.write(buf, 0, len);
            }
            return byteArrayOutputStream.toByteArray();
        } finally {
            byteArrayOutputStream.close();
        }
    }
}

客户端执行日志,准确的解码了响应体

20:36:54.129 [main] INFO  - 昔日龌龊不足夸,今朝放荡思无涯。春风得意马蹄疾,一日看尽长安花。

SpringBoot的响应体压缩配置

实际上,并不需要自己手动去写这种响应体的压缩代码。springboot提供了相关的配置。 SpringBoot2开启响应压缩

最后

使用RestTemplate请求文本数据接口,发现解码后的字符串是乱码。此时就可以怀疑是不是服务器响应了压缩后的数据。解决这个问题,先尝试移除Accept-Encoding请求头,告诉服务器,客户端不需要压缩响应体。如果服务器还是响应压缩后的数据,尝试读取服务器的Content-Encoding头,根据服务器的压缩编码,自己再进行解压缩。

原文: https://springboot.io/t/topic/2868

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
冴羽 冴羽
2年前
VuePress 博客优化之开启 Gzip 压缩
前言在中,我们使用VuePress搭建了一个博客,在中,我们将代码部署到服务器上,最终的效果查看:。今天我们来学习如何开启服务器的Gzip压缩。Gzip压缩关于Gzip压缩,引用MDN的:Gzip是一种用于文件压缩与解压缩的文件格式。它基于Deflate算法,可将文件压缩地更小,从而实现更快的网络传输。Web服务器与现代浏览
Wesley13 Wesley13
2年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
2年前
Hadoop所支持的几种压缩格式
Gzip压缩优点:压缩率比较高,而且压缩/解压速度也比较快;hadoop本身支持,在应用中处理gzip格式的文件就和直接处理文本一样;有hadoop native库;大部分linux系统都自带gzip命令,使用方便。缺点:不支持split。应用场景:当每个文件压缩之后在130M以内的(1个块大小内),都可以考虑用gzip压缩格式。譬如说一
Stella981 Stella981
2年前
Nginx学习笔记——gzip
配置详解gzipon|off开启或关闭gzip,默认关闭gzip\_staticon|off对于静态资源预先压缩gzip\_comp\_level4压缩比(19),建议4gzip\_buffers416k 安装原始数据大小以16k为单位的4倍申请内存(压缩,为什么还要4倍申请,不懂。。。)gzip\_min\_
Wesley13 Wesley13
2年前
Linux 运维 9月30日 笔记 6.1
目录一、压缩打包介绍二、gzip压缩工具三、bzip2压缩工具四、xz压缩工具一、压缩打包介绍1.常见压缩文件Windows.rar.zip.7zLinux.zip.gz.bz2.xz.tar 二、gzip压缩工具
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
芝士年糕 芝士年糕
1年前
如何在Linux解压缩(打开)Gz 文件?
GNU的Gzip是一种流行的数据压缩程序,而GZ文件是由标准Gzip压缩的存档文件。它最初由JeanloupGailly和MarkAdler于1992年10月31日开发。如何解压缩(打开)Gz文件打开Gz文件的程序对于Windows操作系统:PeazipWinRARCorelWinZip7ZipFi
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这