Skip to content

Commit 82045bd

Browse files
committed
Introduce middlewares
1 parent 9d6ac45 commit 82045bd

12 files changed

+713
-3
lines changed

readme.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
This is a library that allow you to build a [RTSP/1.0](https://www.ietf.org/rfc/rfc2326.txt) server.
33

44
# Examples
5+
6+
## Bare minimum server
7+
58
This is a very minimal server that will
69
* dump requests
710
* reply with 200 OK (which is not very useful :-)):
@@ -32,6 +35,59 @@ echo "Open any decent video player (e.g. vlc, mpv) and open rtsp://localhost:554
3235
$loop->run();
3336
```
3437

38+
## Server with middleware stack
39+
This is almost the same example as the previous one, but with a more flexible and trendy middleware stack.
40+
41+
`phpbg/rtsp` comes with some handy middlewares that will help you build a rfc compliant application:
42+
* `AutoCseq`: automatically add cseq header to your responses
43+
* `AutoContentLength`: automatically add content-length to your responses
44+
45+
```php
46+
require __DIR__ . '/../vendor/autoload.php';
47+
48+
$loop = React\EventLoop\Factory::create();
49+
50+
//Normal port is 554 but we use 5540 instead to avoid root required
51+
$socket = new \React\Socket\TcpServer('tcp://0.0.0.0:5540', $loop);
52+
53+
$middlewares = [
54+
function (\PhpBg\Rtsp\Message\Request $request, \React\Socket\ConnectionInterface $connection, $next) {
55+
// echo request middleware
56+
echo $request;
57+
return $next($request, $connection);
58+
},
59+
function (\PhpBg\Rtsp\Message\Request $request, \React\Socket\ConnectionInterface $connection, $next) {
60+
// echo response middleware
61+
$response = $next($request, $connection);
62+
if (!($response instanceof \React\Promise\PromiseInterface)) {
63+
$response = new \React\Promise\FulfilledPromise($response);
64+
}
65+
return $response->then(function($resolvedResponse) {
66+
echo $resolvedResponse."\r\n";
67+
return $resolvedResponse;
68+
});
69+
},
70+
new \PhpBg\Rtsp\Middleware\AutoCseq(),
71+
new \PhpBg\Rtsp\Middleware\AutoContentLength(),
72+
function (\PhpBg\Rtsp\Message\Request $request, \React\Socket\ConnectionInterface $connection) {
73+
// 200 OK response middleware
74+
return \PhpBg\Rtsp\Message\MessageFactory::response();
75+
}
76+
];
77+
78+
$server = new \PhpBg\Rtsp\Server(new \PhpBg\Rtsp\Middleware\MiddlewareStack($middlewares));
79+
80+
$server->on('error', function (\Exception $e) {
81+
echo $e->getMessage() . "\r\n";
82+
echo $e->getTraceAsString() . "\r\n";
83+
});
84+
85+
$server->listen($socket);
86+
echo "Server started\r\n";
87+
echo "Open any decent video player (e.g. vlc, mpv) and open rtsp://localhost:5540\r\n";
88+
$loop->run();
89+
```
90+
3591
# Install
3692

3793
```
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
/**
4+
* MIT License
5+
*
6+
* Copyright (c) 2018 Samuel CHEMLA
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in all
16+
* copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24+
* SOFTWARE.
25+
*/
26+
27+
namespace PhpBg\Rtsp\Middleware;
28+
29+
use PhpBg\Rtsp\Message\Request;
30+
use PhpBg\Rtsp\Message\Response;
31+
use React\Promise\FulfilledPromise;
32+
use React\Promise\PromiseInterface;
33+
use React\Socket\ConnectionInterface;
34+
35+
/**
36+
* Middleware that automatically add content-length header to responses that contains a body.
37+
* As explained in https://www.ietf.org/rfc/rfc2326.txt, the presence of a body implies
38+
* * either you close the connection after the body, to let the receiver detect the end of the body
39+
* * either you set a content-length header: this is what this middleware does
40+
*
41+
* If no body is present then this header won't be added because its value is implicitly 0.
42+
*/
43+
class AutoContentLength
44+
{
45+
46+
/**
47+
* This middleware just looks like a standard middleware: invokable, receive request and return response
48+
*
49+
* @param Request $request
50+
* @param ConnectionInterface $connection
51+
* @param callable $next
52+
* @return Response|PromiseInterface
53+
*/
54+
public function __invoke(Request $request, ConnectionInterface $connection, callable $next)
55+
{
56+
$response = $next($request, $connection);
57+
if (!($response instanceof PromiseInterface)) {
58+
$response = new FulfilledPromise($response);
59+
}
60+
return $response->then(function (Response $resolvedResponse) use ($request) {
61+
if (isset($resolvedResponse->body) && !$resolvedResponse->hasHeader('content-length')) {
62+
$resolvedResponse->setHeader('content-length', strlen($resolvedResponse->body));
63+
}
64+
return $resolvedResponse;
65+
});
66+
}
67+
}

src/Middleware/AutoCseq.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
/**
4+
* MIT License
5+
*
6+
* Copyright (c) 2018 Samuel CHEMLA
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in all
16+
* copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24+
* SOFTWARE.
25+
*/
26+
27+
namespace PhpBg\Rtsp\Middleware;
28+
29+
use PhpBg\Rtsp\Message\Request;
30+
use PhpBg\Rtsp\Message\Response;
31+
use React\Promise\FulfilledPromise;
32+
use React\Promise\PromiseInterface;
33+
use React\Socket\ConnectionInterface;
34+
35+
/**
36+
* Middleware that automatically add cseq header to responses as required by https://www.ietf.org/rfc/rfc2326.txt
37+
*/
38+
class AutoCseq
39+
{
40+
41+
/**
42+
* This middleware just looks like a standard middleware: invokable, receive request and return response
43+
*
44+
* @param Request $request
45+
* @param ConnectionInterface $connection
46+
* @param callable $next
47+
* @return Response|PromiseInterface
48+
*/
49+
public function __invoke(Request $request, ConnectionInterface $connection, callable $next)
50+
{
51+
$response = $next($request, $connection);
52+
if (!($response instanceof PromiseInterface)) {
53+
$response = new FulfilledPromise($response);
54+
}
55+
return $response->then(function (Response $resolvedResponse) use ($request) {
56+
if (!$resolvedResponse->hasHeader('cseq') && $request->hasHeader('cseq')) {
57+
$resolvedResponse->setHeader('cseq', $request->getHeader('cseq'));
58+
}
59+
return $resolvedResponse;
60+
});
61+
}
62+
}

src/Middleware/MiddlewareStack.php

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
3+
/**
4+
* MIT License
5+
*
6+
* Copyright (c) 2018 Samuel CHEMLA
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in all
16+
* copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24+
* SOFTWARE.
25+
*/
26+
27+
namespace PhpBg\Rtsp\Middleware;
28+
29+
use PhpBg\Rtsp\Message\Request;
30+
use PhpBg\Rtsp\Message\Response;
31+
use React\Promise\PromiseInterface;
32+
use React\Socket\ConnectionInterface;
33+
34+
/**
35+
* Middleware that run a stack of middlewares
36+
*/
37+
class MiddlewareStack
38+
{
39+
/**
40+
* @var callable[]
41+
*/
42+
protected $middlewares;
43+
44+
/**
45+
* @param callable[] $middlewares Stack of middlewares you want to run
46+
*/
47+
public function __construct(array $middlewares)
48+
{
49+
if (empty($middlewares)) {
50+
throw new \RuntimeException('No middleware to run');
51+
}
52+
$this->middlewares = array_values($middlewares);
53+
}
54+
55+
/**
56+
* This middleware just looks like a standard middleware: invokable, receive request and return response
57+
* It has no $next handler because it is meant to be the last middleware in stack
58+
*
59+
* @param Request $request
60+
* @param ConnectionInterface $connection
61+
* @return Response|PromiseInterface
62+
*/
63+
public function __invoke(Request $request, ConnectionInterface $connection)
64+
{
65+
return $this->call($request, $connection, 0);
66+
}
67+
68+
/**
69+
* Recursively executes middlewares
70+
*
71+
* @param Request $request
72+
* @param ConnectionInterface $connection
73+
* @param int $position
74+
* @return mixed
75+
*/
76+
protected function call(Request $request, ConnectionInterface $connection, int $position)
77+
{
78+
// final request handler will be invoked without a next handler
79+
if (!isset($this->middlewares[$position + 1])) {
80+
$handler = $this->middlewares[$position];
81+
return $handler($request, $connection);
82+
}
83+
84+
$next = function (Request $request, ConnectionInterface $connection) use ($position) {
85+
return $this->call($request, $connection, $position + 1);
86+
};
87+
88+
// invoke middleware request handler with next handler
89+
$handler = $this->middlewares[$position];
90+
return $handler($request, $connection, $next);
91+
}
92+
}

src/Server.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,12 @@ protected function handleRequest(Request $request, ConnectionInterface $connecti
128128
}
129129
// Convert all non-promise response to promises
130130
// There is little overhead for this, but this allows simpler code
131-
if (!isset($response) || !$response instanceof PromiseInterface) {
131+
if (!($response instanceof PromiseInterface)) {
132132
$response = new FulfilledPromise($response);
133133
}
134134

135-
$response->done(function ($resolvedResponse) use ($connection) {
135+
// We should probably use done() instead of then(), but done() is only part of ExtendedPromiseInterface
136+
$response->then(function ($resolvedResponse) use ($connection) {
136137
if ($resolvedResponse instanceof Response) {
137138
$connection->write($resolvedResponse->toTransport());
138139
return;

0 commit comments

Comments
 (0)