How to consume spring StreamingResponseBody in Angular 8 - java

I have the following code in my springboot backend
#GetMapping(value = "/stream", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity<StreamingResponseBody> stream() {
File file = new File("C:\\test\\test.mp3");
try {
InputStream is = new FileInputStream(file);
StreamingResponseBody resp = outputStream -> {
IOUtils.copy(is, outputStream);
};
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentLength(file.length())
.body(resp);
} catch(Exception e) {
e.printStackTrace();
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(null);
}
I am trying to consume it in my angular app like this
HTML
<p>{{ content }}</p>
Typescript
ngOnInit(): void {
const header = new HttpHeaders().set('Accept', 'application/octet-stream');
this.http.get('http://localhost:8080/stream', {headers: header}).subscribe(
(res: any) => {
this.content = JSON.stringify(res);
},
(err: any) => {
this.content = JSON.stringify(err);
}
);
}
However unless I specify responseType: 'text' for the get method, I get only error. Also, I tried specifying responseType as blob and arraybuffer and specified several combinations of observe: body|response|events. Only {} is shown on the page. Not sure what is wrong here.
I tried following this SO post but not of much use.
Can some one please throw some light on it? Thanks in advance.

Related

How to return response from rest api when uploading media file in an Angular 8 app?

I'm using a file upload example from the following link:
enter link description here
You can see in the example that the server need to return status "progress"
in order to see the progress bar.
What I have in my rest api at the moment:
#POST
#Path("Trip/{tripId}")
#Consumes("multipart/form-data")
#Produces("application/json")
public Response uploadTripVideo(#PathParam("tripId") Integer tripId, MultipartFormDataInput input){
String fileName = "";
Map<String, InputPart> uploadForm = input.getFormData();
InputPart inputPart = uploadForm.get("uploadedFile");
try {
MultivaluedMap<String, String> header = inputPart.getHeaders();
fileName = getFileName(header);
//convert the uploaded file to inputstream
InputStream inputStream = inputPart.getBody(InputStream.class,null);
byte [] bytes = IOUtils.toByteArray(inputStream);
//constructs upload file path
fileName = "C:\\Users\\name\\Documents\\myfolder\\trip\\"+ tripId + "\\video\\" + fileName;
writeFile(bytes,fileName);
} catch (IOException e) {
e.printStackTrace();
}
return Response.status(200)
.entity("uploadFile is called, Uploaded file name : " + fileName).build();
}
here is my service call:
uploadVideo(url: string, file: File): Observable<any> {
let formData = new FormData();
formData.append('uploadedFile', file, file.name);
return this.http.post<any>(this.baseUrl + url, formData, {
reportProgress: true,
observe: 'events'
}).pipe(
map(event => this.getEventMessage(event, formData)),
catchError(this.handleError)
);
}
Any idea how to return a response that should indicate on the progress? The probrem is that the event is not coming when calling the service, here is
the code where I subscribe to the post request:
this.upload.uploadVideo(url, this.videoToUpload)
.subscribe(
(event) => {
console.log(event);
if (event.type === HttpEventType.DownloadProgress) {
console.log("download progress");
}
if (event.type === HttpEventType.Response) {
console.log("donwload completed");
}
this.videoUpload = event;
//console.log("POST call successful value returned in body", val);
},
err => {
this.videoUploadError = err;
//console.log("POST call in error", response);
},
() => {
//console.log("The POST observable is now completed.");
});
What I'm getting is error in the console:
Backend returned code undefined, body was: undefined
UPDATE
I've removed the following code and things start moving:
//.pipe(
// map(event => this.getEventMessage(event, formData)),
// catchError(this.handleError)
// );
You can easily do this by setting the reportProgress flag to true in your POST HttpRequest.
The key here is to create a HttpRequest and pasing it to the HttpClient.request method rather than directly calling the post() method.
Once subscribed to the request, you need to check for the event type as
event.type == HttpEventType.UploadProgress
to perform the logic to show loading percentage as
100 * event.loaded / event.total
and check for the completion as
event.type === HttpEventType.Response
Demo at https://stackblitz.com/edit/angular-http-post-status

corrupted Excel xlsx when exporting

I have spent the last several hours going through different Stack Overflow threads which discuss how to download excel files and passing ByteArrayOutputStreams to the front-end. I have a Spring Boot back-end which creates a custom Excel workbook and the appropriate sheets. However, it appears the binary which is returned from the back-end to the angular 6 front-end maybe incorrectly formatted. I have included the different services and controllers as well as the possibly malformed data.
Response body (Truncated value):
"PK]�uN_rels/.rels���j�0��}
�{㴃1F�^Ơ�2��l%1I,c�[��3�l
l�����H��4�R�l��·����q}*�2������;�*��
t"�^�l;1W)�N�iD)ejuD�cKz[׷:}g����#:�
�3����4�7N�s_ni�G�M*7�����2R�+� �2�/�����b��mC�Pp�ֱ$POyQ�抒�DsZ��IС�'un���~�PK����OPK]�uN[Content_Types].xml�SMO1��+6��m��1���G%��β...."
ExcelWriterService.java
private XSSFWorkbook workbook = new XSSFWorkbook();
public byte[] excelExporter(QueryResultsDataset<DataExportTable> data) {
List<DataExportTable> tableList = data.getTables();
List<Map<String, String>> dataRows = tableList.get(0).getRows();
OutputStream outputStream = new ByteArrayOutputStream();
... Create Excel workbook
workbook.write(outputStream);
outputStream.close();
workbook.close();
} catch (IOException ex) {
// Doing nothing
}
return ((ByteArrayOutputStream) outputStream).toByteArray();
Spring Controller
#RequestMapping(value = "<path>", method = RequestMethod.POST)
public #ResponseBody ResponseEntity<byte[]> export(#RequestBody SavedQuery request, Principal principal) {
//Run the job
QueryResultsDataset dataset = dataExportJob.getQueryResultsDataset();
ExcelWriterService ews = new ExcelWriterService();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
StringBuilder filename = new StringBuilder("hello").append(".xlsx");
headers.add("content-disposition", "inline;filename=" + filename.toString());
headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
QueryResultsDataset<DataExportTable> fixResults = (QueryResultsDataset<DataExportTable>) fixMultiplier(dataset, request);
byte [] bytes = ews.excelExporter(fixResults);
ResponseEntity<byte[]> response = new ResponseEntity<byte[]>(bytes, headers, HttpStatus.OK);
return response;
}
}
Downloader.service.ts (Angular 6 Service)
public exportExcel(search: any) {
const opts = [];
const tables = [];
this.api.download(environment._downloadDataEndPoint, search, {})
.subscribe(response => {
console.log(response);
var contentType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
let blob = new Blob([response], {type: contentType});
let link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = "dataweb.xlsx";
link.click();
}
...
}
Api Service (Angular 6 Service)
download(url: string, body: any, header: any) {
header = JSON.parse(this._auth.getToken());
const headers = new Headers(header);
headers.append('responseType', ResponseContentType.Blob.toString());
const options = new RequestOptions({headers: headers});
const response = this.http.post(url, body, options)
.pipe(catchError((error: any) => observableThrowError(error)));
return response;
}
Any help would be great! I'm out of ideas at the moment. Thanks!
You are setting the response type in the wrong place.
Instead of
const headers = new Headers(header);
headers.append('responseType', ResponseContentType.Blob.toString());
const options = new RequestOptions({headers: headers});
Try
const headers = new Headers(header);
const options = new RequestOptions({
headers: headers,
responseType: ResponseContentType.Blob
});
See the documentation for the RequestOptions type.

Downloading a Csv file From angularjs Jax-Rs and java

Hello everyone i am using angularjs java and rest to implement one report. Based on UI field selected there is a call to Java Layer and from java there is some database call and the returned input stream i am downloading in a csv file.
There is one problem happening if i do the same with hitting the the same url by browser which i m passing through angularjs than i m able to download the file but if by using UI i m making the request than there is no download option and data is returned as a stream in http response to angular.
java code:
enter code here
#Path("/files")
public class DownloadCsvFile {
#GET
#Path("/csv")
#Produces({MediaType.APPLICATION_OCTET_STREAM})
public Response getFile() {
StreamingOutput outp = new StreamingOutput() {
#Override
public void write(OutputStream out) throws IOException,
WebApplicationException {
String url ="http://someurl?
indent=on&q=RCE_POST:2016&sort=id%20asc
&rows=100000&start=0&wt=csv";
final InputStreamReader is = new InputStreamReader(
((HttpURLConnection) (new URL(url)).openConnection())
.getInputStream(),
Charset.forName("UTF-8"));
IOUtils.copy(is, out);
}
};
ResponseBuilder response = Response.ok(outp);
response.header("Content-Disposition", "attachment;
filename=\"testFile_file.csv\"");
return response.build();
} }
AngularJs controller code :
enter code here
var app = angular.module('myApp', ['ngProgress']);
app.controller('myCtrl', function($scope,$http,ngProgressFactory) {
// on submit the fun is called
$scope.LMALLPeriodReport =function()
{
return $http.get("http://localhost:8080/IsaveIdeas/rest/files/csv?
parameters="+parameter)
//parameter contain the selected field in UI
.then(function (response) {
var result = response.data;
alert("printing data");
});
};
The same request from the browser http://localhost:8080/IsaveIdeas/rest/files/csv? parameters={parameter} enable me to download the file.
You can use Blob in your angularjs code like this:
....
.then(function (response) {
var fileName = "yourFileName.csv";
var a = document.createElement("a");
document.body.appendChild(a);
response.data = "\ufeff" + response.data;
var file = new Blob([response.data], {encoding:"UTF-8",type:'application/csv;charset=UTF-8'});
var fileURL = URL.createObjectURL(file);
a.href = fileURL;
a.download = fileName;
a.click();
}

Trouble downloading binary file with angular $http or jquery.ajax

My problem is that I am getting the wrong sized file on the client side. Here is my #Controller ...
#RequestMapping(value = "/download/{id}", method = RequestMethod.GET)
public ResponseEntity<?> download(final HttpServletRequest request,
final HttpServletResponse response,
#PathVariable("id") final int id) throws IOException {
try {
// Pseudo-code for retrieving file from ID.
Path zippath = getZipFile(id);
if (!Files.exists(zippath)) {
throw new IOException("File not found.");
}
ResponseEntity<InputStreamResource> result;
return ResponseEntity.ok()
.contentLength(Files.size(zippath))
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(new InputStreamResource(new FileInputStream(zippath.toFile())));
} catch (Exception ex) {
// ErrorInfo is another class, unimportant
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new ErrorInfo(ex));
}
}
... and here is my client-side code using angular-file-saver ...
$http({url: "export/download/" + exportitem.exportId, withCredentials: true})
.then(function(response) {
function str2bytes(str) {
var bytes = new Uint8Array(str.length);
for (var i=0; i<str.length; i++) {
bytes[i] = str.charCodeAt(i);
}
return bytes;
}
var blob = new Blob([str2bytes(response.data)], {type: 'application/octet-stream'});
FileSaver.saveAs(blob, "download.zip");
}, $exceptionHandler);
The original file is 935673 bytes but response.data is 900728 and passing it through the transformation to Uint8Array results in a Blob that is 900728 in size as well. Either way, the resulting saved file is 900728 bytes (34945 bytes shy). Also it is not quite the same in what gets written. It seems to slightly get bloated but then the last part just seems to be truncated. Any ideas what I might be doing wrong?
UPDATE
I just updated my controller method to be the following and got the exact same result. Grrr.
#RequestMapping(value = "/download/{id}", method = RequestMethod.GET)
public void download(final HttpServletRequest request,
final HttpServletResponse response,
#PathVariable("id") final int id) throws IOException {
// Pseudo-code for retrieving file from ID.
Path zippath = getZipFile(id);
if (!Files.exists(zippath)) {
throw new IOException("File not found.");
}
response.setContentType("application/zip");
response.setHeader("Content-Disposition",
"attachment; filename=download.zip");
InputStream inputStream = new FileInputStream(zippath.toFile());
org.apache.commons.io.IOUtils.copy(inputStream, response.getOutputStream());
response.flushBuffer();
inputStream.close();
}
So the problem turned out to be angular's $http service. I also tried jQuery's ajax method. Both gave the same result. If I instead use the native XMLHttpRequest it works correctly. So the Java code was sound. I first verified this by exposing the file directly to the internet and then both using curl and directly accessing in the browser I managed to download the file of the correct size. Then I found this solution so that I could also download the file via javascript.
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = "blob";
xhr.withCredentials = true;
xhr.onreadystatechange = function (){
if (xhr.readyState === 4) {
var blob = xhr.response;
FileSaver.saveAs(blob, filename);
}
};
xhr.send();
Why does angular or jQuery give the wrong result? I still don't know but if anyone wishes to give an answer that uses those it would be appreciated.
responseType: blob
did the trick for a zip file
Angular 2 +
this.http.get('http://localhost:8080/export', { responseType: ResponseContentType.Blob })
.subscribe((res: any) => {
const blob = new Blob([res._body], { type: 'application/zip' });
saveAs(blob, "fileName.zip");
i just stumbled over the 'responseType' in $http requests, you are probably looking for 'blob': https://docs.angularjs.org/api/ng/service/$http#usage

Download local file in angularJS

I have an application that works on Java and AngularJS.
I create pdf files with Java, using FileOutputStream to store them:
#RequestMapping(value = "/getPdf",
method = RequestMethod.GET)
#RolesAllowed(AuthoritiesConstants.USER)
public List<String> getPdf(#RequestParam(value = "id") Long id){
FileOutputStream fileStream = null;
String fileName = textRepository.findOne(id).getTitle() + ".pdf";
String text = textRepository.findOne(id).getUserText();
try {
fileStream = new FileOutputStream(fileName);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// create an API client instance
Client client = new Client("", "");
client.convertHtml(text, fileName);
try {
fileStream.close();
} catch (IOException e) {
e.printStackTrace();
}
List<String> out = new ArrayList<>();
out.add(fileName);
return out;
}
They are created in the root directory of my application.
Now I want to implement a functionality that lets the user to download a pdf by clicking on a link or a button. I have tried with $window.open(), but I can't manage to get the path to my pdf file.
$scope.getPdf = function (id) {
TextService.getPdf(id).success(function(data){
$window.open('../../../../../../' + data[0], '_blank', 'download');
});
};
Here i get an error saying that Cannot GET /data.pdf
EDIT - solved the problem
I had to do a POST method that sends the file:
#RequestMapping(value = "/getPdf",
method = RequestMethod.POST)
#RolesAllowed(AuthoritiesConstants.USER)
public ResponseEntity<byte[]> getPdf(#RequestBody Long id){
String filename = textRepository.findOne(id).getTitle() + ".pdf";
String text = textRepository.findOne(id).getUserText();
ByteArrayOutputStream pdf = new ByteArrayOutputStream();
// create an API client instance
Client client = new Client("", "");
client.convertHtml(text, pdf);
byte[] content = pdf.toByteArray();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/pdf"));
headers.setContentDispositionFormData("inline", filename);
headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
ResponseEntity<byte[]> response = new ResponseEntity<>(content, headers, HttpStatus.OK);
return response;
}
and back to my AngularJS client i have a service that calls the Java method:
angular.module("eddieApp")
.factory("TextService", function($http){
return{
getPdf: function(id){
return $http.post('texts/getPdf', id, { responseType: 'arraybuffer' });
}
};
});
Now in the controller all i had to do is call the service and open a window with the pdf:
$scope.getPdf = function (id) {
TextService.getPdf(id).success(function(data){
var file = new Blob([data], {type: 'application/pdf'});
var fileURL = ($window.URL || $window.webkitURL).createObjectURL(file);
$window.open(fileURL, '_blank', 'download');
});
};
Hope it helps someone!
If you are serving the angular portion from a webserver, you cannot access the filesystem of the server. That would be a severe security problem.
Why not provide another #RequestMapping(value = "/getFile") which serves the file directly to the user, using the proper MIME type as well?
Here is a similar question Return generated pdf using spring MVC with an answer on how to do that.
i hope to help. I think the problem is in the window.open, first should exist a service that makes a post with the url, call the service will pass the id and now if you make the window.open

Categories

Resources