RESTサービスを使用して大きなISOファイルを送信します。RESTサービスに問題はありません。これで、ファイルを取得する残りのサービス、クライアント(Webアプリ)側でメモリ不足の例外を受け取ります。以下は私のコードです
HttpHeaders headers = new HttpHeaders();//1 Line
headers.setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM));//2 Line
headers.set("Content-Type","application/json");//3 Line
headers.set("Cookie", "session=abc");//4 Line
HttpEntity statusEntity=new HttpEntity(headers);//5 Line
String uri_status=new String("http://"+ip+":8080/pcap/file?fileName={name}");//6 Line
ResponseEntity<byte[]>resp_status=rt.exchange(uri_status, HttpMethod.GET, statusEntity, byte[].class,"File5.iso");//7 Line
私は7行でメモリ不足の例外を受け取ります。バッファリングして部分的に取得する必要がありますが、サーバーからこのファイルを取得する方法はわかりません、ファイルのサイズは約500から700MBです。誰でも助けてください。
例外スタック:
org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is Java.lang.OutOfMemoryError: Java heap space
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.Java:972)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.Java:852)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.Java:882)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.Java:778)
javax.servlet.http.HttpServlet.service(HttpServlet.Java:622)
javax.servlet.http.HttpServlet.service(HttpServlet.Java:729)
org.Apache.Tomcat.websocket.server.WsFilter.doFilter(WsFilter.Java:52)
root cause
Java.lang.OutOfMemoryError: Java heap space
Java.util.Arrays.copyOf(Arrays.Java:3236)
Java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.Java:118)
Java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.Java:93)
Java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.Java:153)
org.springframework.util.FileCopyUtils.copy(FileCopyUtils.Java:113)
org.springframework.util.FileCopyUtils.copyToByteArray(FileCopyUtils.Java:164)
org.springframework.http.converter.ByteArrayHttpMessageConverter.readInternal(ByteArrayHttpMessageConverter.Java:58)
org.springframework.http.converter.ByteArrayHttpMessageConverter.readInternal(ByteArrayHttpMessageConverter.Java:1)
org.springframework.http.converter.AbstractHttpMessageConverter.read(AbstractHttpMessageConverter.Java:153)
org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.Java:81)
org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.Java:627)
org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.Java:1)
org.springframework.web.client.RestTemplate.doExecute(RestTemplate.Java:454)
org.springframework.web.client.RestTemplate.execute(RestTemplate.Java:409)
org.springframework.web.client.RestTemplate.exchange(RestTemplate.Java:385)
com.pcap.webapp.HomeController.getPcapFile(HomeController.Java:186)
私のサーバー側REST正常に動作しているサービスコードは
@RequestMapping(value = URIConstansts.GET_FILE, produces = { MediaType.APPLICATION_OCTET_STREAM_VALUE}, method = RequestMethod.GET)
public void getFile(@RequestParam(value="fileName", required=false) String fileName,HttpServletRequest request,HttpServletResponse response) throws IOException{
byte[] reportBytes = null;
File result=new File("/home/arpit/Documents/PCAP/dummyPath/"+fileName);
if(result.exists()){
InputStream inputStream = new FileInputStream("/home/arpit/Documents/PCAP/dummyPath/"+fileName);
String type=result.toURL().openConnection().guessContentTypeFromName(fileName);
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
response.setHeader("Content-Type",type);
reportBytes=new byte[100];//New change
OutputStream os=response.getOutputStream();//New change
int read=0;
while((read=inputStream.read(reportBytes))!=-1){
os.write(reportBytes,0,read);
}
os.flush();
os.close();
}
ここに私がそれをする方法があります。このヒントに基づいて Spring Jira issue 。
RestTemplate restTemplate // = ...;
// Optional Accept header
RequestCallback requestCallback = request -> request.getHeaders()
.setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
// Streams the response instead of loading it all in memory
ResponseExtractor<Void> responseExtractor = response -> {
// Here I write the response to a file but do what you like
Path path = Paths.get("some/path");
Files.copy(response.getBody(), path);
return null;
};
restTemplate.execute(URI.create("www.something.com"), HttpMethod.GET, requestCallback, responseExtractor);
前述のJiraの問題から:
抽出メソッドからInputStreamを単純に返すことはできないことに注意してください。executeメソッドが戻るまでに、基礎となる接続とストリームはすでに閉じられているためです。
Spring 5では、非同期(非ブロッキングなど)http要求を許可する WebClient
クラスが導入されました。ドキュメントから:
RestTemplateと比較すると、WebClientは次のとおりです。
- ノンブロッキング、リアクティブ、少ないハードウェアリソースで高い同時実行性をサポートします。
- Java 8ラムダを利用する機能APIを提供します。
- 同期シナリオと非同期シナリオの両方をサポートします。
- サーバーからのストリーミングをサポートします。
@ bernie のように、WebClientを使用してこれを実現できます。
public void downloadFileUrl( HttpServletResponse response ) throws IOException {
WebClient webClient = WebClient.create();
// Request service to get file data
Flux<DataBuffer> fileDataStream = webClient.get()
.uri( this.fileUrl )
.accept( MediaType.APPLICATION_OCTET_STREAM )
.retrieve()
.bodyToFlux( DataBuffer.class );
// Streams the stream from response instead of loading it all in memory
DataBufferUtils.write( fileDataStream, response.getOutputStream() )
.map( DataBufferUtils::release )
.then()
.block();
}
Reactive ServerスタックがなくてもWebClientを使用できます。RossenStoyanchev(Spring Frameworkチームのメンバー)は、これを Spring MVC開発者向けの「Reactive」ガイド プレゼンテーションで非常によく説明しています。このプレゼンテーションの中で、Rossen StoyanchevはRestTemplateの廃止を検討したと述べましたが、結局彼らは延期することに決めましたが、それは今後も発生する可能性があります!
これまでのところWebClientを使用する主な欠点は非常に急な学習曲線(リアクティブプログラミング)ですが、将来回避する方法はないと思うので、後者よりも早く見てみる方が良いでしょう。
上記の正解のより良いバージョンは、以下のコードです。このメソッドは、ダウンロードされた情報の実際のソースとして機能する別のアプリケーションまたはサービスにダウンロード要求を送信します。
public void download(HttpServletRequest req, HttpServletResponse res, String url)
throws ResourceAccessException, GenericException {
try {
logger.info("url::" + url);
if (restTemplate == null)
logger.info("******* rest template is null***********************");
RequestCallback requestCallback = request -> request.getHeaders()
.setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
// Streams the response instead of loading it all in memory
ResponseExtractor<ResponseEntity<InputStream>> responseExtractor = response -> {
String contentDisposition = response.getHeaders().getFirst("Content-Disposition");
if (contentDisposition != null) {
// Temporary location for files that will be downloaded from micro service and
// act as final source of download to user
String filePath = "/home/devuser/fileupload/download_temp/" + contentDisposition.split("=")[1];
Path path = Paths.get(filePath);
Files.copy(response.getBody(), path, StandardCopyOption.REPLACE_EXISTING);
// Create a new input stream from temporary location and use it for downloading
InputStream inputStream = new FileInputStream(filePath);
String type = req.getServletContext().getMimeType(filePath);
res.setHeader("Content-Disposition", "attachment; filename=" + contentDisposition.split("=")[1]);
res.setHeader("Content-Type", type);
byte[] outputBytes = new byte[100];
OutputStream os = res.getOutputStream();
int read = 0;
while ((read = inputStream.read(outputBytes)) != -1) {
os.write(outputBytes, 0, read);
}
os.flush();
os.close();
inputStream.close();
}
return null;
};
restTemplate.execute(url, HttpMethod.GET, requestCallback, responseExtractor);
} catch (Exception e) {
logger.info(e.toString());
throw e;
}
}
これにより、リクエスト全体がメモリにロードされなくなります。
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
RestTemplate rest = new RestTemplate(requestFactory);
Java.lang.OutOfMemoryErrorの場合:Javaヒープスペースは、JVMにメモリを追加することで解決できます。
-Xmxnメモリ割り当てプールの最大サイズをバイト単位で指定します。この値は、2 MBより大きい1024の倍数でなければなりません。キロバイトを示すには文字kまたはKを追加し、メガバイトを示すにはmまたはMを追加します。デフォルト値は、システム構成に基づいて実行時に選択されます。
サーバーの展開では、-Xmsと-Xmxは多くの場合同じ値に設定されます。 http://docs.Oracle.com/javase/7/docs/technotes/guides/vm/gc-ergonomics.html のガベージコレクターの人間工学を参照してください。
例:
-Xmx83886080
-Xmx81920k
-Xmx80m
おそらく、あなたが抱えている問題は、実行しようとしているリクエスト(大きなファイルをダウンロードする)に厳密には関係していませんが、プロセスに割り当てられたメモリが十分ではありません。