2015-06-21 166 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與您在身體中發送的實體無關。你的網址不好。打開服務器上的調試日誌,並檢查您實際請求的內容。 –