Spring - download response as a file
Asked Answered
M

7

36

I am writing application using AngularJS and Spring. I would like to send request to the server and download response returned from controller as a file. In controller I have content of csv file (as string) i.e. 1;2;3;4 (1 row, 4 columns). What is the simplest way to download this response as a file?

Below, I posted my simplified code. In Spring controller:

@RequestMapping(value = "/csv", method = GET)
@ResponseBody
public String getCsvFile() {
    return getCsvContent();
}

In javascript (AngularJS)

return $http({method: 'GET', url: 'csv/'});

I was trying to write to the response stream also (below), setting headers, but on client side I always get this content as a string - not as a file to download.

@RequestMapping(value = "/csv", method = GET)
@ResponseBody
public void getCsvFile(HttpServletResponse response) {
    response.setContentType("application/csv");
    response.setHeader("Content-Disposition", "attachment; filename=file.csv");
    response.setContentLength(getCsvContent().getBytes().length);
    ServletOutputStream out = response.getOutputStream();
    out.write(getCsvContent());
    out.flush();
    out.close();
}

Does anyone knows how to write controller's method correctly in order to download response as a file on client side?

Microwave answered 18/6, 2013 at 19:31 Comment(1)
Are you sending the request via Ajax? If so, do it via a regular request.Shavers
A
26

You can't download a file through an XHR request (which is how Angular makes it's requests). See Why threre is no way to download file using ajax request? You either need to go to the URL via $window.open or do the iframe trick shown here: JavaScript/jQuery to download file via POST with JSON data

Aptitude answered 18/6, 2013 at 20:51 Comment(3)
This does not seem to be correct, since you can use Blob to save the file, like e.g. here: #19328249Proctoscope
@ilya-chernomordik Caution: The download attribute is not supported in IE as of IE11 and support from Apple is only just coming out as of 11/2016.Ssr
You can receive and send binary data... which can be converted to a file, right? developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/…Fistic
R
8

I've wrestled with this myself, trying to make it work from the server. Couldn't. Instead...

  1. To clarify on @dnc253's answer, $window.open(URL) is a method for having an Angular application open a given URL in another window. (It's really just a testable angular proxy for the universal window.open().) This is a great solution, preserves your history, and gets the file downloaded and possibly renders it in that fresh browser window if that's supported. But it often runs into popup blockers, which is a huge problem for reliability. Users often simply don't understand what's going on with them. So, if you don't mind immediately downloading the file with the current window, you can simply use the equally effective universal javascript method: location.href = "uriString", which works like a charm for me. Angular doesn't even realize anything has happened. I call it in a promise handler for once my POST/PUT operation has completed. If need be, have the POST/PUT return the URL to call, if you can't already infer it. You'll get the same behavior for the user as if it had downloaded in response to the PUT/POST. For example:

    $http.post(url, payload).then(function(returnData){
        var uriString = parseReturn(returnData);
        location.href="uriString"
    })
    
  2. You can, in fact, download something directly from an XHR request, but it requires full support for the HTML5 file API and is usually more trouble than it's worth unless you need to perform local transformations upon the file before you make it available to the user. (Sadly, I lack the time to provide details on that but there are other SO posts about using it.)

Ramos answered 6/1, 2014 at 2:27 Comment(2)
just to clarify, this would mean that for a CSV export of records from the DB, the server side code will have to process the POST, write the CSV on the server's file system and return a static link to download it using GET. is that correct?Nip
Not my specialty, but I believe there are ways to allow download via GET without needing to write to server's local filesystem first. It's entirely a question of how the server reacts to the request: does it delegate to some kind of 'download-a-named-local-file' subsystem, or does it simply package a stream of bits as if it had been read from file, and then return it to the client? Either can work. The client doesn't care as long as the right bits arrive in the right order. How you get there just goes to objectives and capabilities.Ramos
P
3

It is possible to download a file using XHR request. You can use angular $http to load the file and then use Blob feature of HTML5 to make browser save it. There is a library that can help you with saving: FileSaver.js.

Proctoscope answered 17/7, 2015 at 14:4 Comment(3)
Keep in mind, as of August 2016, there are significant Safari functionality issues (github.com/eligrey/FileSaver.js/issues/12), because Safari doesn't support naming the file to download :/ caniuse.com/#feat=downloadBedlam
It seems that Safari is the new IE for compatibility issues :)Proctoscope
Agreed! And Safari used to be so hip.. ;) I went to the 'IE6 funeral' event in Denver, a few tears, plenty of smiles.. Maybe there should be a 'Safari funeral' some day also as we send it off to a better place (the Trash!) Hah.Bedlam
M
2

Just in case you guys need it, Here a couple of links that can help you:

  1. download csv file from web api in angular js
  2. Export javascript data to CSV file without server interaction

Cheers

Mirthamirthful answered 19/2, 2014 at 3:39 Comment(0)
D
1

I have written comments below to understand code sample. Some one if using, they can follow it , as I named the files accordingly.

  1. IF server is sending blob in the response, then our client should be able to produce it.

  2. As my purpose is solved by using these. I can able to download files, as I have used type: 'application/*' for all files.

  3. Created "downloadLink" variable is just technique used in response so that, it would fill like some clicked on link, then response comes and then its href would be triggered.

controller.js
//this function is in controller, which will be trigered on download button hit.	

  $scope.downloadSampleFile = function() {
//create sample hidden link in document, to accept Blob returned in the response from back end
    
		var downloadLink = document.createElement("a");

		document.body.appendChild(downloadLink);
		downloadLink.style = "display: none";

//This service is written Below how does it work, by aceepting necessary params
		downloadFile.downloadfile(data).then(function (result) {

			var fName = result.filename;
			var file = new Blob([result.data], {type: 'application/*'});
			var fileURL = (window.URL || window.webkitURL).createObjectURL(file);

          
//Blob, client side object created to with holding browser specific download popup, on the URL created with the help of window obj.
          
			downloadLink.href = fileURL;
			downloadLink.download = fName;
			downloadLink.click();
		});
	};




services.js

.factory('downloadFile', ["$http", function ($http) {
	return {
		downloadfile : function () {
			return $http.get(//here server endpoint to which you want to hit the request
              , {
				responseType: 'arraybuffer',
				params: {
					//Required params
				},
			}).then(function (response, status, headers, config) {
				return response;
			});
		},
	};
}])
Decedent answered 7/12, 2015 at 11:16 Comment(0)
P
0

It's working for me :

  • Spring controller : DownloadController.java

    package com.mycompany.myapp.controller;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.apache.commons.io.IOUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.mycompany.myapp.exception.TechnicalException;
    
    
    @RestController
    public class DownloadController {
    
        private final Logger log = LoggerFactory.getLogger(DownloadController.class);
    
        @RequestMapping(value = "/download", method = RequestMethod.GET)
        public void download(@RequestParam ("name") String name, final HttpServletRequest request, final HttpServletResponse response) throws TechnicalException {
            log.trace("name : {}", name);
    
            File file = new File ("src/main/resources/" + name);
            log.trace("Write response...");
            try (InputStream fileInputStream = new FileInputStream(file);
                    OutputStream output = response.getOutputStream();) {
    
                response.reset();
    
                response.setContentType("application/octet-stream");
                response.setContentLength((int) (file.length()));
    
                response.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");
    
                IOUtils.copyLarge(fileInputStream, output);
                output.flush();
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
    
        }
    
    }
    
  • AngularJs Service : download.service.js

    (function() {
        'use strict';
    
        var downloadModule = angular.module('components.donwload', []);
    
        downloadModule.factory('downloadService', ['$q', '$timeout', '$window',
            function($q, $timeout, $window) {
                return {
                    download: function(name) {
    
                        var defer = $q.defer();
    
                        $timeout(function() {
                                $window.location = 'download?name=' + name;
    
                            }, 1000)
                            .then(function() {
                                defer.resolve('success');
                            }, function() {
                                defer.reject('error');
                            });
                        return defer.promise;
                    }
                };
            }
        ]);
    })();
    
  • AngularJs config : app.js

    (function() {
        'use strict';
    
        var myApp = angular.module('myApp', ['components.donwload']);
       /* myApp.config([function () {
    
        }]);
        myApp.run([function () {
    
        }]);*/
    
    
    })();
    
  • AngularJs controller : download.controller.js

    (function() {
        'use strict';
    
        angular.module('myApp')
            .controller('DownloadSampleCtrl', ['downloadService', function(downloadService) {
                this.download = function(fileName) {
                    downloadService.download(fileName)
                        .then(function(success) {
                            console.log('success : ' + success);
                        }, function(error) {
                            console.log('error : ' + error);
                        });
                };
            }]);
    })();
    
  • index.html

    <!DOCTYPE html>
    <html ng-app="myApp">
    
    <head>
        <title>My App</title>
        <link rel="stylesheet" href="bower_components/normalize.css/normalize.css" />
        <link rel="stylesheet" href="assets/styles/main.css" />
        <link rel="icon" href="favicon.ico">
    </head>
    
    <body>
        <div ng-controller="DownloadSampleCtrl as ctrl">
            <button ng-click="ctrl.download('fileName.txt')">Download</button>
        </div>
    
        <script src="bower_components/angular/angular.min.js"></script>
    
        <!-- App config -->
        <script src="scripts/app/app.js"></script>
        <!-- Download Feature -->
        <script src="scripts/app/download/download.controller.js"></script>
        <!-- Components -->
        <script src="scripts/components/download/download.service.js"></script>
    </body>
    
    </html>
    
Protrusion answered 27/6, 2015 at 13:48 Comment(1)
it works in chrome, but not in Mozilla and IE, do you know how to fix it?Busiek
A
0

//JAVA PART

@RequestMapping(value = "/report-excel", method = RequestMethod.GET)
    public ResponseEntity<byte[]> getReportExcel(@RequestParam("bookingStatusType") String bookingStatusType,
            @RequestParam("endDate") String endDate, @RequestParam("product") String product, @RequestParam("startDate") String startDate)throws IOException, ParseException {

//Generate Excel from DTO using any logic after that do the following
byte[] body = wb.getBytes();
HttpHeaders header = new HttpHeaders();
        header.setContentType(new MediaType("application", "xlsx"));
        header.set("Content-Disposition", "inline; filename=" + fileName);
        header.setCacheControl("must-revalidate, post-check=0, pre-check=0");
        header.setContentLength(body.length);

 return new ResponseEntity<byte[]>(body, header, HttpStatus.OK);
}



//HTML PART
<html>
<head>
<title>Test</title>
<meta http-equiv="content-type" content="application/x-www-form-urlencoded; charset=UTF-8">
</head>
<body>
  <form name="downloadXLS" method="get" action="http://localhost:8080/rest/report-excel" enctype="multipart/form-data">
    <input type="text" name="bookingStatusType" value="SALES"></input>
    <input type="text" name="endDate" value="abcd"></input>
    <input type="text" name="product" value="FLIGHT"></input>
    <input type="text" name="startDate" value="abcd"></input>
    <input onclick="document.downloadXLS.submit()" value="Submit"></input>
  </form>
</body>
</html>
Afterdamp answered 3/12, 2015 at 10:23 Comment(1)
@Szabolcs the downloadXLS form will make rest api call to get the file as a stream and open in a new tab to download it through the parameters of Content-dispostion inline indicating through CORS to java scriptAfterdamp

© 2022 - 2024 — McMap. All rights reserved.