Search code examples
javaangularjsspring-web

AngularJS POST for PDF data always fails


I'm trying to work around Internet Explorer's URL limits (refer to this Microsoft support article for more information) in order to generate a PDF.

I have a base-64 encoded String representing a PDF's byte stream in my client-side JS. For Firefox and Chrome, I show the PDF by prepending the String with data:application/pdf;base64, and the browser happily renders the data URI.

For Internet Explorer, I'm trying to send the String back to the servlet in a POST so that it can return the byte stream with content type "application/pdf". Pretty much I'm following the solutions proposed in the following questions, except that I'm passing the actual PDF contents up in the client side request:

My HTTP POST looks as follows:

var pdf; // base-64 encoded string in this variable
$http.post("/convert/pdf", {
    params: {
      base64pdf: pdf
    }
  }, {
    responseType : "arraybuffer"
  }).then( function onSuccess(result) { 
             var file = new Blob([result.data], {type: 'application/pdf'});
             window.navigator.msSaveOrOpenBlob(file, "result.pdf");
           },
           function onFailure() { /* log and do failure case stuff */ });

My server is running a Java spring-webmvc service. The Convert controller looks as follows:

@Controller
@RequestMapping(value = "/convert", produces = "application/pdf")
public class ConvertController {
    @RequestMapping(value = "/pdf", method = RequestMethod.POST)
    public ResponseEntity<byte[]> generatePdf(
        @RequestParam(value="base64pdf", required=true) String base64encodedDataUri,
        HttpServletRequest request) {
      byte[] bytes = Base64.decodeBase64(base64encodedDataUri);

      HttpHeaders headers = new HttpHeaders();
      headers.setContentType(MediaType.parseMediaType("application/pdf"));

      String filename = "test.pdf";
      headers.setContentDispositionFormData(filename, filename);
      ResponseEntity<byte[]> response = new ResponseEntity<>(bytes, headers, HttpStatus.OK);
      return response;
    }
    // other methods
}

The controller code is based off this answer. I don't think there's a problem with it; it tests out fine in unit tests and direct POST requests via the Swagger API interface.

The problem is this. Whenever I trigger this $http.post in IE, the result is always the error callback, and it's always an HTTP 400 error.

I've observed the following:

  • Using IE 11's developer tools Network traffic capturing, I can observe the request being made. The request contains the full and correct base-64-encoded string in the params, which is present in the Request Body.

  • The Request Header looks like this:

Request         POST /convert/pdf HTTP/1.1
Content-Type    application/json;charset=utf-8
Accept          application/json, text/plain, */*
Referer         localhost:8080/
Accept-Language en-US
Accept-Encoding gzip, deflate
User-Agent      Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
Host            localhost:8080
Content-Length  5699
DNT             1
Connection      Keep-Alive
Cache-Cont

rol   no-cache
  • The Response header shows this:

Response                     HTTP/1.1 400 Bad Request
Server                       Apache-Coyote/1.1
Access-Control-Allow-Origin  *
Access-Control-Allow-Methods POST,GET,OPTIONS,DELETE
Access-Control-Max-Age       3600
Access-Control-Allow-Headers x-requested-with
Content-Type                 application/pdf
Content-Length               98
Date                         Mon, 07 Dec 2015 21:54:32 GMT
Connection                   close
  • The Response body contains an invalid PDF. Well, technically the developer tools say it can't render it, and when you save it, the PDF that gets saved isn't a valid PDF.

  • EDIT: I turned up logging in Tomcat8 and observed that Spring is complaining that the "base64pdf" parameter is missing from the request:

2015-12-08 02:29:32,514 INFO [http-nio-8080-exec-4] INFO Required String parameter 'base64pdf' is not present
org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'base64pdf' is not present

So I really have two questions:

  1. How am I losing the base64pdf parameter? Is there a max length on parameters or something? I tried both the original displayed in this question, and passing the param in via data: { base64pdf : pdf } as suggested by user chenzhenjia below.

  2. How can I get this PDF call to work in IE? While on the one hand I'd like to know what I'm doing wrong above, but frankly I'm open to "the right answer" for how to get this base-64-encoded string that's on the client side transformed into a PDF. (That is, if this is an XY problem and I'm barking up the wrong tree, I'd like to know that too.)


I'm using:

  • AngularJS 1.4.8
  • Spring 4.2.3.RELEASE
  • Java 8
  • Tomcat 8

Solution

  • I ended up getting this to work by sending the base64pdf String in the request body.

    In Java, I created a simple model:

    public class PayloadHolder {
      private String payload;
      // getters and setters
    }
    

    Then I updated my Controller's method signature:

    @RequestMapping(value = "/pdf", method = RequestMethod.POST, produces = "application/pdf")
    public ResponseEntity<byte[]> generatePdf(
            @RequestBody(required = true) PayloadHolder payload,
            HttpServletRequest request)
    

    And finally changed the AngularJS request:

    var pdf; // base-64 encoded string in this variable
    var request = {
        payload : pdf
    };
    var config = {
        headers: { 'Accept': 'application/pdf' },
        responseType : "arrayBuffer"
    };
    
    $http.post("/convert/pdf", request, config)
          .then( function onSuccess(result) { 
             var file = new Blob([result.data], {type: 'application/pdf'});
             window.navigator.msSaveOrOpenBlob(file, "result.pdf");
           },
           function onFailure() { /* log and do failure case stuff */ });
    

    I'm not sure why the POST wouldn't work using params, especially because I couldn't find anything in the documentation that would indicate that it shouldn't. I'll post a follow up question addressing this.