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
Related
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();
}
I am writing the code for multipart file upload using the vertx.io .
In Spring boot my code is as follows. I want to write similar in vertex.io
#RequestMapping(value = "/upload", headers=("content-type=multipart/*") ,method = RequestMethod.POST)
public ImportExportResponse upload(#RequestParam("file") MultipartFile inputFile){
log.info(" upload service method starts ");
ImportExportResponse response = new ImportExportResponse();
FileOutputStream fileOutputStream=null;
try{
File outputFile = new File(FILE_LOCATION+inputFile.getOriginalFilename());
fileOutputStream = new FileOutputStream(outputFile);
fileOutputStream.write(inputFile.getBytes());
fileOutputStream.close();
response.setStatus(ImportExportConstants.ResponseStatus.SUCCESS.name());
response.setErrorMessage(EMPTY);
}catch (Exception e) {
log.error("Exception while upload the file . "+e.getMessage());
response.setStatus(ImportExportConstants.ResponseStatus.ERROR.name());
response.setErrorMessage(errorMap.get(SYSTEM_ERROR_CODE));
}
log.info(" upload service method ends. file is copied to a temp folder ");
return response;
}
Here is the same but in vert.x:
Router router = Router.router(vertx);
// Enable multipart form data parsing
router.post("/upload").handler(BodyHandler.create()
.setUploadsDirectory(FILE_LOCATION));
// handle the form
router.post("/upload").handler(ctx -> {
// in your example you only handle 1 file upload, here you can handle
// any number of uploads
for (FileUpload f : ctx.fileUploads()) {
// do whatever you need to do with the file (it is already saved
// on the directory you wanted...
System.out.println("Filename: " + f.fileName());
System.out.println("Size: " + f.size());
}
ctx.response().end();
});
For more examples you can always see the vertx-examples repo.
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
in my application, users can edit an ODF file via WebODF (http://webodf.org/). On save, i want to send the edited file to a servlet, have it convert to PDF via ODFDOM (http://code.google.com/p/xdocreport/wiki/ODFDOMConverterPDFViaIText) and open in a new window.
Currently i am trying to do this via AJAX. Everything works fine up to the point where i try to open the received PDF file.
My Javascript:
function showPDF(pServletUrl)
{
var successCallback = function(pData)
{
var mimetype = "application/vnd.oasis.opendocument.text";
var blob = new Blob([pData.buffer], {type: mimetype});
var formData = new FormData();
formData.append("file", blob, "test.odt");
jQuery.ajax({
type: "POST",
url: pServletUrl,
async: false,
data: formData,
processData: false,
contentType: false,
success: function(pSuccessData)
{
window.open(pSuccessData);
},
error: function(pErrorData)
{
console.log(pErrorData);
}
});
}
var errorCallback = function(data)
{
console.log(error);
}
_canvas.odfContainer().createByteArray(successCallback, errorCallback);
}
My servlet:
public void handleRequest(HttpServletRequest pRequest, HttpServletResponse pResponse) throws ServletException, IOException
{
BufferedInputStream tBufferedInput = null;
BufferedOutputStream tBufferedOutput = null;
try
{
List<FileItem> tItems = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(pRequest);
for (FileItem tItem : tItems)
{
if (!tItem.isFormField())
{
String tFieldname = tItem.getFieldName();
String tFilename = FilenameUtils.getName(tItem.getName());
InputStream tFilecontent = tItem.getInputStream();
if("file".equals(tFieldname))
{
tBufferedInput = new BufferedInputStream(tFilecontent);
pResponse.reset();
pResponse.setHeader("Content-Type", "application/pdf");
pResponse.setHeader("Content-Disposition", "inline; filename=\"" + "test.pdf" + "\"");
tBufferedOutput = new BufferedOutputStream(pResponse.getOutputStream(), 10240);
this.getOdtAsPdf(tBufferedInput, tBufferedOutput);
tBufferedOutput.flush();
}
}
}
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
try
{
tBufferedInput.close();
tBufferedOutput.close();
}
catch(Exception e)
{
}
}
}
private void getOdtAsPdf(InputStream pInputStream, OutputStream pOutputStream) throws Exception
{
OdfDocument tOdfDocument = OdfDocument.loadDocument(pInputStream);
PdfOptions tPdfOptions = PdfOptions.create();
PdfConverter.getInstance().convert(tOdfDocument, pOutputStream, tPdfOptions);
}
It seems like Javascript wants to parse the recieved PDF file as a URL and (obviously) fails doing so. Is there a way to just open the file in a new window or do i have to find another way to do this?
You can't open the file using Ajax. This is a security restriction fo javascript. You have a few workarounds:
use a plugin which gives a Ajax type experience but opens a file in a new window.more details here
have a form which is submitted to a new window. <form target=_blank /> this will cause a new window to open thus not changing the contents of your current page.
Another option (not so neat) is to store the file in session and in the response of your AJAX, pass the id. Then using Javascript make a call using window.open('downloadurl?id') which will send the response of your PDF file.
You can make use an embed tag to display your blob after you make an ajax call.
Use createObjectUrl method to get url from blob and then display your pdf.
I came across a helpful PDF generation code to show the file to the client in a Spring MVC application ("Return generated PDF using Spring MVC"):
#RequestMapping(value = "/form/pdf", produces = "application/pdf")
public ResponseEntity<byte[]> showPdf(DomainModel domain, ModelMap model) {
createPdf(domain, model);
Path path = Paths.get(PATH_FILE);
byte[] pdfContents = null;
try {
pdfContents = Files.readAllBytes(path);
} catch (IOException e) {
e.printStackTrace();
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/pdf"));
String filename = NAME_PDF;
headers.setContentDispositionFormData(filename, filename);
headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
ResponseEntity<byte[]> response = new ResponseEntity<byte[]>(
pdfContents, headers, HttpStatus.OK);
return response;
}
I added a declaration that the method returns a PDF file ("Spring 3.0 Java REST return PDF document"): produces = "application/pdf".
My problem is that when the code above is executed, it immediately asks the client to save the PDF file. I want the PDF file to be viewed first in the browser so that the client can decide whether to save it or not.
I found "How to get PDF content (served from a Spring MVC controller method) to appear in a new window" that suggests to add target="_blank" in the Spring form tag. I tested it and as expected, it showed a new tab but the save prompt appeared again.
Another is "I can't open a .pdf in my browser by Java"'s method to add httpServletResponse.setHeader("Content-Disposition", "inline"); but I don't use HttpServletRequest to serve my PDF file.
How can I open the PDF file in a new tab given my code/situation?
Try
httpServletResponse.setHeader("Content-Disposition", "inline");
But using the responseEntity as follows.
HttpHeaders headers = new HttpHeaders();
headers.add("content-disposition", "attachment; filename=" + fileName)
ResponseEntity<byte[]> response = new ResponseEntity<byte[]>(
pdfContents, headers, HttpStatus.OK);
It should work
Not sure about this, but it seems you are using bad the setContentDispositionFormData, try>
headers.setContentDispositionFormData("attachment", fileName);
Let me know if that works
UPDATE
This behavior depends on the browser and the file you are trying to
serve. With inline, the browser will try to open the file within the
browser.
headers.setContentDispositionFormData("inline", fileName);
Or
headers.add("content-disposition", "inline;filename=" + fileName)
Read this to know difference between inline and attachment
SpringBoot 2
Use this code to display the pdf in the browser.
(PDF in directory resources -> CLASSPATH)
Using ResponseEntity<?>
#GetMapping(value = "/showPDF")
public ResponseEntity<?> exportarPDF( ){
InputStreamResource file = new InputStreamResource(service.exportPDF());
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "inline;attachment; filename=ayuda.pdf")
.contentType(MediaType.APPLICATION_PDF)
.body(file);
}
#Service
public class AyudaServiceImpl implements AyudaService {
private static final Logger LOGGER = LoggerFactory.getLogger(LoadPDF.class);
LoadPDF pdf = new LoadPDF();
public InputStream exportPDF() {
LOGGER.info("Inicia metodo de negocio :: getPDF");
return pdf.getPDF();
}
}
---CLASE
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ResourceUtils;
public class LoadPDF {
private static final Logger LOGGER = LoggerFactory.getLogger(LoadPDF.class);
public InputStream getPDF(){
try {
File file = ResourceUtils.getFile("classpath:ayuda.pdf");
LOGGER.debug("Ruta del archivo pdf: [" + file + "]");
InputStream in = new FileInputStream(file);
LOGGER.info("Encontro el archivo PDF");
return in;
} catch (IOException e) {
LOGGER.error("No encontro el archivo PDF",e.getMessage());
throw new AyudaException("No encontro el archivo PDF", e );
}
}
}
/* Here is a simple code that worked just fine to open pdf(byte stream) file
* in browser , Assuming you have a a method yourService.getPdfContent() that
* returns the bite stream for the pdf file
*/
#GET
#Path("/download/")
#Produces("application/pdf")
public byte[] getDownload() {
byte[] pdfContents = yourService.getPdfContent();
return pdfContents;
}
What happened is that since you "manually" provided headers to the response, Spring did not added the other headers (e.g. produces="application/pdf"). Here's the minimum code to display the pdf inline in the browser using Spring:
#GetMapping(value = "/form/pdf", produces = "application/pdf")
public ResponseEntity<byte[]> showPdf() {
// getPdfBytes() simply returns: byte[]
return ResponseEntity.ok(getPdfBytes());
}