I have a dynamic php site which generates a json string. So if a javascript does 10 requests to this site per minute the json string is generated and echoed 10 times.
I want to limit the requests going through to my server to 1 request per minute.
I thought the cache control header would do the job, but it looks like I'm wrong.
Here is what I tried. I set my php page to this:
<?php
header("Cache-Control: max-age=60");
echo "{'test':'abc'}";
?>
Loaded the site with the browser; it returned {'test':'abc'}
Then I quickly changed the php page to:
<?php
header("Cache-Control: max-age=60");
echo "{'qwe':'123'}";
?>
Reloaded the page quickly and got: {'qwe':'123'}
So the second request went through even though the minute wasn't over. I wanted the first result to be returned from cache for one minute, without doing another request.
What am I doing wrong?
Related
I'm writing a script that will combine and minify requested CSS files and then echo them out. I have most of the work done, however I'm stuck on one small, yet very important piece: Leveraging browser caching.
Most visitors to ours sites are new, and rarely ever come back. So really what we're worried about is caching between page requests in the same session. Like, they hit our main page and then navigate to a few other pages and leave.
The problem I'm having is that I'm storing a timestamp in the session for the last request time for each specific set of files. So if I want main.css and internet.css this request and then main.css and phone.css next page view then the timestamp of the last request will be updated, but if I requested the same set of files again, the timestamp would be unchanged from last time.
Hopefully I'm making sense. The issue is that when a file is unchanged from last request to this one, I return 304 not modified. However, the browser is not caching the css like it should. Any ideas as to why not?
You can take a look at my code here: https://gist.github.com/4235836 (I would normally put it here, but it's kinda long)
I think you should check the request header If-modified-since before sending out a 304:
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $minifier->lastModified)
{
header('HTTP/1.0 304 Not Modified');
exit;
}
Also notice the exit. If your sending out a 304, it means the client already has the latest version, so you should exit your script there.
Edit:
When using expire headers, the browser will assume it already has the latest version. So it wont even make a request to the server, unlike using the HTTP_IF_MODIFIED_SINCE header.
So you might also want to add:
header('Expires: '.gmdate('D, d M Y H:i:s \G\M\T', time() + (60 * 60 * 24)));
Then to make sure it will request a new version once the file has changed, you can do sonething like:
<link rel="stylesheet" type="text/css"
href="minify.php?v=<?php echo filemtime($theFileToMinify) ?>">
I have two PHP scripts which both have an "include_once('authentication.inc');" script near the top. Both scripts reference the same authentication file. That authentication file currently performs a header redirect (like "header('Location: index.php');") if the user is not signed in.
In one file (A.php) the immediate next line of code after the include of the authentication file is:
if(isset($_GET['delete']))
mysql_query("DELETE FROM table WHERE index=".$_GET['delete']);
In the other file (B.php) there are several other includes which occur before the same "delete code" listed above.
So the authenticate.inc file looks like:
if(!valid_credentials($username,$password))
header('Location: index.php');
And file A.php looks like:
include_once('authenticate.inc');
if(isset($_GET['delete']))
mysql_query("DELETE FROM table WHERE index=".$_GET['delete']);
And file B.php looks like:
include_once('authenticate.inc');
include_once('other.php');
include_once('file2.php');
include_once('onemore.php');
if(isset($_GET['delete']))
mysql_query("DELETE FROM table WHERE index=".$_GET['delete']);
Yet when I call A.php?delete=5, that record is deleted from the database while when I call B.php?delete=8 that record is not.
I have checked the 3 intermediary includes and do not see any die() statements, nor any other header redirects.
So while it's clear that A.php is continuing to execute after the header is sent, why isn't B.php doing the same thing? Is the header being sent before the next set of imports?
**
Also: I know to add the die() or exit command after the headers are sent. I'm working on someone else's code and trying to explain behavior, not writing this myself.
**
No way to tell. If the starts are aligned properly, the header coud be sent to the client browser immediately and the bowser will start closing the current connection and request the new URL immediately. This'll cause the current PHP script to start shutting down.
On the other hand, if the caches are slow and the network glitchy, the client browser may not get the redirect header for seconds/minutes/hours, and the script could continue executing indefinitely.
In general you should assume that the moment you've issued a header redirect that the script is basically "walking dead" and should not do any further work.
The sole exception to this rule is that you CAN use ignore_user_abort(TRUE), which tells PHP to NOT shut down when the remote user disconnects. That'd allow you to continue on working even though the browser has shut down the connection and moved on to the new page.
Update your authenticate.inc file to die() after the redirect. This will prevent any other code from executing.
if(!valid_credentials($username,$password)) {
header('Location: index.php');
die();
}
Without it, and depending upon your server configuration, the rest of the PHP code will be executed on the server even after the headers are transmitted back to the client. Until the client closes the connection, the code will run.
Just put an exit() after the header redirect. It will stop all execution after the redirect.
There is probably some output in either of the included files, with echo or other outputting functions. If the browser by then has followed the redirect and aborted the connection, the PHP script will by default exit. You can change this behaviour with ignore_user_abort(true);. You should however use die(); after the Location header. If the query execution is wanted, just put that query before the Location header. Don't forget to use proper escaping for the input, otherwise the script could be a target for a mysql injection attack.
To answer your question, it seems that the browser will wait until your script finished execution and only then will request another location.
Please note that you shouldn't use GET method to delete records.
As for the not deleting id=8 - just debug it. Not a big deal.
A good var_dump() is always better than some vague ideas about headers.
I have a very similar setup to the person here:
PHP Background Processes
i.e a very long script that takes up to 10 minutes. However, I need the person who calls the script redirected back to the homepage while the script works. In other words, I need the user experience to be something like this:
click the update button
script begins to execute, can take up to 10 minutes
user is redirected back to the home page immediately
Is this possible using only PHP? I gather I will need
ignore_user_abort(true);
set_time_limit(0);
But how do I redirect the user? I can't use javascript because output only gets written to the page at long increments, and I want the redirection to be immediate. Can I use headers? Or will that mess with things?
Alternatively, I could use the cron job approach, but I have zero experience in making a cron job or having it run php code (is that even possible?)
Thanks,
Mala
Update:
Using headers to redirect does not work - the page will not load until the script is done. However, eventually the webserver times out and says "Zero-Sized Reply: The requested URL could not be retrieved" (although the script continues running). I guess my only option is to go with the cron job idea. Ick!
The most obvious solution to me would be splitting the redirect and the background calculation in two separate files and let the redirect script execute the 10-minute script:
job.php:
<?php
// do the nasty calculation here
redirect.php:
<?php
// start the script and redirect output of the script to nirvana, so that it
// runs in the background
exec ('php /path/to/your/script/job.php >> /dev/null 2>&1 &');
// now the redirect
header ('Location /index.php');
Assumptions for this to work: You should be on a Linux host with either safe_mode disabled or having set the safe_mode_exec_dir appropriately. When you're running under windows, the exec string needs to be adapted, while the rest about safe_mode remains true.
Notice: When you need to pass arguments to the script, use escapeshellarg() before passing it on, see also the PHP manual on exec
I've tried several methods and none seems to work, I've even tried to use register_shutdown_function() but that also failed. I guess you're stuck with making a cron job.
I just remembered something (but I haven't tested it), you can try to do something like this:
set_time_limit(0);
ignore_user_abort(true);
ob_start(); // not sure if this is needed
// meta refresh or javascript redirect
ob_flush(); // not sure if this is needed
flush();
// code to process here
exit();
Not sure if it'll work but you can try it out.
I have a similar situation with processing logins.
To keep it short...
I get a PDT, IPN and each sends me a logging email.
An email is sent to client on IPN VERIFIED to give serial number and password to client.
As PDT and IPN I use goto to send me a logging email instead of a bunch of sequential ifs.
On reading many answers I studied each to figure what would suit my isssue.
I finally used...
<?php
ignore_user_abort(TRUE); // at very top
As I worked through the progressive checks (no ifs), if they failed I use for example...
$mcalmsg .= "Check [serialnbr]\r\n";
if (empty($_POST['serialnbr']))
{ header('Location: '.$returnurl.'?error=1');
$mcalmsg .= "Missing [serialnbr]\r\n";
goto mcal_email; // Last process at end of script
}
else
{$serialnbr=strtoupper(htmlspecialchars(trim($_POST['serialnbr'])));
$mcalmsg .= "[serialnbr]=$serialnbr\r\n";
}
This (so far) is working just as needed.
Of course there is more in the script but follows the same concept.
Where this says location, there are also 3 information pages that can each be displyed using the same concept.
mcal_email: //last process before ending, always gets here after all else from goto or clearing all checks.
// compose email and send
?> // end of script
Why not try the header approach and see what happens? You could also try a call to php header method and see if this does the trick. I would work on trial and error to see what will solve your problem.
EDIT I just realized that I must have had a massive brain fart while writing the abbreviated code sample. See, I'm using smarty. Thus, I'm actually already using Kips's solution, because smarty displays after the session is saved
I've been working on implementing a resource manager (for condensing, compressing and minifying CSS & JS) for a PHP site I'm working on and have run into an awfully strange problem. So when a user navigates to index.php, files are added to a resource manager object, which combines them into a single file and are included in the page via either <script src="resource.php?id=123&ext=.js"> or <link href="resource.php?id=123&ext=.css" />
What it basically boils down to is that a file path is stored in a session on the accessed page and read from the session on the resource page. In FF, this works perfectly fine. In IE and Chrome, it does not.
Here's a much-abbreviated code sample:
index.php
<?php
session_start();
//Do a ton of stuff
//Including adding several files to the resource object
//Add the resource links to the head
$smarty->append('headSection','<link href="resource.php?id=<?=$resourceID?>&type=.js" />');
</head>
//Save the resource file which:
// - Outputs the file
// - Saves a reference to it in session
$_SESSION[$resourceID] = $file;
//Let Smarty display
$smarty->display($templateFile);
?>
resource.php
<?php
readfile($_SESSION[$_GET['id']] . $_GET['type']);
?>
What it seems like to me is that FF waits for an entire page response before making any new requests to the resources required by the page, while IE and Chrome function by starting a new request the second it is encountered. Due to this, this error basically boils down to a race condition.
Can anyone confirm that this is indeed the way it works? And if so - how would I work around it?
Edit: After the update to your question, then I am not surprised that you are getting a race condition. I don't know why it is working in Firefox, but IE and Chrome are certainly not doing anything illegal by requesting the resources early. One way you could resolve this is with output buffering. At the top of your index.php file, you can add:
ob_start('ob_gzhandler');
This kills two birds with one stone, by: a) making sure that output is buffered, so the browser doesn't see the file until the whole page has been generated; and b) saving you and your users bandwidth by using gzip compression.
Previous answer: That doesn't seem to make sense. Cookies can only be set in the header, which happens before any page content is loaded. So the browser requests index.php, and the PHPSESSID cookie is set in the header. Then the page content is delivered.
I don't have access to a machine with PHP at the moment, but the following might help to test your theory. test1.php sets a session variable, but then takes 30 seconds to completely finish loading. Meanwhile, test2.php (a CSS file) will try to use that session variable as the text color. The text will show up red if the session could be read from test2, or black (default color) otherwise.
test1.php
<?php
session_start();
$_SESSION['mycolor'] = 'red';
?>
<html>
<head>
<link rel="stylesheet" href="test2.php" type="text/css" />
</head>
<body>
Starting test...<br/>
<?php
for($i = 0; $i < 6; $i++) //loop will take 30 seconds to complete
{
echo "$i<br/>\n";
sleep(5);
}
?>
Done!
</body>
</html>
test2.php
<?php
session_start();
?>
body { color: <?php echo $_SESSION['mycolor']; ?>; }
I finally figured out what was needed to fix this. For starters, Kip's suggested solution is correct, however it wasn't actually the solution to my problem as what I said was my problem wasn't actually my problem... more or less.
In one of the tests I was doing, I noticed suddenly that the SessionID was different for the page and for the resource file. I didn't have any idea how that was possible, until I remembered that in another component that I include in the page, I regenerate the SessionID (session_regenerate_id()) to prevent CSRF attacks. Once I commented out that line, everything worked perfectly in every browser.
For me however, this raises a new question... Why isn't session_regenerate_id() preserving session data?
Edit - Follow up:
It seems that this is actually a known issue and is well documented in the comments on the PHP docs page for session_regenerate_id().
Start here: http://www.php.net/manual/en/function.session-regenerate-id.php#81212 and read up.
I have a PHP application containing these files: landing.php, redirect.php, ajax.php
on a page call to landing.php, I execute a javascript code to capture certain data, and issue an AJAX POST to ajax.php which inserts them into DB. Finally php header() redirects to redirect.php
Currently the above feature is using output buffering, but the header() is executed too soon that the AJAX POST is not finished..ie, no DB query is made.
I tried using sleep() usleep() before header() but they are not working. As I am not very familiar with output buffering, would you please offer a kind hand?
Thank you.
<?php ob_start(); ?>
<scripts type="text/javascript">
var data = 'blah..blah..blah..';
ajaxPost('ajax.php', data);
</scripts>
<?php
sleep(2); // <---- I want the script to sleep here and wait for the AJAX to finish
header('c.php)
ob_end_flush();
?>
If I understood you correctly, you have a fundamental misunderstanding on how web applications work.
Your PHP script can't wait for the AJAX bit to execute, because the whole script is first run on the server, and the output -- part of which the AJAX call is -- is then sent to the browser. You have to rethink the way you're doing this.
For instance, you could have the JavaScript first make the AJAX call, and then redirect the browser.
Edit: OK, now that I've thought about this for a while more, I can see how something like this might work when you're not using output buffering, if the browser executes the script as soon as it sees it (without having the full page loaded). If that is indeed the case, then you're still relying on the browser's timing, the quality of the user's internet connection and so on to keep things in sync. That is decidedly not a good thing.
However, the only way that could work is if the AJAX call got outputted to the browser before your header call -- which is not possible! Headers need to be sent before the content in the HTTP response (which is why you're using output buffering in the first place), so either you won't output the JavaScript or the header call will fail. So I recommend you rethink your approach.