Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions src/SocketHttpServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ private function handleClient(
try {
$driver->handleClient($client, $socket, $socket);
} finally {
$client->close();
unset($this->drivers[$id]);
}
} catch (\Throwable $exception) {
Expand Down
104 changes: 104 additions & 0 deletions test/SocketHttpServerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php declare(strict_types=1);

namespace Amp\Http\Server\Test;

use Amp\ByteStream\ReadableStream;
use Amp\ByteStream\WritableStream;
use Amp\Http\HttpStatus;
use Amp\Http\Server\DefaultErrorHandler;
use Amp\Http\Server\Driver\Client;
use Amp\Http\Server\Driver\HttpDriver;
use Amp\Http\Server\Driver\HttpDriverFactory;
use Amp\Http\Server\Driver\SocketClientFactory;
use Amp\Http\Server\ErrorHandler;
use Amp\Http\Server\RequestHandler;
use Amp\Http\Server\RequestHandler\ClosureRequestHandler;
use Amp\Http\Server\Response;
use Amp\Http\Server\SocketHttpServer;
use Amp\PHPUnit\AsyncTestCase;
use Amp\Socket;
use Psr\Log\NullLogger;
use function Amp\delay;

class SocketHttpServerTest extends AsyncTestCase
{
public function testClientIsClosedWhenDriverReturns(): void
{
$driver = new class implements HttpDriver {
public ?Client $client = null;

public function handleClient(
Client $client,
ReadableStream $readableStream,
WritableStream $writableStream,
): void {
$this->client = $client;
// Return normally, as a driver does when the connection ends
// without throwing.
}

public function getPendingRequestCount(): int
{
return 0;
}

public function getApplicationLayerProtocols(): array
{
return [];
}

public function stop(): void
{
}
};

$driverFactory = new class($driver) implements HttpDriverFactory {
public function __construct(private readonly HttpDriver $driver)
{
}

public function createHttpDriver(
RequestHandler $requestHandler,
ErrorHandler $errorHandler,
Client $client,
): HttpDriver {
return $this->driver;
}

public function getApplicationLayerProtocols(): array
{
return [];
}
};

$server = new SocketHttpServer(
new NullLogger(),
new Socket\ResourceServerSocketFactory(),
new SocketClientFactory(new NullLogger()),
httpDriverFactory: $driverFactory,
);

$server->expose(Socket\SocketAddress\fromString('127.0.0.1:0'));
$server->start(
new ClosureRequestHandler(fn () => new Response(HttpStatus::OK)),
new DefaultErrorHandler(),
);

try {
$client = Socket\connect($server->getServers()[0]->getAddress()->toString());
$client->write("GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
delay(0.1);
$client->close();
delay(0.1);

self::assertNotNull($driver->client, 'Driver did not receive a client');
self::assertTrue(
$driver->client->isClosed(),
'Client was not closed after the driver returned; its socket and '
. 'event-loop watcher leak for the lifetime of the process',
);
} finally {
$server->stop();
}
}
}
Loading