XP 框架的 Web 应用程序


README

Build status on GitHub XP Framework Module BSD Licence Requires PHP 7.0+ Supports PHP 8.0+ Latest Stable Version

HTTP 请求的低级功能,包括 xp web 运行器。

示例

use web\Application;

class Service extends Application {

  public function routes() {
    return [
      '/hello' => function($req, $res) {
        $res->answer(200, 'OK');
        $res->send('Hello '.$req->param('name', 'Guest'), 'text/plain');
      }
    ];
  }
}

使用以下命令运行:

$ xp -supervise web Service
@xp.web.srv.Standalone(HTTP @ peer.ServerSocket(Resource id #61 -> tcp://127.0.0.1:8080))
# ...

支持一个开发服务器,速度较慢但允许轻松的编辑/保存/重新加载开发过程。它使用背景中的 PHP 开发服务器

$ xp -supervise web -m develop Service
@xp.web.srv.Develop(HTTP @ `php -S 127.0.0.1:8080 -t  /home/example/devel/shorturl`)
# ...

现在在 http://localhost:8080/hello 打开网站

服务器模型

以下四个服务器模型(可以通过命令行中的 -m <model> 选择):

  • async (自 3.0.0 起为默认值):单线程 Web 服务器。处理器可以在执行长时间操作(如文件上传和下载)时将控制权交还给服务器以服务其他客户端。
  • sequential:与上述相同,但在处理完一个客户端的 HTTP 请求处理器后才会处理下一个请求。
  • prefork:类似于 Apache,它会为处理 HTTP 请求而 fork 出一定数量的子进程。需要 pcntl 扩展。
  • develop:如上所述,建立在 PHP 开发服务器之上。每次请求都会从头开始重新编译应用程序代码和执行应用程序设置,错误和调试输出由 开发控制台 处理。

请求和响应

web.Request 类提供了以下基本功能

use web\Request;

$request= ...

$request->method();       // The HTTP method, e.g. "GET"
$request->uri();          // The request URI, a util.URI instance

$request->headers();      // All request headers as a map
$request->header($name);  // The value of a single header

$request->cookies();      // All cookies
$request->cookie($name);  // The value of a single cookie

$request->params();       // All request parameters as a map
$request->param($name);   // The value of a single parameter

web.Response 类提供了以下基本功能

use web\{Response, Cookie};

$response= ...

// Set status code, header(s) and cookie(s)
$response->answer($status);
$response->header($name, $value);
$response->cookie(new Cookie($name, $value));

// Sends body using a given content type
$response->send($body, $type);

// Transfers an input stream using a given content type. Uses
// chunked transfer-encoding.
yield from $response->transmit($in, $type);

// Same as above, but specifies content length before-hand
yield from $response->transmit($in, $type, $size);

两者都提供了一个 stream() 方法来访问底层的输入和输出流。

处理器

处理器(在某些框架中也称为中间件)是一个函数,它接收一个请求和一个响应,并使用上述功能来处理通信。

use web\Handler;

$redirect= new class() implements Handler {

  public function handle($req, $res) {
    $req->status(302);
    $req->header('Location', 'https://example.com/');
  }
};

此库包含 web.handler.FilesFrom - 一个用于服务文件的处理器。它处理条件请求(带有 If-Modified-Since)以及内容范围请求,并在可用时使用异步功能,请参阅 此处

过滤器

过滤器围绕处理器,可以在处理器调用前后执行任务。您可以使用请求的 pass() 方法传递值 - 处理器可以使用 value($name) / values() 访问这些值。

use web\Filter;
use util\profiling\Timer;
use util\log\{Logging, LogCategory};

$timer= new class(Logging::all()->toConsole()) implements Filter {
  private $timer;

  public function __construct(private LogCategory $cat) {
    $this->timer= new Timer();
  }

  public function filter($request, $response, $invocation) {
    $this->timer->start();
    try {
      yield from $invocation->proceed($request, $response);
    } finally {
      $this->cat->debugf('%s: %.3f seconds', $request->uri(), $this->timer->elapsedTime());
    }
  }
}

通过使用 yield from,您确保在 finally 块中运行时间测量之前,异步处理器已完全执行。

文件上传

文件上传由请求的 multipart() 方法处理。与 PHP 的工作方式不同,文件上传是流式传输的,处理器从接收到的第一个字节开始运行!

use io\Folder;

$uploads= new Folder('...');
$handler= function($req, $res) use($uploads) {
  if ($multipart= $req->multipart()) {

    // See https://mdn.org.cn/en-US/docs/Web/HTTP/Status/100
    if ('100-continue' === $req->header('Expect')) {
      $res->hint(100, 'Continue');
    }

    // Transmit files to uploads directory asynchronously
    $files= [];
    $bytes= 0;
    foreach ($multipart->files() as $name => $file) {
      $files[]= $name;
      $bytes+= yield from $file->transmit($uploads);
    }

    // Do something with files and bytes...
  }
};

早期提示

一个实验性的状态码,可以通过它将标题提前发送到客户端,以便它能够进行优化,例如预加载脚本和样式表。

$handler= function($req, $res) {
  $res->header('Link', [
    '</main.css>; rel=preload; as=style',
    '</script.js>; rel=preload; as=script'
  ]);
  $res->hint(103);

  // Do some processing here to render $html
  $html= ...

  $res->answer(200, 'OK');
  $res->send($html, 'text/html; charset=utf-8');
}

请参阅 https://evertpot.com/http/103-early-hints

内部重定向

除了由 3XX 状态码触发的外部重定向之外,还可以使用 dispatch() 方法在内部重定向请求。这的好处是不需要客户端执行额外的请求。

use web\Application;

class Site extends Application {

  public function routes() {
    return [
      '/home' => function($req, $res) {
        // Home page
      },
      '/' => function($req, $res) {
        // Routes are re-evaluated as if user had called /home
        return $req->dispatch('/home');
      },
    ];
  }
}

日志记录

默认情况下,日志会输出到标准输出,在从 xp web 命令调用的控制台可见。可以通过以下命令行进行影响:

  • -l server.log:将日志写入文件 server.log,如果需要则创建它
  • -l -:将日志写入标准输出
  • -l - -l server.log:将日志写入上述两个位置

在应用程序内部,可以获得更精细的控制以及与 日志库 的集成,请参阅 此处

性能

因为在使用生产服务器时,Web应用程序的代码仅编译一次,所以我们实现了闪电般的请求/响应往返时间

Network console screenshot

另请参阅

此库提供了非常基础的功能。要创建Web前端或REST API,请查看以下基于此构建的库