web-dev-qa-db-ja.com

OutputStreamを持つSpring @Controllerからファイルを返す

Springコントローラーからファイルを返したい。 OutputStreamの実装を提供できるAPIを既に持っているので、それをユーザーに送信する必要があります。

そのため、フローは次のようになります。

出力ストリームの取得->サービスはこの出力ストリームをコントローラーに渡します->コントローラーはユーザーに送信する必要があります

私はそれを行うには入力ストリームが必要だと思うし、次のようなApache Commons API機能も見つけました

IOUtils.copy(InputStream is, OutputStream os)

しかし、問題は、それを反対側に変換します-> osからisではなく、isからos

編集

答えは正しいことをしていないので、明確にするために:
Dropbox APIを使用し、OutputStreamでファイルを受信します。URLの入力中にこの出力ストリームをユーザーに送信したいです。

FileOutputStream outputStream = new FileOutputStream(); //can be any instance of OutputStream
DbxEntry.File downloadedFile = client.getFile("/fileName.mp3", null, outputStream);

それが、outputstreaminputstreamに変換することについて話していた理由ですが、それを行う方法がわかりません。さらに、これを解決するより良い方法があると思います(おそらく出力ストリームからバイト配列を返す)

ドロップボックスからファイルをダウンロードするメソッドにパラメーターを介してサーブレット出力ストリーム[response.getOutputstream()]を渡そうとしましたが、まったく機能しませんでした

編集2

私のアプリの「フロー」は次のようなものです:@Joeblade

  1. ユーザーがURLを入力:/ download/{file_name}

  2. Spring ControllerはURLをキャプチャし、@ Service Layer to downloadファイルを呼び出してそのコントローラーに渡します:

    @RequestMapping(value = "download/{name}", method = RequestMethod.GET)
    public void getFileByName(@PathVariable("name") final String name, HttpServletResponse response) throws IOException {
        response.setContentType("audio/mpeg3");
        response.setHeader("Content-Disposition", "attachment; filename=" + name);
        service.callSomeMethodAndRecieveDownloadedFileInSomeForm(name); // <- and this file(InputStream/OutputStream/byte[] array/File object/MultipartFile I dont really know..) has to be sent to the user
    }
    
  3. @ServiceはDropbox APIを呼び出し、指定されたfile_nameでファイルをダウンロードし、それをすべてOutputStreamに入れてから、それを(何らかの形で。多分OutputStream、byte []配列、または他のオブジェクト-どちらを使用するのが良いかわかりません)コントローラーに渡します:

    public SomeObjectThatContainsFileForExamplePipedInputStream callSomeMethodAndRecieveDownloadedFileInSomeForm(final String name) throws IOException {
        //here any instance of OutputStream - it needs to be passed to client.getFile lower (for now it is PipedOutputStream)
        PipedInputStream inputStream = new PipedInputStream(); // for now
        PipedOutputStream outputStream = new PipedOutputStream(inputStream);
    
    
        //some dropbox client object
        DbxClient client = new DbxClient();
        try {
            //important part - Dropbox API downloads the file from Dropbox servers to the outputstream object passed as the third parameter
            client.getFile("/" + name, null, outputStream);
        } catch (DbxException e){
            e.printStackTrace();
        } finally {
            outputStream.close();
        }
        return inputStream;
    }
    
  4. Controlerはファイルを受信します(上で言ったように、どの形式であるかはまったくわかりません)渡してからユーザーに

そのため、dropboxClient.getFile()メソッドを呼び出してダウンロードしたファイルでOutputStreamを受信し、ダウンロードしたファイルを含むこのOutputStreamをユーザーに送信する必要があります。 ?

11
azalut

ByteArrayOutputStreamByteArrayInputStreamを使用できます。例:

// A ByteArrayOutputStream holds the content in memory
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

// Do stuff with your OutputStream

// To convert it to a byte[] - simply use
final byte[] bytes = outputStream.toByteArray();

// To convert bytes to an InputStream, use a ByteArrayInputStream
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);

他のストリームペアでも同じことができます。例えば。ファイルストリーム:

// Create a FileOutputStream
FileOutputStream fos = new FileOutputStream("filename.txt");

// Write contents to file

// Always close the stream, preferably in a try-with-resources block
fos.close();

// The, convert the file contents to an input stream
final InputStream fileInputStream = new FileInputStream("filename.txt");

また、Spring MVCを使用する場合、ファイルを含むbyte[]を確実に返すことができます。必ず@ResponseBodyで応答に注釈を付けてください。このようなもの:

@ResponseBody
@RequestMapping("/myurl/{filename:.*}")
public byte[] serveFile(@PathVariable("file"} String file) throws IOException {
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 
    DbxEntry.File downloadedFile = client.getFile("/" + filename, null, outputStream);
    return outputStream.toByteArray();
} 
11
wassgren

HttpServletResponseからOutputStreamを取得し、ファイルを書き込みます(この例では、Apache CommonsのIOUtilsを使用)

@RequestMapping(value = "/download", method = RequestMethod.GET)
public void download(HttpServletResponse response) {
    ...
    InputStream inputStream = new FileInputStream(new File(PATH_TO_FILE)); //load the file
    IOUtils.copy(inputStream, response.getOutputStream());
    response.flushBuffer();
    ...
}

例外が発生した場合は、必ずtry/catchを使用してストリームを閉じてください。

24
John Smith

最も望ましい解決策は、 InputStreamResource with ResponseEntityを使用することです。必要なのはContent-Length手動:

@RequestMapping(value = "/download", method = RequestMethod.GET)
public ResponseEntity download() throws IOException {
    String filePath = "PATH_HERE";
    InputStream inputStream = new FileInputStream(new File(filePath));
    InputStreamResource inputStreamResource = new InputStreamResource(inputStream);
    HttpHeaders headers = new HttpHeaders();
    headers.setContentLength(Files.size(Paths.get(filePath));
    return new ResponseEntity(inputStreamResource, headers, HttpStatus.OK);
}
11

この回答 を読むことをお勧めします

@ResponseBody
@RequestMapping("/photo2", method = RequestMethod.GET, produces = MediaType.IMAGE_JPEG_VALUE)
public byte[] testphoto() throws IOException {
    InputStream in = servletContext.getResourceAsStream("/images/no_image.jpg");
    return IOUtils.toByteArray(in);
}

michal.kreuzmanによる回答

私は自分自身に似たものを書くつもりでしたが、もちろんすでに答えられています。

最初にすべてをメモリに入れるのではなく、単にストリームを渡したい場合は、 this answer を使用できますが、これはテストしていません(動作していません)が、正当に見えます:)

@RequestMapping(value = "report1", method = RequestMethod.GET, produces = "application/pdf")
@ResponseBody
public void getReport1(OutputStream out) {
    InputStream in; // retrieve this from wherever you are receiving your stream
    byte[] buffer = new byte[1024];
    int len;
    while ((len = in.read(buffer)) != -1) {
        out.write(buffer, 0, len);
    }
    in.close();
    out.flush(); // out.close? 
}

実は、これは IOUtils.copy /IOUtils.copyLargeとほぼ同じです。 line:2128あなたが言うことは間違った方向をコピーします。

ただし、最初に質問内容を理解してください。出力ストリーム(書き込み用のオブジェクト)から読み取り、入力ストリーム(読み取り用のオブジェクト)に書き込みたい場合、読み取りオプションも提供するオブジェクトに書き込むことが本当に必要だと思います。

そのためには、PipedInputStreamとPipedOutputStreamを使用できます。これらは一緒に接続されているため、出力ストリームに書き込まれたバイトは、対応する入力ストリームから読み取ることができます。

したがって、バイトを受信して​​いる場所では、出力ストリームにバイトを書き込んでいると想定します。これがあります:

// set up the input/output stream so that bytes written to writeToHere are available to be read from readFromhere
PipedInputStream readFromHere = new PipedInputStream();
PipedOutputStream writeToHere = new PipedOutputStream(readFromHere);

// write to the outputstream as you like
writeToHere.write(...)

// or pass it as an outputstream to an external method
someMather(writeToHere);

// when you're done close this end.
writeToHere.close();


// then whenever you like, read from the inputstream
IOUtils.copy(readFromHere, out, new byte[1024]); 

IOUtils.copyを使用する場合、出力ストリームが閉じられるまで読み取りを続けます。そのため、開始する前に既に閉じていることを確認してください(同じスレッドで書き込み/読み取りを実行する場合)か、別のスレッドを使用して出力バッファーに書き込み、最後に閉じます。

それでも探しているものではない場合は、質問を絞り込む必要があります。

2
Joeblade

あなたのケースで最もメモリ効率の良いソリューションは、応答OutputStreamをDropbox APIに渡すことです:

@GetMapping(value = "download/{name}")
public void getFileByName(@PathVariable("name") final String name, HttpServletResponse response)
        throws IOException, DbxException {
    response.setContentType("audio/mpeg3");
    response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + name + "\"");
    response.setContentLength(filesize); // if you know size of the file in advance

    new DbxClient().getFile("/" + name, null, response.getOutputStream());
}

APIによって読み取られたデータは、ユーザーに直接送信されます。どのタイプの追加のバイトバッファも必要ありません。


PipedInputStream/PipedOutputStreamに関しては、2つのスレッド間の通信をブロックするためのものです。 PipedOutputStreamは、1024バイト(デフォルト)の後、他のスレッドがパイプの終わりから読み取りを開始するまでスレッドの書き込みをブロックします(PipedInputStream)。

1
Lu55

応答出力ストリームに書き込む際に留意すべきことの1つは、定期的にラップしたライターでflush()を呼び出すことは非常に良い考えです。これは、接続が切断された場合(たとえば、ユーザーがダウンロードをキャンセルした場合)に、例外が長時間にわたってスローされることがないためです。これは事実上、コンテナのリソースリークになる可能性があります。

1
seismic