On a website we are working on, we have a download link, that must be served to the user. However, when fetching the url the server can either serve an error message in JSON (the appropriate headers and an appropriate http status code will then be set) or serve the file.
Currently, we are using an iframe to download this file, but this prevents us from viewing the error message. While, this can be done in principle, it cannot be done cross-domain and the reading the error data seems to be different between browsers (as the browser will interpret the json as html and create html tags around it)
I have considered using xmlhttprequest2 to download the file, and serve it to the user, however the downloaded file can be large and thus, it must be streamed to the user.
Therefore, I'm looking for a way to either download a file or read the error message depending on the http status code.
API Setup
I'm able to change the API to my wishes, however the API is designed to be a public API and is designed as a REST API. This means, that the API should stay as simple as possible, and workarounds to make specific client-side code work should not have cause any hazzle for other client-side (thus the API and client-side code are decoupled).
The file that is being downloaded is encrypted on the server, and can only be decrypted by information given in the URL. Therefore, chunked transfer is difficult, as extracting a chunk would require the server to decrypt the whole file.
the appropriate headers and an appropriate http status code will then be set
If that statement is correct, you could use the same concept as preflighted requests for Cross-site requests. Preflighted requests first send an HTTP request with the OPTIONS method to the resource on the other domain, in order to determine whether the actual request is safe to send or not.
In your case, instead of having an OPTIONS request automatically send, you could send manually an HEAD request. The HEAD method is identical to GET except that the server do not return a message-body in the response. The informations contained in the HTTP headers in response to a HEAD request should be identical to the information sent in response to a GET request. Since you're only fetching the headers and not the body, you would have no problem with large or even any file, nothing is downloaded except the headers.
Then, you can read these headers or even the status code, and depending on the result of this manually preflighted request, decide if you should either stream the file to the user or fetch the error message if you're encountering an error.
A basic implementation without knowledge of your project using status code could be the following:
function canDownloadFile(url, callback)
{
var http = new XMLHttpRequest();
http.open('HEAD', url);
http.onreadystatechange = function() {
if (http.readyState === XMLHttpRequest.DONE) {
callback(http.status);
}
};
http.send();
}
var canDownloadCallback = function (statusCode) {
if (statusCode === 200) {
// The HEAD request returned an OK status code, the GET will do the same,
// let's download the file...
} else {
// The HEAD request returned something else, something is wrong,
// let's fetch the error message and maybe display it...
}
}
You can return to iframe JS code which would send message to parent window via postMessage, in this way you could capture errors in host window. There wouldn't be any problems with cross-domain, and content easily would be streamed to browser as it was before.
You could use single request, return response as a Blob, check Blob.type to determine where to utilize FileReader .result to retrieve JSON text from Blob and display error message, or set src of <img> element using URL.createObjectURL
var result = document.querySelector("div");
fetch("/path/to/resource")
.then(response => response.blob())
.then(blob => {
if (blob.type === "application/json") {
var reader = new FileReader();
reader.onload = (e) => {
result.innerHTML = JSON.parse(e.target.result).error
};
reader.readAsText(blob);
} else {
var img = document.createElement("img");
img.src = URL.createObjectURL(blob);
result.appendChild(img);
}
});
plnkr http://plnkr.co/edit/wrzLNm1Sp4k1mfNrYOlQ?p=preview
Related
I'm trying to work with the eBay APIs. It's a small personal project that just needs to run locally, and although I know C#, I'm much more comfortable with Javascript so I'm looking for ways to get this done in JS.
I found this promising looking eBay Node API with browser support. Browser support is something I'm looking for, but it also says that
A Proxy server is required to use the API in the Browser.
They give an example file for a proxy server that is a Cloudflare worker.
I'm trying to translate that into something I can run in Node locally using the basic Node HTTP server. I've been following through it and am doing OK so far, figured out the different ways to access the headers and check them, etc., but now I'm at the point where the proxy server is making the proxy request to the eBay APIs. The way the example file is set up, it seems as though the Cloudflare worker intercepts the HTTP request and by default treats it as a Fetch Request. So then when it goes to pass on the request, it just kind of clones it (but is replacing the headers with "cleaned" headers):
// "recHeaders" is an object that is _most_ of the original
// request headers, with a few cleaned out, and "fetchUrl"
// is the true intended URL to query at eBay
const newReq = new Request(event.request, {
"headers": recHeaders
});
const response = await fetch(encodeURI(fetchUrl), newReq);
The problem is that I don't have a Fetch Request to clone - since I'm running a Node HTTP server, what is event.request in the example code is for me a http.IncomingMessage.
So how can I turn that into a Fetch Request? I'm guessing at the very least there's stuff in the message body that needs to get passed along, if not other properties I'm not even aware of...
I don't mind doing the work, i.e. reading the stream to pull out the body, and then putting that into the Request object somehow (or do I even need to do that? Can I just pipe the stream from the IncomingMessage directly into a Request somehow?), but what else besides the body do I need to make sure I get from the IncomingMessage to put into the Request?
How do I turn a Node http.IncomingMessage into a Fetch Request and be sure to include all relevant parts?
I've made a simple function to convert.
const convertIncomingMessageToRequest = (req: ExpressRequest): Request => {
var headers = new Headers();
for (var key in req.headers) {
if (req.headers[key]) headers.append(key, req.headers[key] as string);
}
let request = new Request(req.url, {
method: req.method,
body: req.method === 'POST' ? req.body : null,
headers,
})
return request
}
I develop a JavaScript application which uses oauth2 authentication. Now I want to load/show an image from server which is behind this authentication mechanism. Therefor I send an xmlHttp-request to the rest-server and get the URI of the image as response.
After the request I try to append the URI to the src of an image and the application responds with 401.
How can I tell the browser to reuse my authentication for this image as well?
This is a part of the xmlHttp-request for getting the URI.
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
xhr.setRequestHeader('Authorization','bearer '+token);
xhr.send(null);
xhr.onreadystatechange = function() {
console.log(xhr);
if (xhr.readyState == 4 && xhr.status == 200) {
var img = document.createElement('img');
img.src = xhr.responseURL;
document.body.appendChild(img);
}
}
Did I forgot something?
An image is a static file and I doubt you're going to be able to get OAuth2 protection when you request it via a simple URL. While you can add OAuth2 information in your URL that's probably not a good thing to do because it will expose to the client all the data which should be private and secure.
Maybe you can consider serving the image from a normal protected endpoint as a byte[] or Base64 string which you can then render in your client if you really need to protect the image itself.
So have a protected endpoint which serves the content of the image itself. Of course, you'd only do this if you actually need the image to be private and secure. If you don't then just let it be public and serve from a separate CDN. It doesn't really need to be behind an OAuth2 system.
Though I came here to search for a better answer than the one I have to offer here are my current solutions:
Serve the image as base64 / json via REST -> supports Oauth2 but will increase file size and just feels very wrong.
Use a cookie to sent the authentication.
Using Javascript I want to make multiple POST requests cross domain. In the first instance, I store the response cookies in a variable, then I resend for a second POST request..
For example, in ruby i'd do something like this:
#http = Net::HTTP.new("myhost.com", 80)
// first request
data = "param1=xxxx¶m2=yyyy¶m3=zzzz"
resp = #http.post("/firstrequestform", data, {'User-Agent'=>'me'})
// second request
#cookie = resp['set-cookie']
headers = { "Cookie" => #cookie, "Referer" => "http://myhost.com/firstrequestform" }
data = "param1=xxxx¶m2=yyyy¶m3=zzzz"
resp = #http.post("/secondrequestform", data, headers)
Is it possible to do this in Javascript given cross domain restrictions. Maybe possible using an iframe, but how would you control the cookies? I'd also like to set custom headers within the iFrame, such as the Referer header.
If it's not possible, does anyone know of a browser plugin that can be used to do this?
Thanks.
UPDATE
Unfortunately in this case its not possible to route any request through a 3rd party server (all the code has to be on the client side).
Simplest would be use your server as a proxy. Make an AJAX request to your server, and use your Ruby code shown to make request to other domain and output the response to AJAX request
I stumbled on this command while learning AJAX. The guy who made the tutorial didn't explain this command, what do the parameters inside the command mean and what is it used for... Below is the code I used it in:
<script type="text/javascript">
function insert(){
if(window.XMLHttpRequest){
xmlhttp = new XMLHttpRequest();
}else{
xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
};
xmlhttp.onreadystatechange = function(){
if(xmlhttp.readyState == 4 && xmlhttp.status == 200){
document.getElementById('message').innerHTML = xmlhttp.responseText;
};
};
parameters = 'insert_text='+document.getElementById('insert_text').value;
xmlhttp.open('POST','ajax_posting_data.php',true);
xmlhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xmlhttp.send(parameters);
};
</script>
HTTP is a protocol. Part of that protocol is the concept of request headers. When an xhr happens, text is exchanged between the client and server. Request headers are part of the text that the client sends to the server.
This is a way to set the request headers. The arguments you see are
1) the header to set (in this case, Content-type)
2) the header value. (in this case, x-www-form-urlencoded)
See this for more info.
HTTP requests are messages passed from one computer system to another according to a set routine (a 'protocol' - here HyperText Transfer Protocol) in order to do things like send data, ask for data to be sent back, update data previously sent, etc.
A header is basically a piece of information about the data in the body of the HTTP request. Its purpose is to tell the machine receiving the request what type of data is enclosed in the body of the request, its formatting, the language used, if it's to set a cookie, the date, the host machine, etc.
More than one header can be put on a HTTP request and each header has a 'name' and a 'value' component. On web pages they look like
<meta name="........" content="............."/>
and you find them just below the top of the web page within the element.
To enable people to send HTTP requests from within a JavaScript function, we create a new XMLHttpRequest object, just as your code does so with
const xmlhttp = new XMLHttpRequest();
To this new empty object you intend to add data. Despite its name, XMLHttpRequest also allows sending data in a number of formats other than XML, e.g. HTML code, text, JSON, etc. In your example each data name will be separated from its value by an "=" character and each data/value pairing will be separated from the next pairing by an "&" character. This kind of formatting is known as URL encoding.
We have to tell the receiving computer how the data within the HTTP request body is encoded. There is a standard header to convey this and it is added to the request via the method setRequestHeader(..). This method uses 2 parameters, the header name and the header's value. All this operation is achieved in the line
xmlhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
This setRequestHeader(..) method must be applied to the request after the request is characterized with the open(...) method but before the final request is sent off with the send(.) method.
The open(...) method defines: (1) the type of HTTP request, e.g. GET/POST/PUT etc; (2) the web page that contains the handling script for this request, e.g. some .php file or Node.js request endpoint that makes the appropriate query to the back end database; and (3) the nature of the request dynamics, e.g. asynchronous requests are assigned a value 'true', synchronous requests are assigned 'false'.
The send(.) method attaches the data to be sent within the body of the request, in your case the variable called 'parameters'.
On your broader question of which situations setRequestHeader(..) is used, I would say that it is used in most HTTP request situations. But some types of data added to the body of a HTTP request invoke a default setting for the 'Content-Type' header.
It is exactly what it says. It will set a "header" information for the next XMLHttpRequest.
A header is pretty much a key/value pair. It is used to transmit "meta" information to the target server for the ongoing request. In your particular instance, its used to tell the server which content type is used for this request.
It sets the Content-type HTTP header to contain url encoded data sent from a form.
I'm working on an ad platform. When someone clicks on an image, I want to send a request back to my server to log this action. I have a pre-generated url for this. If I send a request to this url, it will log the data.
My issue is that the log url is on my domain, whereas the javascript is being executed in a client's domain. Without modifying the logging php script (to add something like Access-Control-Allow-Origin: *), is there a way to send this request to the new domain?
Since I'm only logging data, the server only sends back the text "OK" (which is information I don't need).
You should be able to send Ajax HTTP requests to any domain. I don't see what the problem is... It's the response that is restricted with the Same Origin Policy, not the request itself. You cannot access the response of the PHP script if the domains don't match, but the server will process the request normally, even if it's from a different domain.
This is a hack but it's commonly used. On click append an image to the DOM with the src set to the logging URL. To be friendly, have the output from the logging URL be a 1x1 pixel image. You'll have to pass the parameters via a GET string but it will work.
Create any dynamic DOM element with source on your domain (image or iframe), append a logging data to a request.
var logData = function(data){
if(data === undefined){
return;
}
var img=document.createElement("img");
img.setAttribute('src', 'http://another.domain?'+data);
img.setAttribute('height', '1px');
img.setAttribute('width', '1px');
document.body.appendChild(img);}
Your log requests will now appear in IIS logs
Cross-domain script restrictions are enforced at the browser level as a security precaution, so there is not a simple code fix to work around them. However, you can look at JSONP (JSON with padding) as a starting point.
You could create a hidden iframe with the src attribute set to the logging URL. This is at least as ugly as the image approach listed above.