Trying to send a PDF from a servlet to the client. The client sends a POST request through AJAX, and the request is handled by the servlet to generate a PDF and sends the PDF as a response to the client.
I've tried the options posted here to no avail (getting empty/un-openable pdfs): Opening PDF String in new window with javascript
Any help would be apprecieated!
So far, I can only get a formatted PDF String printing in the browser console using this code:
Java Servlet:
response.setContentType("application/pdf");
response.setHeader("Content-disposition","attachment; filename=ProgressReport.pdf");
response.setContentLength((int) pdfFile.length());
OutputStream out = response.getOutputStream();
FileInputStream in = new FileInputStream(pdfFile);
byte[] buffer = new byte[1024];
int length =0;
while ((length = in.read(buffer)) != -1){
out.write(buffer, 0, length);
System.out.println(buffer);
}
in.close();
out.flush();
JS
$.ajax({
url : "GenerateReport",
data : {
...
},
type : "POST",
success : function(result) {
console.log(result);
//window.open("data:application/pdf, " + encodeURI(result));
//download(result);
},
error : function(result) {
...
}
})
PDF String in Browser Console
%PDF-1.4 %����3 0 obj<</Filter/FlateDecode/Length 238>>streamx��QMO�#��x��(��D��!A�x�R��T�-�n��{�5LDB�e2�y�>2�l�Y$1�:a�i.�"�~f ...
I'm not 100% but your window.open is not the best as pop up blockers might prevent it as it's not a user action calling it's an AJAX response.
The better way would be the method outlined in this answer
var hiddenElement = document.createElement('a');
hiddenElement.href = 'data:attachment/text,' + encodeURI(result);
hiddenElement.target = '_blank';
hiddenElement.download = 'myFile.txt';
hiddenElement.click();
The other option is to use Base64 encoding and use "data:image/png;base64,"+result and in your C# you would need to create the buffer the size of the file and then base64 encode the entire buffer
Related
I'm trying to implement a PDF file download functionality with JavaScript.
As a response to a POST request I get a PDF file, in Chrome DevTools console it looks like (the oResult data container, fragment):
"%PDF-1.4↵%����↵4 0 obj↵<</Filter/FlateDecode/Length 986>>stream↵x��
Now I'm trying to initialize the download process:
let blob = new Blob([oResult], {type: "application/pdf"});
let link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "tstPDF";
link.click();
As a result, upon a click on a button I get tstPDF.pdf, it contains the correct number of pages, but the PDF itself is empty, no content is displayed, although it is 6 KB.
When I test the Java server-side module, which generates the PDF, everything is working fine, it sends InputStream through ServletOutputStream. Thus I assume that the issue is somewhere on a client side, perhaps something with MIME, BLOB, encoding, or similar.
Why doesn't the generated PDF display any data?
I solved the issue.
The problem was in a way the data is delivered from the server to the client.
It is critical to assure that the server sends the data in Base64 encoding, otherwise the client side can't deserialize the PDF string back to the binary format. Below, you can find the full solution.
Server-side:
OutputStream pdfStream = PDFGenerator.pdfGenerate(data);
String pdfFileName = "test_pdf";
// represent PDF as byteArray for further serialization
byte[] byteArray = ((java.io.ByteArrayOutputStream) pdfStream).toByteArray();
// serialize PDF to Base64
byte[] encodedBytes = java.util.Base64.getEncoder().encode(byteArray);
response.reset();
response.addHeader("Pragma", "public");
response.addHeader("Cache-Control", "max-age=0");
response.setHeader("Content-disposition", "attachment;filename=" + pdfFileName);
response.setContentType("application/pdf");
// avoid "byte shaving" by specifying precise length of transferred data
response.setContentLength(encodedBytes.length);
// send to output stream
ServletOutputStream servletOutputStream = response.getOutputStream();
servletOutputStream.write(encodedBytes);
servletOutputStream.flush();
servletOutputStream.close();
Client side:
let binaryString = window.atob(data);
let binaryLen = binaryString.length;
let bytes = new Uint8Array(binaryLen);
for (let i = 0; i < binaryLen; i++) {
let ascii = binaryString.charCodeAt(i);
bytes[i] = ascii;
}
let blob = new Blob([bytes], {type: "application/pdf"});
let link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = pdfFileName;
link.click();
Reference topics:
How to convert a PDF generating in response.outputStream to a Base64 encoding
Download File from Bytes in JavaScript
Thanks to this. It really works.
BTW, here's how I do it using spring controller and ajax with pdf generated by jasper
The Controller:
public ResponseEntity<?> printPreview(#ModelAttribute("claim") Claim claim)
{
try
{
//Code to get the byte[] from jasper report.
ReportSource source = new ReportSource(claim);
byte[] report = reportingService.exportToByteArrayOutputStream(source);
//Conversion of bytes to Base64
byte[] encodedBytes = java.util.Base64.getEncoder().encode(report);
//Setting Headers
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/pdf"));
headers.setContentDispositionFormData("pdfFileName.pdf", "pdfFileName.pdf");
headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
headers.setContentLength(encodedBytes.length);
return new ResponseEntity<>(encodedBytes, headers, HttpStatus.OK);
}
catch (Exception e)
{
LOG.error("Error on generating report", e);
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
The ajax:
$.ajax({
type: "POST",
url: "",
data: form.serialize(), //Data from my form
success: function(response)
{
let binaryString = window.atob(response);
let binaryLen = binaryString.length;
let bytes = new Uint8Array(binaryLen);
for (let i = 0; i < binaryLen; i++) {
let ascii = binaryString.charCodeAt(i);
bytes[i] = ascii;
}
let blob = new Blob([bytes], {type: "application/pdf"});
let link = URL.createObjectURL(blob);
window.open(link, '_blank');
},
error: function()
{
}
});
This will load the pdf in new window.
References: Return generated pdf using spring MVC
I am using Spring Boot as backend server and I have a JavaScript frontend.
For sending data between front- and backend I'm using the Axios library, which usually works pretty fine.
The Problem:
The image looks like this in the (Chrome) browser console:
It's a very very long alphanumeric string and that's what I send to the server with the following code:
static uploadFiles(files) {
const data = new FormData();
Object.keys(files).forEach(key => {
data.append("files", new Blob([files[key]], { type: 'image/jpeg' }));
});
const url = API_URL + "uploadFiles";
return axios.post(url, data, RestServices.getAuth({
"Content-Type": "multipart/form-data;boundary=gc0p4Jq0M2Yt08jU534c0p"
}));
}
I have no idea what the boundary thing does but it worked to receive a file in the backend tho...
On backend (spring) side I successfully receive an array of MultipartFiles:
#RequestMapping(value = "/uploadFiles", method = RequestMethod.POST)
#ResponseBody
public boolean uploadFiles(HttpServletRequest request, #RequestParam("files") MultipartFile[] files) throws IOException {
String filePath = Thread.currentThread().getContextClassLoader().getResource("assets/images/").getFile();
InputStream inputStream;
OutputStream outputStream;
for(MultipartFile file : files) {
File newFile = new File(filePath + file.getOriginalFilename() + ".jpg");
inputStream = file.getInputStream();
if (!newFile.exists() && newFile.createNewFile()) {
outputStream = new FileOutputStream(newFile);
int read;
byte[] bytes = new byte[1024];
while ((read = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, read);
}
}
System.out.println(newFile.getAbsolutePath());
}
return true;
}
I've also tried it file.transferTo(newFile); instead of in- and outputstreams - which didn't work either.
After that I get the following output, which means that the image was saved successfully:
/path/to/blob.jpg
If I check the path where the file was uploaded, there is a file named blob.jpg, but if I open it, the windows photo viewer has the following problem:
I've opened the image before and after upload with notepad++:
Before upload:
I think this is a byte array, but If I open the image after upload I get exactly the output of the browser. This means it didn't get converted to a byte array (correct me if I'm wrong) and I believe that's why it's a corrupt image...
My questions are:
What's the problem?
How can I fix it?
I really tried everything which crossed my mind but I ran out of ideas.
Thanks for your help! :-)
I've read following *related* questions (but they **don't** have an answer):
[Question1][5], [Question2][6], and **many** more...
I've finally found an answer on my own!
I think the problem was that I used the e.target.result (which is used to show the image on the frontend) but insted I had to use the JS File object. The standard HTML 5 file input fields return those File objects (as I've read here).
The only thing I had to do now is to make a FormData object, append the File Object, set the FormData as Body and set the Content-Type header and that's it!
const data = new FormData();
data.append("files", fileObject);
return axios.post(url, data, {
"Content-Type": "multipart/form-data"
});
Those JS File Objects are recognized from Java as Multipart files:
#RequestMapping(value = "/uploadFiles", method = RequestMethod.POST)
#ResponseBody
public boolean uploadFiles(HttpServletRequest request, #RequestParam("files") MultipartFile[] files) {
boolean transferSuccessful = true;
for (MultipartFile file : files) {
String extension = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf('.'));
String newFileName = genRandomName() + extension; //set unique name when saving on server
File newFile;
File imageFolder = new File(imageBasePath);
//check if parent folders exist else create it
if(imageFolder .exists() || imageFolder .mkdirs()) {
while ((newFile = new File(imageFolder .getAbsolutePath() + "\\" + newFileName)).exists()) {
newFileName = genRandomName(); //generate new name if file already exists
}
try {
file.transferTo(newFile);
} catch (IOException e) {
e.printStackTrace();
transferSuccessful = false;
}
} else {
LOG.error("Could not create folder at " + imageFolder.getAbsolutePath());
transferSuccessful = false;
}
}
return transferSuccessful;
}
I hope this is helpful :)
This question already has answers here:
Download a file from Servlet using Ajax
(3 answers)
Closed 5 years ago.
I am trying to download a file using ajax and a servlet, but the maximum I get is that in succes I get the file parseado
I have this section of the servlet:
else if(type.equals("downloadDocument")){
String file = request.getParameter("filePath");
File f = new File(file);
if (f.exists() && f.isFile()){
OutputStream out = response.getOutputStream();
FileInputStream in = new FileInputStream(f);
byte[] buffer = new byte[4096];
int length;
while ((length = in.read(buffer)) > -1){
out.write(buffer, 0, length);
}
in.close();
out.flush();
}
And that call:
$.ajax({
type : "POST",
url : "./ServletDocuWindow?downloadDocument",
data : datos,
success : function(r) {
}
});
Instead of downloading the file from AJAX, I pass the request on new separate window. Then, my servlet is called and file is getting downloaded on my local. Also, new window is closed when file starting downloaded
I guess you need to add following code and you should be good to go.
response.setContentType("application/octet-stream");
response.setContentLength((int) downloadFile.length());
// set headers for the response
String headerKey = "Content-Disposition";
String headerValue = String.format("attachment; filename=\"%s\"", AppUtility.getConvertedString(fileName)); //to ensure that there are no space in the file name
response.setHeader(headerKey, headerValue);
Following the advice of #dsp_user I used a to call the servlet and it works perfectly
var doc = document.getElementById("windowDocumentId").value;
var index = $("#lvDocuments").data("kendoListView").select().index();
var link = document.createElement("a");
link.download = $("#lvDocuments").data("kendoListView").dataSource.view()[index].itemText;
link.href = "./ServletDocuWindow?" + doc + "," +$("#lvDocuments").data("kendoListView").dataSource.view()[index].itemText;
link.click();
I am attempting to pass a PDF I have generated on frontend javascript using jsPDF to a Spring Framework MVC backend. Below is the front end code I have written:
var filename = "thefile";
var constructURL = '/daas-rest-services/dashboard/pdfPrintUpload/' + filename;
var url = restService.getUrl(constructURL);
var fileBytes = btoa(pdf.output());
$http.post(url, fileBytes).success(function(data) {
console.log(data);
})
.error(function(e, a) {
console.log(e);
console.log(a);
});
The pdf variable has been generated properly and can confirm is opens correctly when calling pdf.save("filename"). Below is the Java code which has been written on the Spring MVC backend for this call:
#RequestMapping(method = RequestMethod.POST, value = "/pdfPrintUpload/{documentName}")
public #ResponseBody String postPrintDocument(#PathVariable String documentName, #RequestParam byte[] fileBytes) {
String methodName = "postPrintDocument";
if(logger.isLoggable(Level.FINER)){
logger.entering(CLASS_NAME, methodName);
}
String check;
if(fileBytes != null){
check = "not null";
} else {
check = "null ";
}
//Decoding the bytestream
//Save to file location
//return file location
String returnValue = "HI " + documentName + " " + check;
if (logger.isLoggable(Level.FINER)) {
logger.exiting(CLASS_NAME, methodName);
}
return returnValue;
}
Each time I make a request, I am getting 400 Errors telling me:
Error 400: Required byte[] parameter 'fileBytes' is not present
I can confirm in the request payload that a large amount of data is being transmitted, however the backend does not seem to want to accept the parameter.
The purpose of doing this is that I want to be able to get the data from the pdf and then decode it on the backend so I can later publish the pdf to a location on the server. Is there something I am missing in my code for these requests to keep failing, and is there an easier more efficient way to achieve this functionality?
The solution was changing the #RequestParam to #RequestBody. #RequestParam is a parameter which is sent in the path.
#RequestParam vs #PathVariable
Try using ng-file-upload. The link and the examples are available on the link
ng-file-upload
for the sever side code try using this
#RequestMapping(value = "/pdfPrintUpload")
#ResponseBody
public void postPrintDocument(#RequestParam("file") MultipartFile file) {
InputStream is = file.getInputStream();
OutputStream os = new FileOutputStream(/*path to save file*/);
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) > 0)
os.write(buffer, 0, length);
is.close();
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
I have a webservice which returns a byte[] to the client, to show images.
This image is stored in a json object, see fiddle: http://jsfiddle.net/FuGN8/
the array of numerics is assigned to result after i do a simple line of:
result = result["d"];
This is fetched via a AJAX call, so i want to render an image from this data.
Naturally, doing something like:
$("img#mytag").attr("src", result);
would not do what i want.
Is there a javascript command which would do what i am intending?
my Server side code I changed to do:
WebClient wsb = new WebClient();
string url = "...";
byte[] resp = wsb.DownloadData(url);
UTF8Encoding enc = new UTF8Encoding();
return enc.GetString(resp);
but on the client side, since i do not know what the image type would be, i was attempting:
src="data:image/*;base64,"+RET_VAL
and it wasnt doing anything. On a similar note, i also tried:
src="data:image;base64,"+RET_VAL
since the above was doing UTF8 encoding, i also added in a the following:
src:"data:image;base64,"+window.btoa(unescape(encodeURIComponent( RET_VAL )))
You are not using Base64 encoding in your image. Use the method Convert.ToBase64String instead. You could also send the image type in the JSON response in order to apply it to the src attribute. I could get it to work with this code:
[WebMethod]
[ScriptMethod(UseHttpGet = true, ResponseFormat = ResponseFormat.Json)]
public string SendImage()
{
var path = #"C:\teste.png";
byte[] bytes = File.ReadAllBytes(path);
Image image = null;
using (var stream = new MemoryStream(bytes))
image = Image.FromStream(stream);
var json = new Dictionary<string, object>();
json.Add("type", new ImageFormatConverter().ConvertToString(image.RawFormat).ToLower());
json.Add("contents", Convert.ToBase64String(bytes));
return new JavaScriptSerializer().Serialize(json);
}
And this JavaScript code:
$.ajax({
type: 'GET',
url: 'WebService1.asmx/SendImage',
contentType: 'application/json; charset=utf-8',
success: function (response) {
var data = JSON.parse(response.d);
$('<img />').attr('src', 'data:image/' + data.type + ';base64,' + data.contents).appendTo('body');
}
})
Of course you'll have to adapt it as you are using a WebClient to get your image.
The src attribute of your img element expects the image location (its URL), not the actual image bytes.
To put your data as an URL you may use the data URI Scheme. For example, for a .png image:
data:image/png;base64,<your image bytes encoded in base64>