my $PDF = ...; # read PDF binary contents from file my $LOGSTR = ...; # read LOG file contents use MIME::Base64; my $data = { # this is binary that's why we encode it to fit in a JSON string 'pdf' => encode_base64($PDF), # this is text but it does not harm: 'logstr' => encode_base64($LOGSTR) }; # send the data to client: # CGI version: use CGI qw(:standard); use JSON; print header('application/json'); my $json_text = to_json($data); print $json_text; # OR render JSON to go to client with Mojolicious my $c = ... # controller ... $c->render(json => $data); # $data as above #### function ajaxit( url, // server endpoint method, // e.g. POST data // any data to send to the server for this request? ){ var xmlhttp = new XMLHttpRequest(); xhr.open(method, url, true); // this is the function which handles the xhr data transfer xhr.onreadystatechange = function () { // xhr.readyState can be 0 to 4 and each one comes here // 4 means transaction is all done, // 3 means data is received. // for error or success, state is 4 if( xhr.readyState !== 4 ){ // data is being transfered still, wait ... return; } // xhr.readyState=4, all is done, either on error or success: if( xhr.status === 200 ){ // data was received, make it a JS object (basically a hashtable aka JSON) var data = JSON.parse(xhr.responseText); // check integrity here // ... // handle the data your html must have a div to put the 's // with this adivID json_data_handler(data, adivID); } else { // we did not get a HTTP code 200 OK, // if we have a callback to handle errors, call it if( onError == null ){ console.log("ajaxre() : error, "+method+"ing to '"+url+"':\n readyState="+xhr.readyState+", status="+xhr.status); } else { # handle failed request #onError(xhr.readyState, xhr.status); } } }; // now do the XHR request with caller-supplied data xhr.send(data); // this call will be handled by the function above // function is asynchronous and leaves here while request is being made } // this is called when XHR was successful and we got data from server // that data was converted to a JS object (see above ajaxit()) // and this data is given here as dataobj // your html should contain a DIV somewhere (whose id you supply) // to add two tags in there for the user to click function json_data_handler( dataobj, divID ){ // we have already sent an XHR request to server // and it gave us back data (as a xhr.responseText) var obj = JSON.parse(data); // do here some checks if this was successful // ... // atob decodes base64 strings to bytes, // var pdf is now the actual binary pdf content var pdf = atob(dataobj['pdf']); var logstr = atob(dataobj['logstr']); // client-side save-as filename, whatever var saveAsFilename = '...'; // do whatever with pdf data var el = document.getElementById(divID); var a = document.createElement("a"); el.appendChild(a); # edit: set the right headers for pdf data #var blob = new Blob([pdf], {type: "octet/stream"}); var blob = new Blob([pdf], {type: "application/pdf"}); # edit: setting an intermediate variable which should be # revoked when not needed anymore with # window.URL.revokeObjectURL(objurl); var objurl = window.URL.createObjectURL(blob); a.href = objurl; a.download = saveAsFilename+'.pdf'; // do the same for logstr // or, since it is text, display it in the div verbatim }