My web application (HTML5 + JavaScript) needs to display PNG images that are generated by foreign web service.
However, that web service supports POST requests only. (More exactly, it does provide GET requests, but I have to transmit large arguments, due to which the GET URL becomes too long.)
Also, the web service has a different domain than the web application, and doesn't supply proper CORS headers, so Ajax (XMLHTTPRequest) doesn't work.
Is it still possible for my web application to load and display the foreign image via POST request?
I'm asking for a solution that is different from the following nasty workarounds, which are already well-known to me:
without setting up a local proxy that translates the request (and also circumvents the same-origin policy)
without using the remote proxy of some stranger
without using Flash
without using Java Applets
without using OS specific functionality such as ActiveX controls
However, a solution that fails to work with Internet Explorer is acceptible. Even a Firefox or Chrome specific solution is appreciated.
Horrible hack:
Submit a form to an iframe and have the image displayed in the iframe.
(But don't do this, it sounds like the web server is designed to avoid having images being embedded directly in other sites.)
I have some possible solutions...
Solution 1
If your image is less that 25kb you can do the following via YQL: select * from data.uri where url="http://jquery.com/jquery-wp-content/themes/jquery/images/logo-jquery#2x.png" As a result you can just grab the base64 image and carry on. To do a POST via YQL you should add something like and postdata="foo=foo&bar=bar" check out this article.
Caveat: The performance of this method is probably not great. There's a fair amount of latency making the hop from the end user to YQL to the service and then going all the way back. Also there is some server side processing YQL does to base64 encode the image and deliver some JSON response.
Solution 2
Get CORS enabled or go through some other proxy. Once you do so, if you still can't get base64 data then you need to do 2 things. First add a jQuery transport that handles binary. Second process the binary blob and convert it to base64.
Here is a jQuery Binary Transport I found
$.ajaxTransport("+binary", function(options, originalOptions, jqXHR){
// check for conditions and support for blob / arraybuffer response type
if (window.FormData && ((options.dataType && (options.dataType == 'binary')) || (options.data && ((window.ArrayBuffer && options.data instanceof ArrayBuffer) || (window.Blob && options.data instanceof Blob)))))
{
return {
// create new XMLHttpRequest
send: function(headers, callback){
// setup all variables
var xhr = new XMLHttpRequest(),
url = options.url,
type = options.type,
async = options.async || true,
// blob or arraybuffer. Default is blob
dataType = options.responseType || "blob",
data = options.data || null,
username = options.username || null,
password = options.password || null;
xhr.addEventListener('load', function(){
var data = {};
data[options.dataType] = xhr.response;
// make callback and send data
callback(xhr.status, xhr.statusText, data, xhr.getAllResponseHeaders());
});
xhr.open(type, url, async, username, password);
// setup custom headers
for (var i in headers ) {
xhr.setRequestHeader(i, headers[i] );
}
xhr.responseType = dataType;
xhr.send(data);
},
abort: function(){
jqXHR.abort();
}
};
}
});
Once you add the transport you can make any sort of AJAX request.
$.ajax({
type: "POST",
url: 'http://myservice.com/service/v1/somethingsomething',
dataType: 'binary',
success: function(imgData) {
var img = new Image(),
reader = new window.FileReader();
reader.readAsDataURL(imgData);
reader.onloadend = function() {
img.src = reader.result
$('#logo-events').append(img);
}
}
});
The reader should take the Blob and output a base64 version. When the reader is done converting/reading it will create and image and append it somewhere. GET or POST should not matter any more.
I found this related question: Post data to JsonP
And I think that it could be applicable in your case.
Basically, fire your jsonp request to your server (same-origin-policy should not be a problem), and load the response an <img>
Like #Quentin's answer, this hack uses a (hidden) Iframe
Related
Heres my email sending function:
function send() {
var key = "dJdJekCVAFIqvUJ13DEczZjgIh_4MyeIGEHz2GBYKFe";
var message_name = "defender_send_message";
var data = {};
data.value1 = document.getElementById('textBox').value;
data.value2 = localStorage.getItem("AdminsEmail");
var url = "https://maker.ifttt.com/trigger/" + message_name + "/with/key/" + key;
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
if (xmlhttp.status == 200) {
console.log("Message Sent");
}
}
}
xmlhttp.open('POST', url, true);
xmlhttp.responseType = 'json';
xmlhttp.send(new FormData(data));
}
I wanted to create an email sending function with only pure js, not jquery or anything. I get the following errors when i click send:
(ignore the first error i fixed that already)
I had a jquery function that worked (but i had to get rid of it):
var message = localStorage.getItem("Message");
console.log(message + localStorage.getItem("AdminsEmail"));
var key = "dJdJekCVAFIqvUJ13DEczZjgIh_4MyeIGEHz2GBYKFe"; // << YOUR KEY HERE
var message_name = "defender_send_message"; // << YOUR MESSAGE NAME HERE
var url = "https://maker.ifttt.com/trigger/" + message_name + "/with/key/" + key;
$.ajax({
url: url,
data: {value1: message,
value2: localStorage.getItem("AdminsEmail")},
dataType: "jsonp",
complete: function(jqXHR, textStatus) {
console.log("Message Sent");
}
});
why would this work and my other function not?
EDIT 2 : Since it seems the endpoint doesn't actually return JSON, I think your original jQuery code wasn't correct either. You need to do more research into this iftt.com platform and how to use it. From what I can tell, it's meant to be used in a mobile app, not in the browser- it would be a normal POST XHR then, and CORS doesn't apply to mobile apps. They have this page for testing the endpoint- notice that it gives you an example using curl, a command-line tool, where again CORS doesn't apply. So I think you need to rethink things, this service is not designed to be used from a browser, like you are trying to do.
EDIT: since it turns out you are actually trying to use JSONP and not a plain XHR, all you need to do is implement that without jQuery- create a script tag with the server's URL and add a URL parameter to define your callback function to handle the data. This answer should give you the solution.
In your case the code might look like this :
http://www.codeply.com/go/bp/VRCwId81Vr
function foo(data)
{
// do stuff with JSON
console.log(data)
}
var script = document.createElement('script');
script.src = "https://maker.ifttt.com/trigger/defender_send_message/with/key/"+
"dJdJekCVAFIqvUJ13DEczZjgIh_4MyeIGEHz2GBYKFe?callback=foo";
document.getElementsByTagName('head')[0].appendChild(script);
Note that this doesn't work for me(but with your code, you would get Message sent printed to the console, so maybe you thought it was working?)- the response isn't JSON. Most likely the endpoint isn't actually meant to be used for JSONP?
My answer below only applies if you are trying to do a regular XHR in a browser without JSONP.
This happens because of the Cross Origin Resource Sharing policy of your browser. Your code is hosted at localhost, and it is trying to access a resource hosted at maker.ifttt.com through an XmlHttpRequest. In order to allow this to happen, the server at maker.ifttt.com would need to be configured to allow access from the localhost origin. Presumably you can not make that change as you don't control that server.
In your case, the best solution would be to make the request to maker.ifttt.com through your own server- CORS doesn't apply for server-to-server requests. Send the XmlHttpRequest to your server, take the data regarding the email from the request URL parameters, and then make the request to maker.ifttt.com using that data.
I'm using jQuery ajax to load a file kept in FTP server. Need to show percentage of file loaded in Progress loader.
Previously I had HTTP request and using XMLHttpRequest worked. Below is the code that worked.
$.ajax({
xhr: function() {
var xhr = new window.XMLHttpRequest();
// Upload progress
xhr.upload.addEventListener("progress", function(evt){
if (evt.lengthComputable) {
var percentComplete = (evt.loaded / evt.total)*100;
var loadPercent = '<div id="fountainTextG">'+Math.round(percentComplete)+'% complete ..</div>';
$(".sqlLoading").html(loadPercent).removeClass("hide");
jsAPP.sqlLoading = true;
}
}, false);
// Download progress
xhr.addEventListener("progress", function(evt){
if (evt.lengthComputable) {
var percentComplete =(evt.loaded / evt.total)*100;
var loadPercent = '<div id="fountainTextG">'+Math.round(percentComplete)+'% complete ..</div>';
$(".sqlLoading").html(loadPercent).removeClass("hide");
jsAPP.sqlLoading = true;
}
}, false);
return xhr;
},
type: 'POST',
url:'ftp://192.168.1.157/pub/1.json',
dataType: "jsonp",
jsonpCallback:"abc",
success: function(obj){
console.log("File loaded successfully");
},
error:function(err,stat,erroT){
$(".page").html("<div class='data_error'> Sorry! No data available for this city.</div>");
}
});
But this doesn't work on FTP request. Is there any way to show progress loader on FTP ? Kindly Help.
Here is my take on this question after I tried & tested three ways of accessing the file via js.
XMLHttpRequest
Although XMLHttpRequest states it it supports other protocols, it
doesn't seem to access a file served via ftp.
When I tried accessing using the code below, I hit a CORS error as
expected.
XMLHttpRequest cannot load ftp://ftp.funet.fi/pub/standards/RFC/rfc959.txt. Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https, chrome-extension-resource.
The FTP server doesn't seem to serve access control headers, also corroborated by the
post
testFtpLoad : function(){
var xhr = new XMLHttpRequest();
xhr.open("GET", "ftp://ftp.funet.fi/pub/standards/RFC/rfc959.txt", true);
xhr.onload = function (e) {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(xhr.responseText);
} else {
console.error(xhr.statusText);
}
}
};
xhr.onerror = function (e) {
console.error(xhr.statusText);
};
xhr.send(null);
},
Anchor tag
If you are using a modern browser, you could directly feed the ftp to the user using the download attribute (It's a simple <a> tag usage), although this is not what you are looking for.
testUsingAnchorTag : function(){
var $aTag = document.createElement("a");
$aTag.href = "ftp://ftp.funet.fi/pub/standards/RFC/rfc959.txt";
$aTag.id = "temporaryDownloadLink";
$aTag.download = "rfc959.txt";
document.body.appendChild($aTag);
$aTag.click();
setTimeout(function(){
$('#temporaryDownloadLink').remove();
}, 1000);
}
Downside: Though this downloads the file on the user's computer, you
will not be able to track it's progress
File API
I tried accessing the file using the FTP URL but it complained about
the parameters I passed.
var f = new File("ftp://ftp.funet.fi/pub/standards/RFC/rfc959.txt", true)
Even if I were to be successful in passing the right set of params,
it would have complained about the nature of URL since it only
expects a path served by server/from user's computer as mentioned in
this post - correct me if I'm wrong here.
Conclusion:
Finally I'd like to conclude that it may not be possible to serve &
track the progress of a file via FTP from the browser using js
You might have to fallback to your HTTP protocol and serve the files via a server over HTTP to achieve your goal
I've linked to most of the resources that I rummaged through- here are a few more.
Browser event of a download
detect-when-browser-receives-file-download
how-can-i-simulate-an-anchor-click-via-jquery
Sample ftp url
online
File API Documentation
Hope this helps.
I currently have the following working piece of code (angular but applies to any JS framework):
var url = '/endpoint/to/my/file';
$http({
method: 'GET',
url: url
})
.success(function(jdata) {
window.location = url;
})
.error(function(je){
// display errors on page
});
The above is called after a form was completed and the user has clicked on "submit" (the real situation is a bit more complex than this but it is the same idea). I do the form check asynchronously, so there's no page reload.
If the request is successful, returns a binary (a pdf file), if not succesful, the request returns a 400 BadRequest with errors formatted in JS. So what I do is, if successful, I redirect to the same url to have the PDF otherwise I get the JSON error object and do something with it.
How can I refrain from making two requests if the requests is successful?
Note1: on the backend side I would like to keep only one route that does everything, check + return PDF
Note2: the current situation is pretty neat in my opinion, since I have an asynchronous form check and if successful the file downloads directly in the browser since I have "CONTENT-DISPOSITION" -> "attachment" in the HTTP header of the successful response
Update: additional information about the architecture as requested by Emile:
In my use case I have one endpoint that checks inputs (and other external requirements). For security reasons I cannot output the PDF if all requirements are not satisfied so I have to do the check prior to delivering the file ( the file is automatically generated) anyway. So having two endpoints would just be redundant and add some unnecessary complexity.
While writing I think an alternative solution could be to pass an argument on the endpoint while doing the check, so that if successful, it stops and does not generate the PDF, and then redirect to the same endpoint without the flag which will output the PDF.
So I do the check twice but only load (and generate - which is resource intensive) the file only once and I have only one endpoint...
Here's the adapted code:
var url = '/endpoint/to/my/file';
$http({
method: 'GET',
url: url+'?check'
})
.success(function(jdata) {
window.location = url;
})
.error(function(je){
// display errors on page
});
On the backend side (I use Play framework/Scala)
def myendpoint(onlyDoCheck: Boolean = false) = Action{implicit request =>
myForm.bindFromRequest.fold(
e => BadRequest(myErrors),
v => if(onlyDoCheck) Ok(simpleOkResponse) else Ok(doComputationgeneratefileandoutputfile)
)
}
The real deal
The best you could do is split your endpoint.
One for the form and the convenience of having errors without refresh.
Then, on success, redirect to your other endpoint which only downloads the file.
If the file was on the disk and wasn't auto-generated and required to be authenticated to be downloaded, you could hide the file behind a normal endpoint, do the checks, and return the file using X-Accel-Redirect header with nginx or X-Sendfile using apache.
The hack
Disclaimer: This is more of a hack than the best solution. As mention by #Iceman, Safari, IE, Safari-iOS, Opera-mini and some such browsers don't support this particular spec.
In your server-side endpoint, if the file is available without errors, you can set the header to the content-type of the file (like 'application/pdf') so the download will starts automatically.
If there are errors, don't set the header and return a json of the errors to inform the user through javascript.
Since we don't know what's behind, here's a python (Django) example:
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename=your_filename.pdf'
response.write(repport.printReport(participantId))
return response
You can handle the response in the ajax success callback:
$.ajax({
url: 'endpoint.php',
success: function(data) {
var blob = new Blob([data]);
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "filename.pdf";
link.click();
}
});
You could also try the jQuery fileDownload plugin mentioned in this answer.
I have a test suite written in JavaScript running in a browser that runs on an embedded system. The test suite collects a lot of data and I want to push that to the server. I could use a simple HttpRequest, post-method, but that would require a lot of character escaping to send the content. It would much simpler to upload it to the server as a file using http-file-upload.
Is there a way to create an in memory file and use http-file-upload to push it to a server, using client-side JavaScript?
Since the browser of the embedded system is Ekioh and the system itself is a minimal one, technologies such as flash, JavaApplet, SilverLight are not available. Only pure HTML5 and JavaScript are available.
I think a post would be the better way to do this. Dealing with escaped data is a much easier, more established problem then in-memory files and pushing files to the server with client side javascript. Moreover, escaping data is done for a reason. What you're trying to do is going to welcome a lot of security vulnerabilities.
Try doing something like this.
Snippet taken from Write javascript output to file on server
var data = "...";// this is your data that you want to pass to the server (could be json)
//next you would initiate a XMLHTTPRequest as following (could be more advanced):
var url = "get_data.php";//your url to the server side file that will receive the data.
http.open("POST", url, true);
//Send the proper header information along with the request
http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
http.setRequestHeader("Content-length", params.length);
http.setRequestHeader("Connection", "close");
http.onreadystatechange = function() {//Call a function when the state changes.
if(http.readyState == 4 && http.status == 200) {
alert(http.responseText);//check if the data was revived successfully.
}
}
http.send(data);
This worked for me. The key part is to create a file and blob. I use angular JS to do the actual http call. However, once you have a file in memory, it shouldn't be too hard to send the data using your http client.
Note: I do the http call to https://httpbin.org/post. This echoes what the server received/parsed, which is useful while iterating to figure your problem out.
function multiPartPost(bodyObj) {
const url = 'https://httpbin.org/post';
const bodyJson = JSON.stringify(bodyObj);
const blob = new Blob([bodyJson], {
type: 'application/json;charset=UTF-8'
});
const fileName = 'jsonAttrs';
const file = new File([blob], fileName, {type: "text/json;charset=utf-8"});
const formData = new FormData();
formData.append(fileName, file);
return this.$http.post(url, formData, {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
});
}
I have read a lot of topics about CORS & Javascript and about changing the headers in your post but I can't find the right example I am looking for.
So I'm going to first up start with explaining the situation:
I can not change anything to the webserver since this is out of my reach (It's a SAP Cloud Portal)
I can only change the POST code, so I can only control what I send.
The problem I have is described in the following Post:
jQuery $.ajax(), $.post sending "OPTIONS" as REQUEST_METHOD in Firefox
--> My FF & Chrome Headers send a METHOD OPTIONS instead of METHOD POST.
I have written example code that works in IE but not in FF & Chrome:
var dataString = "<result><firstname>example</firstname><lastname>ThisIsSparta</lastname></result>";
var urlString = "http://delyo001.you.local:8000/sap/bc/youconsulting/ws/rest/anonymous/z_names_post";
//Add TO SAP.
var aData =
jQuery.ajax({
type: "POST",
contentType: "application/xml",
url: urlString, // for different servers cross-domain restrictions need to be handled
data: dataString,
dataType: "text",
success: function(xml) { // callback called when data is received
//oModel.setData(data); // fill the received data into the JSONModel
alert("success to post");
},
error: function(xml) { // callback called when data is received
//oModel.setData(data); // fill the received data into the JSONModel
alert("fail to post");
}
});
});
Or
var invocation = new XMLHttpRequest();
var url = 'http://delyo001.you.local:8000/sap/bc/youconsulting/ws/rest/anonymous/z_names_post';
var body = '<result><firstname>perthyrtyrtygop</firstname><lastname>sparta</lastname></result>';
invocation.open('POST', url, true);
invocation.setRequestHeader('X-PINGOTHER', 'pingpong');
invocation.setRequestHeader('Content-Type', 'application/xml');
invocation.send(body);
I have found 2 ways to fix this but without any examples:
- do something with a proxy?
- send specific headers
More information about my problem can be found at:
- http://scn.sap.com/message/13697625#13697625
If you can't set the right headers on the server-side and you can't modify the response for jsonP you should indeed use a proxy.
A proxy script is a sort of middleware. You make a request to the script the script gets the data, and returns it to you. For example php proxy. You can make the same thing in asp, jsp, flash or even java applet.
Now you have your SAP service, a proxy (php)file in a your prefered location, and your local javascript in the same domain as the proxy. You don't even need CORS.
If you want to put the proxy in another domain you have to make sure the php file sends the right headers. (Access-Control-Allow-Origin yourdomain or Access-Control-Allow-Origin * for allow all)