Skip to content
This repository has been archived by the owner on Aug 25, 2022. It is now read-only.

Add Socket.IO v2 support #152

Merged
merged 4 commits into from
May 9, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions src/Engine/SocketIO/Version1X.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,9 @@ protected function handshake()
throw new ServerConnectionFailureException;
}

$decoded = json_decode(substr($result, strpos($result, '{')), true);
$open_curly_at = strpos($result, '{');
$todecode = substr($result, $open_curly_at, strrpos($result, '}')-$open_curly_at+1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tab to space please (code style)

$decoded = json_decode($todecode, true);

if (!in_array('websocket', $decoded['upgrades'])) {
throw new UnsupportedTransportException('websocket');
Expand All @@ -193,7 +195,7 @@ private function upgradeTransport()
'transport' => static::TRANSPORT_WEBSOCKET];

$url = sprintf('/%s/?%s', trim($this->url['path'], '/'), http_build_query($query));
$key = base64_encode(sha1(uniqid(mt_rand(), true), true));
$key = base64_encode(random_bytes(20));
Copy link
Contributor

@Taluu Taluu May 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's not good, it is php7+ only (we don't have the polyfill too, and I don't want to introduce it if we can avoid it), and this lib supports 5.4+. Supporting 7+ only would be changing a lot of stuff (not that I'm against it, but if we are gonna make it to 7.0, everything should be adapted)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I meant the call to random_bytes)


$origin = '*';
$headers = isset($this->context['headers']) ? (array) $this->context['headers'] : [] ;
Expand All @@ -208,7 +210,7 @@ private function upgradeTransport()
}

$request = "GET {$url} HTTP/1.1\r\n"
. "Host: {$this->url['host']}\r\n"
. "Host: {$this->url['host']}:{$this->url['port']}\r\n"
. "Upgrade: WebSocket\r\n"
. "Connection: Upgrade\r\n"
. "Sec-WebSocket-Key: {$key}\r\n"
Expand All @@ -234,7 +236,8 @@ private function upgradeTransport()
$this->write(EngineInterface::UPGRADE);

//remove message '40' from buffer, emmiting by socket.io after receiving EngineInterface::UPGRADE
$this->read();
if ($this->options['version'] === 2)
$this->read();
}
}

191 changes: 4 additions & 187 deletions src/Engine/SocketIO/Version2X.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,213 +26,30 @@
use ElephantIO\Exception\ServerConnectionFailureException;

/**
* Implements the dialog with Socket.IO version 1.x
* Implements the dialog with Socket.IO version 2.x
*
* Based on the work of Mathieu Lallemand (@lalmat)
*
* @author Baptiste Clavié <baptiste@wisembly.com>
* @link https://tools.ietf.org/html/rfc6455#section-5.2 Websocket's RFC
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the comment is not right

class Version2X extends AbstractSocketIO
class Version2X extends Version1X
{
const TRANSPORT_POLLING = 'polling';
const TRANSPORT_WEBSOCKET = 'websocket';

/** {@inheritDoc} */
public function connect()
{
if (is_resource($this->stream)) {
return;
}

$this->handshake();

$errors = [null, null];
$host = sprintf('%s:%d', $this->url['host'], $this->url['port']);

if (true === $this->url['secured']) {
$host = 'ssl://' . $host;
}

$this->stream = stream_socket_client($host, $errors[0], $errors[1], $this->options['timeout'], STREAM_CLIENT_CONNECT, stream_context_create($this->context));

if (!is_resource($this->stream)) {
throw new SocketException($errors[0], $errors[1]);
}

stream_set_timeout($this->stream, $this->options['timeout']);

$this->upgradeTransport();
}

/** {@inheritDoc} */
public function close()
{
if (!is_resource($this->stream)) {
return;
}

$this->write(EngineInterface::CLOSE);

fclose($this->stream);
$this->stream = null;
$this->session = null;
$this->cookies = [];
}

/** {@inheritDoc} */
public function emit($event, array $args)
{
$namespace = $this->namespace;

if ('' !== $namespace) {
$namespace .= ',';
}

return $this->write(EngineInterface::MESSAGE, static::EVENT . $namespace . json_encode([$event, $args]));
}

/** {@inheritDoc} */
public function of($namespace) {
parent::of($namespace);

$this->write(EngineInterface::MESSAGE, static::CONNECT . $namespace);
}

/** {@inheritDoc} */
public function write($code, $message = null)
{
if (!is_resource($this->stream)) {
return;
}

if (!is_int($code) || 0 > $code || 6 < $code) {
throw new InvalidArgumentException('Wrong message type when trying to write on the socket');
}

$payload = new Encoder($code . $message, Encoder::OPCODE_TEXT, true);
$bytes = fwrite($this->stream, (string) $payload);

// wait a little bit of time after this message was sent
usleep((int) $this->options['wait']);

return $bytes;
}

/** {@inheritDoc} */
public function getName()
{
return 'SocketIO Version 1.X';
return 'SocketIO Version 2.X';
}

/** {@inheritDoc} */
protected function getDefaultOptions()
{
$defaults = parent::getDefaultOptions();

$defaults['version'] = 3;
$defaults['use_b64'] = false;
$defaults['transport'] = static::TRANSPORT_POLLING;
$defaults['version'] = 3;

return $defaults;
}

/** Does the handshake with the Socket.io server and populates the `session` value object */
protected function handshake()
{
if (null !== $this->session) {
return;
}

$query = ['use_b64' => $this->options['use_b64'],
'EIO' => $this->options['version'],
'transport' => $this->options['transport']];

if (isset($this->url['query'])) {
$query = array_replace($query, $this->url['query']);
}

$context = $this->context;

if (!isset($context[$this->url['secured'] ? 'ssl' : 'http'])) {
$context[$this->url['secured'] ? 'ssl' : 'http'] = [];
}

$context[$this->url['secured'] ? 'ssl' : 'http']['timeout'] = (float) $this->options['timeout'];

$url = sprintf('%s://%s:%d/%s/?%s', $this->url['scheme'], $this->url['host'], $this->url['port'], trim($this->url['path'], '/'), http_build_query($query));
$result = @file_get_contents($url, false, stream_context_create($context));

if (false === $result) {
throw new ServerConnectionFailureException;
}

$open_curly_at = strpos($result, '{');
$todecode = substr($result, $open_curly_at, strrpos($result, '}')-$open_curly_at+1);
$decoded = json_decode($todecode, true);

if (!in_array('websocket', $decoded['upgrades'])) {
throw new UnsupportedTransportException('websocket');
}

$cookies = [];
foreach ($http_response_header as $header) {
if (preg_match('/^Set-Cookie:\s*([^;]*)/i', $header, $matches)) {
$cookies[] = $matches[1];
}
}
$this->cookies = $cookies;

$this->session = new Session($decoded['sid'], $decoded['pingInterval'], $decoded['pingTimeout'], $decoded['upgrades']);
}

/** Upgrades the transport to WebSocket */
private function upgradeTransport()
{
$query = ['sid' => $this->session->id,
'EIO' => $this->options['version'],
'transport' => static::TRANSPORT_WEBSOCKET];

$url = sprintf('/%s/?%s', trim($this->url['path'], '/'), http_build_query($query));
$key = base64_encode(random_bytes(16));

$origin = '*';
$headers = isset($this->context['headers']) ? (array) $this->context['headers'] : [] ;

foreach ($headers as $header) {
$matches = [];

if (preg_match('`^Origin:\s*(.+?)$`', $header, $matches)) {
$origin = $matches[1];
break;
}
}

$request = "GET {$url} HTTP/1.1\r\n"
. "Host: {$this->url['host']}:{$this->url['port']}\r\n"
. "Upgrade: websocket\r\n"
. "Connection: Upgrade\r\n"
. "Sec-WebSocket-Key: {$key}\r\n"
. "Sec-WebSocket-Version: 13\r\n"
. "Origin: {$origin}\r\n";

if (!empty($this->cookies)) {
$request .= "Cookie: " . implode('; ', $this->cookies) . "\r\n";
}

$request .= "\r\n";

fwrite($this->stream, $request);
$result = fread($this->stream, 12);

if ('HTTP/1.1 101' !== $result) {
throw new UnexpectedValueException(sprintf('The server returned an unexpected value. Expected "HTTP/1.1 101", had "%s"', $result));
}

// cleaning up the stream
while ('' !== trim(fgets($this->stream)));

$this->write(EngineInterface::UPGRADE);
}
}