這花了我相當長的一段時間才解決,所以我想分享它。大多數信息來自SO,我想鞏固到這一個地方。Spring restTemplate execute()POST大文件並獲得響應
我的要求是使用RESTFul POST上傳文件。由於可能大文件,我想流式傳輸文件。我顯然希望能夠閱讀回覆。
我計劃使用Jersey作爲REST服務器和Spring的RestTemplate作爲客戶端(並用於測試)。
我面臨的問題是流式傳輸POST和接收響應。我怎樣才能做到這一點? - (!反問我回答這個問題)
這花了我相當長的一段時間才解決,所以我想分享它。大多數信息來自SO,我想鞏固到這一個地方。Spring restTemplate execute()POST大文件並獲得響應
我的要求是使用RESTFul POST上傳文件。由於可能大文件,我想流式傳輸文件。我顯然希望能夠閱讀回覆。
我計劃使用Jersey作爲REST服務器和Spring的RestTemplate作爲客戶端(並用於測試)。
我面臨的問題是流式傳輸POST和接收響應。我怎樣才能做到這一點? - (!反問我回答這個問題)
我使用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;
}
}
我需要在那個測試中重構許多服務。
沒有必要通過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();
如果這樣的作品(我會很快測試),那麼太棒了! 'RestTemplate.exchange()'對於自動處理Response來說更好,而不需要'ResponseFromHeadersExtractor'(我剛剛注意到我忘了添加 - 現在編輯我的答案)。所以是的,這似乎是一個更好的解決方案。 我做了很多搜索,但沒有找到它。把它扔出去的力量! – HankCa
我無法得到那個工作。我得到一個404,因爲它似乎不匹配PathResource。我已經玩過這個遊戲,但無法完成。有什麼建議麼?在我的答案中看到我的實現(交換InputStream的PathResource inplace)。 – HankCa
@HankCa 404與您在身體中發送的實體無關。你的網址不好。打開服務器上的調試日誌,並檢查您實際請求的內容。 –