I want to get server information via fetch but if on the php page is a session started nothing returns.
Simplified example of my code:
JS:
fetch(url)
PHP:
session_start();
echo json_encode( array('a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5) );
As soon as I remove session part, the json is returned.
When I use XMLHttpRequest everything is working as expected:
var xhttp = new XMLHttpRequest();
xhttp.open("POST", url, true);
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
Maybe I have to change the fetch parameters, but I already tried different but nothing changed.
It seems you don't see response in browser console.
Try
fetch(url).then(function(res){
return res.json();
});
and you will see.
I found this is because of header:
Cache-Control: no-store, no-cache, must-revalidate
Calling session_cache_limiter('') before session_start() will prevent auto sending any caching headers. Calling with other parameters except 'nocache' will send allowing cache headers.
Also control available through php ini directive session.cache_limiter
Related
I am trying to set a session variable using fetch -
const response = await fetch('http://locahost/index.php/session/set', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({token:"token"})
});
The PHP function (inside a class) that does this -
public function setSession($arr){
try{
session_start();
$_SESSION['token'] = $arr['token'];
$responseData = json_encode("SESSION token has been set to ".$_SESSION['token']);
/// sendresponsedata() -> send response back with Access-Control-Allow-Credentials: true
} catch (Error $e) {
/// Some error
}
}
The PHP function is not on the same page as the page making the fetch request. When I console.log the response on the page that sent the request, it correctly shows SESSION token has been set to token.
But if then I try to retrieve the session variable using a different request and a different function -
fetch('http://localhost/index.php/session/get',{
credentials: 'include'
})
The response I get from this is always No ongoing session
public function getSession(){
try {
session_start();
// print json_encode($_SESSION); <---- printing this shows an empty array
$responseData = json_encode((isset($_SESSION["token"])) ? $_SESSION["token"]:"No ongoing session");
/// sendresponsedata() -> send response back with Access-Control-Allow-Credentials: true
} catch (Error $e) {
/// Some error
}
}
I looked at other questions like mine but as far as I could understand, the error was because of not allowing credentials. I couldn't really understand why credentials are needed in this case reading this, but I added them anyway to check first, but that didn't change anything. As far as I could understand fetch request creates a new session everytime so this could be impossible, but this might be possible if I made an AJAX request. I am not sure I understood that correctly however.
The sendresponsedata() function works well as I have made many other fetch requests with more headers, like allowing cross-origin requests and returning required headers on preflight handshakes which all worked (and it is not really a complicated function).
What am I doing wrong and how can I achieve what I need?
Edit: Since posting I have also tried xhr requests and they don't work either.
I'm working on a tracker that should collect some data on the websites of our clients and send it to our api using fetch request when site users leave the page.
The idea was to use beforeunload event handler to send the request, but I've read here that In order to cover most browsers I also need to use unload event handler.
This is the relevant part of tracker code that our clients will put on their websites:
var requestSent = false;
function submitData(element_id, url) {
if (!requestSent) {
var data = JSON.stringify({ourobject});
fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type':'application/x-www-form-urlencoded',
},
body: data,})
.then(response => response.json())
.then((data) => {
console.log('Hello?');
requestSent = true;
});
}
}
window.addEventListener('beforeunload', function (e) { submitData(1, "https://oursiteurl/metrics");});
window.addEventListener('unload', function(event) {submitData(1, "https://oursiteurl/metrics"); });
I've tested this on chrome and both requests pass, instead of just the first one that is successful, this leads to duplicate data in our database.
After putting console log in next to the part where requestSent flag is set to true, I realized that part of the code never gets executed.
If I preserve logs in network tab, it says that both requests are canceled, even though the data gets to our endpoint
Our api is created in Codeigniter, here is the /metrics endpoint
public function submit () {
$this->cors();
$response = [
'status' => 'error',
'message' => 'No data',
];
$data = json_decode(file_get_contents('php://input'), true);
if (empty($data)) {
echo json_encode($response);exit();
}
// process data and do other stuff ...
Cors function:
private function cors() {
// Allow from any origin
if (isset($_SERVER['HTTP_ORIGIN'])) {
// Decide if the origin in $_SERVER['HTTP_ORIGIN'] is one
// you want to allow, and if so:
header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 86400'); // cache for 1 day
}
// Access-Control headers are received during OPTIONS requests
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']))
// may also be using PUT, PATCH, HEAD etc
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']))
header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");
}
}
EDIT:
Thanks to #CBroe for suggesting to use the Beacon API, using it removed the need for both unload and beforeunload event handlers:
submitData now looks like this:
...
if (navigator.sendBeacon) {
let beacon = navigator.sendBeacon(url, data);
console.log( 'Beacon', beacon );
} else { // fallback for older browsers
if (!requestSent) {
console.log( 'Data object from fallback', data );
var xhr = new XMLHttpRequest();
xhr.open("POST", url, false); // third parameter of `false` means synchronous
xhr.send(data);
}
...
Doing it this way allowed me to only keep beforeunload event handler because it works both on ie and chrome:
window.addEventListener('beforeunload', function (e) { submitData(1, "https://oursiteurl/metrics");});
The idea was to use beforeunload event handler to send the request, but I've read here that In order to cover most browsers I also need to use unload event handler.
Both are not terribly suited to make AJAX/fetch requests, they are likely to get cancelled when the page actually unloads.
You should rather use the Beacon API, that was specifically made for this kind of tracking / keep-alive requests.
According to the browser compability list there on MDN, it is not supported by Internet Explorer yet though. If you need tracking for that as well, maybe go with a two-pronged approach - Beacon for the browsers that support it, an AJAX/fetch fallback for IE.
Implementing a simple HTTP server in Qt, with the purpose of streaming real time data to an XMLHttpRequest object (AJAX/JavaScript).
The problem is that the design pattern requires partial transmission of data via the socket connection, changing the readyState in the XHR from '1' (Request) to '2' (Headers received), and then to '3' (Data received) - keeping the request pending. This is also known as "long-polling", or "Comet" and should be possible in most browsers.
However, it stays in request state until the connection is closed, and then readyState '2' and '4' are received. This is normal for HTTP GET, but not desired for this application.
JavaScript:
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
console.log('readyState: ' + this.readyState + ' ' + this.status)
}
request.open("get", "localhost:8080/", true);
request.send();
Qt:
connect(socket, &QTcpSocket::readyRead, [=]()
{
QByteArray data = m_socket->read(1000);
socket->write("HTTP/1.1 200 OK\r\n");
socket->write("Content-Type: text/octet-stream\r\n");
socket->write("Access-Control-Allow-Origin: *\r\n");
socket->write("Cache-Control: no-cache, no-store, max-age=0, must-revalidate\r\n");
socket->flush();
}
So the big question is: How can I make the network system underneath the QtTcpSocket flush pending data after writing the headers (and later, the data), without the need to disconnect first?
A side note: I originally implemented this using WebSockets, but the browser I have to use does not support this.
EDIT:
The HTTP header formatting must have an extra set of "\r\n". Now it works:
connect(socket, &QTcpSocket::readyRead, [=]()
{
QByteArray data = m_socket->read(1000);
socket->write("HTTP/1.1 200 OK\r\n");
socket->write("Content-Type: text/octet-stream\r\n");
socket->write("Access-Control-Allow-Origin: *\r\n");
socket->write("Cache-Control: no-cache, no-store, max-age=0, must-revalidate\r\n");
socket->write("\r\n");
socket->flush();
}
Got it working now after a full day of trying different HTTP header configurations. It seems like user 'peppe' was on to something, and the only thing I needed was to add "\r\n" after the headers! (See edit).
I do get the response data, but I can't get my custom HTTP header data.
Yes, this is a cross-domain request. I am doing an Ajax request with Javascript. I've tried this with XMLHttpRequest and also jQuery $.ajax. I've done my server settings, I have these set when sending data:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET
I do get the response data that I want. But I can't get full HTTP header response.
With PHP, I set the following before sending the text response. So I assume that I should get it with getAllResponseHeaders().
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('My-Own-Test: nodatahere');
But here's what I got.
Content-Type: text/plain; charset=x-user-defined
Cache-Control: must-revalidate, post-check=0, pre-check=0
Expires: 0
It's missing the My-Own-Test. Just for reference sake, here's my Javascript:
var formData = new FormData();
formData.append('username', 'my_username');
formData.append('book_id', 'test password');
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://mydomain.com/proc.php', true);
xhr.overrideMimeType("text/plain; charset=x-user-defined");
xhr.onload = function(e) {
console.log(this.getAllResponseHeaders());
};
xhr.send(formData);
I even tried it with jQuery... same result.
var data_to_send = {
username: 'my_username',
password: 'test_password'
};
var ajaxObj;
ajaxObj = $.ajax({
url: "https://mydomain.com/proc.php",
data: data_to_send,
type: "POST",
beforeSend: function ( xhr ) {
xhr.overrideMimeType("text/plain; charset=x-user-defined");
}
})
.done(function ( data ) {
console.log( ajaxObj.getAllResponseHeaders() );
});
Still... no luck.
But if I go through Firebug or Chrome's Developer Tool, I can see that those tools do return full HTTP header information, including Content-Length, Content-Encoding, Vary, X-Powered-By, Set-Cookie, Server, and of course My-Own-Test.
I wanna thank jbl for pointing me to the right SO question. I got it now...
So, OK... the answer. If you ever wanted to set your own HTTP Header information, and then fetch it using cross-domain Ajax, or something like that, here are some extra HTTP Header you should set on your server side, before sending the response text.
header('Access-Control-Allow-Origin: *');
header("Access-Control-Allow-Methods: POST, GET");
header('Custom-Header: Own-Data');
header('Access-Control-Expose-Headers: Custom-Header');
Example above uses PHP. But use your own language, what ever you use to set them.
When I asked this question, I had all of that except Access-Control-Expose-Headers. After putting that in, my Javascript Ajax can read the content of HTTP Header Custom-Header.
I have this code:
window.onload = function() {
document.cookie = 'foo=bar; expires=Sun, 01 Jan 2012 00:00:00 +0100; path=/';
var xhr = new XMLHttpRequest();
xhr.open("GET", "/showcookie.php",true);
xhr.setRequestHeader("Cookie", "foo=quux");
xhr.setRequestHeader("Foo", "Bar");
xhr.setRequestHeader("Foo", "Baz");
xhr.withCredentials = true;
var pre = document.getElementById('output');
xhr.onreadystatechange = function() {
if (4 == xhr.readyState) {
pre.innerHTML += xhr.responseText + "\n";
}
};
xhr.send(null);
};
and this /showcookie.php
<?php
print_r($_COOKIE);
?>
and it always show
Array
(
[Host] => localhost
[User-Agent] =>
[Accept] =>
[Accept-Language] => pl,en-us;q=0.7,en;q=0.3
[Accept-Encoding] => gzip,deflate
[Accept-Charset] => ISO-8859-2,utf-8;q=0.7,*;q=0.7
[Keep-Alive] => 115
[Connection] => keep-alive
[foo] => Baz
[Referer] =>
[Cookie] => foo=bar
)
Array
(
[foo] => bar
)
I'm using Firefox 3.6.13, Opera 11.00 and Chromium 9.0 on Ubuntu.
Is anybody have the same problem or maybe it's impossible to modify Cookie header.
The Cookie header is one of several which cannot be modified in an XMLHttpRequest. From the specification:
Terminate [execution of the setRequestHeader method] if header is a
case-insensitive match for one of the
following headers:
Accept-Charset
Accept-Encoding
Connection
Content-Length
Cookie
Cookie2
Content-Transfer-Encoding
Date
Expect
Host
Keep-Alive
Referer
TE
Trailer
Transfer-Encoding
Upgrade
User-Agent
Via
… or if the start of header is a
case-insensitive match for Proxy- or
Sec- (including when header is just
Proxy- or Sec-).
The above headers are controlled by
the user agent to let it control those
aspects of transport. This guarantees
data integrity to some extent. Header
names starting with Sec- are not
allowed to be set to allow new headers
to be minted that are guaranteed not
to come from XMLHttpRequest.
I think this might be a hard constraint on the XHR functionality.
Setting the clientside document.cookie caused the Cookie header to be sent in requests as expected. If you want to pass a cookie value in an an ajax request this might be the way to go.
A workaround is to send a custom header to the php script with the cookie string you want to set:
// in the js...
xhr.open("GET", "showcookie.php",true);
//xhr.setRequestHeader("Cookie", "foo=quux");
xhr.setRequestHeader("X-Set-Cookie", "foo2=quux");
xhr.withCredentials = true;
Then in your showcookie.php you can grab the custom header value and fire a set-cookie response header:
$cookie = $_SERVER['HTTP_X_SET_COOKIE'];
// NOTE: really should sanitise the cookie input.
header('Set-Cookie: ' . $cookie);
print_r($_COOKIE);
Note that you wont see a cookie header until the response is parsed by the browser. Also please make sure you sanitise the contents of the X_SET_COOKIE header - this is a proof of concept only:)