Passed
Push — master ( e81a30...72faf3 )
by Luiz Kim
02:26
created

WebSocketServerCommand   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 88
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 15
eloc 51
c 3
b 0
f 0
dl 0
loc 88
rs 10

2 Methods

Rating   Name   Duplication   Size   Complexity  
A configure() 0 3 1
C execute() 0 77 14
1
<?php
2
3
namespace ControleOnline\Command;
4
5
use React\EventLoop\Loop;
0 ignored issues
show
Bug introduced by
The type React\EventLoop\Loop was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use React\Socket\Server;
0 ignored issues
show
Bug introduced by
The type React\Socket\Server was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7
use React\Socket\ConnectionInterface;
0 ignored issues
show
Bug introduced by
The type React\Socket\ConnectionInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use Symfony\Component\Console\Attribute\AsCommand;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Console\Attribute\AsCommand was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
9
use Symfony\Component\Console\Command\Command;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Console\Command\Command was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
use Symfony\Component\Console\Input\InputArgument;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Console\Input\InputArgument was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
11
use Symfony\Component\Console\Input\InputInterface;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Console\Input\InputInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
12
use Symfony\Component\Console\Output\OutputInterface;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Console\Output\OutputInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
13
14
#[AsCommand(
15
    name: 'websocket:start',
16
    description: 'Inicia o servidor WebSocket com ReactPHP (com tentativa de handshake)'
17
)]
18
class WebSocketServerCommand extends Command
19
{
20
    protected function configure(): void
21
    {
22
        $this->addArgument('port', InputArgument::OPTIONAL, 'Porta para o servidor WebSocket', 8080);
23
    }
24
25
    protected function execute(InputInterface $input, OutputInterface $output): int
26
    {
27
        $port = $input->getArgument('port');
28
        $output->writeln("Iniciando servidor WebSocket ReactPHP na porta {$port} (com tentativa de handshake)...");
29
30
        $loop = Loop::get();
31
        $socket = new Server("0.0.0.0:{$port}", $loop);
32
        $clients = new \SplObjectStorage();
33
34
        $socket->on('connection', function (ConnectionInterface $conn) use ($clients, $output) {
35
            $handshakeDone = false;
36
            $buffer = '';
37
38
            $conn->on('data', function ($data) use ($conn, $clients, $output, &$handshakeDone, &$buffer) {
39
                $buffer .= $data;
40
41
                if (!$handshakeDone) {
42
                    // Tenta encontrar o final dos headers HTTP (\r\n\r\n)
43
                    if (strpos($buffer, "\r\n\r\n") !== false) {
44
                        $headers = [];
45
                        $headerLines = explode("\r\n", substr($buffer, 0, strpos($buffer, "\r\n\r\n")));
46
47
                        // Analisa os headers
48
                        foreach ($headerLines as $line) {
49
                            if (strpos($line, ':') !== false) {
50
                                [$key, $value] = explode(':', $line, 2);
51
                                $headers[trim(strtolower($key))] = trim($value);
52
                            }
53
                        }
54
55
                        // Verifica se é uma requisição de upgrade WebSocket
56
                        if (isset($headers['upgrade']) && strtolower($headers['upgrade']) === 'websocket' &&
57
                            isset($headers['connection']) && strtolower($headers['connection']) === 'upgrade' &&
58
                            isset($headers['sec-websocket-key']) && isset($headers['sec-websocket-version']) && $headers['sec-websocket-version'] === '13') {
59
60
                            $secWebSocketKey = $headers['sec-websocket-key'];
61
                            $magicString = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
62
                            $hash = base64_encode(sha1($secWebSocketKey . $magicString, true));
63
                            $response = "HTTP/1.1 101 Switching Protocols\r\n";
64
                            $response .= "Upgrade: websocket\r\n";
65
                            $response .= "Connection: Upgrade\r\n";
66
                            $response .= "Sec-WebSocket-Accept: " . $hash . "\r\n\r\n";
67
68
                            $conn->write($response);
69
                            $handshakeDone = true;
70
                            $clients->attach($conn);
71
                            $output->writeln("Nova conexão WebSocket estabelecida! ({$conn->resourceId})");
72
73
                            // Remove os headers do buffer para processar dados WebSocket futuros
74
                            $buffer = substr($buffer, strpos($buffer, "\r\n\r\n") + 4);
75
76
                        } else {
77
                            $output->writeln("Requisição de handshake inválida. Fechando conexão.");
78
                            $conn->close();
79
                        }
80
                    }
81
                } else {
82
                    // Lógica para lidar com dados WebSocket (decodificação de frames, etc.) virá aqui
83
                    // Por enquanto, apenas reenvia os dados para outros clientes
84
                    foreach ($clients as $client) {
85
                        if ($client !== $conn) {
86
                            $client->write($data);
87
                        }
88
                    }
89
                }
90
            });
91
92
            $conn->on('close', function () use ($conn, $clients, $output) {
93
                $clients->detach($conn);
94
                $output->writeln("Conexão fechada! ({$conn->resourceId})");
95
            });
96
        });
97
98
        $output->writeln('Servidor WebSocket ReactPHP iniciado (com tentativa de handshake)!');
99
        $loop->run();
100
101
        return Command::SUCCESS;
102
    }
103
}