legionth/http-server-react

此软件包已被废弃且不再维护。作者建议使用 react/http 软件包。


README

基于 ReactPHP 的 PHP HTTP 服务器。

目录

通知(2017 年 6 月)

此存储库仅是一个原型,将不再提供支持。该原型的每个功能都已迁移到官方的 ReactPHP HTTP-Server

使用方法

HttpServer

HTTP 服务器需要一个套接字和回调函数才能工作。套接字将用于服务器和客户端之间的通信。回调函数用于对请求做出反应并返回响应。回调函数 必须 返回一个承诺或响应对象。HttpServer 类使用 PSR-7 Middleware 对象。并且这些对象也需要在 回调函数 中使用。

创建回调函数

HttpServer 使用一个回调函数。这个回调函数只有一个参数,即请求对象,并期望返回一个响应对象。

创建自己的回调函数以根据您的意愿对响应做出反应(例如,检查响应,从数据库中获取值并发送响应)。但请注意,数据库或文件操作等阻塞操作会导致服务器变慢。

$callback = function (ServerRequestInterface $request) {
    $content = '<html>
<body>
    <h1> Hello World! </h1>
    <p> This is your own little server. Written in PHP :-) </p>
</body>
</html>';

    return new Response(
        200,
        array(
            'Content-Length' => strlen($content),
            'Content-Type' => 'text/html'
        ),
        $content
    );
};

$socket = new Socket($loop);
$socket->listen(10000, 'localhost');

$server = new HttpServer($socket, $callback);
$loop->run();

此示例将在将请求发送到此服务器的每个请求上响应一个简单的 HTML 网站。但是,只要请求的头部到达服务器,就会立即向客户端发送响应。如果请求包含正文数据,则会忽略这些数据,并在向客户端发送响应时关闭 TCP 连接。要处理正文数据,您必须使用流。

v0.4.0 版本开始,每个版本都将流请求。这意味着您的回调函数中的请求对象的正文。

流式请求使得从客户端到服务器发送大量数据成为可能,这些数据以小块的形式发送。例如,您可以在应用程序收到正文的具体部分时开始处理请求的计算。

您的回调和中间件函数中的请求对象的正文将是一个 ReadableStreamInterface

每个请求正文流在成功完成流时将发送一个结束事件。我们必须使用 promise 来确保只有在请求流完成时才向客户端发送响应。

下一个示例将与前一个示例做同样的事情,但会等待请求流完成。

$callback = function (ServerRequestInterface $request) {
    return new Promise(function ($resolve, $reject) use ($request) {
        $request->getBody->on('end', function () use (&$contentLength, $resolve) {
            $content = '<html>
<body>
    <h1> Hello World! </h1>
    <p> This is your own little server. Written in PHP :-) </p>
</body>
</html>';

            return new Response(
                200,
                array(
                    'Content-Length' => strlen($content),
                    'Content-Type' => 'text/html'
                ),
                $content
            );
        });
    };
}

请求体始终是一个 ReactPHP 流。目前不需要使用 StreamInterface 的 PSR-7 方法,并且在此开发阶段它们没有任何功能,但在未来的开发中可能会使用。

以下示例中,将添加一个监听器到 'data' 事件,仅计算传输的字符串数据长度。在请求体流结束时,将通过 HTTP 响应以文本形式发送传输数据的长度给客户端。

$callback = function (ServerRequestInterface $request) {
    return new Promise(function ($resolve, $reject) use ($body) {
        $contentLength = 0;
        $request->getBody()->on('data', function ($chunk) use ($resolve, &$contentLength) {
            $contentLength += strlen($chunk);
        });

        $request->getBody()->on('end', function () use (&$contentLength, $resolve) {
            $content = "Transferred data length: " . $contentLength ."\n";
            $resolve(
                new Response(
                    200,
                    array(
                        'Content-Length' => strlen($content),
                        'Content-Type' => 'text/html'
                    ),
                    $content
                )
            );
        });
    });
};

这只是一个示例,您可以使用 BufferedSink(来自 reactphp/stream)来避免这些代码行。

此示例仅流式传输请求体。响应体也可以流式传输。请查看 流式响应 章节。

必须在回调和中间件函数中作为第一个参数使用 ServerRequestInterface。请求参数具有 PSR-7 接口定义的默认值,并且可以通过中间件进行更改。

查看 examples 文件夹以了解服务器可能的样子。

ChunkedDecoder

ChunkedDecoder 用于解码 HTTP 请求中通过 Transfer-Encoding: chunked 发送的单独数据块。HTTP 服务器将编码后的请求体发送到回调函数。

此类基于 ReactPHP 流HttpServer 将保存数据块,直到请求体完成,然后将解码后的请求转发给回调函数。

异常处理

回调函数中的代码可以抛出异常,但这不应影响服务器运行。因此,每个未捕获的异常都将被 HttpServer 捕获,并在发生异常时向客户端发送 'HTTP 500 内部服务器错误' 响应。

示例

<?php

$loop = React\EventLoop\Factory::create();

$callback = function (ServerRequestInterface $request) use ($loop) {
    throw new Exception();
};

$socket = new Socket($loop);
$socket->listen(10000, 'localhost');

$server = new HttpServer($socket, $callback);
$loop->run();

此示例将在任何请求上导致 'HTTP 500 内部服务器错误'。

提示:这是未捕获异常时的默认响应。如果您希望用户在浏览器中看到比任何空站点更多的内容,请捕获您的异常并创建自己的带有头部和体的 Response 对象。

<?php
$callback = function (ServerRequestInterface $request) {
    try {
        //something that could go wrong
    } catch(Exception $exception) {
        return new Response(500, array('Content-Length' => 5), 'error');
    }
}
$httpServer = new HttpServer($socket, $callback);

回调函数的返回类型

回调函数的返回类型 必须 是一个 响应对象 或一个 promise

对于重型计算,您应考虑使用 promises。不使用它们可能会导致服务器变慢。

$callback = function (ServerRequestInterface $request) {
    return new Promise(function ($resolve, $reject) use ($request) {
        $request->getBody()->on('end', function () {
            $response = heavyCalculationFunction();
            $resolve($response);
        });
    });
};

查看 examples 文件夹以了解如何在回调函数中使用 promises。

promise 必须 返回一个响应对象,否则将导致客户端的 'HTTP 500 内部服务器错误' 响应。

不允许其他类型,否则将导致客户端的 'HTTP 500 内部服务器错误' 响应。

中间件

创建您自己的中间件

您可以创建自己的中间件。这些中间件位于 HttpServer 和用户回调函数之间。当创建响应对象时,HttpServer 会调用回调函数。添加中间件后,HttpServer 首先会调用这个中间件。下一个链环将是另一个中间件或链尾的回调函数。每个中间件都必须返回一个响应对象。否则,HttpServer 将返回“500 内部服务器错误”消息。中间件不仅可以操作请求对象,还可以操作其他添加的中间件或回调函数返回的响应对象。

这类似于fig 标准的概念。

添加尽可能多的中间件,只需遵循以下设计即可。

$callback = function (ServerRequestInterface $request) {
    return new Response();
}

$middleware = function (ServerRequestInterface $request, callable $next) {
    // check or maninpulate the request object
    ...
    // call of next middleware chain link
    return $next($request);
}

$server = new HttpServer($socket, $callback);
$server->addMiddleware($middleware);

确保在您的中间件代码中添加 return $next($request)。否则,将返回最后一个调用的中间件的响应。return $next($request) 将调用下一个中间件或用户回调函数,如果它是这个中间件链的最后一部分。

添加的中间件将按照添加的顺序执行。

...

$timeBlockingMiddleware = function (ServerRequestInterface $request, callable $next) {
    // Will call the next middleware from 00:00 till 16:00
    // otherwise an 403 Forbidden response will be sent to the client
    if (((int)date('Hi') < 1600 && (int)date('Hi') > 0) {
        return $next($request);
    }
    return new Response(403);
};

$addHeaderToRequest = function (ServerRequestInterface $request, callable $next) {
    $request = $request->withAddedHeader('Date', date('Y-m-d'));
    return $next($request);
};

$addHeaderToResponse = function (ServerRequestInterface $request, callable $next) {
    $response = $next($request);
    $response = $response->withAddedHeader('Age', '12');
    return $response;
};

$server = new HttpServer($socket, $callback);
$server->addMiddleware($timeBlockingMiddleware);
$server->addMiddleware($addHeaderToRequest);
$server->addMiddleware($addHeaderToResponse);

在这个示例中,首先调用 $timeBlockingMiddleWare,其次是 $addHeaderToRequest,然后是 $addHeaderToResponse。链的最后一部分是 callback 函数。

这个小示例应展示您如何使用中间件,例如检查或操作请求/响应对象。

查看 examples/middleware 了解如何添加多个中间件。

流式响应

由于计算需要一段时间才能完成,因此可以将数据直接流式传输到客户端,而不需要缓冲整个数据。流式传输使得能够将大量数据分小块发送到客户端。

使用 HttpBodyStream 的一个实例,并将其用作您想返回的 Response 对象的正文。

$callback = function (ServerRequestInterface $request) {
    $input = new ReadableStream();
    $responseBody = new HttpBodyStream($input);
    
    // your computation
    // emit via `$input`
    
    $promise = new Promise(function ($resolve, $reject) use ($request, $responseBody) {
        $request->getBody()->on('end', function () use ($resolve, $responseBody){
            $resolve(new Response(200, array(), $responseBody));
        });
    });

    return $promise;
}

HttpServer 将使用从 ReadableStream 发射的数据直接将此数据发送到客户端。如果您使用 HttpBodyStream,整个传输将被 分块编码,将忽略为 Transfer-Encoding 设置的其他值。

查看 examples 文件夹了解您的计算可能看起来像什么。

HTTPS 服务器

可以使用 ReactPHP 的 socket 包中的 SecureServer 将 HTTP 服务器设置为 HTTPS 服务器。

以下示例显示了如何使用它

$socket = new Socket($loop);
$secureSocket = new SecureServer(
    $socket,
    $loop,
    array('local_cert' => 'secret.pem')
);

$secureSocket->listen(10000, 'localhost');

$secureSocket->on('error', function (Exception $e) {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

$server = new HttpServer($secureSocket, $callback);

查看 examples 文件夹了解您的 HTTPS 服务器可能看起来像什么。

要执行此示例,您必须使用自签名证书。您可以使用 脚本 生成自签名证书

安装

新用户 Composer 吗?

这将安装最新支持的版本。

$ composer require legionth/http-server-react:^0.1

有关版本升级的详细信息,请参阅 CHANGELOG

许可

MIT