最近接到一个需求,我们要调用其他项目Restful接口,并要求数据传输使用gzip压缩以减小传输过程中的网络开销。
请求中通过添加Header标识Content-Encoding :gzip 来标识已压缩的请求。
整个流程如图:
1. 调用方采用的是:Spring 的 RestTemplate ,底层是OkHttp的client
先声明 RestTemplate
@Bean("gzipRestTemplate") public RestTemplate restTemplate() { OkHttpClient okHttpClient = new OkHttpClient.Builder() .addInterceptor(new GzipRequestInterceptor())//添加gzip拦截器--只过滤header有"Content-Encoding :gzip " .build(); OkHttp3ClientHttpRequestFactory requestFactory = new OkHttp3ClientHttpRequestFactory(okHttpClient); RestTemplate restTemplate = new RestTemplate(requestFactory); restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter()) ; return restTemplate; }
gzip压缩拦截器(注:只对header中Content-Encoding =gzip的请求压缩)
import java.io.IOException; import okhttp3.Interceptor; import okhttp3.MediaType; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import okio.BufferedSink; import okio.GzipSink; import okio.Okio; public class GzipRequestInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); String contentEncoding = originalRequest.header("Content-Encoding");
//只对有body的请求(比如:PUT POST ) 且 Content-Encoding=gzip进行处理 if (originalRequest.body() == null || contentEncoding == null || !(contentEncoding.trim().toLowerCase().equals("gzip"))) { return chain.proceed(originalRequest); } Request compressedRequest = originalRequest.newBuilder() .header("Content-Encoding", "gzip") .method(originalRequest.method(), gzip(originalRequest.body())) .build(); return chain.proceed(compressedRequest); } private RequestBody gzip(final RequestBody body) { return new RequestBody() { @Override public MediaType contentType() { return body.contentType(); } @Override public long contentLength() { return -1; // 无法提前知道压缩后的数据大小 } @Override public void writeTo(BufferedSink sink) throws IOException { BufferedSink gzipSink = Okio.buffer(new GzipSink(sink)); body.writeTo(gzipSink); gzipSink.close(); } }; } }
请求时带着gzip的header
private HttpHeaders getGzipHeader() { HttpHeaders headers = new HttpHeaders(); headers.set("Content-Encoding", "gzip"); MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8"); headers.setContentType(type); return headers; }
调用代码:
List<User> rs = new ArrayList<>(); User testBean = new User(); testBean.setAge("12"); testBean.setName("Tom11"); testBean.setPassword("ccc"); rs.add(testBean); String url = "http://localhost:8000/remote/createUser"; HttpEntity<List<User>> entity = new HttpEntity<List<User>>(rs,getGzipHeader()); ParameterizedTypeReference<Result<Integer>> parameterizedTypeReference = new ParameterizedTypeReference<Result<Integer>>(){}; ResponseEntity<Result<Integer>> resp = gzipRestTemplate.exchange(url, HttpMethod.POST, entity, parameterizedTypeReference); System.out.println(resp.getBody());
2.服务提供方
请求包装类 实现HttpServletRequestWrapper接口
import java.io.IOException; import java.util.zip.GZIPInputStream; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import org.slf4j.Logger; public class GzipRequestWrapper extends HttpServletRequestWrapper{ private HttpServletRequest request; private static final Logger LOGGER = org.slf4j.LoggerFactory.getLogger(GzipRequestWrapper.class); public GzipRequestWrapper(HttpServletRequest request) { super(request); this.request = request; } @Override public ServletInputStream getInputStream() throws IOException { ServletInputStream stream = request.getInputStream(); String contentEncoding = request.getHeader("Content-Encoding"); if(null!=contentEncoding) { contentEncoding =contentEncoding.trim().toLowerCase(); } // 如果对内容进行了压缩,则解压 if (null != contentEncoding && contentEncoding.indexOf("gzip") != -1) { try { final GZIPInputStream gzipInputStream = new GZIPInputStream( stream); ServletInputStream newStream = new ServletInputStream() { @Override public int read() throws IOException { return gzipInputStream.read(); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener arg0) { } }; return newStream; } catch (Exception e) { LOGGER.debug("ungzip content fail.", e); } } return stream; } }
编写过滤器:
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
@WebFilter(filterName="gzipFilter",urlPatterns="/") public class GzipFilter implements Filter { @Override public void destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { chain.doFilter(new GzipRequestWrapper((HttpServletRequest) request),response); } @Override public void init(FilterConfig arg0) throws ServletException { } }
spring boot 注册过滤器
@Bean public FilterRegistrationBean filterRegistrationBean() { FilterRegistrationBean registrationBean = new FilterRegistrationBean(); GzipFilter httpBasicFilter = new GzipFilter(); registrationBean.setFilter(httpBasicFilter); List<String> urlPatterns = new ArrayList<String>(); urlPatterns.add("/*"); registrationBean.setUrlPatterns(urlPatterns); return registrationBean; }
接口定义:
@RequestMapping(value= "/remote/createUser", method=RequestMethod.POST) public Result<Integer> test(@RequestBody List<User> data ) { System.out.println(JSON.toJSON(data)); Result<Integer> r = new Result<>(); r.setData(30); r.setStatus(200); return r; }
至此结束