-
-
Notifications
You must be signed in to change notification settings - Fork 145
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Close inactive connections #423
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ | |
use React\EventLoop\LoopInterface; | ||
use React\Http\Message\Response; | ||
use React\Http\Message\ServerRequest; | ||
use React\Http\Middleware\InactiveConnectionTimeoutMiddleware; | ||
use React\Promise; | ||
use React\Promise\CancellablePromiseInterface; | ||
use React\Promise\PromiseInterface; | ||
|
@@ -85,6 +86,7 @@ final class StreamingServer extends EventEmitter | |
private $callback; | ||
private $parser; | ||
private $loop; | ||
private $idleConnectionTimeout; | ||
|
||
/** | ||
* Creates an HTTP server that invokes the given callback for each incoming HTTP request | ||
|
@@ -96,15 +98,17 @@ final class StreamingServer extends EventEmitter | |
* | ||
* @param LoopInterface $loop | ||
* @param callable $requestHandler | ||
* @param float $idleConnectTimeout | ||
* @see self::listen() | ||
*/ | ||
public function __construct(LoopInterface $loop, $requestHandler) | ||
public function __construct(LoopInterface $loop, $requestHandler, $idleConnectTimeout = InactiveConnectionTimeoutMiddleware::DEFAULT_TIMEOUT) | ||
{ | ||
if (!\is_callable($requestHandler)) { | ||
throw new \InvalidArgumentException('Invalid request handler given'); | ||
} | ||
|
||
$this->loop = $loop; | ||
$this->idleConnectionTimeout = $idleConnectTimeout; | ||
|
||
$this->callback = $requestHandler; | ||
$this->parser = new RequestHeaderParser(); | ||
|
@@ -134,7 +138,27 @@ public function __construct(LoopInterface $loop, $requestHandler) | |
*/ | ||
public function listen(ServerInterface $socket) | ||
{ | ||
$socket->on('connection', array($this->parser, 'handle')); | ||
$socket->on('connection', array($this, 'handle')); | ||
} | ||
|
||
/** @internal */ | ||
public function handle(ConnectionInterface $conn) | ||
{ | ||
$timer = $this->loop->addTimer($this->idleConnectionTimeout, function () use ($conn) { | ||
$conn->close(); | ||
}); | ||
$loop = $this->loop; | ||
$conn->once('data', function () use ($loop, $timer) { | ||
$loop->cancelTimer($timer); | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @WyriHaximus This looks like a good starting point 👍 However, the way it's currently designed, this will only wait for some data and then wait infinitely for a complete HTTP request header. I would suggest moving this logic to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @clue Correct, that is intentional. My initial intent was to first create this PR and do it on a connection level, and then do a follow up to do it on both the connection and request level. But if you think it would be better to do both I can fold that into this PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @WyriHaximus Without knowing the complete scope of this feature I would lean towards including the timeout on the request level, but I'll leave this up to you to decide 👍 Either way I think the above snippet should be updated without the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @clue updated the PR with that change as per your suggestion. As discussed this morning I'll also file the follow-up PR taking care of the idle request closing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
$conn->on('end', function () use ($loop, $timer) { | ||
$loop->cancelTimer($timer); | ||
}); | ||
Comment on lines
+154
to
+156
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be unneeded given the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, will drop this one 👍 |
||
$conn->on('close', function () use ($loop, $timer) { | ||
$loop->cancelTimer($timer); | ||
}); | ||
|
||
$this->parser->handle($conn); | ||
} | ||
|
||
/** @internal */ | ||
|
@@ -350,7 +374,7 @@ public function handleResponse(ConnectionInterface $connection, ServerRequestInt | |
|
||
// either wait for next request over persistent connection or end connection | ||
if ($persist) { | ||
$this->parser->handle($connection); | ||
$this->handle($connection); | ||
} else { | ||
$connection->end(); | ||
} | ||
|
@@ -371,10 +395,10 @@ public function handleResponse(ConnectionInterface $connection, ServerRequestInt | |
// write streaming body and then wait for next request over persistent connection | ||
if ($persist) { | ||
$body->pipe($connection, array('end' => false)); | ||
$parser = $this->parser; | ||
$body->on('end', function () use ($connection, $parser, $body) { | ||
$that = $this; | ||
$body->on('end', function () use ($connection, $that, $body) { | ||
$connection->removeListener('close', array($body, 'close')); | ||
$parser->handle($connection); | ||
$that->handle($connection); | ||
}); | ||
} else { | ||
$body->pipe($connection); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
<?php | ||
|
||
namespace React\Http\Middleware; | ||
|
||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
use React\Http\Io\HttpBodyStream; | ||
use React\Http\Io\PauseBufferStream; | ||
use React\Promise; | ||
use React\Promise\PromiseInterface; | ||
use React\Promise\Deferred; | ||
use React\Stream\ReadableStreamInterface; | ||
|
||
/** | ||
* Closes any inactive connection after the specified amount of seconds since last activity. | ||
* | ||
* This allows you to set an alternative timeout to the default one minute (60 seconds). For example | ||
* thirteen and a half seconds: | ||
* | ||
* ```php | ||
* $http = new React\Http\HttpServer( | ||
* new React\Http\Middleware\InactiveConnectionTimeoutMiddleware(13.5), | ||
* $handler | ||
* ); | ||
* | ||
* > Internally, this class is used as a "value object" to override the default timeout of one minute. | ||
* As such it doesn't have any behavior internally, that is all in the internal "StreamingServer". | ||
*/ | ||
final class InactiveConnectionTimeoutMiddleware | ||
{ | ||
const DEFAULT_TIMEOUT = 60; | ||
|
||
/** | ||
* @var float | ||
*/ | ||
private $timeout; | ||
|
||
/** | ||
* @param float $timeout | ||
*/ | ||
public function __construct($timeout = self::DEFAULT_TIMEOUT) | ||
{ | ||
$this->timeout = $timeout; | ||
} | ||
|
||
public function __invoke(ServerRequestInterface $request, $next) | ||
{ | ||
return $next($request); | ||
} | ||
|
||
/** | ||
* @return float | ||
*/ | ||
public function getTimeout() | ||
{ | ||
return $this->timeout; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider a 60 second timeout for all headers instead