Spring restTemplate execute( ) POST 大文件并获得响应
Spring restTemplate execute( ) POST large files and obtain a response
这花了我很多时间才算出来,所以我想分享一下。大部分信息来自SO,我想合并到一个地方。
我的要求是使用 RESTFul POST 上传文件。由于文件可能很大,我想流式传输文件。我显然希望能够阅读回复。
我计划使用 Jersey 作为 REST 服务器,使用 Spring 的 RestTemplate 作为客户端(并用于测试)。
我遇到的问题是流式传输 POSTs 并收到响应。我怎样才能做到这一点? (反问-我来答!)
我正在使用 SpringBoot 1.2.4.RELEASE
,Jersey 被拉入:
compile("org.springframework.boot:spring-boot-starter-jersey")
我使用出色的 Spring Starter Project 创建了该项目(Spring Tool Suite > New
或者您可以通过我相信的网站完成,毫无疑问 IntelliJ 也具有此功能)。并选择了 'Jersey (JAX-RS)' 选项。在 gradle build.gradle
中我还添加了依赖:
compile('commons-io:commons-io:2.4')
我写了这个服务器端代码。
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
import org.me.fileStore.service.FileStoreService;
@RestController
@Path("/filestore")
public class FileStoreRestService {
private static Logger logger = LoggerFactory.getLogger(FileStoreRestService.class);
@Autowired
private FileStoreService fileStoreService;
@POST
@Path("upload")
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
@Produces(MediaType.APPLICATION_JSON)
public Response Upload(InputStream stream) throws IOException, URISyntaxException { //
String location = fileStoreService.upload(stream); // relative path
URI loc = new URI(location);
Response response = Response.created(loc).build();
System.out.println("POST - response: " + response + ", :" + response.getHeaders());
return response;
}
我遇到最多麻烦的地方是获得带有位置的响应。
首先,我必须处理大文件流。正如您在下面的测试中看到的那样,我遵循了 。无论我根据 post:
尝试使用 HttpMessageConverterExtractor
做什么,我都没有获得响应
final HttpMessageConverterExtractor<String> responseExtractor =
new HttpMessageConverterExtractor<String>(String.class, restTemplate.getMessageConverters());
找到后我写道:
private static class ResponseFromHeadersExtractor implements ResponseExtractor<ClientHttpResponse> {
@Override
public ClientHttpResponse extractData(ClientHttpResponse response) {
System.out.println("StringFromHeadersExtractor - response headers: " + response.getHeaders());
return response;
}
}
这给了我这个测试:
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.ResponseExtractor;
import org.springframework.web.client.RestTemplate;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = FileStoreApplication.class)
@WebAppConfiguration
@IntegrationTest("server.port:9000")
public class FileStoreRestServiceTest {
private static Logger logger = LoggerFactory.getLogger(FileStoreRestServiceTest.class);
protected final Log logger2 = LogFactory.getLog(getClass());
String base = "http://localhost:9000/filestore";
private RestTemplate restTemplate = new TestRestTemplate();
@Test
public void testMyMethodExecute() throws IOException {
String content = "This is file contents\nWith another line.\n";
Path theTestFilePath = TestingUtils.getTempPath(content);
InputStream inputStream = Files.newInputStream(theTestFilePath);
String url = base + "/upload";
final RequestCallback requestCallback = new RequestCallback() {
@Override
public void doWithRequest(final ClientHttpRequest request) throws IOException {
request.getHeaders().setContentType(MediaType.APPLICATION_OCTET_STREAM);
IOUtils.copy(inputStream, request.getBody());
}
};
final RestTemplate restTemplate = new RestTemplate();
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
restTemplate.setRequestFactory(requestFactory);
ClientHttpResponse response = restTemplate.execute(url, HttpMethod.POST, requestCallback,
new ResponseFromHeadersExtractor());
URI location = response.getHeaders().getLocation();
System.out.println("Location: " + location);
Assert.assertNotNull(location);
Assert.assertNotEquals(0, location.getPath().length());
}
private static class ResponseFromHeadersExtractor implements ResponseExtractor<ClientHttpResponse> {
@Override
public ClientHttpResponse extractData(ClientHttpResponse response) {
System.out.println("StringFromHeadersExtractor - response headers: " + response.getHeaders());
return response;
}
}
我需要将该测试中的很多内容重构到某些服务中。
一个RequestCallback
就没必要折腾这么多了。只需使用 PathResource
.
PathResource pathResource = new PathResource(theTestFilePath);
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(pathResource), String.class);
Spring 将使用 ResourceHttpMessageConverter
将给定 Path
标识的文件序列化为请求 body。在内部,Spring 4.x 实现使用 4096 字节的缓冲区大小(这也是 IOUtils#copy(..)
使用的)。
显然,您可以提供所需的响应类型。上面的示例期望响应 body 作为 String
。使用 ResponseEntity
,您可以使用
访问所有响应 headers
HttpHeaders responseHeaders = response.getHeaders();
这花了我很多时间才算出来,所以我想分享一下。大部分信息来自SO,我想合并到一个地方。
我的要求是使用 RESTFul POST 上传文件。由于文件可能很大,我想流式传输文件。我显然希望能够阅读回复。
我计划使用 Jersey 作为 REST 服务器,使用 Spring 的 RestTemplate 作为客户端(并用于测试)。
我遇到的问题是流式传输 POSTs 并收到响应。我怎样才能做到这一点? (反问-我来答!)
我正在使用 SpringBoot 1.2.4.RELEASE
,Jersey 被拉入:
compile("org.springframework.boot:spring-boot-starter-jersey")
我使用出色的 Spring Starter Project 创建了该项目(Spring Tool Suite > New
或者您可以通过我相信的网站完成,毫无疑问 IntelliJ 也具有此功能)。并选择了 'Jersey (JAX-RS)' 选项。在 gradle build.gradle
中我还添加了依赖:
compile('commons-io:commons-io:2.4')
我写了这个服务器端代码。
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
import org.me.fileStore.service.FileStoreService;
@RestController
@Path("/filestore")
public class FileStoreRestService {
private static Logger logger = LoggerFactory.getLogger(FileStoreRestService.class);
@Autowired
private FileStoreService fileStoreService;
@POST
@Path("upload")
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
@Produces(MediaType.APPLICATION_JSON)
public Response Upload(InputStream stream) throws IOException, URISyntaxException { //
String location = fileStoreService.upload(stream); // relative path
URI loc = new URI(location);
Response response = Response.created(loc).build();
System.out.println("POST - response: " + response + ", :" + response.getHeaders());
return response;
}
我遇到最多麻烦的地方是获得带有位置的响应。
首先,我必须处理大文件流。正如您在下面的测试中看到的那样,我遵循了 。无论我根据 post:
尝试使用HttpMessageConverterExtractor
做什么,我都没有获得响应
final HttpMessageConverterExtractor<String> responseExtractor =
new HttpMessageConverterExtractor<String>(String.class, restTemplate.getMessageConverters());
找到后我写道:
private static class ResponseFromHeadersExtractor implements ResponseExtractor<ClientHttpResponse> {
@Override
public ClientHttpResponse extractData(ClientHttpResponse response) {
System.out.println("StringFromHeadersExtractor - response headers: " + response.getHeaders());
return response;
}
}
这给了我这个测试:
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.ResponseExtractor;
import org.springframework.web.client.RestTemplate;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = FileStoreApplication.class)
@WebAppConfiguration
@IntegrationTest("server.port:9000")
public class FileStoreRestServiceTest {
private static Logger logger = LoggerFactory.getLogger(FileStoreRestServiceTest.class);
protected final Log logger2 = LogFactory.getLog(getClass());
String base = "http://localhost:9000/filestore";
private RestTemplate restTemplate = new TestRestTemplate();
@Test
public void testMyMethodExecute() throws IOException {
String content = "This is file contents\nWith another line.\n";
Path theTestFilePath = TestingUtils.getTempPath(content);
InputStream inputStream = Files.newInputStream(theTestFilePath);
String url = base + "/upload";
final RequestCallback requestCallback = new RequestCallback() {
@Override
public void doWithRequest(final ClientHttpRequest request) throws IOException {
request.getHeaders().setContentType(MediaType.APPLICATION_OCTET_STREAM);
IOUtils.copy(inputStream, request.getBody());
}
};
final RestTemplate restTemplate = new RestTemplate();
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
restTemplate.setRequestFactory(requestFactory);
ClientHttpResponse response = restTemplate.execute(url, HttpMethod.POST, requestCallback,
new ResponseFromHeadersExtractor());
URI location = response.getHeaders().getLocation();
System.out.println("Location: " + location);
Assert.assertNotNull(location);
Assert.assertNotEquals(0, location.getPath().length());
}
private static class ResponseFromHeadersExtractor implements ResponseExtractor<ClientHttpResponse> {
@Override
public ClientHttpResponse extractData(ClientHttpResponse response) {
System.out.println("StringFromHeadersExtractor - response headers: " + response.getHeaders());
return response;
}
}
我需要将该测试中的很多内容重构到某些服务中。
一个RequestCallback
就没必要折腾这么多了。只需使用 PathResource
.
PathResource pathResource = new PathResource(theTestFilePath);
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(pathResource), String.class);
Spring 将使用 ResourceHttpMessageConverter
将给定 Path
标识的文件序列化为请求 body。在内部,Spring 4.x 实现使用 4096 字节的缓冲区大小(这也是 IOUtils#copy(..)
使用的)。
显然,您可以提供所需的响应类型。上面的示例期望响应 body 作为 String
。使用 ResponseEntity
,您可以使用
HttpHeaders responseHeaders = response.getHeaders();