2015-06-21 1014 views
1

这花了我相当长的一段时间才解决,所以我想分享它。大多数信息来自SO,我想巩固到这一个地方。Spring restTemplate execute()POST大文件并获得响应

我的要求是使用RESTFul POST上传文件。由于可能大文件,我想流式传输文件。我显然希望能够阅读回复。

我计划使用Jersey作为REST服务器和Spring的RestTemplate作为客户端(并用于测试)。

我面临的问题是流式传输POST和接收响应。我怎样才能做到这一点? - (!反问我回答这个问题)

回答

1

我使用SpringBoot 1.2.4.RELEASE与新泽西州通过拉进:

compile("org.springframework.boot:spring-boot-starter-jersey") 

我创造了灿烂的春天启动项目的项目(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; 
    } 

我最烦恼的地方在于得到一个位置的响应。首先,我不得不处理流式大文件。正如您在下面的测试中看到的,我遵循了https://stackoverflow.com/a/15785322/1019307。我没有获得响应,无论我与HttpMessageConverterExtractor按该职位的尝试:

final HttpMessageConverterExtractor<String> responseExtractor = 
new HttpMessageConverterExtractor<String>(String.class, restTemplate.getMessageConverters()); 

找到https://stackoverflow.com/a/6006147/1019307后,我写道:

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; 
    } 
} 

我需要在那个测试中重构许多服务。

3

没有必要通过RequestCallback通过所有这些篮球。只需使用PathResource即可。

PathResource pathResource = new PathResource(theTestFilePath); 
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(pathResource), String.class); 

Spring将使用一个ResourceHttpMessageConverter序列化由给定Path对请求主体标识的文件。在内部,Spring 4.x实现使用4096字节的缓冲区大小(这也是IOUtils#copy(..)使用的)。

显然,您可以提供您想要的响应类型。上面的示例期望响应正文为String。用ResponseEntity,你可以访问所有的回应标题

HttpHeaders responseHeaders = response.getHeaders(); 
+0

如果这样的作品(我会很快测试),那么太棒了! 'RestTemplate.exchange()'对于自动处理Response来说更好,而不需要'ResponseFromHeadersExtractor'(我刚刚注意到我忘了添加 - 现在编辑我的答案)。所以是的,这似乎是一个更好的解决方案。 我做了很多搜索,但没有找到它。把它扔出去的力量! – HankCa

+0

我无法得到那个工作。我得到一个404,因为它似乎不匹配PathResource。我已经玩过这个游戏,但无法完成。有什么建议么?在我的答案中看到我的实现(交换InputStream的PathResource inplace)。 – HankCa

+0

@HankCa 404与您在身体中发送的实体无关。你的网址不好。打开服务器上的调试日志,并检查您实际请求的内容。 –