ajax response as pdf, how to show it? - java

I have a web service that generate a pdf. In my GAE application I have a button, when i click i use an ajax's function.
$('#test').click(function(){
$.ajax({
url: 'provaws.do',
type: 'get',
dataType: 'html',
success : function(data) {
}
});
});
this is the method in java that's call ws, using UrlFetch:
#RequestMapping(method = RequestMethod.GET, value = PROVAWS_URL)
public void prova(HttpServletRequest httpRequest, HttpServletResponse httpResponse, HttpSession httpSession) throws IOException{
try {
URL url = new URL("http://XXXXX/sap/bc/zcl_getpdf/vbeln/yyyyyy");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestProperty("Authorization","Basic " + Base64.encodeBase64String(("username:password").getBytes()));
connection.setConnectTimeout(60000);
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
// OK
ByteArrayOutputStream bais = new ByteArrayOutputStream();
InputStream is = null;
try {
is = connection.getInputStream();
byte[] byteChunk = new byte[4096];
int n;
while ( (n = is.read(byteChunk)) > 0 ) {
bais.write(byteChunk, 0, n);
}
}
catch (IOException e) {
}
finally {
if (is != null) { is.close(); }
}
httpResponse.setContentType("application/pdf");
httpResponse.setHeader("content-disposition","attachment; filename=yyyyy.pdf");
httpResponse.getOutputStream().write(bais.toString().getBytes("UTF-8"));
httpResponse.getOutputStream().flush();
}
....
}
With Firebug i see the repsonse:
%PDF-1.3
%âãÏÓ
2 0 obj
<<
/Type /FontDescriptor
/Ascent 720
/CapHeight 660
/Descent -270
/Flags 32
/FontBBox [-177 -269 1123 866]
/FontName /Helvetica-Bold
/ItalicAngle 0
....
What i need to set in ajax's function to show the pdf?
Thanks in advance

I don't know Java well, but in my understanding your mechanism may not be right.
Here are my corrections:
Instead of sending files in stream, the server-side code(JAVA) should generate the pdf at backend, put the file in file system, and then return the URI of file to Ajax response.
For Ajax code, it get the url from server, then show the new url in DOM. Then user can follow this link to read/download PDF.
Side note:
I checked further that there are methods for streaming data by Ajax, though jQuery's ajax() can't handle that. But I think for a PDF file rendering, streaming is overkill.
Refs: jquery ajax, read the stream incrementally?, http://ajaxpatterns.org/HTTP_Streaming#In_A_Blink*

Related

File upload with Jersey, File not being decoded

I've been trying to do file upload to a server from Javascript to a Jakarta REST service. The file is not getting decoded and I can't find what's wrong.
The upload code I'm using in javascript is basic FormData upload (vanillajs):
var url=.....
var xhttp = new XMLHttpRequest();
var formData = new FormData();
xhttp.onreadystatechange = () => {
if (this.readyState == 4)
{
if (this.status == 200)
{
if (successCallback != null) successCallback(this.responseText);
}
else
{
if (errorCallback != null) errorCallback(this.status, this.statusText);
}
}
};
xhttp.open("POST", url, true);
formData.append("file", file);
xhttp.send(formData);
The way I'm getting the file is to use a file input, and when the user loads an image, I load a picture of it and use that (React):
const [imgSrc, setImgSrc] = React.useState('');
const loadImage=(e)=>{
var file=e.target.files[0];
var reader = new FileReader();
reader.onload = (e2)=>{
setImgSrc(e2.target.result);
}
reader.readAsDataURL(file);
}
<input type="file" accept="image/*" onChange={(e)=>loadImage(e)} />
That seems to work fine. When I look at the request headers I see:
Content-Type: multipart/form-data; boundary=....
and the payload starts with:
file: data:image/jpeg;base64,/9j/...
Then the REST service I'm using to receive this is:
#POST
#Path("/submit")
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadImage(
#FormDataParam("file") File fileBody,
#FormDataParam("file") FormDataContentDisposition metadata)
{
// set up S3 bucket, all that works fine
s3.putObject("mybucket", "pick_a_name", new FileInputStream(fileBody), null);
}
and that works, BUT, the file content is the literal content of the payload:
data:image/jpeg;base64,/9j/...
So it's not being recognized as a base 64 encoded file content and being decoded.
I've gone through various iterations and questions, including variations that result in 400 or 415 errors, but as far as I can tell these things look like they should line up.
A few more data points:
Tomcat 10
Jakarta 3.0.0
Jersey 3.0.3 (and jersey media multipart 3.0.3)
Java 8
Any suggestions would be appreciated. Thanks!
I'd like to get a "real" (what's the correct problem and solution) answer, but in the meantime I went with a hack:
InputStream outStream = hackBase64Decode(fileBody);
s3.putObject("mybucket", "pick_a_name", outStream, null);
private InputStream hackBase64Decode(File fileBody) throws Exception
{
FileInputStream fs = new FileInputStream(fileBody);
int ch;
do {
ch = fs.read();
} while (ch != ',');
return Base64.getDecoder().wrap(fs);
}
The best that can be said for that is it works. Again, though, would prefer a real answer.

How to download a file that gets processed over an HTTP request in Java?

I'm writing a program that builds stuff in a GUI (blah blah blah... irrelevant details), and the user is allowed to export that data as a .tex file which can be compiled to a PDF. Since I don't really want to assume they have a TeX environment installed, I'm using an API (latexonline.cc). That way, I can construct an HTTP GET request, send it to the API, then (hopefully!) return the PDF in a byte-stream. The issue, though, is that when I submit the request, I'm only getting the page data back from the request instead of the data from the PDF. I'm not sure if it's because of how I'm doing my request or not...
Here's the code:
... // preceding code
DataOutputStream dos = new DataOutputStream(new FileOutputStream("test.pdf"));
StringBuilder httpTex = new StringBuilder();
httpTex.append(this.getTexCode(...)); // This appends the TeX code (nothing wrong here)
// Build the URL and HTTP request.
String texURL = "https://latexonline.cc/compile?text=";
String paramURL = URLEncoder.encode(httpTex.toString(), "UTF-8");
URL url = new URL(texURL + paramURL);
byte[] buffer = new byte[1024];
try {
InputStream is = url.openStream();
int bufferLen = -1;
while ((bufferLen = is.read(buffer)) > -1) {
this.getOutputStream().write(buffer, 0, bufferLen);
}
dos.close();
is.close();
} catch (IOException ex) {
ex.printStackTrace();
}
Edit: Here's the data I'm getting from the GET request:
https://pastebin.com/qYtGXUsd
Solved! I used a different API and it works perfectly.
https://github.com/YtoTech/latex-on-http

Download file in java when Content-Length is incorrectly set?

I have an android app that downloads and uses a file at runtime. The file is valid as I can download it via the browser and open it up, etc. However my app kept reporting that the file is corrupted.
After investigation I discovered the server (which I have no control over) is returning an incorrect "Content-Length:" (~180 vs ~120000). The header is the culprit as I confirmed the issue by downloading the file with curl - which also resulted in a truncated file.
After some research I concluded that my use of BufferedInputStream to append to a ByteArrayBuffer is autosizing the byte array to the url connections content length. To circumvent this, I tried to use ByteArrayOutputStream instead, however this solved nothing.
Anybody know of a way to download a file if the Content-Length is incorrectly set? A browser can.
Here's my latest attempt:
public static void downloadFileFromRemoteUrl(String urlString, String destination){
try {
URL url = new URL(urlString);
File file = new File(destination);
URLConnection urlConnection = url.openConnection();
InputStream inputStream = urlConnection.getInputStream();
byte[] buffer = new byte[1024];
int curLength = 0;
int newLength = 0;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
while((newLength = inputStream.read(buffer))>0)
{
curLength += newLength;
byteArrayOutputStream.write(buffer, 0, newLength);
}
FileOutputStream fos = new FileOutputStream(file);
fos.write(byteArrayOutputStream.toByteArray());
fos.close();
android.util.Log.d("DB UPDATE", "Done downloading database. Size: " + byteArrayOutputStream.toByteArray().length);
}
catch (MalformedURLException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
}
After some research I concluded that my use of BufferedInputStream to append to a ByteArrayBuffer is autosizing the byte array to the url connections content length.
Nonsense. You are crediting those classes with paranormal powers. How could an output stream possibly become aware of the Content-length header? The URLConnection's input stream is being terminated at the content-length. Correctly.
To circumvent this, I tried to use ByteArrayOutputStream instead, however this solved nothing.
Of course not.
Anybody know of a way to download a file if the Content-Length is incorrectly set?
You could use a Socket and engage in HTTP yourself, which is less trivial than it sounds. But the problem is at the server and that's where it should be fixed. Complain. Or else #Zong Yu is correct and the page is HTML containing JavaScript, say.
NB You don't need to read the entire file into memory:
while((newLength = inputStream.read(buffer))>0)
{
curLength += newLength;
fos.write(buffer, 0, newLength);
}
My final "solution" was to realize I was dealing with a 301 redirect response and not the actual resource! I updated the section that handles my url, checking for a 301 and if exists, update the url. The new url contained the Content-Length that corresponded with the file I was downloading.
// start by creating an http url connection object
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
// determine if this is a redirect
boolean redirect = false;
int status = httpURLConnection.getResponseCode();
if (status != HttpURLConnection.HTTP_OK) {
if (status == HttpURLConnection.HTTP_MOVED_TEMP
|| status == HttpURLConnection.HTTP_MOVED_PERM
|| status == HttpURLConnection.HTTP_SEE_OTHER)
redirect = true;
}
// if it is, we need a new url
if (redirect) {
String newUrl = httpURLConnection.getHeaderField("Location");
httpURLConnection = (HttpURLConnection) new URL(newUrl).openConnection();
}
Try Fetch. Fetch is an in app download manager for Android. It's very easy to use. Find the GitHub page here. The project comes with several demos that you can try out. Disclaimer: I'm the creator of Fetch, and it is open source.

Saving HTML5 <canvas> Image w/ Java Servlet

I know there are a lot of StackOverflow questions about this already, but I've searched through as many as I could find and have yet to get my code working, so I am finally posting my own question.
My goal is to save an image that is on an HTML5 <canvas> in my webpage to a file on my server. I was hoping to accomplish this using a Java servlet.
My JavasScript grabs the canvas image data like this:
var canvas = document.getElementById("screenshotCanvas");
var context = canvas.getContext("2d");
var imageDataURL = canvas.toDataURL('image/png');
// I'm not if I need to do this, I've tried several different ways to no avail
//imageDataURL = imageDataURL.replace("image/png", "image/octet-stream");
//imageDataURL = imageDataURL.replace(/^data:image\/(png|jpeg);base64,/,"");
$.ajax({
url: screenshotCreateUrl,
type: "POST",
data: { imgBase64: imageDataURL },
error: function(jqXHR, textStatus, errorThrown) {
// Handle errors
},
success: function(data, textStatus, jqXHR) {
// Do some stuff
}
});
My Java servlet tries to save the image like so:
try {
HttpServletRequestWrapper wrappedRequest = new HttpServletRequestWrapper(request);
HttpServletRequestWrapper requestWithWrapper = (HttpServletRequestWrapper) wrappedRequest.getRequest();
byte[] contentData = requestWithWrapper.getContentData();
byte[] decodedData = Base64.decodeBase64(contentData);
FileOutputStream fos = new FileOutputStream("testOutput.png");
fos.write(decodedData);
fos.close();
} catch(Exception e) {
// Handle exceptions
}
The servlet successfully writes out an image file, but it does not open properly and does not contain all the image data in it. My Javascript successfully grabs the <canvas> image data, which looks like this:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAACWCAYAAABkW7XSAAAgAElEQVR4nJTa51NcaZ7ge+QBIZBAwgoEkvDeJt577733njRk4r3PhDRAJkkmiTdCXqWqUlVXtZl209OzvWN27t17/5rvvoCu7t6ZmN158YnfcyJOnIjz4vnGEyeOWdRmAP+RaI3fT2I0PiTtBJF9KKD8TTKVb5IpPosl+zCMdFMgKQY/otTPCFl3IljhSPiWB5E7XkToPAnVexNq8CVyP5Cwg0CCDwII2vfHx+TDC6MXz3df8mznGc7bTthu2PJwxRrrufvcnzbn4bQFjnNWuK3a4r3hQrDmBSGbLwlRvyR0w5OQ9ZeErHsRsuFLyIYvQeu+BCh88Zf74K/wxU8ZgL8qEH9VEAHq0L8RqAkjeCuCsO1IwrVRhGujCNNdCdVFEayNJEQXdUUfTYg+mmC9AH99BL47YfjuhOK7G4KvIRhfQzDee4F47QXibQrCe98f730/vPf98N3zw9/kT+B+IG . . . [and so on]
Any ideas what I am missing here? I feel like i am making some tiny mistake that I just can't spot.
Had the same task and was able to make it work (without jQuery and with the help of maclema's reply), by using multipart/form-data content type:
var xhr = new XMLHttpRequest();
xhr.open("post", "AddressOfYourServlet", false);
var boundary = Math.random().toString().substr(2);
xhr.setRequestHeader("content-type",
"multipart/form-data; charset=utf-8; boundary=" + boundary);
var multipart = "--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=myImg\r\n" +
"Content-type: image/png\r\n\r\n" +
canvas.toDataURL("image/png") + "\r\n" +
"--" + boundary + "--\r\n";
xhr.send(multipart);
To go asynchronously or if you have more parts to send (e.g. multiple images) or if you want to work with the response, see How to send multipart/form-data form content by ajax (no jquery)?
Your servlet's doPost method would look something like:
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Part part = request.getPart("myImg");
BufferedReader br = new BufferedReader(new InputStreamReader(part.getInputStream(),
Charset.forName("utf-8")));
String sImg = br.readLine();
sImg = sImg.substring("data:image/png;base64,".length());
byte[] bImg64 = sImg.getBytes();
byte[] bImg = Base64.decodeBase64(bImg64); // apache-commons-codec
FileOutputStream fos = new FileOutputStream("img.png");
fos.write(bImg);
}
Hope this helps.
You want to get the post parameter and not the content data of the request. As well you will also need to strip the encoding information.
Try this:
try {
HttpServletRequestWrapper wrappedRequest = new HttpServletRequestWrapper(request);
HttpServletRequestWrapper requestWithWrapper = (HttpServletRequestWrapper) wrappedRequest.getRequest();
String imageString = wrappedRequest.getParameter("imgBase64");
imageString = imageString.substring("data:image/png;base64,".length);
byte[] contentData = imageString.getBytes();
byte[] decodedData = Base64.decodeBase64( contentData );
FileOutputStream fos = new FileOutputStream("testOutput.png");
fos.write(decodedData);
fos.close();
} catch(Exception e) {
// Handle exceptions
e.printStackTrace();
}

Cookie problems with IE and Chrome (working with java)

I'm trying to send an image to the client from a servlet, and add a cookie containing the id of the image to the repsonse. ( i don't want to display the same image more than N times).
Looks like Internet Explorer doesn't care about the cookies and i always get a null reference when i call request.getCookies();. With Opera everything works great.
Chrome sees the cookies but i get the following exception when i write the image to the outputStream :
ClientAbortException: java.net.SocketException: Software caused connection abort: socket write error
I haven't tried yet Mozilla.
Is there a workaround for Internet Explorer, except cookies? Sessions work with my Internet Explorer.
Any ideas for the exception raised when i use Chrome ? ( the image is less than 1 MB).
Here's the servlet code:
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("image/jpeg");
response.addHeader ("Content-Disposition", "attachment");
response.setHeader("Cache-Control", "no-cache,no-store,must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
HttpSession session = request.getSession();
String requestURI = request.getParameter("requestURI");
String resolution = request.getParameter("resolution");
Cookie[] cookies = request.getCookies();
try
{
if (cookies == null)
coada = (new BannerChooser().Choose(1));
String filePath = null;
Iterator it = coada.iterator();
boolean found =false;
while ((!found) && it.hasNext())
{
found = true;
if (cookies!=null)
for (int i = 0; i < cookies.length; i++)
if ( Integer.parseInt(cookies[i].getValue()) == ((BannerNota)it.next()).getB().getId())
{
found = false;
break;
}
if (found)
{
BannerNota bannerToDisplay = (BannerNota)it.next();
Cookie cookie = new Cookie(bannerToDisplay.getB().getId().toString(),bannerToDisplay.getB().getId().toString());
cookie.setMaxAge(60*60*24);
cookie.setPath("/licenta");
filePath = bannerToDisplay.getB().getPath();
response.addCookie(cookie);
break;
}
}
filePath = "h:/program files/Workspace/licenta/WebRoot/" + filePath;
File f = new File(filePath);
byte[] b = new byte[(int)f.length()];
FileInputStream fis = new FileInputStream(f);
fis.read(b);
ServletOutputStream out = response.getOutputStream();
out.write(b);
out.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
Cookies are domain based. Many browsers reject cookies on embedded resources (CSS/JS/images) which are served from other domain than the page is been served from.
You want to manage the cookie using JavaScript instead. Google Analytics is also doing it that way. At quirksmode.org you can find a nice tutorial how to manage cookies using JavaScript. Any cookie-based information can then be sent as a request parameter on the image URL.

Categories

Resources