I'm trying to open a non-blocking stream in PHP (5.3.2 & 5.4.4). I do the following:
$fp = fopen($url, 'r');
if ($fp === false)
return false;
print('stream opened'.PHP_EOL);
stream_set_blocking($fp, 0);
The url points to a php file:
<?php sleep(10); ?>
<html><body>Hello</body></html>
The problem is that fopen() seems to block before I am even able to setup the stream as non blocking. Indeed, the stream opened message is printed after 10 seconds and not directly.
When doing a fopen on a url, the HTTP headers are sent at that moment. Since no context has been defiened (and it is not possible to configure contexts with the non-blocking option), fopen waits for the http headers to be sent and blocks.
A workaround is to use fsockopen which only opens the tcp connecion and does nothing more. The drawback of this approach is that the HTTP request has to be created manually.
Here is an (optimizable) implementation that reads data from an url in a non blocking way.
function parse_http_url($url)
{
$parts = parse_url($url);
if ($parts === false) return false;
if (!isset($parts['scheme']))
$parts['scheme'] = 'http';
if ($parts['scheme'] !== 'http' && $parts['scheme'] !== 'https')
return false;
if (!isset($parts['port']))
$parts['port'] = ($parts['scheme'] === 'http') ? 80 : 443;
if(!isset($parts['path']))
$parts['path'] = '/';
$parts['uri'] = $parts['path'];
if (!empty($parts['query']))
$parts['uri'] .= '?'.$parts['query'];
return $parts;
}
function url_get_contents($url, $options = null) {
if(!($url_parts = parse_http_url($url))) return false;
$timeout = intval(#$options['http']['timeout']);
if (!($fp = fsockopen($url_parts['host'], $url_parts['port'], $errno, $errstr, $timeout))) return false;
stream_set_blocking($fp, 0);
if($timeout > 0) {
stream_set_timeout($fp, $timeout);
$sleep_time = (($timeout * 1000000) / 100); # 1% of timeout in ms
$stop_time = microtime(true) + $timeout;
} else {
$sleep_time = 10000; # 10 ms
}
if (!isset($options['http']['method'])) $options['http']['method'] = 'GET';
if (!isset($options['http']['header'])) $options['http']['header'] = '';
$request = "{$options['http']['method']} {$url_parts['uri']} HTTP/1.1\r\n{$options['http']['header']}\r\n";
if (fwrite($fp, $request) === false) {
fclose($fp);
return false;
}
$content = '';
$buff_size = 4096;
do {
$rd = fread($fp, $buff_size);
if ($rd === false) {
fclose($fp);
return false;
}
$content .= $rd;
$meta = stream_get_meta_data($fp);
if ($meta['eof']) {
fclose($fp);
if(empty($content)) return false;
// HTTP headers should be separated with \r\n only but lets be safe
$content = preg_split('/\r\n|\r|\n/', $content);
$resp = explode(' ', array_shift($content));
$code = isset($resp[1]) ? intval($resp[1]) : 0;
if ($code < 200 || $code >= 300) {
$message = isset($resp[2]) ? $resp[2] : 'Unknown error';
trigger_error("Error {$code} {$message}", E_USER_WARNING);
return false;
}
// Skip headers
while (!empty($content) && array_shift($content) !== '');
return implode("\n", $content);
}
if ($meta['timed_out']) {
fclose($fp);
return false;
}
if (isset($stop_time) && microtime(true) >= $stop_time) {
fclose($fp);
return false;
}
if ($meta['unread_bytes'] === 0) {
usleep($sleep_time);
}
} while(true);
}
I create some socket with stream_socket_client(), and put them in a array named $sockets.
When I run the code, an error is coming:
Strict Standards: Only variables should be passed by reference in
D:\www\study\cl\s.php on line 63
Code:
while (count($sockets)) {
$read = $write = $sockets;
stream_select($read, $write, $e = null, $timeout); //line 63
if (count($read)) {
foreach ($read as $r) {
$id = array_search($r, $sockets);
$data = fgets($r, 1024);
echo $data;
if (strlen($data) == 0) {
if ($status[$id] == "in progress") {
$status[$id] = "failed to connect";
}
fclose($r);
unset($sockets[$id]);
} else {
$status[$id] .= $data;
}
}
foreach ($write as $w) {
$id = array_search($w, $sockets);
fwrite($w, "HEAD / HTTP/1.0rnHost: " . $host . "rnrn");
$status[$id] = "waiting for response";
}
} else {
foreach ($sockets as $id => $s) {
$status[$id] = "timed out " . $status[$id];
}
break;
}
}
This line:
stream_select($read, $write, $e = null, $timeout);
should be:
$e=null;
stream_select($read, $write, $e, $timeout);
While the first code will work, PHP will throw the E_STRICT message. I'm not sure why the PHP developers decided so.
However, you should check the return type of stream_select():
$ret = stream_select($read, $write, $e, $timeout);
if($ret === FALSE) {
die('Error: stream_select');
}
Once you are checking the return type, you can also use the silence operator #:
$ret = #stream_select($read, $write, $e = NULL, $timeout);
if($ret === FALSE) {
die('Error: stream_select');
}
Is there any way (other than checking for ping responses) to detect when a client stream (I don't know if a stream would be any different from sockets) becomes unavailable (ie there is no longer any connection but no clean disconnection was made)?
Using this code:
#!/usr/bin/env php
<?php
$socket = stream_socket_server(
'tcp://192.168.1.1:47700',
$errno,
$errstr,
STREAM_SERVER_BIND|STREAM_SERVER_LISTEN,
stream_context_create(
array(),
array()
)
);
if (!$socket) {
echo 'Could not listen on socket'."\n";
}
else {
$clients = array((int)$socket => $socket);
$last = time();
while(true) {
$read = $clients;
$write = null;
$ex = null;
stream_select(
$read,
$write,
$ex,
5
);
foreach ($read as $sock) {
if ($sock === $socket) {
echo 'Incoming on master...'."\n";
$client = stream_socket_accept(
$socket,
5
);
if ($client) {
stream_set_timeout($client, 1);
$clients[(int)$client] = $client;
}
}
else {
echo 'Incoming on client '.((int)$sock)."...\n";
$length = 1400;
$remaining = $length;
$buffer = '';
$metadata['unread_bytes'] = 0;
do {
if (feof($sock)) {
break;
}
$result = fread($sock, $length);
if ($result === false) {
break;
}
$buffer .= $result;
if (feof($sock)) {
break;
}
$continue = false;
if (strlen($result) == $length) {
$continue = true;
}
$metadata = stream_get_meta_data($sock);
if ($metadata && isset($metadata['unread_bytes']) && $metadata['unread_bytes']) {
$continue = true;
$length = $metadata['unread_bytes'];
}
} while ($continue);
if (strlen($buffer) === 0 || $buffer === false) {
echo 'Client disconnected...'."\n";
stream_socket_shutdown($sock, STREAM_SHUT_RDWR);
unset($clients[(int)$sock]);
}
else {
echo 'Received: '.$buffer."\n";
}
echo 'There are '.(count($clients) - 1).' clients'."\n";
}
}
if ($last < (time() - 5)) {
foreach ($clients as $id => $client) {
if ($client !== $socket) {
$text = 'Yippee!';
$ret = fwrite($client, $text);
if ($ret !== strlen($text)) {
echo 'There seemed to be an error sending to the client'."\n";
}
}
}
}
}
}
if ($socket) {
stream_socket_shutdown($socket, STREAM_SHUT_RDWR);
}
and a sockets client on a different computer, I can connect to the server, send and receive data, and disconnect cleanly and everything functions as expected. If, however, I pull the network connection on the client computer, nothing is detected on the server side - the server keeps on listening to the client socket, and also writes to it without any error manifesting itself.
As I understand it, calling feof($stream) will tell you if the remote socket disconnected, but I'm not absolutely certain about that. I'm using ping/pong myself while continuing to research a solution.
You need to set a socket timeout, in which case you get an error if a client does not respond in a timely fashion.
Check PHP's stream_set_timeout function:
http://www.php.net/manual/en/function.stream-set-timeout.php
Also check socket_set_option:
http://php.net/manual/en/function.socket-set-option.php
and finally, check out this great article on how to use sockets in PHP effectively:
"PHP Socket Programming, done the Right Way™"
http://christophh.net/2012/07/24/php-socket-programming/
I have a socket client that would read data from the server.
However, it does not leave the do..while loop as soon as there are no more data left to read? why is that so? Thanks
while (true)
{
$data_old=$data;
$data = file_get_contents("userInput.txt");
if($data_old != $data)
{
socket_write($socket, $data, strlen($data));
do
{
$line =#socket_read($socket,2048);
echo $line. "\n";
}
while($line != "");
}
}
I believe your problem is that the execution never leaves the while (true) loop and not the while($line != "") one, try this:
while (true)
{
$data_old = $data;
$data = file_get_contents('userInput.txt');
if ($data_old != $data)
{
socket_write($socket, $data, strlen($data));
while (true)
{
$line = #socket_read($socket, 2048);
echo $line. "\n";
if ($line == '')
{
break 2;
}
}
}
}
Is the socket is non-blocking you may also want to use socket_select() with a timeout.
i solved it using another method. By my server sending a (" ") statement to the client after all the data has been sent.
Client side will then exit that loop upon receiving the statement.
while (true)
{
$data_old=$data;
$data = file_get_contents("userInput.txt");
if($data_old != $data)
{
socket_write($socket, $data, strlen($data));
do
{
$line =#socket_read($socket,2048);
if($line != " ")
echo $line. "\n";
}
while($line != " ");
}
}
all. I have a problem retrieving certain POST values incoming to a script I have set up. The POST keys are not changeable as they come from a request from PayPal. However I need some way to get these values dynamically.
When a person purchases multiple items, I get POST values returned such as:
item_name=foo
item_name1=bar
item_name2=blah
I was thinking that I'll have to do some sort of Regular Expression to get the values, but I'm not sure about the syntax, or if it's even possible to use RegEx on a superglobal.
maybe something like:
$exp = "/item_name(d+)/";
foreach($_POST as $key => $val)
{
preg_match($exp,$key,$match);
}
As you can tell, I'm really bad with RegEx's. Any help would be greatly appreciated. :)
--EDIT--
Okay, there's two main POST keys that I need to catch. The problem is there may be any number (positive integer) after the key name:
item_name
item_name1
item_name2
item_number
item_number1
item_number2
But the ending digit can go all the way to 10, depending upon the number of products purchased.
What I need to do is grab the values for each one of these, and add each item_number to a string to be inserted into a transaction table in my database.
Perhaps something closer to:
$itms = '';
foreach($_POST as $key => $val)
{
if (strpos($key, 'item_name') === 0)
{
if($itms = '')
{
$itms = $val;
}
else
{
$itms .= ','.$val;
}
}
// don't really want to do it like this because i'd have to do it 10 times
if (strpos($key, 'item_name1') === 0)
{
if($itms = '')
{
$itms = $val;
}
else
{
$itms .= ','.$val;
}
}
}
// insert into DB after collecting all values
And yes, this is from an IPN.
--EDIT 2 --
This is what I have for my IPN listener script:
case 'ipnHandle' :
$header = '';
$retArray = array();
$req = 'cmd=_notify-validate';
foreach ($_POST as $key => $value)
{
$value = urlencode(stripslashes($value));
$req .= "&$key=$value";
}
// post back to PayPal system to validate
$header .= "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
$fp = fsockopen ('ssl://www.sandbox.paypal.com', 443, $errno, $errstr, 30);
// assign posted variables to local variables
$item_name = $_POST['item_name'];
$item_number = $_POST['item_number'];
$payment_status = $_POST['payment_status'];
$payment_amount = $_POST['mc_gross'];
$payment_currency = $_POST['mc_currency'];
$txn_id = $_POST['txn_id'];
$receiver_email = $_POST['receiver_email'];
$payer_email = $_POST['payer_email'];
if (!$fp)
{
$err_msg = 'Could not open the Paypal site to Verify Transaction';
}
else
{
fputs ($fp, $header . $req);
while (!feof($fp))
{
$res = fgets ($fp, 1024);
if (strcmp ($res, "VERIFIED") == 0)
{
if($_POST['receiver_email'] == "chaoskreator#gmail.com")
{
if($_POST['payment_status'] == 'Completed')
{
$sql = 'SELECT transaction_id FROM '.SHOP_TRANS_TABLE.' WHERE transaction_pid = '.$_POST['txn_id'];
$result = $db->sql_query($sql);
$row = $db->sql_fetchrow($result);
if($row != 0 && $row != null)
{
$id_exp = '/^item_number(\d*)$/';
$qty_exp = '/^quantity(\d*)$/';
$price_exp = '/^mc_gross(\d*)$/';
$items = array();
$qtys = array();
$prices = array();
foreach($_POST as $key => $val)
{
$match = Array();
if(preg_match($id_exp, $key, $match))
{
$items[] = $val;
}
if(preg_match($qty_exp, $key, $match))
{
$qtys[] = $val;
}
if(preg_match($price_exp, $key, $match))
{
$prices[] = $val;
}
}
}
}
}
$itmStr = implode(",", $items);
$qtyStr = implode(",", $qtys);
$priceStr = implode(",", $prices);
$data = '';
$file = "verifyLog.txt";
$fh = fopen($file, "a+");
foreach($_POST as $key => $value)
{
if($data == '')
{
$data = '['.$key.']'.$value;
}
else
{
$data .= ',['.$key.']'.$value;
}
}
$data .= "\r\n".$itmStr."\r\n".$qtyStr."\r\n".$priceStr;
fwrite($fh, $data);
fclose($fh);
}
else if (strcmp ($res, "INVALID") == 0)
{
$data = '';
$file = "failLog.txt";
$fh = fopen($file, "a+");
foreach($_POST as $value)
{
if($data == '')
{
$data = '['.$key.']'.$value;
}
else
{
$data .= ',['.$key.']'.$value;
}
}
fwrite($fh, $data);
fclose($fh);
}
}
fclose ($fp);
}
break;
And this is the response ans logged to verifyLog.txt, with no comma-delimited string appended to the end...:
[test_ipn]1,[payment_type]instant,[payment_date]20:47:04 Apr 19, 2011 PDT,[payment_status]Completed,[payer_status]verified,[first_name]John,[last_name]Smith,[payer_email]buyer#paypalsandbox.com,[payer_id]TESTBUYERID01,[business]seller#paypalsandbox.com,[receiver_email]seller#paypalsandbox.com,[receiver_id]TESTSELLERID1,[residence_country]US,[item_name]something,[item_number]AK-1234,[item_name1]somethingElse,[item_number1]1234346dfg,[quantity]1,[quantity1]1,[shipping]8.50,[tax]2.02,[mc_currency]USD,[mc_fee]0.44,[mc_gross]15.34,[mc_gross_1]12.34,[mc_handling]2.06,[mc_handling1]1.67,[mc_shipping]3.02,[mc_shipping1]1.02,[txn_type]cart,[txn_id]4420347,[notify_version]2.4,[custom]xyz123,[invoice]abc1234,[charset]windows-1252,[verify_sign]XXXXXXXXXXXX
This is drinving me nuts. lol
If you are just matching a prefix, then regular expressions are arguably overkill. A simple string comparison such as the following would allow you to identify the items
if (strpos($key, 'item_name') === 0) {
// $key starts with item_name....
}
The below code will check each one to see if it is one of the item keys, and grab the ID if so. Your example showed one that did not have the digits on it, and since I am not familiar with the Paypal stuff you're dealing with, I went ahead and set the ID to 0 when such a match is made.
<?php
$exp = '/^item_name(\d*)$/';
$values = Array();
foreach( $_POST as $key => $val )
{
$match = Array();
//If this is one of the item name variables
if( preg_match( $exp, $key, $match ) )
{
//then put it into the array of captured values
$values[] = $val;
}
}
$commaSeparatedForDb = implode( ',', $values );
// find all the matching field names
$fields_to_do = preg_grep('/^item_name\d*$/', $_POST);
// loop over them
foreach($files_to_do as $field) {
// extract what # they have
$id = substr($field, 9); // either blank, or 1,2,3,4,etc...
$name = $_POST["item_name$id"];
$number = $_POST["item_number$id"];
... do stuff ...
}
try this
$i = 0;
do {
$key = 'item_name';
if ($i > 0) {
$key .= $i; // append number to key if > 0
}
if (isset($_POST[$key])) {
echo "found $key => ".$_POST[$key];
// do something with it
}
$i++;
} while (isset($_POST[$key]));
BASED ON EDITED QUESTION ABOVE
if($_POST['payment_status'] == 'Completed')
{
$sql = 'SELECT transaction_id FROM '.SHOP_TRANS_TABLE.' WHERE transaction_pid = '.$_POST['txn_id'];
$result = $db->sql_query($sql);
// where is sql error checking, what if this sql has failed??
$row = $db->sql_fetchrow($result);
if($row != 0 && $row != null) {
// have you checked to see if the code is ever reaching this point?
// is it possible your database row doesn't exist
// this part here looks fine for extracting $_POST values
$id_exp = '/^item_number(\d*)$/';
$qty_exp = '/^quantity(\d*)$/';
$price_exp = '/^mc_gross(\d*)$/';
// maybe we should declare these arrays earlier.
// if this test fails, "if($row != 0 && $row != null) "
// you are still imploding the arrays below even though they haven't been declared
$items = array();
$qtys = array();
$prices = array();
foreach($_POST as $key => $val) {
// no need to store the matches as you aren't using them anyway
// $match = Array();
// changed 3x if statements to elseif
// this is not essential and won't effect the outcomes
// but is good practice as there is no point testing all 3 cases if first/second case already matches
if(preg_match($id_exp, $key)) {
$items[] = $val;
} elseif(preg_match($qty_exp, $key)) {
$qtys[] = $val;
} elseif (preg_match($price_exp, $key)) {
$prices[] = $val;
}
}
}
$itmStr = implode(",", $items);
$qtyStr = implode(",", $qtys);
$priceStr = implode(",", $prices);