最近接到一个需求,我们要调用其他项目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;
}
至此结束