Skip to content

Commit

Permalink
Merge pull request #42 from sirn-se/v2.2-dev
Browse files Browse the repository at this point in the history
2.0 of phrity/net
  • Loading branch information
sirn-se authored Mar 8, 2024
2 parents e19db49 + c3f5229 commit 129db7c
Show file tree
Hide file tree
Showing 12 changed files with 56 additions and 69 deletions.
8 changes: 4 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,16 @@
},
"require": {
"php": "^8.0",
"phrity/net-uri": "^1.2",
"phrity/net-stream": "^1.3",
"phrity/util-errorhandler": "^1.0",
"phrity/net-uri": "^2.0",
"phrity/net-stream": "^2.0",
"phrity/util-errorhandler": "^1.1",
"psr/http-message": "^1.0 | ^2.0",
"psr/log": "^1.0 | ^2.0"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.0",
"phpunit/phpunit": "^9.0 | ^10.0 | ^11.0",
"phrity/net-mock": "1.3",
"phrity/net-mock": "^2.0",
"squizlabs/php_codesniffer": "^3.5"
}
}
10 changes: 5 additions & 5 deletions docs/Contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ Base your patch on corresponding version branch, and target that version branch

| Version | Branch | PHP | Status |
| --- | --- | --- | --- |
| [`2.2`](https://github.com/sirn-se/websocket-php/tree/2.2.0) | `v2.2-main` | `^8.1` | Future version |
| [`2.1`](https://github.com/sirn-se/websocket-php/tree/2.1.0) | `v2.1-main` | `^8.0` | Current version |
| [`2.0`](https://github.com/sirn-se/websocket-php/tree/2.0.0) | `v2.0-main` | `^8.0` | Bug fixes only |
| [`1.7`](https://github.com/sirn-se/websocket-php/tree/1.7.0) | `v1.7-master` | `^7.4\|^8.0` | Bug fixes only |
| [`2.2`](https://github.com/sirn-se/websocket-php/tree/2.2.0) | `v2.2-main` | `^8.0` | Current version |
| [`2.1`](https://github.com/sirn-se/websocket-php/tree/2.1.0) | `v2.1-main` | `^8.0` | Bug fixes only |
| [`2.0`](https://github.com/sirn-se/websocket-php/tree/2.0.0) | - | `^8.0` | Not supported |
| [`1.7`](https://github.com/sirn-se/websocket-php/tree/1.7.0) | - | `^7.4\|^8.0` | Not supported |
| [`1.6`](https://github.com/sirn-se/websocket-php/tree/1.6.0) | - | `^7.4\|^8.0` | Not supported |
| [`1.5`](https://github.com/sirn-se/websocket-php/tree/1.5.0) | - | `^7.4\|^8.0` | Not supported |
| [`1.4`](https://github.com/sirn-se/websocket-php/tree/1.4.0) | - | `^7.1` | Not supported |
Expand Down Expand Up @@ -70,7 +70,7 @@ make coverage
## Contributors

* Sören Jensen (maintainer)
* Fredrik Liljegren
* Fredrik Liljegren (orginator)
* Armen Baghumian Sankbarani
* Ruslan Bekenev
* Joshua Thijssen
Expand Down
4 changes: 2 additions & 2 deletions docs/Examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ The random client will use random options and continuously send/receive random m
Example use:
```
php examples/random_client.php --uri ws://echo.websocket.org // Connect to echo.websocket.org
php examples/random_client.php --timeout 5 --fragment_size 16 // Specify settings
php examples/random_client.php --timeout 5 --framesize 16 // Specify settings
php examples/random_client.php --debug // Use runtime debugging
```

Expand All @@ -96,6 +96,6 @@ The random server will use random options and continuously send/receive random m
Example use:
```
php examples/random_server.php --port 8080 // // Listen on port 8080
php examples/random_server.php --timeout 5 --fragment_size 16 // Specify settings
php examples/random_server.php --timeout 5 --framesize 16 // Specify settings
php examples/random_server.php --debug // Use runtime debugging
```
23 changes: 9 additions & 14 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ public function connect(): void

$host_uri = (new Uri())
->withScheme($this->socketUri->getScheme() == 'wss' ? 'ssl' : 'tcp')
->withHost($this->socketUri->getHost(Uri::IDNA))
->withHost($this->socketUri->getHost(Uri::IDN_ENCODE))
->withPort($this->socketUri->getPort(Uri::REQUIRE_PORT));

$stream = null;
Expand Down Expand Up @@ -408,7 +408,7 @@ public function connect(): void
}

if (!$this->persistent || $stream->tell() == 0) {
$response = $this->performHandshake($host_uri);
$response = $this->performHandshake($this->socketUri);
}

$this->logger->info("[client] Client connected to {$this->socketUri}");
Expand Down Expand Up @@ -464,28 +464,23 @@ public function getHandshakeResponse(): Response|null
* Perform upgrade handshake on new connections.
* @throws HandshakeException On failed handshake
*/
protected function performHandshake(Uri $host_uri): Response
protected function performHandshake(Uri $uri): Response
{
$http_uri = (new Uri())
->withPath($this->socketUri->getPath(), Uri::ABSOLUTE_PATH)
->withQuery($this->socketUri->getQuery());

// Generate the WebSocket key.
$key = $this->generateKey();

$request = new Request('GET', $http_uri);
$request = new Request('GET', $uri);

$request = $request
->withHeader('Host', $host_uri->getAuthority())
->withHeader('User-Agent', 'websocket-client-php')
->withHeader('Connection', 'Upgrade')
->withHeader('Upgrade', 'websocket')
->withHeader('Sec-WebSocket-Key', $key)
->withHeader('Sec-WebSocket-Version', '13');

// Handle basic authentication.
if ($userinfo = $this->socketUri->getUserInfo()) {
$request = $request->withHeader('authorization', 'Basic ' . base64_encode($userinfo));
if ($userinfo = $uri->getUserInfo()) {
$request = $request->withHeader('Authorization', 'Basic ' . base64_encode($userinfo));
}

// Add and override with headers.
Expand All @@ -503,7 +498,7 @@ protected function performHandshake(Uri $host_uri): Response

if (empty($response->getHeaderLine('Sec-WebSocket-Accept'))) {
throw new HandshakeException(
"Connection to '{$this->socketUri}' failed: Server sent invalid upgrade response.",
"Connection to '{$uri}' failed: Server sent invalid upgrade response.",
$response
);
}
Expand All @@ -521,7 +516,7 @@ protected function performHandshake(Uri $host_uri): Response
throw $e;
}

$this->logger->debug("[client] Handshake on {$http_uri->getPath()}");
$this->logger->debug("[client] Handshake on {$uri->getPath()}");
$this->connection->setHandshakeRequest($request);
$this->connection->setHandshakeResponse($response);

Expand All @@ -542,7 +537,7 @@ protected function generateKey(): string
}

/**
* Ensure URI insatnce to use in client.
* Ensure URI instance to use in client.
* @param UriInterface|string $uri A ws/wss-URI
* @return Uri
* @throws BadUriException On invalid URI
Expand Down
10 changes: 7 additions & 3 deletions src/Http/HttpHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,16 @@ public function pull(): MessageInterface
foreach ($headers as $header) {
$parts = explode(':', $header, 2);
if (count($parts) == 2) {
$message = $message->withAddedHeader($parts[0], $parts[1]);
if ($message->getheaderLine($parts[0]) === '') {
$message = $message->withHeader($parts[0], trim($parts[1]));
} else {
$message = $message->withAddedHeader($parts[0], trim($parts[1]));
}
}
}
if ($message instanceof Request) {
$uri = new Uri("//{$message->getHeaderLine('host')}{$path}");
$message = $message->withUri($uri);
$uri = new Uri("//{$message->getHeaderLine('Host')}{$path}");
$message = $message->withUri($uri, true);
}

return $message;
Expand Down
7 changes: 0 additions & 7 deletions src/Http/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -164,22 +164,15 @@ public function getAsArray(): array

private function handleHeader(string $name, mixed $value): void
{
// @todo: Add all available characters, these are just some of them.
if (!preg_match('|^[0-9a-zA-Z#_-]+$|', $name)) {
throw new InvalidArgumentException("'{$name}' is not a valid header field name.");
}
$value = is_array($value) ? $value : [$value];
if (empty($value)) {
throw new InvalidArgumentException("Invalid header value(s) provided.");
}
foreach ($value as $content) {
if (!is_string($content) && !is_numeric($content)) {
throw new InvalidArgumentException("Invalid header value(s) provided.");
}
$content = trim($content);
if ('' === $content) {
throw new InvalidArgumentException("Invalid header value(s) provided.");
}
$this->headers[strtolower($name)][$name][] = $content;
}
}
Expand Down
15 changes: 9 additions & 6 deletions src/Http/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ public function __construct(string $method = 'GET', UriInterface|string|null $ur
{
$this->uri = $uri instanceof Uri ? $uri : new Uri((string)$uri);
$this->method = $method;
if ($this->uri->getHost()) {
$this->headers = ['host' => ['Host' => [$this->uri->getAuthority()]]];
}
$this->headers = ['host' => ['Host' => [$this->formatHostHeader($this->uri)]]];
}

/**
Expand Down Expand Up @@ -104,9 +102,7 @@ public function withUri(UriInterface $uri, bool $preserveHost = false): self
if (isset($new->headers['host'])) {
unset($new->headers['host']);
}
if ($host = $uri->getHost()) {
$new->headers = array_merge(['host' => ['Host' => [$uri->getAuthority()]]], $new->headers);
}
$new->headers = array_merge(['host' => ['Host' => [$this->formatHostHeader($uri)]]], $new->headers);
}
return $new;
}
Expand All @@ -122,4 +118,11 @@ public function getAsArray(): array
"{$this->getMethod()} {$this->getRequestTarget()} HTTP/{$this->getProtocolVersion()}",
], parent::getAsArray());
}

private function formatHostHeader(Uri $uri): string
{
$host = $uri->getHost();
$port = $uri->getPort();
return $host && $port ? "{$host}:{$port}" : $host;
}
}
6 changes: 3 additions & 3 deletions tests/suites/client/ConfigTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public function testUriInstanceWsDefaultPort(): void
$client->setStreamFactory(new StreamFactory());

$this->expectWsClientConnect(port: 80);
$this->expectWsClientPerformHandshake('localhost:80', '/my/mock/path');
$this->expectWsClientPerformHandshake('localhost', '/my/mock/path');
$client->connect();

$this->expectSocketStreamIsConnected();
Expand All @@ -123,7 +123,7 @@ public function testUriInstanceWssDefaultPort(): void
$client->setStreamFactory(new StreamFactory());

$this->expectWsClientConnect(scheme: 'ssl', port: 443);
$this->expectWsClientPerformHandshake('localhost:443', '/my/mock/path');
$this->expectWsClientPerformHandshake('localhost', '/my/mock/path');
$client->connect();

$this->expectSocketStreamIsConnected();
Expand All @@ -141,7 +141,7 @@ public function testUriStringAuthorization(): void
$this->expectWsClientPerformHandshake(
'localhost:8000',
'/my/mock/path',
"authorization: Basic dXNlbmFtZTpwYXNzd29yZA==\r\n"
"Authorization: Basic dXNlbmFtZTpwYXNzd29yZA==\r\n"
);
$client->connect();

Expand Down
4 changes: 2 additions & 2 deletions tests/suites/http/HttpHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,14 @@ public function testPullServerRequest(): void
$this->assertInstanceOf(HttpHandler::class, $handler);

$this->expectSocketStreamReadLine()->setReturn(function () {
return "GET /a/path?a=b HTTP/1.1\r\nHost: test.com:123\r\n\r\n";
return "GET /a/path?a=b HTTP/1.1\r\nA: \r\nA: 0\r\nA: B\r\nHost: test.com:123\r\n\r\n";
});
$request = $handler->pull();
$this->assertInstanceOf(ServerRequest::class, $request);
$this->assertEquals('/a/path?a=b', $request->getRequestTarget());
$this->assertEquals('GET', $request->getMethod());
$this->assertEquals('1.1', $request->getProtocolVersion());
$this->assertEquals(['Host' => ['test.com:123']], $request->getHeaders());
$this->assertEquals(['Host' => ['test.com:123'], 'A' => ['0', 'B']], $request->getHeaders());
$this->assertTrue($request->hasHeader('Host'));
$uri = $request->getUri();
$this->assertInstanceOf(UriInterface::class, $uri);
Expand Down
24 changes: 12 additions & 12 deletions tests/suites/http/RequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,15 @@ public function testEmptyRequest(): void
$this->assertEquals('GET', $request->getMethod());
$this->assertInstanceOf(UriInterface::class, $request->getUri());
$this->assertEquals('1.1', $request->getProtocolVersion());
$this->assertEquals([], $request->getHeaders());
$this->assertEquals(['Host' => ['']], $request->getHeaders());
$this->assertFalse($request->hasHeader('none'));
$this->assertEquals([], $request->getHeader('none'));
$this->assertEquals('', $request->getHeaderLine('none'));
$this->assertInstanceOf(Stringable::class, $request);
$this->assertEquals('WebSocket\Http\Request(GET )', "{$request}");
$this->assertEquals([
'GET / HTTP/1.1',
'Host: ',
], $request->getAsArray());
}

Expand Down Expand Up @@ -239,15 +240,10 @@ public function testHeaderValueInvalidVariants(mixed $value): void

public static function provideInvalidHeaderValues(): Generator
{
yield [''];
yield [' '];
yield [['0', '']];
yield [[null]];
yield [[[0]]];
yield [[]];
}


/**
* @dataProvider provideValidHeaderValues
*/
Expand All @@ -257,15 +253,19 @@ public function testHeaderValueValidVariants(mixed $value, array $expected): voi
$request = new Request();
$request = $request->withHeader('name', $value);
$this->assertInstanceOf(Request::class, $request);
$this->assertEquals($expected, $request->getHeaders());
$this->assertEquals($expected, $request->getHeader('name'));
}

public static function provideValidHeaderValues(): Generator
{
yield ['null', ['name' => ['null']]];
yield ['0 ', ['name' => ['0']]];
yield [' 0', ['name' => ['0']]];
yield [['0', '1'], ['name' => ['0', '1']]];
yield [0, ['name' => ['0']]];
yield ['', ['']];
yield [' ', ['']];
yield [['0', ''], ['0', '']];
yield [[], []];
yield ['null', ['null']];
yield ['0 ', ['0']];
yield [' 0', ['0']];
yield [['0', '1'], ['0', '1']];
yield [0, ['0']];
}
}
11 changes: 1 addition & 10 deletions tests/suites/http/ResponseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,21 +117,12 @@ public function testWithBodyError(): void
$response->withBody($factory->createStream());
}

public function testHaederNameError(): void
public function testHeaderNameError(): void
{
$response = new Response();
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionCode(0);
$this->expectExceptionMessage("'.' is not a valid header field name.");
$response->withHeader('.', 'invaid name');
}

public function testHaederValueError(): void
{
$response = new Response();
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionCode(0);
$this->expectExceptionMessage("Invalid header value(s) provided.");
$response->withHeader('name', '');
}
}
3 changes: 2 additions & 1 deletion tests/suites/http/ServerRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public function testEmptyRequest(): void
$this->assertEquals('GET', $request->getMethod());
$this->assertInstanceOf(UriInterface::class, $request->getUri());
$this->assertEquals('1.1', $request->getProtocolVersion());
$this->assertEquals([], $request->getHeaders());
$this->assertEquals(['Host' => ['']], $request->getHeaders());
$this->assertFalse($request->hasHeader('none'));
$this->assertEquals([], $request->getHeader('none'));
$this->assertEquals('', $request->getHeaderLine('none'));
Expand All @@ -51,6 +51,7 @@ public function testEmptyRequest(): void
$this->assertEquals('WebSocket\Http\ServerRequest(GET /)', "{$request}");
$this->assertEquals([
'GET / HTTP/1.1',
'Host: ',
], $request->getAsArray());
}

Expand Down

0 comments on commit 129db7c

Please sign in to comment.