How to return an iText PDF document to the client

2019-09-06 09:10发布

问题:

I am trying to return an iText generated PDF from the server side to the client side to enable the user to store it. I am following How to convert iTextPDF Document to Byte Array (AceFunk)

private static ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

  public static byte[] main(java.util.List<Transcript> listymAwards, String scoutName, String groupName) {
  Document document = new Document(PageSize.A4, 0f, 0f, 0f, 0f);

try {

  //PdfWriter.getInstance(document, new FileOutputStream(FILE));
  PdfWriter.getInstance(document, byteArrayOutputStream);  // Do this BEFORE document.open()

  document.open();
  addMetaData(document);
  addImages(document);
  addTitlePage(document, scoutName);

  //Add the table of achievements
  if (listymAwards == null || listymAwards.isEmpty()) {
      //Nothing to do.
      //System.out.println("Scout not found.");
  }else{

      Paragraph preface = new Paragraph();

      PdfPTable table = new PdfPTable(3);
      table.setWidths(new int[]{1, 3, 1});
      table.setHeaderRows(1);

      PdfPCell c1 = new PdfPCell(new Phrase("Section"));
      c1.setHorizontalAlignment(Element.ALIGN_CENTER);
      table.addCell(c1);

      c1 = new PdfPCell(new Phrase("Award"));
      c1.setHorizontalAlignment(Element.ALIGN_CENTER);
      table.addCell(c1);

      c1 = new PdfPCell(new Phrase("Date"));
      c1.setHorizontalAlignment(Element.ALIGN_CENTER);
      table.addCell(c1);
      table.setHeaderRows(1);

      String storedName = null;
      int noRows = 0;
      String firstTable = "Yes";
      DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd");
      DateFormat df2 = new SimpleDateFormat("dd-MM-yyyy");
      // We add three empty lines
      addEmptyLine(preface, 1);
      addEmptyLine(preface, 1);
      addEmptyLine(preface, 1);

      for (final Transcript scoutNamesDescription : listymAwards) {
          if (firstTable.equals("Yes") && noRows > 30){ // Change this to number of rows required
              noRows = 0;
              firstTable = "No";
              document.add(table);
              document.newPage();
              table.flushContent();
          }else{
              if (firstTable.equals("No") && noRows > 50){ // Change this to number of rows required
                  // We add three empty lines if not the first table
                  document.add(preface);
              }
          }

          noRows++;

          if (scoutNamesDescription.getSection().equals(storedName)){
              table.addCell(" ");
          }else{
              storedName = scoutNamesDescription.getSection();
              table.addCell(scoutNamesDescription.getSection());
          }
          table.addCell(scoutNamesDescription.getAwardName());

          Date awardedDate = df1.parse(scoutNamesDescription.getAwardedDate());
          String awardedString = df2.format(awardedDate);
          table.addCell(awardedString);
      }
      //Print the remaining rows.
      // We add three empty lines if not the first table
      if (firstTable.equals("No")){
          document.add(preface);
      }else{
          firstTable = "No";
      }
      document.add(table);
  }

  //Add signature
  addSignaturePage(document, groupName);

  document.close();


} catch (Exception e) {
  e.printStackTrace();
}

byte[] pdfBytes = byteArrayOutputStream.toByteArray();
return pdfBytes;

}

This is returned to the server side:

    byte[] pdfBytes = ScoutTranscript.main(listymAwards, scoutName, groupName);
    System.out.println("Point 3");

    return pdfBytes;

Which is then returned to the client side:

Window.open("data:application/pdf;base64,"+result,"_parent", "location=no");

Where I get the error message:

This site can’t be reached

The webpage at data:application/pdf;base64,[B@154 might be temporarily down or it may have moved permanently to a new web address. 

回答1:

Error #1:

Let's start with a small test that doesn't involve iText in any way. Try this:

byte[] test = "Test".getBytes();
System.out.println("Test " + test);

What is written to the output? In my case, it's:

Test [B@3da3da69

The [ indicates that I am trying to convert an array to a String; the B indicates that the array contains bytes; the @ separates the type from the ID; the characters that follow are the ID in hexadecimal format (a hashcode). See Java: Syntax and meaning behind "[B@1ef9157"? Binary/Address?

If result is of type byte[] and you have this line:

Window.open("data:application/pdf;base64,"+result,"_parent", "location=no");

Then "data:application/pdf;base64,"+result results in something like "data:application/pdf;base64,[B@154". That doesn't make any sense, does it?

Now try this:

byte[] test = "Test".getBytes();
System.out.println("Test " + new String(test));

The output is:

Test Test

You were using a byte[] as if it was a String. This is your first error.

I was going to say something nasty because this is not an error that a Java developer would make. However, I've just read your bio, and I see that you're new at Java and you are (probably) teaching yourself how to code in Java (just like I did 20 years ago), so I censored myself ;-)

Error #2:

You can not solve your problem by replacing your code by:

Window.open("data:application/pdf;base64,"+ new String(result),"_parent", "location=no");

You can't do that because you are making a second error: the bytes in result represent a binary file, and the JavaScript in your browser expects a Base64 encoded file. Base64 encoding is used to convert binary to text, and vice-versa. See What is base 64 encoding used for?

If you want to send the binary PDF file to the browser as a Base64-encoded string, you have to Base64 encode your bytes. This can be done with this class: http://itextsupport.com/apidocs/itext5/latest/com/itextpdf/text/pdf/codec/Base64.html

For instance:

Window.open("data:application/pdf;base64,"+ Base64.encodeBytes(result),"_parent", "location=no");

This should already work for some browsers, but not for all. I have no idea where you are using Window.open(), nor why you want to involve Base64. You may want to elaborate on that. In my opinion, it's a bad idea.

How it should be done:

Typically, you will write a Servlet that runs in an application server. That servlet can be reached from a browser using a URL. You do not need to save the generated file on the server as suggested in the other answer (I down-voted that answer because it is not helpful and totally wrong). Your approach that creates a ByteArrayOutputStream and then gets a byte[] is correct, but you have to serve those bytes to a HttpServletResponse object.

See How can I serve a PDF to a browser without storing a file on the server side? for a full example.

Regarding Window.open()

You can use Window.open() on the client side, to open a web page in a new window. For instance:

window.open("http://www.itextpdf.com");

You can serve a page that has contains this snippet, but in your case, you have to replace http://www.itextpdf.com with the URL that was defined for your servlet.

You may have found your "solution" here: Opening PDF String in new window with javascript

But if you read the comments, you'll notice that this approach is problematic in combination with some browsers.



标签: java gwt itext