roadrunner-server / roadrunner

🤯 High-performance PHP application server, process manager written in Go and powered with plugins
https://docs.roadrunner.dev
MIT License
7.92k stars 411 forks source link

Is Yii 2.x Supported? #78

Closed sxnazirov closed 4 years ago

sxnazirov commented 5 years ago

Thanks for author! Can this be used to host a Yii 2.x web app?

Alex-Bond commented 5 years ago

Thanks for author! Can this be used to host a Yii 2.x web app?

Unfortunately, all Yii versions not supporting PSR-7. Maybe in Yii 3 but I checked a few days ago and it looks like no support in it neither.

samdark commented 5 years ago

3.x supports PSR-7: https://github.com/yiisoft/yii-web/blob/master/composer.json#L68

Alex-Bond commented 5 years ago

3.x supports PSR-7: https://github.com/yiisoft/yii-web/blob/master/composer.json#L68

@samdark How I can start the app with PSR request? Do you have example?

samdark commented 5 years ago

By calling $app->handleRequest($request).

Alex-Bond commented 5 years ago

By calling $app->handleRequest($request).

Ok. Will try within next few days. If it works @wolfy-j will add a wiki page.

charlesportwoodii commented 5 years ago

For Yii2 I have a very early working PSR-7 bridge I've been working on for a while that bridges a Psr\Http\Message\ServerRequestInterface to yii\web\Request and yii\web\Response back to a Psr\Http\Message\ResponseInterface for consumption by PSR-7 servers like RoadRunner.

It's available at: https://github.com/charlesportwoodii/yii2-psr7-bridge

At the moment it can mostly run the yii2-basic-app without any issues, but there's still a few things that need to be worked out on it. Pull requests are welcome to improve it.

wolfy-j commented 5 years ago

@charlesportwoodii have you tried to bench with keep-alive on?

charlesportwoodii commented 5 years ago

@wolfy-j Yes and no.

siege -R <(echo connection = keep-alive) burns through sockets when keep-alive is configured, which causes siege to abort the test only after a few seconds. Since the metrics gathered were mainly to determine if the work-effort was worth pursuing or not, I didn't dive in much further since I was testing in non real-world conditions (both siege and rr running on the same machine, testing over lo). For real benchmarks I'd probably test against a remote server and with a few other tools.

Once the library is a bit more complete I plan on doing some some actual benchmarking in real-world conditions.

Alex-Bond commented 5 years ago

Update: i tried to connect RR to Yii3. There some problems with integration. I already contacted @samdark about them.

Alex-Bond commented 5 years ago

This is worker file for Yii3 is my fix (@https://github.com/yiisoft/yii-web/pull/40) will be merged:

<?php
require __DIR__ . '/vendor/autoload.php';

use hiqdev\composer\config\Builder;
use yii\di\Container;
use yii\helpers\Yii;
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;

$relay = new Spiral\Goridge\StreamRelay(STDIN, STDOUT);
$psr7 = new Spiral\RoadRunner\PSR7Client(new Spiral\RoadRunner\Worker($relay));
$httpFoundationFactory = new HttpFoundationFactory();

while ($req = $psr7->acceptRequest()) {
    try {
        $container = new Container(require Builder::path('web'));
        Yii::setContainer($container);
        /** @var \yii\web\Application $app */
        $app = $container->get('app');

        $request = $httpFoundationFactory->createRequest($req);
        /** @var \yii\web\Request $yiiReq */
        $yiiReq = $container->get('request');
        $yiiReq->setMethod($request->getMethod());
        $yiiReq->setUri($req->getUri());
        $yiiReq->setServerParams($request->server->all());
        $yiiReq->setProtocolVersion($request->getProtocolVersion());
        $yiiReq->setCookieParams($req->getCookieParams());
        $yiiReq->setQueryParams($request->query->all());
        $yiiReq->setAttributes($request->attributes->all());
        $yiiReq->setParsedBody($req->getParsedBody());
        $req->getBody()->rewind();
        $yiiReq->setBody($req->getBody());

        $container->set('request', $yiiReq);
        $response = $app->handleRequest($yiiReq);
        $psr7->respond($response->send(true));
    } catch (\Throwable $e) {
        $psr7->getWorker()->error((string)$e);
    }
}
Alex-Bond commented 5 years ago

@wolfy-j @sxnazirov Small update - so, the actual problem with Yii3 is much deeper than i expected. I recommend closing this issue for now with resolution "Not possible at this moment, ETA unavailable". I keep working on pull requests to Yii3 to fix some fundamental problems that prevent us from using it with RR.

As of Yii2 - i believe it will have the same problem with resetting data between requests. @charlesportwoodii we probably need to talk in order to verify your solution. As i know Yii2 don't have terminate process as well.

Tech info: At this moment Yii using too much direct strout printing. I'm working on rewriting request handling and error processing (at this moment most of the error writes to stdout, not response). As the second problem that right now on in backlog - terminating request. At this moment Yii doesn't have any proper termination sequence, so data between requests will be not reset.

wolfy-j commented 5 years ago

Agreed, we can come back to this issue once Yii3 is released. Current answer is - NO, it's not possible.

samdark commented 5 years ago

@Alex-Bond is working on it for 3.0.

charlesportwoodii commented 5 years ago

@Alex-Bond

For the Yii2 bridge I mentioned, the issues you mentioned (Yii2's error handler and resetting the application) are solved.

The main loop with the bridge presently looks as follows:

while ($request = $psr7->acceptRequest()) {
    try {
        // The application state is automatically reset at start of each request. `handle` also calls `\yii\Psr7\web\Application::terminate()` to flush logs and detach Yii2's global events.
        $response = $application->handle($request);
        $psr7->respond($response);
    } catch (\Throwable $e) {
        $psr7->getWorker()->error((string)$e);
    }

    // Automatically stop the worker if it approaches 90% of the defined memory limit to avoid crashes due to memory leaks
    if ($application->clean()) {
        $psr7->getWorker()->stop();
        return;
    }
}

With the configuration looking like:

env:
  YII_DEBUG: true
  YII_ENV: dev
  YII_ALIAS_WEBROOT: ./web/
  YII_ALIAS_WEB: 'http://127.0.0.1:8080/'
http:
  address:   0.0.0.0:8080
  workers:
    command: "php ./roadrunner"
    pool:
      numWorkers: 1
static:
  # root directory for static file (http would not serve .php and .htaccess files).
  dir:   "web"

  # list of extensions for forbid for serving.
  forbid: [".php"]

I have a running checklist detailing the current progress at the bottom of https://github.com/charlesportwoodii/yii2-psr7-bridge. Excluding a few items such as bootstraped components and some session issues, everything else works for the most part. These issues are only a problem for web apps since you can work-around them in RESTful API's.

This bridge is still pretty early, but at least for Yii2 the problem seems solvable.

samdark commented 5 years ago

That's great for 2.0. Keep up digging. For 3.0 it's a good way to check if framework is ready to work in such mode.

charlesportwoodii commented 5 years ago

@samdark

I think the only major issue is going to be around yii\web\Session. Yii::$app->getSession->close() doesn't seem to do the trick as I would expect. yii\web\CacheSession or a custom session handler could probably solve that problem - I just haven't gotten there yet.

Aside from that, the only real "annoyances" right now are:

Everything else either just works or I have a work-around for. I'll give another update once I'm a bit further along.

Alex-Bond commented 5 years ago

@charlesportwoodii that you for a clarification! I checked your code but didn't find how you reset database connections. Can you point me?

charlesportwoodii commented 5 years ago

@Alex-Bond,

That's one of those not-yet-implemented but I have a plan for tasks. = )

It'll probably go here once I add it. Yii2 doesn't enable the db component by default, and it can be named something other than db, so I just need to search through the components for instances of yii/db/Connection and manually call yii/db/Connection::close() on them after I find them.

wolfy-j commented 5 years ago

I'm going to highlight this ticket in README in case if someone else can help.

charlesportwoodii commented 5 years ago

Minor updates:

  1. \yii\db\Connection instances are identified and closed at the end of each request: https://github.com/charlesportwoodii/yii2-psr7-bridge/blob/master/src/web/Application.php#L160.
  2. I found a way to get \yii\web\Session to work with the PSR-7 response object and play nicely with ext/session. All the normal extensions of yii\web\Session should work out of the box since there aren't any custom classes involved in that.

I also pushed out an instance of yii-app-basic with a sqlite user database for people to work with.

Functionally this is probably ~90% complete aside from test cases. There's a couple of small changes I'll work on in the next few days time pending. All the big ticket items seem to be working now with yii-app-basic and a separate JSON API I've been working with.

free6k commented 5 years ago

@charlesportwoodii, good job, you are is big lad!

SamMousa commented 5 years ago

@charlesportwoodii your method of checking connections assumes they are all components of the main application; they could easily be inside modules as well.

The issues you run into are approximately the same as they are in a testing framework (I've been rewriting the Yii2 Codeception connector on and off over the past year) A cleaner approach could be this one: https://github.com/Codeception/Codeception/blob/3.0/src/Codeception/Lib/Connector/Yii2/ConnectionWatcher.php

It used an event based approach.

The rest of the application is reset here: https://github.com/Codeception/Codeception/blob/3.0/src/Codeception/Lib/Connector/Yii2.php#L107

Just in case it might be of use to you!

charlesportwoodii commented 5 years ago

@SamMousa Good point on the database connections. I'll be sure to take a look at the reset Codeception uses to see if other items can be incorporated.

Thanks for the tip!

charlesportwoodii commented 5 years ago

@SamMousa

Thanks again for the tip on the connections that I overlooked. I'm implemented a similar solution that closes database connections for associated modules.

For anyone else following along at home, I've also updated the project to include a better event handler that properly de-registers every event at the end of the event loop. Previously, events not manually offed would be duplicated until the process was finally terminated. This is fixed now - at least in the Yii2 App Basic package which I've updated. I plan on throwing a larger project like Craft3 which I had previous trouble with at it to see if it's fully resolved there so that even larger projects can take advantage of it.

samdark commented 5 years ago

@charlesportwoodii Yii 3 with Spiral should go way smoother because of PSR-7 and middleware. Validating that it works is on my too long TODO list :)

samdark commented 5 years ago

Yii 3 experiment: https://forum.yiiframework.com/t/using-roadrunner-as-a-server/127060

wolfy-j commented 4 years ago

Closing this issue due to the inactivity. Please take a look at the upcoming Yii3 for the integrated RoadRunner support.