如何gzip由org.springframework.web.client.RestTemplate?
我正在使用Spring 4.2.6
与Spring Boot 1.3.5
(Java SE,而不是Android或Javascript在网络浏览器中)。
我正在制作一些非常大的POST
请求,我希望压缩请求正文。
答案 0 :(得分:12)
我提出了两个解决方案,一个更简单,没有流媒体,另一个支持流媒体。
如果您不需要流媒体,请使用自定义ClientHttpRequestInterceptor
,即Spring功能。
RestTemplate rt = new RestTemplate();
rt.setInterceptors(Collections.singletonList(interceptor));
interceptor
可能是:
ClientHttpRequestInterceptor interceptor = new ClientHttpRequestInterceptor() {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
request.getHeaders().add("Content-Encoding", "gzip");
byte[] gzipped = getGzip(body);
return execution.execute(request, gzipped);
}
}
getGzip
我copied
private byte[] getGzip(byte[] body) throws IOException {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
try {
GZIPOutputStream zipStream = new GZIPOutputStream(byteStream);
try {
zipStream.write(body);
} finally {
zipStream.close();
}
} finally {
byteStream.close();
}
byte[] compressedData = byteStream.toByteArray();
return compressedData;
}
配置拦截器后,所有请求都将被压缩。
这种方法的缺点是它不支持流媒体,因为ClientHttpRequestInterceptor
收到的内容为byte[]
如果您需要流媒体,请创建自定义ClientHttpRequestFactory
,例如GZipClientHttpRequestFactory
,然后像这样使用它:
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
ClientHttpRequestFactory gzipRequestFactory = new GZipClientHttpRequestFactory(requestFactory);
RestTemplate rt = new RestTemplate(gzipRequestFactory);
GZipClientHttpRequestFactory
的位置:
public class GZipClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper {
public GZipClientHttpRequestFactory(ClientHttpRequestFactory requestFactory) {
super(requestFactory);
}
@Override
protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory)
throws IOException {
ClientHttpRequest delegate = requestFactory.createRequest(uri, httpMethod);
return new ZippedClientHttpRequest(delegate);
}
}
ZippedClientHttpRequest
是:
public class ZippedClientHttpRequest extends WrapperClientHttpRequest
{
private GZIPOutputStream zip;
public ZippedClientHttpRequest(ClientHttpRequest delegate) {
super(delegate);
delegate.getHeaders().add("Content-Encoding", "gzip");
// here or in getBody could add content-length to avoid chunking
// but is it available ?
// delegate.getHeaders().add("Content-Length", "39");
}
@Override
public OutputStream getBody() throws IOException {
final OutputStream body = super.getBody();
zip = new GZIPOutputStream(body);
return zip;
}
@Override
public ClientHttpResponse execute() throws IOException {
if (zip!=null) zip.close();
return super.execute();
}
}
最后WrapperClientHttpRequest
是:
public class WrapperClientHttpRequest implements ClientHttpRequest {
private final ClientHttpRequest delegate;
protected WrapperClientHttpRequest(ClientHttpRequest delegate) {
super();
if (delegate==null)
throw new IllegalArgumentException("null delegate");
this.delegate = delegate;
}
protected final ClientHttpRequest getDelegate() {
return delegate;
}
@Override
public OutputStream getBody() throws IOException {
return delegate.getBody();
}
@Override
public HttpHeaders getHeaders() {
return delegate.getHeaders();
}
@Override
public URI getURI() {
return delegate.getURI();
}
@Override
public HttpMethod getMethod() {
return delegate.getMethod();
}
@Override
public ClientHttpResponse execute() throws IOException {
return delegate.execute();
}
}
此方法使用chunked transfer encoding创建请求,如果已知大小,则可以更改此设置内容长度标头。
ClientHttpRequestInterceptor
和/或自定义ClientHttpRequestFactory
方法的优点是它可以与RestTemplate的任何方法一起使用。另一种方法是,仅使用RequestCallback方法传递execute
,这是因为RestTemplate的其他方法在内部创建了自己的RequestCallback来生成内容。
BTW似乎有little support to decompress gzip request on the server。还相关:Sending gzipped data in WebRequest?指向Zip Bomb问题。我想你必须为它写some code。
答案 1 :(得分:2)
主要想法是创建requestCallback
,它会将您要从gzipOutputStream
直接发送的数据直接复制到request
流。
RequestCallback requestCallback = new RequestCallback() {
@Override
public void doWithRequest(ClientHttpRequest request) throws IOException {
GZIPOutputStream gzipOutputStream;
try {
gzipOutputStream = new GZIPOutputStream(request.getBody());
} catch (IOException ignored) {
return;
}
request.getHeaders().add("Content-Type", "application/octet-stream");
request.getHeaders().add("Content-Encoding", "gzip");
try {
String data = "Test data.";
gzipOutputStream.write(data.getBytes(StandardCharsets.UTF_8));
gzipOutputStream.flush(); // Optional in this example.
gzipOutputStream.finish();
} catch (IOException ignored) {
}
}
};
现在您可以在下一个方式使用它:
RestTemplate restTemplate = new RestTemplate();
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
restTemplate.setRequestFactory(requestFactory);
ResponseExtractor<String> responseExtractor = new HttpMessageConverterExtractor<>(String.class,
restTemplate.getMessageConverters());
String response = restTemplate.execute("http://localhost:8080/gzip.php", HttpMethod.POST, requestCallback,
responseExtractor);
System.out.println(response);
链接:
答案 2 :(得分:1)
进一步来自@TestoTestini的上述答案,如果我们利用Java 7+的“ try-with-resources”语法,因为ByteArrayOutputStream
和GZIPOutputStream
都实现了closeable(),我们可以将getGzip函数缩小为以下内容:
private byte[] getGzip(byte[] body) throws IOException {
try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream()) {
try (GZIPOutputStream zipStream = new GZIPOutputStream(byteStream)) {
zipStream.write(body);
}
byte[] compressedData = byteStream.toByteArray();
return compressedData;
}
}
(我找不到评论@TestoTestini的原始答案并保留上述代码格式的方法,因此找不到此答案。)
答案 3 :(得分:0)
由于我无法评论@roj的帖子,因此我在这里写一个答案。
@roj代码段虽然很简洁,但实际上与@Testo Testini的代码段不同。
Testo在关闭流之前:
byteStream.toByteArray();
在@rog答案中,此位置在stream.close()
之前,因为流在try / resource块中。
如果您需要使用try-with-resources,应在zipStream
之前关闭byteStream.toByteArray();
完整的代码段应为:
private byte[] getGzip(byte[] body) throws IOException {
try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
GZIPOutputStream zipStream = new GZIPOutputStream(byteStream)) {
zipStream.write(body);
zipStream.close();
byte[] compressedData = byteStream.toByteArray();
return compressedData;
}
}
出现错误(“压缩文件在到达流结束标记之前已结束”),以上内容解决了我的错误,我认为我应该与大家分享。