简体   繁体   中英

Grails and pdf.js and rendering a pdf via an ajax call

I am adding a feature to our application that requires an embedded pdf viewer. The user will need to be able to view the pdf while simultaneously accessing some other forms located in another area of the screen.

Providing the file url is not acceptable for security reasons. Instead, the view request must go to our application which will provide additional security checks. If the checks are successful, then the pdf is returned as a stream. The file directory can never be accessed directly via a browser.

From what I have read, it seems like pdf.js should work well with this as the PDFJS.getDocument method will accept a Uint8 array as well as a uri. I've been googling on how others have solved this using a base64ToUint8Array function.

On the client side we use jQuery and bootstrap, and pdf.js and pdf.viewer.js are loaded as part of the page load.

This is all background for my current issue, which is that the server uses grails and I don't know how to send out the binary pdf data via a grails controller class.

Here is what I currently have in the controller:

def view = {
        SolutionFile solutionFile = SolutionFile.get(params.sfileId)
        InputStream pdfStream = fileStoreService.writeBinary( solutionFile.fileName, solutionFile.file)
pdfStream
    }

What is returned to the browser is generated html based on our core layout gsp file, without any included template. I don't see the pdf file data anywhere.

I get an 'Invalid Character error- string contains an invalid character' when PDFJS.getDocument is called, so I suspect that the returned response is not in base64 either.

Here is a mockup of the html:

Here is the ajax call that calls PDFJS:

jQuery(document).ready(function() {

    PDFJS.disableStream = true;
    PDFJS.disableWorker = true; 

    function base64ToUint8Array(base64) {//base64 is an byte Array sent from server-side
        var raw = atob(base64); //This is a native function that decodes a base64-encoded string.
        var uint8Array = new Uint8Array(new ArrayBuffer(raw.length));
        for (var i = 0; i < raw.length; i++) {
          uint8Array[i] = raw.charCodeAt(i);
        }
         return uint8Array;
         }


    jQuery('.remote-tab').click(function(e) {
        e.preventDefault();
        var url = jQuery(this).attr("data-url");
        var dataparams = jQuery(this).attr("data-params");
        var params;
        if (dataparams != null) {
            params = JSON.parse(dataparams);
        }
       var canvasId = jQuery(this).attr("data-loadinto");

      jQuery(this.hash).find('#'+canvasId).load(url, params, function(responseTxt, statusTxt, xhr){
        console.log("about to call PDFJS");
              PDFJS.getDocument(base64ToUint8Array(responseTxt)).then(function(pdfFile) {
            PDFJS.disableWorker = true; 
            var pageNumber = 1;
            pdfFile.getPage(pageNumber).then(function(page) {
                  var scale = 1;
                  var viewport = page.getViewport(scale);
                  var canvas = document.getElementById('canvas');
                  var context = canvas.getContext('2d');

                  var renderContext = {
                      canvasContext: context,
                      viewport: viewport
                  };
                  canvas.height = viewport.height;
                  canvas.width = viewport.width;
                  console.log("rendering page");
                  page.render(renderContext);
            });
        });

        }); 
    });
});

Any help explaining what I need to do so that the streamed PDF is returned to the ajax function correctly would be much appreciated.

My underlying question is how can I send a base64-encoded binary file to the browser when the server side is written in grails, specifically how can a method written in a grails controller bypass the expected gsp template generation?

I believe I have found the solution to that in this post: Writing binary content directly to the client bypassing the Grails view layer

The key is that the response buffer needs to be flushed as the last action in the controller method, and must be done in that controller method.

Here is what the code in my controller method now looks like:

def view = {
    SolutionFile solutionFile = SolutionFile.get(params.sfileId)
    response.setContentType("application/octet-stream");
    fileStoreService.writeBinary( response, solutionFile.fileName, solutionFile.file)   
    response.flushBuffer()
}

The other thing to note is that the setContentType must be done before the response buffer is written. I'm not yet sure I've got the correct content type set, but that is a different part of the story.

The requirement that the returned result be a base64-encoded binary file is because I want to use pdf.js on the client side. Specifically, I want to use PDFJS.getDocument rather than modifying the supplied viewer.html file. PDFJS.getDocument will only accept a pdf URI (which is not acceptable in this case) or a Uint8 array (which can be generated from a base64-encoded binary file. )

For security requirements, the stored files can not be directly accessed outside of the application, so we have a service that will fetch the correct file given a file id. I created a writeBinary method which returns the encoded binary file. (At least I hope so, still working on that.)

void writeBinary( response, fileName, fileId) {  
  File f=new File(getUrl(fileId))
  FileInputStream fis = new FileInputStream(f)
  try {
     byte[] buf = FileUtils.readFileToByteArray(f)      
     response.outputStream << buf.encodeBase64()
    }
  catch (Exception e) {
    log.error(e.printStackTrace());
    }
  finally {
    if (fis != null) fis.close()
  }
}   

So, I think I have answered the question I originally posted. However, the client-side pdf.js part is still not working.
I will be working on that, and may ask a new question once I get clarity on the problem. I won't reuse this question, since I made it too difficult to read through.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM