use React\Socket\Server as SocketServer; use React\Http\Server; use React\Http\Response; use React\EventLoop\Factory; use Psr\Http\Message\ServerRequestInterface; // init the event loop $loop = Factory::create(); // set up the components $server = new Server( function (ServerRequestInterface $request) { return new Response( 200, ['Content-Type' => 'text/plain'], "Hello world\n" ); }); $socket = new SocketServer('127.0.0.1:8000', $loop); $server->listen($socket); echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . "\n"; // run the application $loop->run();
Request
object and returns a Response
object. The constructor of the Response
class accepts the response code, the headers and the response body. In our case, in response to each request, we return the same static line Hello world.ReadableStreamInterface
instance) as the response body, which allows us to transmit the data stream directly to the body. For example, we can open the bunny.mp4 file (you can download it from Github ) in read mode, create a ReadableResourseStream
with it, and provide this stream as the response body: $server = new Server( function (ServerRequestInterface $request) use ($loop) { $video = new ReadableResourceStream( fopen('bunny.mp4', 'r'), $loop ); return new Response( 200, ['Content-Type' => 'video/mp4'], $video ); });
ReadableResponseStream
instance, we need a cycle of events, we have to pass it to the closure. In addition, we changed the Content-Type
header to video/mp4
, so that the browser understands that in response we send it a video.Content-Length
header, since ReactPHP automatically uses chunked transfer and sends the corresponding Transfer_Encoding: chunked
header Transfer_Encoding: chunked
.ReadableResourseStream
instance directly in the server's callback function. Remember the asynchrony of our application. If we create a stream outside the callback and just pass it, no streaming will happen. Why? Because the process of reading a video file and processing incoming server requests work asynchronously. This means that while the server is waiting for new connections, we also begin to read the video file.data
event. We can assign a handler to this event that will issue a message every time we read the data from the file: use React\Http\Server; use React\Http\Response; use React\EventLoop\Factory; use React\Stream\ReadableResourceStream; use Psr\Http\Message\ServerRequestInterface; $loop = Factory::create(); $video = new ReadableResourceStream( fopen('bunny.mp4', 'r'), $loop ); $video->on('data', function(){ echo "Reading file\n"; }); $server = new Server( function (ServerRequestInterface $request) use ($stream) { return new Response( 200, ['Content-Type' => 'video/mp4'], $stream ); }); $socket = new \React\Socket\Server('127.0.0.1:8000', $loop); $server->listen($socket); echo 'Listening on ' . str_replace('tcp:', 'http:', $socket->getAddress()) . "\n"; $loop->run();
$loop->run();
, the server starts to expect incoming requests, and at the same time we start reading the file.getQueryParams()
method, which returns a GET array, similarly to the $_GET
global variable: $server = new Server( function (ServerRequestInterface $request) use ($loop) { $params = $request->getQueryParams(); $file = $params['video'] ?? ''; if (empty($file)) { return new Response( 200, ['Content-Type' => 'text/plain'], 'Video streaming server' ); } $filePath = __DIR__ . DIRECTORY_SEPARATOR . 'media' . DIRECTORY_SEPARATOR . $file; $video = new ReadableResourceStream( fopen($filePath, 'r'), $loop ); return new Response( 200, ['Content-Type' => 'video/mp4'], $video ); });
video
parameter, we think that this is the name of the video file that the user wants to see. Then we build the path to this file, open the readable stream and pass it in response.Content-Type
value in the header. We need to define it in accordance with the specified file. $server = new Server( function (ServerRequestInterface $request) use ($loop) { $params = $request->getQueryParams(); $file = $params['video'] ?? ''; if (empty($file)) { return new Response( 200, ['Content-Type' => 'text/plain'], 'Video streaming server' ); } $filePath = __DIR__ . DIRECTORY_SEPARATOR . 'media' . DIRECTORY_SEPARATOR . $file; if (!file_exists($filePath)) { return new Response( 404, ['Content-Type' => 'text/plain'], "Video $file doesn't exist on server." ); } $video = new ReadableResourceStream( fopen($filePath, 'r'), $loop ); return new Response( 200, ['Content-Type' => 'video/mp4'], $video ); });
mime_content_type()
function that returns the MIME type of a file. With its help, we can determine the MIME type of the requested video file and replace it with the Content-Type
value specified in the header: $server = new Server( function (ServerRequestInterface $request) use ($loop) { $params = $request->getQueryParams(); $file = $params['video'] ?? ''; if (empty($file)) { return new Response( 200, ['Content-Type' => 'text/plain'], 'Video streaming server' ); } if (!file_exists($filePath)) { return new Response( 404, ['Content-Type' => 'text/plain'], "Video $file doesn't exist on server." ); } $video = new ReadableResourceStream( fopen($filePath, 'r'), $loop ); $type = mime_content_type($filePath); return new Response( 200, ['Content-Type' => $type], $video ); });
Content-Type
value that is fixed in the header, now it is automatically determined according to the requested file.basename()
function to take only the file name from the request, cutting the file path if it was specified: // ... $filePath = __DIR__ . DIRECTORY_SEPARATOR . 'media' . DIRECTORY_SEPARATOR . basename($file); // ...
VideoStreaming
class. To be able to use this class as a call handler for the request, we need to embed the magic __invoke()
method into it. After that, it will be enough for us to simply pass the instance of this class as a callback to the Server
constructor: // ... $loop = Factory::create(); $videoStreaming = new VideoStreaming($loop); $server = new Server($videoStreaming);
VideoStreaming
. It requires one dependency - the instance of the event loop, which will be embedded through the constructor To begin with, you can simply copy the code from the callback to the __invoke()
method, and then do its refactoring: class VideoStreaming { // ... /** * @param ServerRequestInterface $request * @return Response */ function __invoke(ServerRequestInterface $request) { $params = $request->getQueryParams(); $file = $params['video'] ?? ''; if (empty($file)) { return new Response( 200, ['Content-Type' => 'text/plain'], 'Video streaming server' ); } $filePath = __DIR__ . DIRECTORY_SEPARATOR . 'media' . DIRECTORY_SEPARATOR . basename($file); if (!file_exists($filePath)) { return new Response( 404, ['Content-Type' => 'text/plain'], "Video $file doesn't exist on server." ); } $video = new ReadableResourceStream( fopen($filePath, 'r'), $this->eventLoop ); $type = mime_content_type($filePath); return new Response( 200, ['Content-Type' => $type], $video ); } }
__invoke()
method. Let's see what is happening here: class VideoStreaming { // ... /** * @param ServerRequestInterface $request * @return Response */ function __invoke(ServerRequestInterface $request) { $file = $this->getFilePath($request); if (empty($file)) { return new Response( 200, ['Content-Type' => 'text/plain'], 'Video streaming server' ); } return $this->makeResponseFromFile($file); } /** * @param ServerRequestInterface $request * @return string */ protected function getFilePath(ServerRequestInterface $request) { // ... } /** * @param string $filePath * @return Response */ protected function makeResponseFromFile($filePath) { // ... } }
getFilePath()
, is very simple. We get the request parameters using the $request->getQueryParams()
method. If they do not have the file
key, we simply return a simple string indicating that the user has opened the server with no GET parameters. In this case, we can show a static page or something like that. Here we return a simple text message Video streaming server. If the user specified a file in the GET request, we create the path to this file and return it: class VideoStreaming { // ... /** * @param ServerRequestInterface $request * @return string */ protected function getFilePath(ServerRequestInterface $request) { $file = $request->getQueryParams()['file'] ?? ''; if (empty($file)) return ''; return __DIR__ . DIRECTORY_SEPARATOR . 'media' . DIRECTORY_SEPARATOR . basename($file); } // ... }
makeResponseFromFile()
method is also very simple. If there is no file for the specified path, we immediately return a 404 error. Otherwise, we open the requested file, create a readable stream and return it in the response body: class VideoStreaming { // ... /** * @param string $filePath * @return Response */ protected function makeResponseFromFile($filePath) { if (!file_exists($filePath)) { return new Response( 404, ['Content-Type' => 'text/plain'], "Video $filePath doesn't exist on server." ); } $stream = new ReadableResourceStream( fopen($filePath, 'r'), $this->eventLoop ); $type = mime_content_type($filePath); return new Response( 200, ['Content-Type' => $type], $stream ); } }
use React\Http\Response; use React\EventLoop\Factory; use React\EventLoop\LoopInterface; use React\Stream\ReadableResourceStream; use Psr\Http\Message\ServerRequestInterface; class VideoStreaming { /** * @var LoopInterface */ protected $eventLoop; /** * @param LoopInterface $eventLoop */ public function __construct(LoopInterface $eventLoop) { $this->eventLoop = $eventLoop; } /** * @param ServerRequestInterface $request * @return Response */ function __invoke(ServerRequestInterface $request) { $file = $this->getFilePath($request); if (empty($file)) { return new Response( 200, ['Content-Type' => 'text/plain'], 'Video streaming server' ); } return $this->makeResponseFromFile($file); } /** * @param string $filePath * @return Response */ protected function makeResponseFromFile($filePath) { if (!file_exists($filePath)) { return new Response( 404, ['Content-Type' => 'text/plain'], "Video $filePath doesn't exist on server." ); } $stream = new ReadableResourceStream( fopen($filePath, 'r'), $this->eventLoop ); $type = mime_content_type($filePath); return new Response( 200, ['Content-Type' => $type], $stream ); } /** * @param ServerRequestInterface $request * @return string */ protected function getFilePath(ServerRequestInterface $request) { $file = $request->getQueryParams()['file'] ?? ''; if (empty($file)) return ''; return __DIR__ . DIRECTORY_SEPARATOR . 'media' . DIRECTORY_SEPARATOR . basename($file); } }
Source: https://habr.com/ru/post/341306/
All Articles