php/f02FetchServer.php

<?php

require_once('env.php');
if (isset($_GET['sleep'])) {
    if (! is_numeric($t = $_GET['sleep'])) {
        echo "sleep $t not an integer";
    } else {
        $b = date('c');
        sleep($t);
        echo "<br>$b<br>sleep $t<br>" . date('c');
    }
        
} else {
    echo 'bad inpuT $_GET ' . a2str($_GET);
}
exit;
outBegin(__file__);
$dbg=9;
$hostnm = gethostname();
out('start at ' . date('c') . ', pid ' . getmypid() . ", host $hostnm, php " . phpversion());
$prot = $hostnm === 'wk13' ? 'ws' : 'wss' . err("security policy for websocket ports on host $hostnm?????") ;
$host = $hostnm === 'wk13' ? "192.168.1.135" : 'wlkl.ch';
$port = 4321;

function sErr($t) {
    err($t . ' ' . ($c = socket_last_error()) . ' => ' . socket_strerror($c));
}

function sSend($s, $d) { # send data to socket s
    if (false === $r = socket_send($s, $d, strlen($d), 0))
        sErr("socket_send $d");
    else
        out("senttt $r bytes data", preg_replace('/[[:^print:]]/', '?', $d));
}

function wsDecode($e, &$t) { # decode  websocket message e into clear text t, return opcode, according to rfc6455 https://www.rfc-editor.org/rfc/rfc6455.html
    ($fin = ord($e[0]) >> 7) || err("implement continuation wsFin $fin");
    $op = ord($e[0]) & 0x0f;
    if(0x7e > $len = ord($e[1]) & 0x7f) {
        $o = 2;
    } elseif ($len === 0x7e) { # 16bit 
        $o = 4;
        $len = unpack('n', $e, 2)[1];
    } else { # $len === 0x7f 64bit
        $o = 10;
        $len = unpack('J', $e, 2)[1];
    }
    if (! ($isMskd = ord($e[1]) >> 7)) {
        $t = $s = substr($e, 2);
    } else {
        (strlen($e) >= $o+4) || err("wsDecode to short for mask " . bin2hex($e));
        $msk = substr($e, $o, 4);
        $s = substr($e, $o+4);
    }
    if ($len != strlen($s))
        err("strlen " . strlen($s) . " <> len $len");
    if ($isMskd) { # decode with mask
        $t = $s ^ str_repeat($msk, (3+strlen($s)) >> 2);
    }
    dbg1( "wsDecode: fin $fin, opcode $op" 
        . ($isMskd ? (", mask " . bin2hex($msk)) : ', not masked') . ", len $len"
        . " decoded $t from " . bin2hex(substr($e, 0, 20))); 
    return $op;
}

function wsEncode($op, $t) { # encode text t with opCode op  and return encoded data, according to rfc6455 https://www.rfc-editor.org/rfc/rfc6455.html
    (($op & ~ 0xf) === 0) || err("encode bad op $op");
    $r = chr(0x80 | $op);
    $r .= (($l = strlen($t)) < 0x7e) ? chr($l) : ($l <= 0xffff ? "\x7e" . pack('n', $l) : "\x7f" . pack('J', $l));
    return $r . $t;
}

function wsSend($c, $m) { # send in websocket protocoll
    sSend($c, wsEncode(1, $m));
}

function sSendHTTP($s, $ii, $m) {
    out($m = "<html><body>$m <ul><li>$ii</li><li>at " .  date('c') . "</ul></li>");
    /* $hm = "HTTP/1.1 200 OK\nDate: Mon, 27 Jul 2009 12:28:53 GMT\nServer: Apache/2.2.14 (Win32)\nLast-Modified: Wed, 22 Jul 2009 19:15:56 GMT"
            . "\nContent-Length: " . strlen($m) ."\nContent-Type: text/html\nConnection: Closed\n\n$m"; */
    sSend($s, "HTTP/1.1 200 OK\nContent-Length: " . strlen($m) ."\nContent-Type: text/html\nConnection: keep-alive\n\n$m");
}


dbg1("Creating master socket...");
if (false === $sockSrv = socket_create(AF_INET, SOCK_STREAM, SOL_TCP))
    sErr('socket_create');
$max_clients = 10;

dbg1("Setting socket options...");
socket_set_option($sockSrv, SOL_SOCKET, SO_REUSEADDR, 1) || sErr('socket_set_option');
dbg1("Binding socket host=$host, port=$port...");
socket_bind($sockSrv, $host, $port) || sErr('socket_bind');
dbg1("Listening...");
socket_listen($sockSrv, $max_clients) || sErr('socket_listen');

$sckts = [$sockSrv];
$sckII = ['sockSrv'];
dbg1('sckts', count($sckts), $sckts);
$nn = null;
while(TRUE) {
    dbg1("while ... select sckts " . count($sckts), $sckts);
    $reaS = $sckts;
    (false !== $ready = socket_select($reaS, $nn, $nn, null)) || sErr("socket_select ready=$ready");
    dbg1("socket_select returned " . $ready);
    foreach ($reaS as $k => $s) {
        if ($s !== ($sckts[$k] ?? '?none?')) {
            err("key changed $k => $s but sockets", $sckts);
            $kO = $k;
            (false === ($k = $array_search($s, $sckts, true))) && err('selected socket', $s, 'not in', $sckts);
        }
        if ($s === $sockSrv) { 
            (false !== $c = socket_accept($sockSrv)) || sErr('socket_accept');
            (false === (array_search($c, $sckts, true))) || err("accept", $c, 'already in sckts', $sckts);
            $sckts[$nx=count($sckts)] = $c;
            socket_getsockname($c, $cSH, $cSP) || sErr('socket_getsockname');
            socket_getpeername($c, $cPH, $cPP) || sErr('socket_getpeername');
            $sckII[$nx] = "socket from server $cSH:$cSP to peer $cPH:$cPP";
            if ($prot === 'http') {
                sSendHTTP($s, $sckII[$nx], 'accept connect');
            } elseif ($prot === 'ws') {
                (false === $handsh = socket_read($c, 5000)) && sErr('socket_read handshake');
                dbg1("handshake read $handsh");
                dbg1('match', preg_match('#Upgrade: +([^\r\n]*)#', $handsh, $matches), $matches);
                dbg1('match', preg_match('#Sec-WebSocket-Key: +([^\r\n]*)#', $handsh, $matches), $matches);
                dbg1("handshake sec key ". bin2hex($matches[1]) ." <{$matches[1]}>");
                $sah1 = sha1($matches[1] . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
                $rKey = base64_encode(pack('H*', $sah1));
                # sSend($c, "HTTP/1.1 101 Switching Protocols\nUpgrade: websocket\nConnection: Upgrade\nSec-WebSocket-Accept: $rKey\n\n");
                sSend($c, "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" .
                            "Upgrade: websocket\r\n" .
                            "Connection: Upgrade\r\n" .
                            "WebSocket-Origin: $host\r\n" .
                            "WebSocket-Location: ws://$host:$port\r\n".  # /deamon.php
                            "Sec-WebSocket-Accept:$rKey\r\n\r\n");
                wsSend($c, "ws connected 1 to {$sckII[$nx]}");    
            } else err("prot $prot");
        } else {
            $buf = ' ';
            socket_clear_error();
            if (false === $buf = @socket_read($s, 2048)) {
                if (SOCKET_ENOTCONN === $e = socket_last_error()) {
                    dbg1('recv buf', $buf);
                    out("endpoint not connected $sckII[$k] ===>" . ' => ' . socket_strerror($e));
                    socket_close($s);
                    $sckts[$k] = $sckts[count($sckts) -1];
                    $sckII[$k] = $sckII[count($sckts) -1];
                    array_pop($sckts);
                    array_pop($sckII);
                } else {
                    sErr('socket_recv');
                }
            } else {
                out("server received from client " . strlen($buf) . ' ' . bin2hex(substr($buf, 0, 10)), preg_replace('/[[:^print:]]/', '', $buf));
                $op = wsDecode($buf, $txt);
                if ($op === 1) { # text message
                    wsSend($s, "server received text from client $sckII[$k]: $txt"); 
                } elseif ($op === 8) { # close handshake
                    sSend($s, "\x88\x00"); # send back close handshake
                    socket_close($s);
                    dbg1("closed $sckII[$k] after closeHandshake");
                    $sckts[$k] = $sckts[count($sckts) -1];
                    $sckII[$k] = $sckII[count($sckts) -1];
                    array_pop($sckts);
                    array_pop($sckII);
               } else {
                    err("implement websocket opcode $op");
                }
            }
        }
    } #
    
    

} #while
outEnd(__file__);