is this thing listening?
This commit is contained in:
commit
8809203953
|
@ -0,0 +1,12 @@
|
||||||
|
# FuckWebSockets
|
||||||
|
|
||||||
|
Synchronous library to create a fucking websocket server.
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
[x] Read
|
||||||
|
[ ] Write
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
WTFPL
|
|
@ -0,0 +1,690 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class fuckwebsockets{
|
||||||
|
|
||||||
|
public const op_continue = 0x00; // 0
|
||||||
|
public const op_text = 0x01; // 1
|
||||||
|
public const op_binary = 0x02; // 2
|
||||||
|
public const op_disconnect = 0x08; // 8
|
||||||
|
public const op_ping = 0x09; // 9
|
||||||
|
public const op_pong = 0x0a; // 10
|
||||||
|
|
||||||
|
public const close_normal = 1000; // 1000
|
||||||
|
public const close_going_away = 1001; // 1001
|
||||||
|
public const close_protocol = 1002; // 1002
|
||||||
|
public const close_bad_data = 1003; // 1003
|
||||||
|
public const close_no_status = 1005; // 1005
|
||||||
|
public const close_abnormal = 1006; // 1006
|
||||||
|
public const close_bad_payload = 1007; // 1007
|
||||||
|
public const close_policy = 1008; // 1008
|
||||||
|
public const close_too_big = 1009; // 1009
|
||||||
|
public const close_mis_ext = 1010; // 1010
|
||||||
|
public const close_srv_error = 1011; // 1011
|
||||||
|
public const close_tls = 1015; // 1015
|
||||||
|
|
||||||
|
public function create_server(array $options){
|
||||||
|
|
||||||
|
$this->options =
|
||||||
|
array_merge(
|
||||||
|
[
|
||||||
|
"ip" => "localhost", // ip.
|
||||||
|
"port" => 8080, // port. anything under 1000 requires root
|
||||||
|
"ip_source" => null, // "X-Forwarded-For" to get IP from that header, null for raw IP
|
||||||
|
"origin_whitelist" => [], // leave empty to allow any origins. Eg: ["http://localhost"]
|
||||||
|
"max_header_size" => 1024, // in bytes
|
||||||
|
"max_header_rcvtime" => 10, // in seconds
|
||||||
|
"max_tcp_sndtime" => 30, // in seconds
|
||||||
|
"tcp_write_size" => 4096, // in bytes
|
||||||
|
"tcp_keepalive" => true,
|
||||||
|
"ws_ping_interval" => 0, // 0 for disabled, int for ping interval
|
||||||
|
"max_ws_rcv_size" => 4096, // in bytes
|
||||||
|
"http_headers" => [] // Eg ["Host" => "localhost"]
|
||||||
|
],
|
||||||
|
$options
|
||||||
|
);
|
||||||
|
|
||||||
|
if($this->options["ip_source"] !== null){
|
||||||
|
|
||||||
|
$this->options["ip_source"] = strtolower($this->options["ip_source"]);
|
||||||
|
}
|
||||||
|
unset($options);
|
||||||
|
|
||||||
|
// define socket on all processes
|
||||||
|
$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
|
||||||
|
|
||||||
|
if(
|
||||||
|
// make it so we can reuse the address on forced close
|
||||||
|
!socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1) ||
|
||||||
|
!socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, ["sec" => $this->options["max_tcp_sndtime"], "usec" => 0]) ||
|
||||||
|
!socket_set_option($this->socket, SOL_SOCKET, SO_KEEPALIVE, $this->options === true ? 1 : 0) ||
|
||||||
|
!@socket_bind($this->socket, $this->options["ip"], $this->options["port"]) ||
|
||||||
|
!@socket_listen($this->socket, 5)
|
||||||
|
){
|
||||||
|
|
||||||
|
throw new Exception("Could not start websocket server: " . socket_strerror(socket_last_error($this->socket)));
|
||||||
|
socket_close($this->socket);
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
$pid = pcntl_fork();
|
||||||
|
|
||||||
|
if($pid === -1){
|
||||||
|
|
||||||
|
throw new Exception("Failed to create incoming connection handler process");
|
||||||
|
}
|
||||||
|
elseif($pid){
|
||||||
|
|
||||||
|
//
|
||||||
|
// parent:
|
||||||
|
// wait for incoming unix signals that correspond to application events
|
||||||
|
//
|
||||||
|
while(true){
|
||||||
|
|
||||||
|
//echo "a\n";
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}else{
|
||||||
|
|
||||||
|
//
|
||||||
|
// child:
|
||||||
|
// subprocess for handling incoming connections
|
||||||
|
//
|
||||||
|
|
||||||
|
// reap dead children
|
||||||
|
pcntl_signal(SIGCHLD, SIG_IGN);
|
||||||
|
|
||||||
|
while(true){
|
||||||
|
|
||||||
|
$this->client = new StdClass();
|
||||||
|
if(($this->client->socket = @socket_accept($this->socket)) === false){
|
||||||
|
|
||||||
|
// socket accept failed
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pid = pcntl_fork();
|
||||||
|
if($pid === -1){
|
||||||
|
|
||||||
|
echo "Warn: failed to create connection handler\n";
|
||||||
|
continue;
|
||||||
|
|
||||||
|
}elseif($pid){
|
||||||
|
|
||||||
|
// parent: loop over to socket_accept call
|
||||||
|
continue;
|
||||||
|
}else{
|
||||||
|
|
||||||
|
//
|
||||||
|
// connection handler
|
||||||
|
//
|
||||||
|
$this->client->request = new StdClass();
|
||||||
|
$this->client->request->handshake = false;
|
||||||
|
$this->client->request->headers = [];
|
||||||
|
|
||||||
|
if($this->options["ip_source"] === null){
|
||||||
|
|
||||||
|
socket_getpeername(
|
||||||
|
$this->client->socket,
|
||||||
|
$this->client->ip,
|
||||||
|
$this->client->port
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
|
||||||
|
$this->client->ip = "x.x.x.x";
|
||||||
|
$this->client->port = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->log($this->client, "TCP connect");
|
||||||
|
|
||||||
|
$buffer = null;
|
||||||
|
$len = 0;
|
||||||
|
$time_start = microtime(true) + $this->options["max_header_rcvtime"];
|
||||||
|
$newline = false;
|
||||||
|
$firstline = true;
|
||||||
|
|
||||||
|
// Parse incoming handshake init
|
||||||
|
while(true){
|
||||||
|
|
||||||
|
$timeout = $time_start - microtime(true);
|
||||||
|
|
||||||
|
if($timeout <= 0){
|
||||||
|
|
||||||
|
$this->http_disconnect($this->client, 408, "Read timeout");
|
||||||
|
}
|
||||||
|
|
||||||
|
$times = explode(".", $timeout, 2);
|
||||||
|
|
||||||
|
if(count($times) === 2){
|
||||||
|
|
||||||
|
$usec = (int)substr($times[1], 0, 6);
|
||||||
|
}else{
|
||||||
|
|
||||||
|
$usec = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// timeout after configured amount
|
||||||
|
socket_set_option(
|
||||||
|
$this->client->socket,
|
||||||
|
SOL_SOCKET,
|
||||||
|
SO_RCVTIMEO,
|
||||||
|
[
|
||||||
|
"sec" => (int)$times[0],
|
||||||
|
"usec" => $usec
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// hang till we receive data
|
||||||
|
$this->socket_recv($this->client, $bytes, 1, MSG_WAITALL);
|
||||||
|
|
||||||
|
$len++;
|
||||||
|
|
||||||
|
if($len >= $this->options["max_header_size"]){
|
||||||
|
|
||||||
|
$this->http_disconnect($this->client, 413, "Headers exceeded configured size");
|
||||||
|
}
|
||||||
|
|
||||||
|
if($bytes == "\r"){
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($bytes == "\n"){
|
||||||
|
|
||||||
|
if($newline === true){
|
||||||
|
|
||||||
|
// received all headers
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($firstline){
|
||||||
|
|
||||||
|
$firstline = false;
|
||||||
|
|
||||||
|
if(
|
||||||
|
preg_match(
|
||||||
|
'/^([A-Z]+) ([^ ]+) HTTP\/([0-9.]+)$/i',
|
||||||
|
$buffer,
|
||||||
|
$header
|
||||||
|
)
|
||||||
|
=== 0
|
||||||
|
){
|
||||||
|
|
||||||
|
$this->http_disconnect($this->client, 400, "Received malformed HTTP method");
|
||||||
|
}
|
||||||
|
|
||||||
|
$header[1] = strtoupper($header[1]);
|
||||||
|
|
||||||
|
if($header[1] !== "GET"){
|
||||||
|
|
||||||
|
$this->http_disconnect($this->client, 405, "Received invalid HTTP method " . $header[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->client->request->path = $header[2];
|
||||||
|
$this->client->request->http_version = (float)$header[3];
|
||||||
|
$buffer = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$newline = true;
|
||||||
|
$buffer = explode(":", $buffer, 2);
|
||||||
|
|
||||||
|
if(count($buffer) !== 2){
|
||||||
|
|
||||||
|
$this->http_disconnect($this->client, 400, "Received invalid header line");
|
||||||
|
}
|
||||||
|
|
||||||
|
$buffer[0] = strtolower(trim($buffer[0]));
|
||||||
|
$buffer[1] = trim($buffer[1]);
|
||||||
|
|
||||||
|
if(
|
||||||
|
strlen($buffer[0]) === 0 ||
|
||||||
|
strlen($buffer[1]) === 0
|
||||||
|
){
|
||||||
|
|
||||||
|
$this->http_disconnect($this->client, 400, "Received 0-length header name or value");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->client->request->headers[$buffer[0]] = $buffer[1];
|
||||||
|
|
||||||
|
$buffer = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$newline = false;
|
||||||
|
$buffer .= $bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanitize headers
|
||||||
|
if(
|
||||||
|
!isset($this->client->request->headers["host"]) ||
|
||||||
|
|
||||||
|
!isset($this->client->request->headers["connection"]) ||
|
||||||
|
!$this->check_header_value("upgrade", $this->client->request->headers["connection"]) ||
|
||||||
|
|
||||||
|
!isset($this->client->request->headers["upgrade"]) ||
|
||||||
|
!$this->check_header_value("websocket", $this->client->request->headers["upgrade"]) ||
|
||||||
|
|
||||||
|
!isset($this->client->request->headers["sec-websocket-version"]) ||
|
||||||
|
!$this->check_header_value("13", $this->client->request->headers["sec-websocket-version"]) ||
|
||||||
|
|
||||||
|
!isset($this->client->request->headers["sec-websocket-key"]) ||
|
||||||
|
strlen(base64_decode($this->client->request->headers["sec-websocket-key"])) !== 16
|
||||||
|
){
|
||||||
|
|
||||||
|
$this->http_disconnect(
|
||||||
|
$this->client,
|
||||||
|
405,
|
||||||
|
"Not a WebSocket client"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get IP
|
||||||
|
if($this->options["ip_source"] !== null){
|
||||||
|
|
||||||
|
if(!isset($this->client->request->headers[$this->options["ip_source"]])){
|
||||||
|
|
||||||
|
$this->http_disconnect(
|
||||||
|
$this->client,
|
||||||
|
405,
|
||||||
|
"Missing " . $this->options["ip_source"] . " header"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts =
|
||||||
|
explode(
|
||||||
|
":",
|
||||||
|
$this->client->request->headers[$this->options["ip_source"]],
|
||||||
|
2
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->client->ip = $parts[0];
|
||||||
|
|
||||||
|
if(count($parts) === 2){
|
||||||
|
|
||||||
|
$this->client->port = $parts[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check origin
|
||||||
|
if(
|
||||||
|
count($this->options["origin_whitelist"]) !== 0 &&
|
||||||
|
(
|
||||||
|
!isset($this->client->request->headers["origin"]) ||
|
||||||
|
!in_array($this->client->request->headers["origin"], $this->options["origin_whitelist"])
|
||||||
|
)
|
||||||
|
){
|
||||||
|
|
||||||
|
$this->http_disconnect(
|
||||||
|
$this->client,
|
||||||
|
403,
|
||||||
|
"Origin not allowed, got " . $this->client->request->headers["origin"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// send handshake confirmation
|
||||||
|
$this->http_respond(
|
||||||
|
$this->client,
|
||||||
|
101,
|
||||||
|
array_merge(
|
||||||
|
$this->options["http_headers"],
|
||||||
|
[
|
||||||
|
"Connection" => "Upgrade",
|
||||||
|
"Upgrade" => "websocket",
|
||||||
|
"Sec-WebSocket-Accept" =>
|
||||||
|
base64_encode(
|
||||||
|
sha1(
|
||||||
|
$this->client->request->headers["sec-websocket-key"] .
|
||||||
|
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
|
||||||
|
true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->client->request->handshake = true;
|
||||||
|
$this->log($this->client, "Handshake done");
|
||||||
|
|
||||||
|
// handle incoming messages
|
||||||
|
$buffer = null;
|
||||||
|
|
||||||
|
// remove socket timeout
|
||||||
|
socket_set_option(
|
||||||
|
$this->client->socket,
|
||||||
|
SOL_SOCKET,
|
||||||
|
SO_RCVTIMEO,
|
||||||
|
[
|
||||||
|
"sec" => 0,
|
||||||
|
"usec" => 0
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
while(true){
|
||||||
|
|
||||||
|
// decode message
|
||||||
|
$this->socket_recv($this->client, $header, 2, MSG_WAITALL);
|
||||||
|
|
||||||
|
$opcode = ord($header[0]) & 0x0F;
|
||||||
|
$finalmessage = ord($header[0]) & 0x80;
|
||||||
|
$masked = ord($header[1]) & 0x80;
|
||||||
|
$messagelength = ord($header[1]) & 0x7F;
|
||||||
|
|
||||||
|
// disconnect if frame isn't masked
|
||||||
|
if(!$masked){
|
||||||
|
|
||||||
|
$this->ws_disconnect($this->client, self::close_bad_data, "Received unmasked frame");
|
||||||
|
}
|
||||||
|
|
||||||
|
// get extension length
|
||||||
|
$extensionlength = 0;
|
||||||
|
|
||||||
|
if($messagelength >= 0x7E){
|
||||||
|
|
||||||
|
$extensionlength = 2;
|
||||||
|
|
||||||
|
if($messagelength == 0x7F){
|
||||||
|
|
||||||
|
$extensionlength = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
$messagelength = 0;
|
||||||
|
|
||||||
|
$this->socket_recv($this->client, $header, $extensionlength, MSG_WAITALL);
|
||||||
|
|
||||||
|
for($i=0; $i<$extensionlength; $i++){
|
||||||
|
|
||||||
|
$messagelength += ord($header[$i]) << ($extensionlength - $i - 1) * 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($messagelength > $this->options["max_ws_rcv_size"]){
|
||||||
|
|
||||||
|
$this->ws_disconnect($this->client, self::close_too_big, "Frame exceeded " . $this->options["max_ws_rcv_size"] . "b limit");
|
||||||
|
}
|
||||||
|
|
||||||
|
// get mask
|
||||||
|
$this->socket_recv($this->client, $mask, 4, MSG_WAITALL);
|
||||||
|
|
||||||
|
// extract data from frame
|
||||||
|
$this->socket_recv($this->client, $frame_data, $messagelength, MSG_WAITALL);
|
||||||
|
|
||||||
|
// unmask data
|
||||||
|
for($i=0; $i<strlen($frame_data); $i++){
|
||||||
|
|
||||||
|
$frame_data[$i] = $frame_data[$i] ^ $mask[$i % 4];
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle data
|
||||||
|
switch($opcode){
|
||||||
|
|
||||||
|
case self::op_text:
|
||||||
|
case self::op_binary:
|
||||||
|
if($finalmessage === false){
|
||||||
|
// partial message, come back later
|
||||||
|
|
||||||
|
if(strlen($buffer) > 0){
|
||||||
|
|
||||||
|
// message is partial but client is
|
||||||
|
// sending more op_text frames? makes no sense!
|
||||||
|
$this->ws_disconnect($this->client, self::close_protocol, "Did not send continue frame for partial message");
|
||||||
|
}
|
||||||
|
|
||||||
|
$buffer = $frame_data;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// final message
|
||||||
|
$this->log($this->client, "Text frame: {$frame_data}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case self::op_continue:
|
||||||
|
$buffer .= $frame_data;
|
||||||
|
|
||||||
|
if(strlen($buffer) > $this->options["max_ws_rcv_size"]){
|
||||||
|
|
||||||
|
$this->ws_disconnect($this->client, self::close_too_big, "Message exceeded " . $this->options["max_ws_rcv_size"] . " bytes limit");
|
||||||
|
}
|
||||||
|
|
||||||
|
if($finalmessage === true){
|
||||||
|
|
||||||
|
// we received the entire thing, trigger event
|
||||||
|
$this->log($this->client, "Text frame (buffered): {$buffer}");
|
||||||
|
$buffer = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case self::op_ping:
|
||||||
|
if(strlen($frame_data) > 128){
|
||||||
|
|
||||||
|
$this->ws_disconnect($this->client, self::close_too_big, "Ping frame exceeded 128 bytes");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->ws_send($this->client, self::op_pong, $frame_data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case self::op_pong:
|
||||||
|
$this->log($this->client, "Received pong: " . $frame_data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
case self::op_disconnect:
|
||||||
|
$code = unpack("n", $frame_data)[1];
|
||||||
|
$frame_data = substr($frame_data, 2);
|
||||||
|
|
||||||
|
$this->log($this->client, $code . ": Client disconnected. " . $frame_data);
|
||||||
|
die();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// unknown frame
|
||||||
|
$this->ws_disconnect($this->client, self::close_bad_data, "Unsupported opcode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function socket_recv(object $client, &$bytes, int $len, int $const){
|
||||||
|
|
||||||
|
$state = socket_recv($client->socket, $bytes, $len, $const);
|
||||||
|
|
||||||
|
if($state === 0){
|
||||||
|
|
||||||
|
$this->log($client, "Remote TCP close");
|
||||||
|
exit(0);
|
||||||
|
}elseif($state === false){
|
||||||
|
|
||||||
|
if($client->request->handshake === false){
|
||||||
|
|
||||||
|
$this->http_disconnect($client, 408, "Read timeout");
|
||||||
|
}else{
|
||||||
|
|
||||||
|
$this->ws_disconnect($client, self::close_no_status, "Read timeout");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function socket_write(object $client, string $bytes){
|
||||||
|
|
||||||
|
$read_ptr = 0;
|
||||||
|
$len = strlen($bytes);
|
||||||
|
|
||||||
|
while(true){
|
||||||
|
|
||||||
|
$sent =
|
||||||
|
socket_write(
|
||||||
|
$client->socket,
|
||||||
|
substr(
|
||||||
|
$bytes,
|
||||||
|
$read_ptr,
|
||||||
|
$this->options["tcp_write_size"]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if($sent === false){
|
||||||
|
|
||||||
|
$this->log("Message send failure at byte " . $read_ptr . " (closed)");
|
||||||
|
exit(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$read_ptr += $sent;
|
||||||
|
|
||||||
|
if($read_ptr === $len){
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ws_send(object $client, int $opcode, string $message){
|
||||||
|
|
||||||
|
$this->socket_write(
|
||||||
|
$client,
|
||||||
|
$this->ws_encode(
|
||||||
|
$opcode,
|
||||||
|
$message
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ws_disconnect(object $client, int $disconnect_opcode, string $message){
|
||||||
|
|
||||||
|
$this->ws_send(
|
||||||
|
$client,
|
||||||
|
self::op_disconnect,
|
||||||
|
pack("n", $disconnect_opcode) .
|
||||||
|
$message
|
||||||
|
);
|
||||||
|
|
||||||
|
socket_close($client->socket);
|
||||||
|
$this->log($client, $disconnect_opcode . ": " . $message . " (closed)");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ws_encode(int $opcode, string $message){
|
||||||
|
|
||||||
|
// 0x80 = hardcoded end msg bit=true & mask=false (8bit)
|
||||||
|
// $opcode = 1 to 4 bits
|
||||||
|
$header = 0x80 | $opcode;
|
||||||
|
$len = strlen($message);
|
||||||
|
|
||||||
|
if($len < 126){
|
||||||
|
|
||||||
|
$header = pack("CC", $header, $len);
|
||||||
|
}elseif($len < 65536){
|
||||||
|
|
||||||
|
$header = pack("CCn", $header, 126, $len); // 16bit len
|
||||||
|
}else{
|
||||||
|
|
||||||
|
$header = pack("CCJ", $header, 127, $len); // 64bit len
|
||||||
|
}
|
||||||
|
|
||||||
|
return $header . $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function log(object $client, string $message){
|
||||||
|
|
||||||
|
echo $client->ip . ":" . $client->port . "] " . $message . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function http_disconnect(object $client, int $errcode, string $message){
|
||||||
|
|
||||||
|
$this->http_respond(
|
||||||
|
$client,
|
||||||
|
$errcode,
|
||||||
|
array_merge(
|
||||||
|
$this->options["http_headers"],
|
||||||
|
[
|
||||||
|
"Content-Type" => "text/plain",
|
||||||
|
"Sec-WebSocket-Version" => "13",
|
||||||
|
"Content-Length" => strlen($message),
|
||||||
|
"Connection" => "close"
|
||||||
|
]
|
||||||
|
),
|
||||||
|
$message
|
||||||
|
);
|
||||||
|
|
||||||
|
socket_close($client->socket);
|
||||||
|
$this->log($client, $errcode . ": " . $message . " (closed)");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function http_respond(object $client, int $errcode, array $headers, string $message = null){
|
||||||
|
|
||||||
|
switch($errcode){
|
||||||
|
|
||||||
|
case 100: $errtext = "Continue"; break;
|
||||||
|
case 101: $errtext = "Switching Protocols"; break;
|
||||||
|
case 200: $errtext = "OK"; break;
|
||||||
|
case 201: $errtext = "Created"; break;
|
||||||
|
case 202: $errtext = "Accepted"; break;
|
||||||
|
case 203: $errtext = "Non-Authoritative Information"; break;
|
||||||
|
case 204: $errtext = "No Content"; break;
|
||||||
|
case 205: $errtext = "Reset Content"; break;
|
||||||
|
case 206: $errtext = "Partial Content"; break;
|
||||||
|
case 300: $errtext = "Multiple Choices"; break;
|
||||||
|
case 301: $errtext = "Moved Permanently"; break;
|
||||||
|
case 302: $errtext = "Moved Temporarily"; break;
|
||||||
|
case 303: $errtext = "See Other"; break;
|
||||||
|
case 304: $errtext = "Not Modified"; break;
|
||||||
|
case 305: $errtext = "Use Proxy"; break;
|
||||||
|
case 400: $errtext = "Bad Request"; break;
|
||||||
|
case 401: $errtext = "Unauthorized"; break;
|
||||||
|
case 402: $errtext = "Payment Required"; break;
|
||||||
|
case 403: $errtext = "Forbidden"; break;
|
||||||
|
case 404: $errtext = "Not Found"; break;
|
||||||
|
case 405: $errtext = "Method Not Allowed"; break;
|
||||||
|
case 406: $errtext = "Not Acceptable"; break;
|
||||||
|
case 407: $errtext = "Proxy Authentication Required"; break;
|
||||||
|
case 408: $errtext = "Request Time-out"; break;
|
||||||
|
case 409: $errtext = "Conflict"; break;
|
||||||
|
case 410: $errtext = "Gone"; break;
|
||||||
|
case 411: $errtext = "Length Required"; break;
|
||||||
|
case 412: $errtext = "Precondition Failed"; break;
|
||||||
|
case 413: $errtext = "Request Entity Too Large"; break;
|
||||||
|
case 414: $errtext = "Request-URI Too Large"; break;
|
||||||
|
case 415: $errtext = "Unsupported Media Type"; break;
|
||||||
|
case 500: $errtext = "Internal Server Error"; break;
|
||||||
|
case 501: $errtext = "Not Implemented"; break;
|
||||||
|
case 502: $errtext = "Bad Gateway"; break;
|
||||||
|
case 503: $errtext = "Service Unavailable"; break;
|
||||||
|
case 504: $errtext = "Gateway Time-out"; break;
|
||||||
|
case 505: $errtext = "HTTP Version not supported"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$headers_write = null;
|
||||||
|
foreach($headers as $name => $value){
|
||||||
|
|
||||||
|
$headers_write .= $name . ": " . $value . "\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->socket_write(
|
||||||
|
$client,
|
||||||
|
"HTTP/1.1 " . $errcode . " " . $errtext . "\r\n" .
|
||||||
|
$headers_write .
|
||||||
|
"\r\n" .
|
||||||
|
$message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function check_header_value(string $value, string $header){
|
||||||
|
|
||||||
|
$headers = explode(",", $header);
|
||||||
|
|
||||||
|
foreach($headers as $part){
|
||||||
|
|
||||||
|
if(trim(strtolower($part)) == $value){
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||||
|
Version 2, December 2004
|
||||||
|
|
||||||
|
Copyright (C) 2024 lolcat <will@lolcat.ca>
|
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute verbatim or modified
|
||||||
|
copies of this license document, and changing it is allowed as long
|
||||||
|
as the name is changed.
|
||||||
|
|
||||||
|
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. You just DO WHAT THE FUCK YOU WANT TO.
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
new app();
|
||||||
|
|
||||||
|
class app{
|
||||||
|
|
||||||
|
public function __construct(){
|
||||||
|
|
||||||
|
include "lib/fuckwebsockets.php";
|
||||||
|
$this->ws = new fuckwebsockets();
|
||||||
|
|
||||||
|
$this->ws->create_server(
|
||||||
|
[
|
||||||
|
"ip" => "localhost",
|
||||||
|
"port" => 8080
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onConnect($client){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onFailedConnect($source, $opcode, $reason){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onPing($client, $message){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onPong($client, $message){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onMessage($client, $opcode, $message){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onDisconnect($client, $opcode, $reason){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body{
|
||||||
|
background:#1d2021;
|
||||||
|
color:#bdae93;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page{
|
||||||
|
height:500px;
|
||||||
|
width:400px;
|
||||||
|
overflow-y:scroll;
|
||||||
|
border:1px solid #a89984;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Messages:</h1>
|
||||||
|
<div class="page"></div>
|
||||||
|
<input type="text" placeholder="Enter message" id="area" autofocus>
|
||||||
|
<script>
|
||||||
|
var page = document.getElementsByClassName("page")[0]
|
||||||
|
|
||||||
|
var host = 'ws://localhost:8080/';
|
||||||
|
var socket = new WebSocket("ws://localhost:8080");
|
||||||
|
socket.onmessage = function(e){
|
||||||
|
page.innerHTML += "<br>" + e.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onerror = function(e){
|
||||||
|
|
||||||
|
page.innerHTML += "<h3>Connection error</h3>";
|
||||||
|
}
|
||||||
|
|
||||||
|
var area = document.getElementById("area");
|
||||||
|
|
||||||
|
area.onkeyup = function(e){
|
||||||
|
|
||||||
|
if(e.key == "Enter"){
|
||||||
|
|
||||||
|
socket.send(area.value);
|
||||||
|
area.value = "";
|
||||||
|
area.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue