Javascript HTTP data stream reset response - javascript

I've been having a hard time writing websockets in PHP, so I decided to try to download a datastream.
It works, I can get content in different times, parse it, and use it, but the entire response is saved into memory...
Is there a way to reset the request's response every time in onreadystatechange function? (where that comment is)
Removing the comment, I get this error:
test.html:20 Uncaught TypeError: Cannot assign to read only property
'responseText' of object '#'
A working code for streaming:
class HTTPStream {
constructor(url, callback, error) {
this.request = new XMLHttpRequest();
var previous_text = '';
if (typeof error === "function")
this.request.onerror = error;
this.request.onreadystatechange = () => {
if (this.request.readyState > 2) {
var new_response = this.request.responseText.substring(previous_text.length);
if(new_response != "") {
var result = JSON.parse(new_response);
callback(result);
}
previous_text = this.request.responseText;
//this.request.responseText = "";
}
};
this.request.open("GET", url, true);
this.request.send();
}
cancel() {
this.request.abort();
}
}
new HTTPStream('test.php', (m) => console.log(m));
Here is a log:
EDIT:
By suggestions, I tried doing this, but unfortunately you can set it to writable only once, and that is it. I have lots of outputs, not just once.

Related

Intercept XHR and change request headers and url before send in JavaScript

I want to intercept all XHR requests being sent, and change their URL and headers before the request gets sent.
Found this similar question but there are no answers there.
I tried hooking XMLHttpRequest.prototype.open, But it only gives me access to the response:
(function () {
var origOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function () {
console.log(arguments); // prints method ("GET"), URL
console.log(this); // prints response, responseText, responseURL, status, statusText, and onXXX handlers
origOpen.apply(this, arguments);
};
})();
Also tried hooking XMLHttpRequest.prototype.setRequestHeader, but it only gives me access to each header value being set, one by one, and I can't associate it to the URL of the request:
(function () {
var origSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
XMLHttpRequest.prototype.setRequestHeader = function (header, value) {
console.log("header", header);
console.log("value", value);
origSetRequestHeader.apply(this, arguments);
};
})();
I managed to hook XMLHttpRequest.prototype.send to set a custom header, but since I want to change an existing header key, it appends my new value instead of replacing the existing one. Other people encountered the same problem: 1, 2:
(function () {
var origSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function () {
arguments[1] = myNewUrl; // arguments[1] holds the URL
this.setRequestHeader('existingHeaderKey', newHeaderValue)
origSend.apply(this, arguments);
};
})();
How can I accomplish this?
The XMLHttpRequest(xhr) interface exposes a very few things. So there is limitation on what you can intercept.
However, we can wrap the xhr objects in Proxy and collect data until send is called. And before sending the request we modify the data at one spot.
const OriginalXHR = XMLHttpRequest;
// wrap the XMLHttpRequest
XMLHttpRequest = function() {
return new Proxy(new OriginalXHR(), {
open(method, url, async, username = null, password = null) {
lg('open');
// collect URL and HTTP method
this.modMethod = method;
this.modUrl = url;
this.open(...arguments);
},
setRequestHeader(name, value) {
lg('set header');
if (!this.modReqHeaders) {
this.modReqHeaders = {};
}
// collect headers
this.modReqHeaders[name] = value;
// do NOT set headers here. Hold back!
// this.setRequestHeader(name, value);
},
send(body = null) {
lg('processing request...');
// do the final processing
// ...
// don't forget to set headers
for (const [name, value] of Object.entries(this.modReqHeaders)) {
this.setRequestHeader(name, value);
}
lg('sending request =>' +
'\n\t\tmethod: \t' + this.modMethod +
'\n\t\turl:\t\t' + this.modUrl +
'\n\t\theaders:\t' + JSON.stringify(this.modReqHeaders));
this.send(body);
},
get(xhr, key) {
if (!key in xhr) return undefined;
let value = xhr[key];
if (typeof value === "function") {
// if wrapped, use the function in proxy
value = this[key] || value;
return (...args) => value.apply(xhr, args);
} else {
//return properties
return value;
}
},
set(xhr, key, value) {
if (key in xhr) {
xhr[key] = value;
}
return value;
}
});
}
console.warn('XMLHttpRequest has been patched!\n XMLHttpRequest: ', XMLHttpRequest);
let url = 'https://baconipsum.com/api/?type=all-meat&sentences=1&start-with-lorem=1';
function getData() {
console.log('fetching lorem ipsum');
let xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById("demo").innerText = this.response[0];
}
};
xhr.open("GET", url, true);
xhr.setRequestHeader('Referer', 'www.google.com');
xhr.setRequestHeader('Accept-Encoding', 'x-compress; x-zip')
xhr.setRequestHeader('Accept-Language', 'de-US,en;q=0.5');
xhr.send();
}
//fancy logging, looks good in dark mode
function lg(msg) {
console.log('%c\t Proxy: ' + msg, 'background: #222; color: #bada55');
}
#demo {
min-height: 100px;
background-color: wheat;
}
<button onclick="getData()">Get data</button>
<div id="demo"></div>
<p>Note: look in the Developer Console for debug logs</p>
You can wrap remaining xhr methods or attributes in the proxy handler as per your requirement.
This may not be as good as service workers. But service workers have following drawbacks:
A service worker is run in a worker context: it therefore has no DOM access, and runs on a different thread to the main JavaScript that powers your app, so it is non-blocking. It is designed to be fully async; as a consequence, APIs such as synchronous XHR and Web Storage can't be used inside a service worker.
Service workers only run over HTTPS, for security reasons. Having modified network requests, wide open to man in the middle attacks would be really bad. In Firefox, Service Worker APIs are also hidden and cannot be used when the user is in private browsing mode.ref

Why isn't JavaScript throwing exceptions as the result of an asynchronous HTTP (AJAX) request?

I've written the following JavaScript function which is very simple, it takes an ID and which then is sent to a PHP page, which echoes out some JSON. It passes the ID into the PHP page via GET.
For example, if I was to make the following GET request: /processing/getAccountInfo.php?id=5, it would return this (got from a database): {"username":"carefulnow","profileImg":null}. This is correct, so I know my PHP is working fine.
My JavaScript code that originally called the AJAX now needs to process it, echo it out and check it for errors. If the PHP function encounters any errors, the returned JSON will contain an "errorMsg" which contains the name of the PHP exception that it encountered including a specific message (Exception::getMessage()). An example error result would be {"errorMsg":"PDOException: some error..."}.
function getAccountInfo(id) {
var loading = document.getElementById("loading");
var inner = document.getElementById("accInfInner");
var name = document.getElementById("accInfName");
var img = document.getElementById("accInfImg");
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
try {
var response = JSON.parse(this.responseText);
if (response.errorMsg === undefined) {
if (response.username === undefined || response.profileImg === undefined) {
throw new Error("Could not get the username and profile image. Try logging in then back out.");
}
name.innerHTML = response.username;
// The profile image is not required.
if (response.profileImg !== null) {
img.src = response.profileImg;
} else {
img.src = "/assets/img/defaultuser.jpg";
}
loading.style.display = "none";
inner.style.display = "block";
} else {
throw new Error(response.errorMsg);
}
} catch (exception) {
inner.innerHTML = "Error. Hover for details.";
inner.title = exception.message;
}
}
};
xmlhttp.open("GET", "/processing/getAccountInfo.php?id=" + id, true);
xmlhttp.send();
}
The problem, however, is that the throw statements aren't working. If an error is thrown from the PHP, nothing happens (no errors in the console)! My IDE (PHPStorm 2017.1) gives the following error: "'throw' of exception caught locally", which after a lot of searching, I cannot find anyone else having this same issue. Is it something to do with the GET request been asynchronous, or is it something a lot simpler I'm not seeing?

Is there a way to get params from a XMLHttpRequest response?

Say I'm interested in checking the params that were sent with the XMLHttpRequest.
For instance, if I sent a POST petition with param 'option=1' can I retrieve that from the response?
I checked for the methods and properties but haven't seen a way to get it.
Fire a XMLHTTPRequest and examine the response object in your browser's JS console (F12 for Chrome/Firefox).
I believe the data is not there, at least I once changed the XMLHttpRequest open() method for a project (of course, I might have been just too stupid to find it). That way, my default error handler knows the original URL when printing errors to the user/sending errors to the error reporting backend.
Rough code snippet, pulled from a projects init-code:
/**
* Check XMLHttpRequest availability
*/
var ajax = null;
var proto = null;
if (window.XMLHttpRequest) {
ajax = new XMLHttpRequest ();
proto = XMLHttpRequest.prototype;
} else if (window.ActiveXObject) {
try {
ajax = new ActiveXObject("Msxml2.XMLHTTP.6.0");
proto = ActiveXObject("Msxml2.XMLHTTP.6.0").prototype;
} catch (e) { }
}
if (ajax == null) {
alert ("Can not create AJAX object. You need a more recent browser!");
return;
}
/**
* Update ajax prototype to store the URL (for better error handling)
*/
try {
var origOpen = proto.open;
proto.open = function (method, url) {
this._url = url;
return origOpen.apply (this, arguments);
}
} catch (e) {
console.log ("Can not patch XMLHttpRequest to store URL. Console output will omit them...");
}
You would need to adapt this for POST data passed to the send() function instead. Be aware, that the method is probably bad style, and my JS style might be even worse!
Better: But you could always pass the POST data directly to the callback function, without storing it in the XMLHTTPRequest object:
var postData = "SomeStuff-Foobar123";
var ajax = new XMLHttpRequest (); //add magic for other browsers here
ajax.open ("POST", "ajax.php", true);
ajax.onreadystatechange = function () {
if (this.readyState != 4 || this.status != 200) {
console.log ("Not ready, yet...");
return 0;
}
//response is in this.responseText
//but you can still access the parent objects!
console.log ("Done with Code 200. POSTed data: " + postData);
}
ajax.send (postData);
As Bergi said it's not possible to retrieve the parameters that were sent with the request on the response. So I'm closing the question.
Thanks to everyone who helped!

Adjacently Dependent AJAX (improved)

This question was posted a couple of days ago, but since I'm a nub it was filled with spaghetti code and that sort of thing (please pardon the form handling as well) That aside, I've added some notes and given some context, but the problem still lies in the second AJAX call.
This is the error that Chrome is throwing "Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https, chrome-extension-resource."
I have hidden the URL because it contains an API key that I would rather not share.
Any and all criticisms are warmly welcomed
/*
This module will take a user's name, return an ID
then search more stats in the api with the ID.
*/
var search = document.getElementById('search');
search.addEventListener('click', function(){
var demo = document.getElementById('demo');
var player_name = document.getElementById('player_name').value;
var player_id;
// Interpolated API URLs
var name_url = 'URL.pre'+player_name+'URL.end';
var stats_url; //nested in the second ajax call to pass updated player_id
// Get player ID
var xhr = new XMLHttpRequest();
var id_return_text;
xhr.onload = function(){
if(xhr.status === 200) {
id_return_text = JSON.parse(xhr.responseText);
player_id = id_return_text[player_name].id;
demo.innerHTML = id_return_text[player_name].name +', your player ID is: '+player_id;
}
};
xhr.open('GET', name_url, true);
xhr.send();
// Search stats with ID
var xhr_2 = new XMLHttpRequest();
var stats_return_text;
xhr.done = function(){
stats_url = "URL.pre"+player_id+"URL.end";
if(xhr_2.status == 200) {
stats_return_text = JSON.parse(xhr_2.responseText);
demo.innerHTML += stats_return_text['playerStatsSummaries'].playerStatType;
}
};
xhr_2.open("GET",stats_url, true);
xhr_2.send();
});
<div id="container">
<img id="duck" src="duck.png" alt="duck">
<div class="form_wrapper">
<h1 id="app_header">*QUACK* What's Your player ID?</h1>
<form>
<input
type="text"
id="player_name"
placeholder="Summoner Name">
<input type="button" id="search" value="Search">
</form>
</div>
<p id="demo"></p>
</div>
<script type="text/javascript" src="script.js"></script>
So your primary error was that if you need to make CORS requests (or any AJAX requests, really), you need to run the code from a server (even localhost).
Google (and most browsers) will freak out at you if your page's protocol is "file:///" and you're trying to load things from the internet (or vice versa). And "file:///" cannot make requests for other files, either.
Future reference: you also can't make "http" requests from an "https" page.
That out of the way, the second issue (the one that was being hidden by CORS security), is that your AJAX requests are being run in parallel right now.
In order to make this work the way you think it should (after the first one returns, run the second one), you would need to:
move all of the code at the bottom, relating to xhr_2 inside of the xhr.onload
move all of the code inside of xhr.done at the bottom inside of the xhr.onload and replace all of the duplicate information (and use the references to the returned results directly)
This results in something like:
var search = document.getElementById('search');
search.addEventListener('click', function(){
var demo = document.getElementById('demo');
var player_name = document.getElementById('player_name').value;
var player_id;
// Interpolated API URLs
var name_url = 'https://na.api.pvp.net/api/lol/na/v1.4/summoner/by-name/'+player_name+'?api_key=<THIS IS THE API KEY>';
var stats_url; //nested in the second ajax call to pass updated player_id
// Get player ID
var xhr = new XMLHttpRequest();
var id_return_text;
xhr.onload = function(){
if(xhr.status === 200) {
id_return_text = JSON.parse(xhr.responseText);
player_id = id_return_text[player_name].id;
demo.innerHTML = id_return_text[player_name].name +', your player ID is: '+player_id;
// Dropped the XHR_2 stuff here
var xhr_2 = new XMLHttpRequest();
var stats_return_text;
stats_url = "https://na.api.pvp.net/api/lol/na/v1.3/stats/by-summoner/"+player_id+"/summary?season=SEASON2016&api_key=<THIS IS THE API KEY>";
// CHANGED THIS TO BE XHR_2.onload -- IN HERE I KNOW XHR_1 IS ALREADY FINISHED
xhr_2.onload = function(){
if(xhr_2.status == 200) {
stats_return_text = JSON.parse(xhr_2.responseText);
demo.innerHTML += stats_return_text['playerStatsSummaries'].playerStatType;
}
};
xhr_2.open("GET",stats_url, true);
xhr_2.send();
}
};
xhr.open('GET', name_url, true);
xhr.send();
});
That should solve practically all of your woes.
The point of this is that onload is a callback which gets fired long after the program has been run, but xhr_2 was firing immediately after you requested data for xhr_1 (not after it was returning the data).
As such, player_id was undefined.
We want to wait until after we know we have player_id, and we know we have it (or some error) when we're inside the callback to xhr_1.onload.
This gets terribly confusing and very nested, and while I think that Promises and Async Functions / Generators are brilliant solutions for managing that complexity, that's way beyond the scope of this; so instead, I'd suggest looking at some functional composition, to simplify all of this:
function noop () { } // do nothing
function getJSON (url, onload, onerror) {
var xhr = new XMLHttpRequest();
onload = onload || noop; // what I've been given or nothing
onerror = onerror || noop; // " "
xhr.onload = function () {
var data;
var error;
try {
// it's possible for parse to throw on malformed JSON
data = JSON.parse(xhr.responseText);
} catch (e) {
error = e;
}
return error ? onerror(error) : onload(data); // fire one or the other (don't fall into the handler, if onload throws)
};
xhr.onerror = onerror;
xhr.open("GET", url);
xhr.send();
}
// localize URL construction
function buildPlayerIdUrl (name) { return "https://______" + name + "_____"; }
function buildPlayerStatsUrl (id) { return "https://______" + id + "_____"; }
// gets player by name and runs a function after the player has been loaded
function getPlayer (player_name, done, error) {
var id_url = buildPlayerIdUrl(player_name);
function buildPlayer (response) {
var player = response[player_name];
return player;
}
function onload (response) {
done(buildPlayer(response));
}
// Load the JSON, build the player, pass the player to done()
getJSON(url, onload, error);
}
// get stats by player id and runs a function after the stats have been loaded
function getPlayerStats (player_id, done, error) {
var stats_url = buildPlayerStatsUrl(player_id);
function buildStats (response) {
var summary = response.playerStatsSummaries;
return summary;
}
function onload (response) {
done(buildStats(response));
}
// Load the JSON, build the stats, pass the stats to done()
getJSON(stats_url, onload, error);
}
// perform a search by player name
// note: All changes in step-number (1, 2, 3) are asynchronous,
// and thus, must be nested in callbacks of some sort
function search (player_name) {
// Step 1: load the player
getPlayer(playerName, function (player) {
// Step 2a: update the DOM with the player name/id
updatePlayerDom(player);
// Step 2b: load the player stats
getPlayerStats(player.id, function (stats) {
// Step 3: update the DOM with the stats
updateStatsDom(stats);
});
});
}
// player DOM update; keeping it nice and simple
function updatePlayerDom (player) {
document.querySelector(".Player-id").textContent = player.id;
document.querySelector(".Player-name").textContent = player.name;
}
// stats DOM update; same as above
function updateStatsDom (stats) {
document.querySelector(".Player-stats").textContent = stats.playerStatType;
}
// bootstrap yourself to your UI
some_button.onclick = function () {
var player_name = some_input.value;
search(player_name); // kick the whole thing off
};
It's definitely more code, but it's also simpler to make edits to each individual piece, without stepping on the toes of other pieces.
It's (hopefully) also easier to see the _eventual timeline_ of all of the pieces, and how they flow, inside of the search( ) itself.

Setting server response data to a variable to work with

Hey guys I am using a executePostHttpRequest function that looks exactly like the code posted below. Currently when I run the function I get a server response with the appropriate data but I am not sure how I can work with the response data? how do I store it in to a variable to work with?
Javascript executePostHttpRequest
function executePostHttpRequest(url, toSend, async) {
console.log("====== POST request content ======");
console.log(toSend);
var xmlhttp = new XMLHttpRequest(); // new HttpRequest instance
xmlhttp.open("POST", url, async);
xmlhttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xmlhttp.setRequestHeader("Content-length", toSend.length);
xmlhttp.send(toSend);
console.log("====== Sent POST request ======");
}
Here is what I am doing to execute it. Using Javascript
var searchCriteria = JSON.stringify({
displayName : search_term
});
console.log("Search: "+searchCriteria) //Search: {"name":"John, Doe"}
var response = executePostHttpRequest("/web/search", searchCriteria, true);
console.log(response) //undefined
So currently the console.log for response shows undefined. But if I take a look at the network tab on Chrome Dev Tools and look at the /web/search call I see a JSON string that came back that looks something like this.
[{"id":"1","email":"john.doe#dm.com","name":"John, Doe"}]
I'd like to be able to display the data from this response to a HTML page by doing something like this.
$("#id").html(response.id);
$("#name").html(response.name);
$("#email").html(response.email);
I tried taking another route and using Jquery POST instead by doing something like this.
var searchCriteria = JSON.stringify({
displayName : search_term
});
console.log("Search: "+searchCriteria) //Search: {"name":"John, Doe"}
$.post("/web/search", {
sendValue : searchCriteria
}, function(data) {
$.each(data, function(i, d) {
console.log(d.name);
});
}, 'json').error(function() {
alert("There was an error searching users! Please contact administrator.");
});
But for some reason when this runs I get the "There was an error" with no response from the server.
Could someone assist me with this? Thank you for taking your time to read it.
Your executePostHttpRequest function doesn't do anything with the data it's receiving. You would have to add an event listener to the XMLHttpRequest to get it:
function getPostData(url, toSend, async, method) {
// Create new request
var xhr = new XMLHttpRequest()
// Set parameters
xhr.open('POST', url, async)
// Add event listener
xhr.onreadystatechange = function () {
// Check if finished
if (xhr.readyState == 4 && xhr.status == 200) {
// Do something with data
method(xhr.responseText);
}
}
}
I've added the method parameter for you to add a function as parameter.
Here's an example of what you were trying to do:
function displayStuff(jsonString) {
// Parse JSON string
var data = JSON.parse(jsonString)
// Loop over data
for (var i = 0; i < data.length; i++) {
// Get element
var element = data[i]
// Do something with its attributes
console.log(element.id)
console.log(element.name)
}
}
getPostData('/web/search', searchCriteria, true, displayStuff)

Categories