Files
michaelschiemer/src/Framework/Http/WebSocketConnection.php

167 lines
3.7 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\Http;
/**
* Repräsentiert eine aktive WebSocket-Verbindung
*/
final class WebSocketConnection
{
private bool $isConnected = true;
private array $attributes = [];
public function __construct(
private readonly string $id,
private $socket
) {}
/**
* Sendet eine Text-Nachricht
*/
public function send(string $message): bool
{
if (!$this->isConnected) {
return false;
}
try {
$frame = $this->createFrame($message, 0x1); // Text frame
return fwrite($this->socket, $frame) !== false;
} catch (\Throwable $e) {
$this->disconnect();
return false;
}
}
/**
* Sendet JSON-Daten
*/
public function sendJson(array $data): bool
{
return $this->send(json_encode($data));
}
/**
* Sendet Binärdaten
*/
public function sendBinary(string $data): bool
{
if (!$this->isConnected) {
return false;
}
try {
$frame = $this->createFrame($data, 0x2); // Binary frame
return fwrite($this->socket, $frame) !== false;
} catch (\Throwable $e) {
$this->disconnect();
return false;
}
}
/**
* Sendet einen Ping
*/
public function ping(string $data = ''): bool
{
if (!$this->isConnected) {
return false;
}
try {
$frame = $this->createFrame($data, 0x9); // Ping frame
return fwrite($this->socket, $frame) !== false;
} catch (\Throwable $e) {
$this->disconnect();
return false;
}
}
/**
* Schließt die Verbindung
*/
public function close(int $code = 1000, string $reason = ''): void
{
if (!$this->isConnected) {
return;
}
try {
$payload = pack('n', $code) . $reason;
$frame = $this->createFrame($payload, 0x8); // Close frame
fwrite($this->socket, $frame);
} catch (\Throwable $e) {
// Fehler beim Senden des Close-Frames ignorieren
}
$this->disconnect();
}
/**
* Trennt die Verbindung
*/
public function disconnect(): void
{
$this->isConnected = false;
if (is_resource($this->socket)) {
fclose($this->socket);
}
}
/**
* Erstellt einen WebSocket-Frame
*/
private function createFrame(string $payload, int $opcode): string
{
$payloadLength = strlen($payload);
$frame = '';
// Erste Byte: FIN (1) + RSV (000) + Opcode (4 bits)
$frame .= chr(0x80 | $opcode);
// Payload-Länge
if ($payloadLength < 126) {
$frame .= chr($payloadLength);
} elseif ($payloadLength < 65536) {
$frame .= chr(126) . pack('n', $payloadLength);
} else {
$frame .= chr(127) . pack('J', $payloadLength);
}
return $frame . $payload;
}
/**
* Setzt ein Attribut für diese Verbindung
*/
public function setAttribute(string $key, mixed $value): void
{
$this->attributes[$key] = $value;
}
/**
* Gibt ein Attribut zurück
*/
public function getAttribute(string $key, mixed $default = null): mixed
{
return $this->attributes[$key] ?? $default;
}
/**
* Prüft, ob die Verbindung aktiv ist
*/
public function isConnected(): bool
{
return $this->isConnected;
}
/**
* Gibt die Verbindungs-ID zurück
*/
public function getId(): string
{
return $this->id;
}
}