Header use in PHP - php

Pretty simple question: which one of these two PHP (version 5+) header call is the "best"?
header('Not Modified', true, 304);
header('HTTP/1.1 304 Not Modified');
I'm pretty sure the first one is the most polyvalent one, but just curious if PHP would "fix" the second one if under HTTP 1.0...
Thanks!
Edit: One of these header crashes PHP on my Web host. Follow-up question at:
PHP header() call "crashing" script with HTTP 500 error

I would use this one:
header($_SERVER['SERVER_PROTOCOL'].' 304 Not Modified', true, 304);
$_SERVER['SERVER_PROTOCOL'] contains the protocol used in the request like HTTP/1.0 or HTTP/1.1.
Edit    I have to admit that my suggestion is senseless. After a few tests I noticed that if the first parameter is a valid HTTP status line, PHP will use that status line regardless if and what second status code was given with the third parameter. And the second parameter (documentation names it replace) is useless too as there can not be multiple status lines.
So the second and third parameter in this call are just redundant:
header($_SERVER['SERVER_PROTOCOL'].' 304 Not Modified', true, 304);
Use just this instead:
header($_SERVER['SERVER_PROTOCOL'].' 304 Not Modified');

There are two things about the behaviour of the first header call that are worth pointing out:
If you provide the 3rd argument, PHP will ignore the first string argument and send the correct response for the given number. This might make the first method less prone to programmer errors.
PHP seems to respond with a HTTP/1.1 response even when the request was made with HTTP/1.0

I'd go with the second one, as the http response code argument is only supported >= PHP 4.3.0 (which could affect code portability).
I've done this many times and not come across any client that doesn't support HTTP/1.1, so unless you have a special case I shouldn't think that would ever be a problem.

I would normally go with the second example - however, when recently benchmarking an application using apachebench, we noticed ab hanging often.
After debugging, it was identified that the header in this style:
header('HTTP/1.1 304 Not Modified')
Was the culprit (Yeah, I have no idea) and after changing it to,
header('Not Modified', true, 304);
Believe it or not ab started working. Very strange but something to think about. I'll probably use the second method going forward.

For future reference the http_response_code() function is coming in 5.4:
http://www.php.net/manual/en/function.http-response-code.php
An alternative is:
if (!function_exists('http_response_code')) {
function http_response_code($code = NULL) {
if ($code !== NULL) {
switch ($code) {
case 100: $text = 'Continue'; break;
case 101: $text = 'Switching Protocols'; break;
case 200: $text = 'OK'; break;
case 201: $text = 'Created'; break;
case 202: $text = 'Accepted'; break;
case 203: $text = 'Non-Authoritative Information'; break;
case 204: $text = 'No Content'; break;
case 205: $text = 'Reset Content'; break;
case 206: $text = 'Partial Content'; break;
case 300: $text = 'Multiple Choices'; break;
case 301: $text = 'Moved Permanently'; break;
case 302: $text = 'Moved Temporarily'; break;
case 303: $text = 'See Other'; break;
case 304: $text = 'Not Modified'; break;
case 305: $text = 'Use Proxy'; break;
case 400: $text = 'Bad Request'; break;
case 401: $text = 'Unauthorized'; break;
case 402: $text = 'Payment Required'; break;
case 403: $text = 'Forbidden'; break;
case 404: $text = 'Not Found'; break;
case 405: $text = 'Method Not Allowed'; break;
case 406: $text = 'Not Acceptable'; break;
case 407: $text = 'Proxy Authentication Required'; break;
case 408: $text = 'Request Time-out'; break;
case 409: $text = 'Conflict'; break;
case 410: $text = 'Gone'; break;
case 411: $text = 'Length Required'; break;
case 412: $text = 'Precondition Failed'; break;
case 413: $text = 'Request Entity Too Large'; break;
case 414: $text = 'Request-URI Too Large'; break;
case 415: $text = 'Unsupported Media Type'; break;
case 500: $text = 'Internal Server Error'; break;
case 501: $text = 'Not Implemented'; break;
case 502: $text = 'Bad Gateway'; break;
case 503: $text = 'Service Unavailable'; break;
case 504: $text = 'Gateway Time-out'; break;
case 505: $text = 'HTTP Version not supported'; break;
default:
exit('Unknown http status code "' . htmlentities($code) . '"');
break;
}
$protocol = (isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0');
header($protocol . ' ' . $code . ' ' . $text);
$GLOBALS['http_response_code'] = $code;
} else {
$code = (isset($GLOBALS['http_response_code']) ? $GLOBALS['http_response_code'] : 200);
}
return $code;
}
}
In this example I am using $GLOBALS, but you can use whatever storage mechanism you like... I don't think there is a way to return the current status code:
https://bugs.php.net/bug.php?id=52555
For reference the error codes I got from PHP's source code:
http://lxr.php.net/opengrok/xref/PHP_5_4/sapi/cgi/cgi_main.c#354
And how the current http header is sent, with the variables it uses:
http://lxr.php.net/opengrok/xref/PHP_5_4/main/SAPI.c#856

I think Gumbo's answer is the most sensible so far. However try this:
<?php
header('Gobbledy Gook', true, 304);
?>
If the first string is not a proper header it is discarded. If iy does look like a valid header it is appended to the headers - try this:
<?php
header('Cache-Control: max-age=10', true, 304);
?>
The manual for header() and note the special cases - in general I think its not advisable to rely on such built-in heuristics.
However, I'm guessing that your actually interested in getting the content cached well by proxies/browsers. In most instances, latency is far more of a problem of a problem than bandwidth. Next consider how a browser behaves when cached content is stale - in the absence of updated caching information, it keeps making repeated requests to the server to see if the content is still stale.
I.e. in most cases ignoring the conditional part of requests (or even better stripping them on the webserver) actually improves performance.

Related

How to use match expression instead of switch expression

We can use match expression instead of switch case in PHP 8.
How to write match expression correctly for the following switch case?
switch($statusCode) {
case 200:
case 300:
$message = null;
break;
case 400:
$message = 'not found';
break;
case 500:
$message = 'server error';
break;
default:
$message = 'unknown status code';
break;
}
There is an important thing that must remember with match. It is type sensitive, not as a switch statement. Thus it's very important to cast the variable properly. In the case of HTTP codes often it is sent in string format, e.g. "400".
It may give a lot of pain during debugging when we don't know about it. If $statusCode was a string, the default option would be always invoked. My modified version of an accepted answer:
$message = match((int) $statusCode) {
200, 300 => null,
400 => 'not found',
500 => 'server error',
default => 'unknown status code',
};

JSON encode gives "malformed json" error on a file that is completely valid JSON

So, i'm creating php page that sends JSON data to other app. I got the code working, but then it mysteriosly stopped working. I'm getting malformed JSON error, even though JSON validator says my file is fine. When i try other JSON file it works fine.
Here's the JSON:
[{
"value1": "Example",
"value2": "Other example"
}]
And here's the PHP (don't mind the Finnish variable names):
$url = "velat.json";
$velat_json = file_get_contents($url);
$velatarray = array();
$velat = json_decode($velat_json);
switch (json_last_error()) {
case JSON_ERROR_NONE:
echo ' - No errors';
break;
case JSON_ERROR_DEPTH:
echo ' - Maximum stack depth exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
echo ' - Underflow or the modes mismatch';
break;
case JSON_ERROR_CTRL_CHAR:
echo ' - Unexpected control character found';
break;
case JSON_ERROR_SYNTAX:
echo ' - Syntax error, malformed JSON';
break;
case JSON_ERROR_UTF8:
echo ' - Malformed UTF-8 characters, possibly incorrectly encoded';
break;
default:
echo ' - Unknown error';
break;
}
foreach($velat as $velka){
$velatarray[] = $velka;
}
header("Access-Control-Allow-Origin: *"); //CORS
header("Content-type: application/json");
print json_encode($velatarray, JSON_PRETTY_PRINT);
What's really weird is that it USED to work. I added feature that allowed me to add new items to JSON file via angular app and even that worked. Then i deleted the added item manually and tried encoding it in utf-8 and suddenly it stopped working, even after i undid all changes and reversed it back to bare bones. How is this happening?
Edit: fixed, BOM was causing the issue

Can't read JSON using file_get_contents

I'm getting null (blank page) from this:
api.php code:
$json_data = array(
'status' => 'failed',
'version' => '0.0.5'
);
header("Content-type: application/json");
echo json_encode($json_data, JSON_PRETTY_PRINT);
getapi.php code:
$json_file = file_get_contents("http://domain.com/api.php");
$json_data = json_decode($json_file);
print_r($json_data);
$json_file = file_get_contents("http://domain.com/api.php");
echo $json_file; //{"status":"failed","version":"0.0.5"}
$json_data = json_decode($json_file);
print_r($json_data); // blank page
Another answer I would like to submit is the one of trying to debug your json string since you have already told us that the content is pulled in correctly by your file_get_contents()
I have the following script from the PHP.net documentation on json_last_error()
echo 'Decoding: ' . $string;
json_decode($json_file);
switch (json_last_error()) {
case JSON_ERROR_NONE:
echo ' - No errors';
break;
case JSON_ERROR_DEPTH:
echo ' - Maximum stack depth exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
echo ' - Underflow or the modes mismatch';
break;
case JSON_ERROR_CTRL_CHAR:
echo ' - Unexpected control character found';
break;
case JSON_ERROR_SYNTAX:
echo ' - Syntax error, malformed JSON';
break;
case JSON_ERROR_UTF8:
echo ' - Malformed UTF-8 characters, possibly incorrectly encoded';
break;
default:
echo ' - Unknown error';
break;
}
echo PHP_EOL;
Edit
It looks like you are having trouble with your character set. Try forcing utf-8 as follows.
Change:
header("Content-type: application/json");
To:
header('Content-Type: application/json; charset=utf-8');
The problem is probably with your PHP.ini file and your PHP settings not allowing you to acces remote urls.
The setting you are looking for is allow_url_fopen.
You have two ways of getting around it without changing php.ini, one of them is to use fsockopen(), and the other is to use cURL.
If you have access to your php.ini file though, I would suggest editing it.
Some reading
Documentation on allow_url_fopen
By the first you should be sure that your api.php is available through HTTP.
open URL http://domain.com/api.php in some browser to check, you must see the available response.
If you still see blank page then probably your webserver is not configured properly or api.php file store in not public folder aka. public_html or web.
Run following command:
$ php -i|grep 'allow_url_fopen'
allow_url_fopen => On => On
Also please check your PHP configuration, option allow-url-fopen should be set to true.
Also please pay attention that constant JSON_PRETTY_PRINT available since PHP 5.4.0
Next snippet will help you to troubleshoot, place it on the top of your script, but don't forget to remove them after:
error_reporting(E_ALL);
ini_set('display_errors', true);
ini_set('display_startup_errors', true);
Try to use function above instead json_decode, it include utf8 fix inside
function parseJson($content, $options = true)
{
$content = fixUtf8String(trim($content));
$decoded = json_decode(utf8_decode($content), $options);
if (($jsonLastErr = json_last_error()) != JSON_ERROR_NONE) {
switch ($jsonLastErr) {
case JSON_ERROR_DEPTH:
throw new Exception('JSON Decoding failed: Maximum stack depth exceeded');
case JSON_ERROR_CTRL_CHAR:
throw new Exception('JSON Decoding failed: Unexpected control character found');
case JSON_ERROR_SYNTAX:
throw new Exception('JSON Decoding failed: Syntax error');
default:
throw new Exception('JSON Decoding failed');
}
}
return $decoded;
}
function fixUtf8String($s)
{
if (empty($s)) {
return $s;
}
$s = preg_match_all(
"#[\x09\x0A\x0D\x20-\x7E]|
[\xC2-\xDF][\x80-\xBF]|
\xE0[\xA0-\xBF][\x80-\xBF]|
[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|
\xED[\x80-\x9F][\x80-\xBF]#x", $s, $m
);
return implode("", $m[0]);
}

Unit testing of json encoding/decoding against errors

So basically I have the code which is giving me the message from json_last_error():
$msg = 'Unknown error';
switch (json_last_error()) {
case JSON_ERROR_NONE:
$msg = null;
break;
case JSON_ERROR_DEPTH:
$msg = 'Maximum stack depth exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
$msg = 'Underflow or the modes mismatch';
break;
case JSON_ERROR_CTRL_CHAR:
$msg = 'Unexpected control character found';
break;
case JSON_ERROR_SYNTAX:
$msg = 'Syntax error, malformed JSON';
break;
case JSON_ERROR_UTF8:
$msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
break;
}
return $msg;
For testing purposes I want to raise all the errors from this list to have 100% coverage, but I can't raise JSON_ERROR_STATE_MISMATCH.
Can anyone help giving me the example with either encoding or decoding, with any parameters, which can produce this error?
$j = '{"j": 1 ] }';
json_decode($j);
var_dump(json_last_error() === JSON_ERROR_STATE_MISMATCH); // true
How I found it: just checked the source code :-)

Can I catch exit() and die() messages?

I'd like to be able to catch die() and exit() messages. Is this possible? I'm hoping for something similar to set_error_handler and set_exception_handler. I've looked at register_shutdown_function() but it seems to contain no context for the offending die() and exit() calls.
I realize that die() and exit() are bad ways to handle errors. I am not looking to be told not to do this. :) I am creating a generic system and want to be able to gracefully log exit() and die() if for some reason someone (not me) decides this is a good idea to do.
Yes you can, but you need ob_start, ob_get_contents, ob_end_clean and register_shutdown_function
function onDie(){
$message = ob_get_contents(); // Capture 'Doh'
ob_end_clean(); // Cleans output buffer
callWhateverYouWant();
}
register_shutdown_function('onDie');
//...
ob_start(); // You need this to turn on output buffering before using die/exit
#$dumbVar = 1000/0 or die('Doh'); // "#" prevent warning/error from php
//...
ob_end_clean(); // Remember clean your buffer before you need to use echo/print
According to the PHP manual, shutdown functions should still be notified when die() or exit() is called.
Shutdown functions and object destructors will always be executed even if exit() is called.
It doesn't seem to be possible to get the status sent in exit($status). Unless you can use output buffering to capture it, but I'm not sure how you'd know when to call ob_start().
Maybe override_function() could be interesting, if APD is available
As best as I can tell this is not really possible. Some of the solutions posted here may work but they require a lot of additional work or many dependencies. There is no way to easily and reliable trap the die() and exit() messages.
Why do not use custom error handling instead? If not, you could always use LD_PRELOAD and C Code injection to catch it :) Or recompile php with your customizations :P
If you use the single point of entry method. (index.php) I can recommend this for your error handling:
Short version:
ob_start();
register_shutdown_function('shutdownHandler');
include('something');
define(CLEAN_EXIT, true);
function shutdownHandler() {
if(!defined("CLEAN_EXIT") || !CLEAN_EXIT) {
$msg = "Script stopped unexpectedly: ".ob_get_contents();
//Handle premature die()/exit() here
}
}
Additional steps and more detailed:
Roughly my way of doing it. I have even more going on than I show here (handling database transactions/rollback/sending e-mails/writing logs/displaying friendly error messages/user error reporting/etc), but this is the basic idea behind all of it).
Hope it helps someone.
<?php
//Some initialization
//starting output buffering. (fatalErrorHandler is optional, but I recommend using it)
ob_start('fatalErrorHandler');
//Execute code right at the end. Catch exit() and die() here. But also all other terminations inside PHPs control
register_shutdown_function('shutdownHandler');
//handling other errors: Also optional
set_error_handler('errorHandler');
try {
//Include of offensive code
include(...);
}
catch (Exception $ex) {
//Handling exception. Be careful to not raise exceptions here again. As you can end up in a cycle.
}
//Code reached this point, so it was a clean exit.
define(CLEAN_EXIT, true);
//Gets called when the script engine shuts down.
function shutdownHandler() {
$status = connection_status();
$statusText = "";
switch ($status) {
case 0:
if (!defined("CLEAN_EXIT") || !CLEAN_EXIT) {
$msg = "Script stopped unexpectedly: ".ob_get_contents();
//Handle premature die()/exit() here
}
else {
//Clean exit. Just return
return;
}
case 1: $statusText = "ABORTED (1)"; break;
case 2: $statusText = "TIMEOUT (2)"; break;
case 3: $statusText = "ABORTED & TIMEOUT (3)"; break;
default : $statusText = "UNKNOWN ($status)"; break;
}
//Handle other exit variants saved in $statusText here ob_get_contents() can have additional useful information here
}
// error handler function (This is optional in your case)
function errorHandler($errno, $errstr, $errfile, $errline) {
$msg = "[$errno] $errstr\nOn line $errline in file $errfile";
switch ($errno) {
case E_ERROR: $msg = "[E_ERROR] ".$msg; break;
case E_WARNING: $msg = "[E_WARNING] ".$msg; break;
case E_PARSE: $msg = "[E_PARSE] ".$msg; break;
case E_NOTICE: $msg = "[E_NOTICE] ".$msg; break;
case E_CORE_ERROR: $msg = "[E_CORE_ERROR] ".$msg; break;
case E_CORE_WARNING: $msg = "[E_CORE_WARNING] ".$msg; break;
case E_COMPILE_ERROR: $msg = "[E_COMPILE_ERROR] ".$msg; break;
case E_COMPILE_WARNING: $msg = "[E_COMPILE_WARNING] ".$msg; break;
case E_USER_ERROR: $msg = "[E_USER_ERROR] ".$msg; break;
case E_USER_WARNING: $msg = "[E_USER_WARNING] ".$msg; break;
case E_USER_NOTICE: $msg = "[E_USER_NOTICE] ".$msg; break;
case E_STRICT: $msg = "[E_STRICT] ".$msg; break;
case E_RECOVERABLE_ERROR: $msg = "[E_RECOVERABLE_ERROR] ".$msg; break;
case E_DEPRECATED: $msg = "[E_DEPRECIATED] ".$msg; break;
case E_USER_DEPRICIATED: $msg = "[E_USER_DEPRICIATED] ".$msg; break;
default: $msg = "[UNKNOWN] ".$msg; break;
}
//Handle Normal error/notice/warning here.
$handled = ...
if ($handled)
return true; //handled. Proceed execution
else
throw Exception($msg); //Be careful. this might quickly become cyclic. Be sure to have code that catches and handles exceptions. Else die() here after logging/reporting the error.
}
function fatalErrorHandler(&$buffer) {
$matches = null;
//Checking if the output contains a fatal error
if (preg_match('/<br \/>\s*<b>([^<>].*)error<\/b>:(.*)<br \/>$/', $buffer, $matches) ) {
$msg = preg_replace('/<.*?>/','',$matches[2]);
//Handle Fatal error here
return "There was an unexpected situation that resulted in an error. We have been informed and will look into it."
}
//No fatal exception. Return buffer and continue
return $buffer;
}
Catching exits is useful in automated tests. The way I do it is I throw a special runtime exception instead of calling exit directly.
<?php
class CatchableExit extends RuntimeException
{
}
class Quitter
{
public function run($i)
{
echo "Quitter called with \$i = $i \n";
throw new CatchableExit();
}
}
class Runner
{
public function run()
{
trigger_error('I am a harmless warning', E_USER_WARNING);
trigger_error('And I am a notice', E_USER_NOTICE);
for ($i = 0; $i < 10; $i++) {
$q = new Quitter();
try {
$q->run($i);
} catch (CatchableExit $e) {
}
}
}
}
function exception_handler(Throwable $exception)
{
if ($exception instanceof CatchableExit) {
exit();
}
}
set_exception_handler('exception_handler');
$runner = new Runner();
$runner->run();
yes: write a function and use that instead.
function kill($msg){
// Do your logging..
exit($msg);
}

Categories