Reputation: 8941
I'm trying to make asynchronous HTTP POST requests from PHP server to an Nginx server. I read this very informative blog post about the issue, decided to use the technique with pfsockopen
function, and imported the code doing this written by the blog author. I include the key sections below:
private function createSocket() {
if ($this->socket_failed)
return false;
$protocol = $this->ssl() ? "ssl" : "tcp";
$host = $this->options["host"];
$port = $this->ssl() ? 443 : 80;
$timeout = $this->options["timeout"];
try {
# Open our socket to the API Server.
# Since we're try catch'ing prevent PHP logs.
$socket = @pfsockopen($protocol . "://" . $host, $port, $errno,
$errstr, $timeout);
# If we couldn't open the socket, handle the error.
if (false === $socket) {
...
return false;
}
return $socket;
} catch (Exception $e) {
...
return false;
}
}
private function makeRequest($socket, $req, $retry = true) {
$bytes_written = 0;
$bytes_total = strlen($req);
$closed = false;
# Write the request
while (!$closed && $bytes_written < $bytes_total) {
try {
# Since we're try catch'ing prevent PHP logs.
$written = @fwrite($socket, substr($req, $bytes_written));
} catch (Exception $e) {
...
$closed = true;
}
if (!isset($written) || !$written) {
$closed = true;
} else {
$bytes_written += $written;
}
}
# If the socket has been closed, attempt to retry a single time.
if ($closed) {
fclose($socket);
if ($retry) {
$socket = $this->createSocket();
if ($socket) return $this->makeRequest($socket, $req, false);
}
return false;
}
$success = true;
...
return $success;
}
It mostly works very well, but I'm observing the following problem. Usually, after about 30 seconds of inactivity, when I send a request this way, Nginx responds with a TCP RST (reset) packet, which I read indicates that the socket has been closed on the remote end. I can also see the connection statuses with netstat -na | grep 8080
and by that time, the connection PHP -> Nginx is in state CLOSE_WAIT
, while Nginx -> PHP is in FIN_WAIT2
. This guide confirms that this means the remote server wants to close the connection.
I know that the issue is caused by the connection timeout settings on the remote end, because when I increase it, connections shown by netstat
remain in state ESTABLISHED
for longer, and if PHP server decides to reuse such a connection, it works fine. However, I don't want to set connection timeout to a very high value, as I'm worried of running out of ports.
Is there any way I can detect in PHP that a connection went into a CLOSE_WAIT
state? I tried the following and none of it works:
pfsockopen
is false.$errno
variable written by pfsockopen
.pfsockopen
and fwrite
.fwrite($socket)
returns false.fflush($socket)
returns false.stream_get_meta_data($socket)['timed_out']
.Upvotes: 1
Views: 1180
Reputation: 373
AFAIK the best way is to select for read and, if readable, do an fread().
// $socket was opened via pfsockopen(...)
stream_set_blocking($socket,0);
while(1){
// the do..while(0) is a trick to break out
do{
$rs = array($socket); $ws = NULL; $es = NULL;
$ok = stream_select($rs,$ws,$es,0,100000);
if ( $ok === false ) break; // select err, close and reconn
if ( $ok <= 0 ) break 2; // select tout, conn is up/ok
$rply = fread($socket,1024); // make one/two fread()
if ( $rply === false ) break; // read err
if ( strlen($rply) == 0 ) break; // conn reset by peer
break 2; // conn is up/ok
}while(0);
// here i must reconn
fclose($socket);
$socket = pfsockopen(...);
if ( $socket ) stream_set_blocking($socket,0);
else return false;
break;
}//endwhile1
// here the conn is up/ok
// check if some in $rply to be handled...
Upvotes: 0
Reputation: 5988
That's the one-million question, from what I've read the only way to check that is doing fread($socket)
and if returned value is false there has been an error, that could be millions of things but if the remote server has closed the connection the fread
will always return false. Sorry if this disappoints you, but I think there's no other way.
Upvotes: 1