zoukankan      html  css  js  c++  java
  • RestTemplate与Gzip压缩

    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

  • 相关阅读:
    将单向链表按某值划分为左边小、中间相等、右边大的形式
    数组中的数字按某值划分为左边小、中间相等、右边大的形式
    Kendo UI for jQuery管理数据有妙招!轻松将数据存储为JSON
    DevExpress Xamarin.Forms v21.1
    界面控件Telerik UI for WinForm初级教程
    WPF应用程序的主题颜色如何修改?DevExpress调色板工具很好用
    DevExpress WinForm模板库可快速创建Windows样式的应用程序界面
    Kendo UI for jQuery数据管理使用教程:Spreadsheet
    开发框架DevExtreme入门级教程
    跨平台.NET应用程序界面开发新亮点
  • 原文地址:https://www.cnblogs.com/kevinblandy/p/13805468.html
Copyright © 2011-2022 走看看