Download a file using Angular 6 and Spring Rest API - java

I have a problem downloading a file from a rest api using angular 6
Back-end method
#RequestMapping(value = "/print/{id}")
public ResponseEntity<byte[]> generateReport(#PathVariable("id") long id_project){
Map<String, Object> mapper = new HashMap<String, Object>();
byte[] content = exportService.export(mapper, ReportUtils.testReport, ReportUtils.FileFormat.PDF.toString());
return new ResponseEntity<>(content, Utils.getPDFHeaders("Situation_test.pdf"), HttpStatus.OK);
}
Mathod getHeader
public static HttpHeaders getPDFHeaders(String fileName) {
HttpHeaders head = new HttpHeaders();
head.setContentType(MediaType.parseMediaType("application/pdf"));
head.add("content-disposition", "attachment; filename=" + fileName);
head.setContentDispositionFormData(fileName, fileName);
head.setCacheControl("must-revalidate, post-check=0, pre-check=0");
return head;
}
My Angular Service
download(url: string): any {
let headers = new HttpHeaders();
headers = headers.append('Authorization', 'Bearer ' + this.getToken());
this.http.get(this.API_URL + url, {headers: headers}).subscribe((res) => {
const file = new Blob([res], {
type: 'application/pdf',
});
const a = document.createElement('a');
a.href = this.API_URL + (<any>res)._body;
a.target = '_blank';
document.body.appendChild(a);
a.click();
return res;
}, error => {
let alert: any = {
title: 'Notify Title',
body: 'Notify Body',
};
alert.body = error.error.message || JSON.stringify(error.error);
alert.title = error.error.error;
alert = this.alertService.handleError(error);
alert.position = 'rightTop';
console.log(error);
this.alertService.notifyError(alert);
return error;
});
}
I have already tried my API using PostMan and it word perfectly but in Angular it give me this error
HttpErrorResponse {headers: HttpHeaders, status: 200, statusText: "OK", url: "http://localhost:8080/api/projects/print/1", ok: false, …}
error: {error: SyntaxError: Unexpected token % in JSON at position 0 at JSON.parse (<anonymous>) at XMLHttp…, text: "%PDF-1.4↵%����↵3 0 obj↵<</Filter/FlateDecode/Lengt…25f1>]/Root 8 0 R/Size 10>>↵startxref↵1049↵%%EOF↵"}
headers: HttpHeaders {normalizedNames: Map(0), lazyUpdate: null, lazyInit: ƒ}
message: "Http failure during parsing for http://localhost:8080/api/projects/print/1"
name: "HttpErrorResponse"
ok: false
status: 200
statusText: "OK"
url: "http://localhost:8080/api/projects/print/1"

Try adding content-type to your request headers.
You can try this as an exemple:
let headers = new Headers({'Content-Type': 'application/pdf', 'Accept': 'application/pdf'});

Related

How to terminate request flow at spring cloud api gateway and redirecting to different URL route Path

I have implemented the following microservice applications.
Request flow.
Ui Client(http://localhost:8080) ------> spring cloud
gateway(http://localhost:8081) ------> user
microservice(http://localhost:8602)(api
endpoint=/api/v1/users/bulkUpload)
I am sending an Ajax request to user microservice through the spring cloud gateway service.
Ajax request contains refresh token as a cookie value.
$.ajax({
type: "POST",
enctype: 'multipart/form-data',
url: "http://localhost:8081/api/v1/users/bulkUpload",
xhrFields: {
withCredentials: true
},
data: newData,
processData: false,
contentType: false,
crossDomain: false,
cache: false,
timeout: 600000,
success: function (data) {
......
}
but if the refresh token is not available in Ajax request I want to terminate the request at API gateway level and i want to redirect the user to the Ui client logout page(http://localhost:8080/Logout).
for that, I have implemented a spring cloud gateway filter as follows.
#Component
public class AccessTokenCheckingGlobalFilterPre extends AbstractGatewayFilterFactory<AccessTokenCheckingGlobalFilterPre.Config> {
#Value("${security.oauth2.client.client-id}")
private String client_id;
#Value("${security.oauth2.client.client-secret}")
private String client_secret;
public AccessTokenCheckingGlobalFilterPre() {
super(AccessTokenCheckingGlobalFilterPre.Config.class);
}
#Override
public GatewayFilter apply(AccessTokenCheckingGlobalFilterPre.Config config) {
return (exchange, chain) -> {
Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
if(route!=null && request.getPath().toString().contains("oauth/token")) {
return chain.filter(exchange.mutate().request(request).response(response).build());
}else {
MultiValueMap<String, HttpCookie> cookies = request.getCookies();
List<HttpCookie> accessTokenList = cookies.get("access_token");
List<HttpCookie> refreshTokenList = cookies.get("refresh_token");
HttpHeaders heraders = request.getHeaders();
String access_token="";
String refresh_token = "";
if(accessTokenList != null) {
access_token = accessTokenList.get(0).getValue();
}
if(refreshTokenList != null) {
refresh_token = refreshTokenList.get(0).getValue();
}
if(access_token.isEmpty() && !refresh_token.isEmpty()) {
RestTemplate restTemplate = new RestTemplate();
String credentials = client_id + ":" + client_secret;
String encodedCredentials = new String(Base64.encode(credentials.getBytes()));
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.add("Authorization", "Basic " + encodedCredentials);
HttpEntity<String> requestEntity = new HttpEntity<String>(headers);
String access_token_url = "http://localhost:8602/oauth/token";
access_token_url += "?grant_type=refresh_token";
access_token_url += "&refresh_token=" + refresh_token;
ResponseEntity<String> aouthResponse = restTemplate.exchange(access_token_url, HttpMethod.POST, requestEntity, String.class);
String responseJson = access_token = aouthResponse.getBody();
ObjectMapper mapper = new ObjectMapper();
JsonNode node = null;
try {
node = mapper.readTree(responseJson);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
String newRefreshToken = node.path("refresh_token").asText();
String newAccessToken = node.path("access_token").asText();
int expiresIn = Integer.parseInt(node.path("expires_in").asText());
int refreshTokenExpireTime = Integer.parseInt(node.path("refreshTokenExpires_In").asText());
ResponseCookie accessTokenCookie = ResponseCookie.from("access_token", newAccessToken)
.path("/")
.maxAge(expiresIn)
.build();
ResponseCookie refreshTokenCookie = ResponseCookie.from("refresh_token", newRefreshToken)
.path("/")
.maxAge(refreshTokenExpireTime)
.build();
response.addCookie(refreshTokenCookie);
response.addCookie(accessTokenCookie);
access_token = newAccessToken;
}else if(refresh_token.isEmpty()){
//request.getHeaders().setLocation(URI.create("http://localhost:8080/Logout"));
response.setStatusCode(HttpStatus.PERMANENT_REDIRECT);
response.getHeaders().setLocation(URI.create("http://localhost:8080/Logout"));
//response.setComplete();
return chain.filter(exchange.mutate().request(request).response(response).build());
}
final String value = access_token;
request = request.mutate()
.headers(httpHeaders -> httpHeaders.add("Authorization", "Bearer " + value))
.build();
}
return chain.filter(exchange.mutate().request(request).response(response).build());
};
}
public static class Config {
}
}
this logic has been implemented to check whether refresh token present in the request.
unless it is redirecting Ui client logout page(http://localhost:8080/Logout).
}else if(refresh_token.isEmpty()){
//request.getHeaders().setLocation(URI.create("http://localhost:8080/Logout"));
response.setStatusCode(HttpStatus.PERMANENT_REDIRECT);
response.getHeaders().setLocation(URI.create("http://localhost:8080/Logout"));
//response.setComplete();
return chain.filter(exchange.mutate().request(request).response(response).build());
}
but still, no request termination happening at the API gateway level and the request still is forwarding to user microservice.
How to terminate the request flow at apigateway filter and redirect back to ui client logout page.
Instead of
}else if(refresh_token.isEmpty()){
//request.getHeaders().setLocation(URI.create("http://localhost:8080/Logout"));
response.setStatusCode(HttpStatus.PERMANENT_REDIRECT);
response.getHeaders().setLocation(URI.create("http://localhost:8080/Logout"));
//response.setComplete();
return chain.filter(exchange.mutate().request(request).response(response).build());
}
to redirect the user, use
}else if(refresh_token.isEmpty()){
response.setStatusCode(HttpStatus.FOUND); //302
response
.getHeaders()
.set("Location", "/logout");
return response.setComplete();
}

How can i implement download file From API

I can't download file from my friend service java code from angular that i implement and i don't know how to implement download file from angular please help me
My Code Angular
private _download$ = new Subject<void>();
**this._download$.pipe(switchMap(()=>this._downloadFile())).subscribe(resultBlob =>{
console.log("Success to Next ")
this.loadFile(resultBlob)
},(exception:any)=>{
console.log("exception: ",exception)
},()=>{
console.log("Complete: ")
})
}**
**private _downloadFile(){
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/octet-stream',
responseType : 'blob',
Accept : 'application/octet-stream',
observe : 'response'
})
};**
return this.http.get<Blob>('/col-corecollection-service/exclusion/download-file/'+this.exclusionCode,httpOptions)
}
private download(){
this._download$.next();
}
loadFile(data: any) {
var blob = new Blob([data], {type: 'application/octet-stream'});
var downloadURL = window.URL.createObjectURL(data);
window.open(downloadURL);
}**
And I have code service that my friend implement like this
#GetMapping("/exclusion/download-file/{exclCode}")
#ResponseStatus(HttpStatus.OK)
public ResponseEntity<Resource> exclusionDownloadFile(
#Size(min = 1, max = 50)
#PathVariable String exclCode
) {
if (!this.exclusionService.existsExclusionById(exclCode)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Exclusion code " + exclCode + " not found.", null);
}
SearchExclFilenameExclBlobByIdResponse downloadFile = this.exclusionService.searchExclFilenameExclBlob(exclCode);
byte[] fileData = downloadFile.getExclBlob();
Resource resource = new InputStreamResource(new ByteArrayInputStream(fileData));
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + downloadFile.getExclFilename() + "\"")
.body(resource);
}
when i click my download button and then it can't downloadFile to me please help
Try passing responseType outside headers
Remove responseType from header:
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/octet-stream',
Accept : 'application/octet-stream',
observe : 'response'
})
};
Here is updated code for passing responseType outside headers:
return this.http.get<Blob>('/col-corecollection-service/exclusion/download-file/'+this.exclusionCode,{headers: httpOptions.headers, responseType: 'blob'})
Then if you want download use fileSaver:
var file = new Blob([data], { type: "application/octet-stream" });
this.fileSaverService.save(file);
I use ngx-filesaver which provides the fileSaver directive.
<button class="mdi mdi-download btn btn-primary"
fileSaver
[fileName]="'yourfile.xxx'"
[url]="'/col-corecollection-service/exclusion/download-file/' + this.exclusionCode"
(change)="upload($event)">
Download
</button>

How to receive file csv from java with angular

I have a rest api which returns a file.csv
and then I check that the response is 200, and datas are also in responsed.body.
But the brower didn't download the csv file.
Here is the API :
ResponseEntity<Resource> exportCsv() throws IOException {
/*
*
*/
InputStreamResource resource = new InputStreamResource(new FileInputStream(file));
HttpHeaders headers = new HttpHeaders();
headers.add("Content-disposition", "attachment; filename=sample.csv");
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
return ResponseEntity.ok()
.headers(headers)
.contentLength(file.length())
.contentType(MediaType.parseMediaType("text/csv"))
.body(resource);
}
Here is the Angular
this.stickService.exportCsv( this.stickSearch ).subscribe(
response => this.downloadFile(response),
err => console.log('Error downloading the file.' + err.message
));
downloadFile(data: Response) {
const blob = new Blob([data], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
window.open(url);
}
exportCsv( stickSearch: StickSearch ): Observable<any> {
const headers = this.oAuthService.putTokenInHeaders(this.headers);
let params = new HttpParams({encoder: new HttpURIEncoder()});
if (stickSearch.searchString() !== '') {
params = params
.append('search', stickSearch.searchString())
}
return this.httpClient.get(this.exportCsvUrl + '/exportCsv',
{
responseType: 'text',
params: params,
headers
});
}
I got correct data at response body.
But the download failed. 'Error downloading the file.Http failure during parsing for myURL '
Thanks for helping
It work now, this is consequence
Thanks a lot !
I can see that you are taking the output provided by the server and then building an URL off that CSV. That won't be necessary.
If you just want to download the CSV, then all you are missing is the following in your Java code:
headers.add("Content-disposition", "attachment; filename=sample.csv");
Once you have the header in place, you can get rid of the downloadFile method and probably change the this.httpClient.get request into a window.open.
See if this solves your problem and provide feedback in either case.
By default HttpClient expects that data from server will come in json format. Angular tries to parse text body as json. And that leads to exсeption. In the stickService when you do request the data, you have to specify type or result as text:
constructor (private httpClient: HttpClient){
}
public exportCsv(stickSearch: any) {
return httpClient.get('http://someurl', {responseType: 'text'});
}
Another one point that you use window.open(url). Most browsers block popup windows by default. So maybe it would be better to use an anchor element.
downloadFile(data: Response) {
const blob = new Blob([data], { type: 'text/csv' });
const url = window.URL.createObjectURL(blob);
const anchor = document.createElement('a');
anchor.download = 'myfile.txt'; // here you can specify file name
anchor.href = url;
document.body.appendChild(anchor);
anchor.click();
document.body.removeChild(anchor);
}
Here is the I'm using for this feature.
First, use 'Accept' headers.
Second, set responseType to 'text', the default is 'json'.
Third, the download code.
getCsv(): Observable<any> {
let headers = new HttpHeaders();
headers = headers.set('Accept', 'application/csv');
return this.http.get('SOME__URL', {
headers: headers,
responseType: 'text'
});
}
exportReportCSV() {
getCsv().subscribe( response => {
const link = document.createElement('a');
link.href = window.URL.createObjectURL(new Blob([response], {type: 'text/csv'}));
link.download = this.report.name + '.csv';
link.click();
});

OAuth token Unauthorized on browser

I'm using Angular 7 and I got a problem with Headers.
This is my code:
signin() {
let signinData = this.signinForm.value;
this.encoded = btoa("my-trusted-client:secret");
let header = new Headers();
header.append('Authorization', 'Basic ' + this.encoded);
header.append('Content-type', 'application/x-www-form-urlencoded; charset=utf-8');
this.http.post(this.url + '/oauth/token?grant_type=password&username=' + signinData.username + '&password=' + signinData.password, { headers: header })
.subscribe(data => {
console.log(data);
})
}
Output Error:
error: "Unauthorized"
message: "Unauthorized"
path: "/barometre/oauth/token"
status: 401
timestamp: "2019-04-19T12:35:47.699+0000"
When I test it on Postman I got the results:
Edit:
The problem was on the request signature. I changed this:
this.http.post(this.url + '/oauth/token?grant_type=password&username=' + signinData.username + '&password=' + signinData.password, { headers: header })
By This:
this.http.post(this.url + '/oauth/token', params, { headers: header })
with
let params: URLSearchParams = this.serialize(this.data);
and generate new function
serialize(obj: any): URLSearchParams {
let params: URLSearchParams = new URLSearchParams();
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
var element = obj[key];
params.set(key, element);
}
}
return params;
console.log(params);
};
You need to pass HttpHeaders in post. Check here
const header= {
headers: new HttpHeaders({
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
'Authorization': 'Basic ' + this.encoded
})
};
Also make sure that your Authorization token is valid.
The problem was on the request signature. I changed this:
this.http.post(this.url + '/oauth/token?grant_type=password&username=' + signinData.username + '&password=' + signinData.password, { headers: header })
By This:
this.http.post(this.url + '/oauth/token', params, { headers: header })
with
let params: URLSearchParams = this.serialize(this.data);
and generate new function
serialize(obj: any): URLSearchParams {
let params: URLSearchParams = new URLSearchParams();
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
var element = obj[key];
params.set(key, element);
}
}
return params;
console.log(params);
};

Angular Spring file upload

I'm trying to hit the Spring RestController and just getting:
o.s.web.servlet.PageNotFound : Request method 'POST' not supported
I believe something is missing to map the controller.
<div class="col-sm-1" style="background-color:#cccccc;" align="center"><span class="file-input btn btn-primary btn-file">Import file<input type="file" onchange="angular.element(this).scope().uploadScriptFile(this.files)"></input></span></div>
$scope.uploadCtrFile = function(files) {
console.log(">>>>>>>>>>>>>>>>uploadCtrFile");
var fd = new FormData();
//Take the first selected file
fd.append("file", files[0]);
console.log(">>>>>>>>>>>>>>>>uploadCtrFile angular.toJson: "
+ angular.toJson(fd, 2));
$http.post('/rest/uploadCtrFile/', fd,{
withCredentials: true,
headers: {'Content-Type': undefined },
transformRequest: angular.identity
}).success(function(fd, status, headers, config) {
$scope.success = ">>>>>>>>>>>>>>>>uploadCtrFile Success: "+JSON.stringify({data: fd});
console.log($scope.success);
})
.error(function(fd, status, headers, config) {
$scope.success = ( "failure message: " + JSON.stringify({data: fd}));
console.log($scope.success);
});
};
The controller looks like...
#RequestMapping(value = "/uploadCtrFile/", headers = "'Content-Type': 'multipart/form-data'", method = RequestMethod.POST)
#ResponseBody
public void uploadCtrFile(MultipartHttpServletRequest request, HttpServletResponse response) {
Iterator<String> itr=request.getFileNames();
MultipartFile file=request.getFile(itr.next());
String fileName=file.getOriginalFilename();
log.debug(">>>>>>>>>>>>>>>>>>>submitted uploadCtrFile: "+fileName);
}
The front end shows these messages...
">>>>>>>>>>>>>>>>uploadCtrFile angular.toJson: {}" tl1gen.js:607:0
"failure message: {"data": {"timestamp":1457380766467,"status":405,"error":"Method Not Allowed","exception":"org.springframework.web.HttpRequestMethodNotSupportedException","message":"Request method 'POST' not supported","path":"/rest/uploadCtrFile/"}}"
What am I missing ?
You send undefined as the value of Content-Type, here:
headers: {'Content-Type': undefined }
But your controller requires the Content-Type with multipart/form-data value:
#RequestMapping(..., headers = "'Content-Type': 'multipart/form-data'", ...)
You should either send the correct Content-Type header in your request, like:
headers: {'Content-Type': 'multipart/form-data'}
or remove the headers options from your controller definition:
#RequestMapping(value = "/uploadCtrFile/", method = RequestMethod.POST)

Categories

Resources