Java Web应用动态生成PDF文件的问题整理

来源:互联网 发布:js new date 加1天 编辑:程序博客网 时间:2024/05/18 13:27
概要:Java Web应用在给浏览器返回动态生成的PDF文件时,要确保发送的HTTP header消息中正确设置了相应参数,否则浏览器可能无法获取到或者获取到错误的PDF文件。整理了下这些参数的设置方法,还有遇到的一些问题,记录下来避免以后再走弯路。

HTTP header部分相关参数主要有:
contentType:     说明 body部分的格式,是HTML还是PDF、JPG等
Cache-Control:     告诉浏览器发送的内容能否缓存,静态图片等缓存可以避免重复向服务器请求,而一些每次都会改变的内容则不希望缓存,比如查询账户余额情况。
实际上由于不同浏览器对标准的支持差异、网络环境等因素,还需要进行一些特殊处理。

Java Web应用生成PDF、图片等二进制内容,推荐的方式是使用servlet, 下面是《iText in Action》中的例子:

public class Hello extends HttpServlet {
protected void doGet(
HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
try {
String text = request.getParameter("text");
if (text == null || text.trim().length() == 0) {
text = "You didn't enter any text.";
}
Document document = new Document();
ByteArrayOutputStream baos
= new ByteArrayOutputStream();
PdfWriter.getInstance(document, baos);
document.open();
document.add(new Paragraph(String.format(
"You have submitted the following text in memory using the %s method:",
request.getMethod())));
document.add(new Paragraph(text));
document.close();
response.setHeader("Expires", "0");
response.setHeader("Cache-Control",
"must-revalidate, post-check=0, pre-check=0");
response.setHeader("Pragma", "public");
response.setContentType("application/pdf");
response.setContentLength(baos.size());
OutputStream os = response.getOutputStream();
baos.writeTo(os);
os.flush();
os.close();
}
catch(DocumentException e) {
throw new IOException(e.getMessage());
}
}

其中除了前面说明的两个参数外,还指定了几个其它的参数,说明如下:
response.setContentType("application/pdf");     //文件的类型
response.setHeader("Content-Disposition", "inline; filename=\"filename.pdf\"");     //老版本IE不能正确处理contentType,需要这样设置避免把PDF文件当成文本内容。
response.setHeader("Expires", "0");     //说明内容的有效期,如果设置为0或者-1,会立即过期,浏览器下次使用时会重新获取,否则会使用缓存的数据。
response.setHeader("Cache-Control",
"must-revalidate, post-check=0, pre-check=0");     //禁用缓存:no-cache,但需要这样设置以避免一些浏览器的multiple-hit问题
response.setHeader("Pragma", "public");     //禁止网络上的proxy服务器缓存内容
response.setContentLength(baos.size());     //指定文件大小,不指定的话某些浏览器不能正确处理文件大小。

disposition-type 通过设置inline或attachment可以指定浏览器如何处理文件内容,如inline将直接在浏览器中显示,而attachment则弹出对话框让用户下载。在希望浏览器直接显示PDF文件,或者下载JPEG图片,而不是默认的下载和直接显示时有用。设置filename可以指定保存文件名。在实际使用中,有时会遇到中文文件名乱码的情况,需要试验给filename设置的文字编码和head中指定的编码的关系。

另外推测在使用JSF页面、PrimeFaces的download控件等产生输出时,服务器端默认是设置了Expires、Cache-control等参数,向浏览器指明为动态生成的内容。具体情况待确认。

使用JasperReport生成PDF格式报表时,需要将输出写到OutputStream中,可以通过以下方式转换为InputStream:

  ByteArrayOutputStream out = new ByteArrayOutputStream();

  class1.putDataOnOutputStream(out);

  class2.processDataFromInputStream(

  new ByteArrayInputStream(out.toByteArray())

  );

但是文件如果太大,可能会内存溢出。
另外如果先写为磁盘文件,再从磁盘上打开文件读取传递给response, 可能读取程序会找不到文件或读到旧版本的文件,原因应该是创建PDF和读PDF文件间隔时间太短,生成的PDF文件被Java I/O代码缓冲,还没有写到磁盘上。解决方法是使用同一个文件句柄,或者将文件路径返回给浏览器,由浏览器另外发起一个get请求下载文件,需要注意应确保文件名唯一,并且过期的文件能被妥善处理,避免变成垃圾占用空间。

除了使用servlet外,还可以使用JSP来生成PDF、JPG等二进制文件,但不推荐,因为为存在几种潜在的风险:
1、某些服务器系统会假定JSP输出总是文本格式,会导致客户端不能正确显示。
2、JSP源文件中不小心添加的多余的空格、换行等都可能会被加到输出流中,从而破坏数据。
3、对response.getOutputStream()多余的调用,可能导致OutputStream opened twice错误。

参考资料:
[1] Bruno Lowagie, iText in Action, Manning Publishing Co.,2006
[2] W3C RFC2616
http://www.w3.org/Protocols/rfc2616/rfc2616.html
[3] 吴碧宇, Http header详解, 2008
http://www.cnblogs.com/wubiyu/archive/2008/02/27/1084189.html


0 0
原创粉丝点击