I am using the iText library in Java to generate a pdf file. The idea is that a user fills in some information and that when the user clicks on the generate button the pdf is shown in a new tab in the browser. Now I have stumbled upon some problems doing this, which are :
- the URL does not change, so instead of /application/user.pdf I get /application/dashboard.xhtml
- I can save the pdf file in all browsers except for Chrome.
Please note that I don't want to save it on disc but simply show the pdf in the browser so the user can choose if he wants to save it.
Here is the code that I use to generate my pdf :
public static void createPdf(User user, byte languageNumber, HttpServletResponse response) {
Document document = new Document();
try {
/* PdfWriter writer = PdfWriter.getInstance(document,
new FileOutputStream("c://" + user.getUsername() + "_" + languageCode + ".pdf"));*/
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfWriter.getInstance(document, baos);
document.addTitle("Your CV");
document.addSubject("This is your CV");
document.addKeywords("CV");
document.addAuthor(user.getUsername());
document.open();
document.add(
new Paragraph(user.getPersonalInformation().getFirstname() + " " + user.getPersonalInformation().getLastname()));
document.close();
// setting some response headers
response.setHeader("Expires", "0");
response.setHeader("Cache-Control",
"must-revalidate, post-check=0, pre-check=0");
response.setHeader("Pragma", "public");
// setting the content type
response.setContentType("application/pdf");
response.setContentLength(baos.size());
//ServletOutputStream out = response.getOutputStream();
OutputStream out = response.getOutputStream();
baos.writeTo(out);
out.flush();
out.close();
} catch (DocumentException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
*This method is behind a button on my JSF page *
public String exportPdf() {
user = userService.retrieveLoginUser();
FacesContext context = FacesContext.getCurrentInstance();
try {
Object response = context.getExternalContext().getResponse();
if (response instanceof HttpServletResponse) {
HttpServletResponse hsr = (HttpServletResponse) response;
PdfCreator.createPdf(user, selectLanguage, hsr);
//Tell JSF to skip the remaining phases of the lifecycle
context.responseComplete();
}
return "../" + user.getUsername() + ".pdf";
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Used technologies :
- JSF 2.0
- Facelets
- iText
Thanks in advance :D
The way that I have achieved this in the past is by creating a seperate Servlet to serve PDF documents directly. In the web.xml file you would specify the servlet mapping to *.pdf.
What you can do then is rather than override the FacesServlet response bytes to server the PDF file you just redirect the response to filename.pdf, passing needed parameters in the URL.
Your PDF servlet can actually do the work of building the necessary PDF, it will open in a seperate tab and the URL will match the response redirect.
Does chrome open the PDF and then not render it correctly? In that case, please open an issue at http://new.crbug.com and attach an example PDF file that shows the problem. Reply with the issue number here.
Related
This question already has answers here:
How can I serve a PDF to a browser without storing a file on the server side?
(4 answers)
Closed 6 years ago.
I use iText/Pdfbox to create a PDF document. Everything works when I create the PDF using a standalone Java class like this:
public static void main(String[] args){
...
...
...
}
The document is created correctly.
But I need create a PDF document from a Servlet. I paste the code into the get or post method, run that servlet on the server, but the PDF document isn't created!
This code works as a standalone application:
This code doesn't work:
Please read the documentation. For instance the answer to the question How can I serve a PDF to a browser without storing a file on the server side?
You are currently creating a file on your file system. You aren't using the response object, meaning you aren't sending any bytes to the browser. This explains why nothing happens in the browser.
This is a simple example:
public class Hello extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("application/pdf");
try {
// step 1
Document document = new Document();
// step 2
PdfWriter.getInstance(document, response.getOutputStream());
// step 3
document.open();
// step 4
document.add(new Paragraph("Hello World"));
document.add(new Paragraph(new Date().toString()));
// step 5
document.close();
} catch (DocumentException de) {
throw new IOException(de.getMessage());
}
}
}
However, some browsers experience problems when you send bytes directly like this. It's safer to create the file in memory using a ByteArrayOutputStream and to tell the browser how many bytes it can expect in the content header:
public class PdfServlet extends HttpServlet {
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
// Get the text that will be added to the PDF
String text = request.getParameter("text");
if (text == null || text.trim().length() == 0) {
text = "You didn't enter any text.";
}
// step 1
Document document = new Document();
// step 2
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfWriter.getInstance(document, baos);
// step 3
document.open();
// step 4
document.add(new Paragraph(String.format(
"You have submitted the following text using the %s method:",
request.getMethod())));
document.add(new Paragraph(text));
// step 5
document.close();
// setting some response headers
response.setHeader("Expires", "0");
response.setHeader("Cache-Control",
"must-revalidate, post-check=0, pre-check=0");
response.setHeader("Pragma", "public");
// setting the content type
response.setContentType("application/pdf");
// the contentlength
response.setContentLength(baos.size());
// write ByteArrayOutputStream to the ServletOutputStream
OutputStream os = response.getOutputStream();
baos.writeTo(os);
os.flush();
os.close();
}
catch(DocumentException e) {
throw new IOException(e.getMessage());
}
}
}
For the full source code, see PdfServlet. You can try the code here: http://demo.itextsupport.com/book/
You wrote in a comment
This demo writes the PDF file into the browser. I want to save the PDF on my hard drive.
This question could be interpreted in two different ways:
You want to write the file to a specific directory on the user's disk drive without any user interaction. This is forbidden! It would be a serious security hazard if a server could force a file to be written anywhere on a user's disk drive.
You want to show a dialog box so that the user can save the PDF on his disk drive in a directory of his choice instead of just showing the PDF in the browser. In this case, please take a closer look at the documentation. You'll see this line: response.setHeader("Content-disposition","attachment;filename="+ "testPDF.pdf"); You can set the Content-disposition to inline if you want the PDF to open in the browser, but in the question, the Content-disposition is set to attachment which triggers a dialog box to open.
See also How to show a Save As dialog for a iText generated PDF?
I followed the discussion on spark github page as well as stack overflow to understand how to upload files using spark and apache file uploads.
Now I want the user to have an option to download the image on click.
For example my uploaded files get stored in /tmp/imageName.jpg on the server.
On the client side i want to give the user an option to download the file when the user clicks in the hyperlink.
click here
When the user click on the hyperlink I will call the function with the file path but can't understand how to send the image in response.
I do know that HTML5 has download attribute but that would require the files to be kept in public folder on the server which is not possible.
I went through the previous similar question add tried to replicate for my scenario without success
How can I send a PNG of a QR-code in a HTTP response body (with Spark)?
How download file using java spark?
Edit:
I did follow the link provided in the answer to force download the image, but using response.raw() i'm not able to get the response
response.type("application/force-download");
response.header("Content-Transfer-Encoding", "binary");
response.header("Content-Disposition","attachment; filename=\"" + "xxx\"");//fileName);
try {
HttpServletResponse raw = response.raw();
PrintWriter out = raw.getWriter();
File f= new File("/tmp/Tulips.jpg");
InputStream in = new FileInputStream(f);
BufferedInputStream bin = new BufferedInputStream(in);
DataInputStream din = new DataInputStream(bin);
while(din.available() > 0){
out.print(din.read());
out.print("\n");
}
}
catch (Exception e1) {
e1.printStackTrace();
}
response.status(200);
return response.raw();
Edit 2:
I'm not sure what is the difference between using response.body () vs response.raw().someFunction(). In either case I can seem to send the data back in response. Even if i write a simple response.body("hello") it doesn't reflect in my response.
Is there a difference in how a file would be read as opposed to an image ? Exampling using ImageIO class ?
Below is the solution that work for me:
Service.java
get(API_CONTEXT + "/result/download", (request, response) -> {
String key = request.queryParams("filepath");
Path path = Paths.get("/tmp/"+key);
byte[] data = null;
try {
data = Files.readAllBytes(path);
} catch (Exception e1) {
e1.printStackTrace();
}
HttpServletResponse raw = response.raw();
response.header("Content-Disposition", "attachment; filename=image.jpg");
response.type("application/force-download");
try {
raw.getOutputStream().write(data);
raw.getOutputStream().flush();
raw.getOutputStream().close();
} catch (Exception e) {
e.printStackTrace();
}
return raw;
});
Angular Code
$scope.downloadImage= function(filepath) {
console.log(filepath);
window.open('/api/v1/result/download?filepath='+filepath,'_self','');
}
I am trying to show pdf document in a iframe. I have set the source of the iframe to a servlet and passing some parameter to the servlet.
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
String docName = request.getParameter("docName");
String id = request.getParameter("id");
if (StringUtils.isNotBlank(id) && StringUtils.isNotBlank(docName)) {
DocumentService service = DamServiceProvider.PROVIDER.getDocumentService();
FileInBean fileInBean = new FileInBean();
fileInBean.setDocName(docName);
fileInBean.setId(Integer.valueOf(id));
FileDataBean fileDataBean = service.getFileDataBean(fileInBean);
if (fileDataBean.getStatusCode() == 0) {
Map<String, String> headerFieldMap = fileDataBean.getHeaderFieldMap();
String contentType = headerFieldMap.get("Content-type");
String contentLength = headerFieldMap.get("Content-Length");
String contentDisposition = headerFieldMap.get("Content-Disposition");
byte[] stream = fileDataBean.getStream();
ByteArrayInputStream inputStream = new ByteArrayInputStream(stream);
OutputStream outputStream = response.getOutputStream();
response.reset();
response.setBufferSize(4096);
response.setContentLength(Integer.valueOf(contentLength));
response.setContentType(contentType);
response.setHeader("Content-Disposition", contentDisposition);
System.out.println(contentDisposition);
IOUtils.copy(inputStream, outputStream);
outputStream.close();
inputStream.close();
}
}
} catch (Exception ex) {
Log.error(this, ex.getMessage());
}
}
Now in my page I have a master–detail interface. The master part contains a carousel of series of pdf file items. On clicking the item I am refreshing the detail view which contains the iframe.
I can see the servlet get called. Most of the times the iframe is displaying the pdf document. But sometimes it is showing weird xml structure which contains xml tags and some unreadable output. Please see the attach image:
This is not happening for a particular file. If a file shows this output, sometime later if click the item it shows the valid pdf and if an item shows a valid pdf sometime later it shows this kind of output if I click on it. When the iframe shows this type of output my browser displays an information that this pdf document might be corrupted.
I have checked the repository where the files are and I have found no issues there. All of them are valid pdf and I can download and open them by pdf reader.
I am unable to find the cause of this issue. Any pointer would be very helpful.
Update - 1
I have checked the output. It ends with %%EOF and has %PDF in the beginning.
Update - 2
I have checked in Chrome's Network Console the GET is returning mainly three types of content-type: application/pdf, text/plain, application/octet-stream.
application/pdf: it is showing the pdf.
text/plain it is showing the content that I mentioned above.
application/octet-stream didn't arise in Firefox but in Chrome and in that case it is opening the download file window.
I have placed a log in the servlet to see the content-type that returned from service. For all the cases it is application/pdf.
I think it maybe a problem with the content-Type, you can confirm if this is the espected in your browser with the developer tools (in the network console for Chrome).
try something like this.
File pdfFile = new File(this.pdfStoreLocation + pdfFileName);
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=" + pdfFileName);
response.setContentLength((int) pdfFile.length());
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(pdfFile));
BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream());
// byte array declared
byte[] buf = new byte[2048];
boolean eof = false;
while (!eof) {
int length = bis.read(buf);
if (length == -1) {
eof = true;
}else {
bos.write(buf, 0, length);
}
}
try {
bis.close();
}catch (IOException ex) {
LOGGER.error("Exception in closing buffered input stream on pdf file->" + this.pdfStoreLocation + pdfFileName);
}
try {
bos.flush();
}catch (IOException ex) {
LOGGER.error("Exception in fliushing buffered output stream on pdf file->"
+ this.pdfStoreLocation + pdfFileName);
}
bos.close();
Im trying to achieve the same download file through JSF as described in those posts:
JSF2 download file returns xhtml page source
How to provide a file download from a JSF backing bean?
http://bharatonjava.wordpress.com/2013/02/01/downloading-file-in-jsf-2/
http://prabinhada.blogspot.com/2012/06/how-to-download-file-using-jsf.html
My problem is that I actually can download the file, but its name is just wrong ( on whichever browser ).
It's a XML file I marshall in the download method but browser always receives a file index.jsf ( but mime type is set at text/xml and file size correct )
It runs on:
Mojarra 2.1.7
Richfaces 4.3.3.Final
jboss-7.1.1.Final
Please see below if you spot any problem
public void exportEDL() throws Exception {
String name = this.file.getName();
FacesContext facesContext = FacesContext.getCurrentInstance();
ExternalContext externalContext = facesContext.getExternalContext();
HttpServletResponse response = (HttpServletResponse) externalContext.getResponse();
BufferedOutputStream output = null;
StringWriter sw = null;
try {
...
sw = new StringWriter();
// edl is the jaxb i want to return as xml file
marshaller.marshal(this.edl, sw);
// Init servlet response.
response.reset();
response.setBufferSize(DEFAULT_BUFFER_SIZE);
response.setContentType("text/xml");
response.setContentLength( sw.getBuffer().length() );
response.setHeader("Content-Disposition", "attachment;filename\"" + name + "\"");
output = new BufferedOutputStream(response.getOutputStream(), DEFAULT_BUFFER_SIZE);
output.write(sw.toString().getBytes("UTF-8"));
// Finalize task.
sw.flush();
output.flush();
} catch ( Exception e ) {
FacesMessage msg = new FacesMessage( FacesMessage.SEVERITY_ERROR, null, e.getMessage() );
facesContext.addMessage("Error", msg);
return;
} finally {
// Gently close streams.
IOUtil.close(sw);
IOUtil.close(output);
clearUploadData();
}
// Inform JSF that it doesn't need to handle response.
facesContext.responseComplete();
// facesContext.renderResponse();
// facesContext.release();
}
I tried with something else than a XML marshalling without better results.
Have you guys manage to download a file with correct name + extension?
It's a commandButton in the view:
<h:commandButton action="#{fileUploadBean.exportEDL()}"
Thanks!
You seem to be missing a =, I think this would be right:
response.setHeader("Content-Disposition", "attachment;filename=\"" + name + "\"");
or just
response.setHeader("Content-Disposition", "attachment;filename=" + name);
In this situation, I have created a zip file containing search result files, and am trying to send it to the user. Here is the chunk of code I am currently trying to use.
File[] zippable = new File[files.size()];
File resultFile = ZipCreator.zip(files.toArray(zippable), results);
InputStream result = new FileInputStream(resultFile);
IOUtils.copy(result, response.getOutputStream());
However, this currently doesn't work quite right. Instead of returning the zip file that I have created, it returns an html file. If I manually change the file extension afterwards, I can see that the contents of the file are still the search results that I need. So the problem just lies in returning the proper extension to the response.
Does anyone have any advice for this situation?
You need to set the Content-Type response header to the value application/zip (or application/octet-stream, depending on the target browser). Additionally, you may want to send additional response headers indicating attachment status and filename.
You need to set the content type header to application/octet-stream prior to streaming the results. Depends on what implementation of response you are using on how you actually do this.
Here is some working code, just in case anyone needs it:
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
// The zip file you want to download
File zipFile = new File(zipsResourcesPath + zipFileName);
response.setContentType("application/zip");
response.addHeader("Content-Disposition", "attachment; filename=" + zipFileName);
response.setContentLength((int) zipFile.length());
try {
FileInputStream fileInputStream = new FileInputStream(zipFile);
OutputStream responseOutputStream = response.getOutputStream();
int bytes;
while ((bytes = fileInputStream.read()) != -1) {
responseOutputStream.write(bytes);
}
} catch (IOException e) {
logger.error("Exception: " + e);
}
}
And the HTML:
<a class="btn" href="/path_to_servlet" target="_blank">Download zip</a>
Hope this helps!
So I found a hack for this : ) Just add ".zip" in your filename and set your content type as application/zip. Works like a charm.
response.setContentType("application/zip");
String licenseFileName = eId;
response.setHeader("Content-disposition", "attachment; filename=\"" + licenseFileName +".zip");