Giter Site home page Giter Site logo

hhxsv5 / laravel-s Goto Github PK

View Code? Open in Web Editor NEW
3.8K 196.0 473.0 5.4 MB

LaravelS is an out-of-the-box adapter between Laravel/Lumen and Swoole.

License: MIT License

PHP 99.19% Shell 0.81%
swoole laravel lumen async server http websocket tcp udp process task timer performance

laravel-s's Introduction

LaravelS Logo

English Docs | 中文文档

🚀 LaravelS is an out-of-the-box adapter between Laravel/Lumen and Swoole

Latest Version PHP Version Swoole Version Total Downloads Build Status Code Intelligence Status License


Continuous Updates

  • Please Watch this repository to get the latest updates.

Table of Contents

Features

Benchmark

Requirements

Dependency Requirement
PHP >=8.2 Recommend 8.2
Swoole >=5.0 Recommend 5.1.1
Laravel/Lumen >=10 Recommend 10

Install

1.Require package via Composer(packagist).

# PHP >=8.2
composer require "hhxsv5/laravel-s:~3.8.0"

# PHP >=5.5.9,<=7.4.33
# composer require "hhxsv5/laravel-s:~3.7.0"

# Make sure that your composer.lock file is under the VCS

2.Register service provider(pick one of two).

  • Laravel: in config/app.php file, Laravel 5.5+ supports package discovery automatically, you should skip this step

    'providers' => [
        //...
        Hhxsv5\LaravelS\Illuminate\LaravelSServiceProvider::class,
    ],
  • Lumen: in bootstrap/app.php file

    $app->register(Hhxsv5\LaravelS\Illuminate\LaravelSServiceProvider::class);

3.Publish configuration and binaries.

After upgrading LaravelS, you need to republish; click here to see the change notes of each version.

php artisan laravels publish
# Configuration: config/laravels.php
# Binary: bin/laravels bin/fswatch bin/inotify

4.Change config/laravels.php: listen_ip, listen_port, refer Settings.

5.Performance tuning

  • Adjust kernel parameters

  • Number of Workers: LaravelS uses Swoole's Synchronous IO mode, the larger the worker_num setting, the better the concurrency performance, but it will cause more memory usage and process switching overhead. If one request takes 100ms, in order to provide 1000QPS concurrency, at least 100 Worker processes need to be configured. The calculation method is: worker_num = 1000QPS/(1s/1ms) = 100, so incremental pressure testing is needed to calculate the best worker_num.

  • Number of Task Workers

Run

Please read the notices carefully before running, Important notices(IMPORTANT).

  • Commands: php bin/laravels {start|stop|restart|reload|info|help}.
Command Description
start Start LaravelS, list the processes by "ps -ef|grep laravels"
stop Stop LaravelS, and trigger the method onStop of Custom process
restart Restart LaravelS: Stop gracefully before starting; The service is unavailable until startup is complete
reload Reload all Task/Worker/Timer processes which contain your business codes, and trigger the method onReload of Custom process, CANNOT reload Master/Manger processes. After modifying config/laravels.php, you only have to call restart to restart
info Display component version information
help Display help information
  • Boot options for the commands start and restart.
Option Description
-d|--daemonize Run as a daemon, this option will override the swoole.daemonize setting in laravels.php
-e|--env The environment the command should run under, such as --env=testing will use the configuration file .env.testing firstly, this feature requires Laravel 5.2+
-i|--ignore Ignore checking PID file of Master process
-x|--x-version The version(branch) of the current project, stored in $_ENV/$_SERVER, access via $_ENV['X_VERSION'] $_SERVER['X_VERSION'] $request->server->get('X_VERSION')
  • Runtime files: start will automatically execute php artisan laravels config and generate these files, developers generally don't need to pay attention to them, it's recommended to add them to .gitignore.
File Description
storage/laravels.conf LaravelS's runtime configuration file
storage/laravels.pid PID file of Master process
storage/laravels-timer-process.pid PID file of the Timer process
storage/laravels-custom-processes.pid PID file of all custom processes

Deploy

It is recommended to supervise the main process through Supervisord, the premise is without option -d and to set swoole.daemonize to false.

[program:laravel-s-test]
directory=/var/www/laravel-s-test
command=/usr/local/bin/php bin/laravels start -i
numprocs=1
autostart=true
autorestart=true
startretries=3
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/supervisor/%(program_name)s.log

Cooperate with Nginx (Recommended)

Demo.

gzip on;
gzip_min_length 1024;
gzip_comp_level 2;
gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml application/x-httpd-php image/jpeg image/gif image/png font/ttf font/otf image/svg+xml;
gzip_vary on;
gzip_disable "msie6";
upstream swoole {
    # Connect IP:Port
    server 127.0.0.1:5200 weight=5 max_fails=3 fail_timeout=30s;
    # Connect UnixSocket Stream file, tips: put the socket file in the /dev/shm directory to get better performance
    #server unix:/yourpath/laravel-s-test/storage/laravels.sock weight=5 max_fails=3 fail_timeout=30s;
    #server 192.168.1.1:5200 weight=3 max_fails=3 fail_timeout=30s;
    #server 192.168.1.2:5200 backup;
    keepalive 16;
}
server {
    listen 80;
    # Don't forget to bind the host
    server_name laravels.com;
    root /yourpath/laravel-s-test/public;
    access_log /yourpath/log/nginx/$server_name.access.log  main;
    autoindex off;
    index index.html index.htm;
    # Nginx handles the static resources(recommend enabling gzip), LaravelS handles the dynamic resource.
    location / {
        try_files $uri @laravels;
    }
    # Response 404 directly when request the PHP file, to avoid exposing public/*.php
    #location ~* \.php$ {
    #    return 404;
    #}
    location @laravels {
        # proxy_connect_timeout 60s;
        # proxy_send_timeout 60s;
        # proxy_read_timeout 120s;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Real-PORT $remote_port;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header Scheme $scheme;
        proxy_set_header Server-Protocol $server_protocol;
        proxy_set_header Server-Name $server_name;
        proxy_set_header Server-Addr $server_addr;
        proxy_set_header Server-Port $server_port;
        # "swoole" is the upstream
        proxy_pass http://swoole;
    }
}

Cooperate with Apache

LoadModule proxy_module /yourpath/modules/mod_proxy.so
LoadModule proxy_balancer_module /yourpath/modules/mod_proxy_balancer.so
LoadModule lbmethod_byrequests_module /yourpath/modules/mod_lbmethod_byrequests.so
LoadModule proxy_http_module /yourpath/modules/mod_proxy_http.so
LoadModule slotmem_shm_module /yourpath/modules/mod_slotmem_shm.so
LoadModule rewrite_module /yourpath/modules/mod_rewrite.so
LoadModule remoteip_module /yourpath/modules/mod_remoteip.so
LoadModule deflate_module /yourpath/modules/mod_deflate.so

<IfModule deflate_module>
    SetOutputFilter DEFLATE
    DeflateCompressionLevel 2
    AddOutputFilterByType DEFLATE text/html text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml application/x-httpd-php image/jpeg image/gif image/png font/ttf font/otf image/svg+xml
</IfModule>

<VirtualHost *:80>
    # Don't forget to bind the host
    ServerName www.laravels.com
    ServerAdmin [email protected]

    DocumentRoot /yourpath/laravel-s-test/public;
    DirectoryIndex index.html index.htm
    <Directory "/">
        AllowOverride None
        Require all granted
    </Directory>

    RemoteIPHeader X-Forwarded-For

    ProxyRequests Off
    ProxyPreserveHost On
    <Proxy balancer://laravels>  
        BalancerMember http://192.168.1.1:5200 loadfactor=7
        #BalancerMember http://192.168.1.2:5200 loadfactor=3
        #BalancerMember http://192.168.1.3:5200 loadfactor=1 status=+H
        ProxySet lbmethod=byrequests
    </Proxy>
    #ProxyPass / balancer://laravels/
    #ProxyPassReverse / balancer://laravels/

    # Apache handles the static resources, LaravelS handles the dynamic resource.
    RewriteEngine On
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-d
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} !-f
    RewriteRule ^/(.*)$ balancer://laravels%{REQUEST_URI} [P,L]

    ErrorLog ${APACHE_LOG_DIR}/www.laravels.com.error.log
    CustomLog ${APACHE_LOG_DIR}/www.laravels.com.access.log combined
</VirtualHost>

Enable WebSocket server

The Listening address of WebSocket Sever is the same as Http Server.

1.Create WebSocket Handler class, and implement interface WebSocketHandlerInterface.The instant is automatically instantiated when start, you do not need to manually create it.

namespace App\Services;
use Hhxsv5\LaravelS\Swoole\WebSocketHandlerInterface;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;
/**
 * @see https://www.swoole.co.uk/docs/modules/swoole-websocket-server
 */
class WebSocketService implements WebSocketHandlerInterface
{
    // Declare constructor without parameters
    public function __construct()
    {
    }
    // public function onHandShake(Request $request, Response $response)
    // {
           // Custom handshake: https://www.swoole.co.uk/docs/modules/swoole-websocket-server-on-handshake
           // The onOpen event will be triggered automatically after a successful handshake
    // }
    public function onOpen(Server $server, Request $request)
    {
        // Before the onOpen event is triggered, the HTTP request to establish the WebSocket has passed the Laravel route,
        // so Laravel's Request, Auth information are readable, Session is readable and writable, but only in the onOpen event.
        // \Log::info('New WebSocket connection', [$request->fd, request()->all(), session()->getId(), session('xxx'), session(['yyy' => time()])]);
        // The exceptions thrown here will be caught by the upper layer and recorded in the Swoole log. Developers need to try/catch manually.
        $server->push($request->fd, 'Welcome to LaravelS');
    }
    public function onMessage(Server $server, Frame $frame)
    {
        // \Log::info('Received message', [$frame->fd, $frame->data, $frame->opcode, $frame->finish]);
        // The exceptions thrown here will be caught by the upper layer and recorded in the Swoole log. Developers need to try/catch manually.
        $server->push($frame->fd, date('Y-m-d H:i:s'));
    }
    public function onClose(Server $server, $fd, $reactorId)
    {
        // The exceptions thrown here will be caught by the upper layer and recorded in the Swoole log. Developers need to try/catch manually.
    }
}

2.Modify config/laravels.php.

// ...
'websocket'      => [
    'enable'  => true, // Note: set enable to true
    'handler' => \App\Services\WebSocketService::class,
],
'swoole'         => [
    //...
    // Must set dispatch_mode in (2, 4, 5), see https://www.swoole.co.uk/docs/modules/swoole-server/configuration
    'dispatch_mode' => 2,
    //...
],
// ...

3.Use SwooleTable to bind FD & UserId, optional, Swoole Table Demo. Also you can use the other global storage services, like Redis/Memcached/MySQL, but be careful that FD will be possible conflicting between multiple Swoole Servers.

4.Cooperate with Nginx (Recommended)

Refer WebSocket Proxy

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}
upstream swoole {
    # Connect IP:Port
    server 127.0.0.1:5200 weight=5 max_fails=3 fail_timeout=30s;
    # Connect UnixSocket Stream file, tips: put the socket file in the /dev/shm directory to get better performance
    #server unix:/yourpath/laravel-s-test/storage/laravels.sock weight=5 max_fails=3 fail_timeout=30s;
    #server 192.168.1.1:5200 weight=3 max_fails=3 fail_timeout=30s;
    #server 192.168.1.2:5200 backup;
    keepalive 16;
}
server {
    listen 80;
    # Don't forget to bind the host
    server_name laravels.com;
    root /yourpath/laravel-s-test/public;
    access_log /yourpath/log/nginx/$server_name.access.log  main;
    autoindex off;
    index index.html index.htm;
    # Nginx handles the static resources(recommend enabling gzip), LaravelS handles the dynamic resource.
    location / {
        try_files $uri @laravels;
    }
    # Response 404 directly when request the PHP file, to avoid exposing public/*.php
    #location ~* \.php$ {
    #    return 404;
    #}
    # Http and WebSocket are concomitant, Nginx identifies them by "location"
    # !!! The location of WebSocket is "/ws"
    # Javascript: var ws = new WebSocket("ws://laravels.com/ws");
    location =/ws {
        # proxy_connect_timeout 60s;
        # proxy_send_timeout 60s;
        # proxy_read_timeout: Nginx will close the connection if the proxied server does not send data to Nginx in 60 seconds; At the same time, this close behavior is also affected by heartbeat setting of Swoole.
        # proxy_read_timeout 60s;
        proxy_http_version 1.1;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Real-PORT $remote_port;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header Scheme $scheme;
        proxy_set_header Server-Protocol $server_protocol;
        proxy_set_header Server-Name $server_name;
        proxy_set_header Server-Addr $server_addr;
        proxy_set_header Server-Port $server_port;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_pass http://swoole;
    }
    location @laravels {
        # proxy_connect_timeout 60s;
        # proxy_send_timeout 60s;
        # proxy_read_timeout 60s;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Real-PORT $remote_port;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header Scheme $scheme;
        proxy_set_header Server-Protocol $server_protocol;
        proxy_set_header Server-Name $server_name;
        proxy_set_header Server-Addr $server_addr;
        proxy_set_header Server-Port $server_port;
        proxy_pass http://swoole;
    }
}

5.Heartbeat setting

  • Heartbeat setting of Swoole

    // config/laravels.php
    'swoole' => [
        //...
        // All connections are traversed every 60 seconds. If a connection does not send any data to the server within 600 seconds, the connection will be forced to close.
        'heartbeat_idle_time'      => 600,
        'heartbeat_check_interval' => 60,
        //...
    ],
  • Proxy read timeout of Nginx

    # Nginx will close the connection if the proxied server does not send data to Nginx in 60 seconds
    proxy_read_timeout 60s;

6.Push data in controller

namespace App\Http\Controllers;
class TestController extends Controller
{
    public function push()
    {
        $fd = 1; // Find fd by userId from a map [userId=>fd].
        /**@var \Swoole\WebSocket\Server $swoole */
        $swoole = app('swoole');
        $success = $swoole->push($fd, 'Push data to fd#1 in Controller');
        var_dump($success);
    }
}

Listen events

System events

Usually, you can reset/destroy some global/static variables, or change the current Request/Response object.

  • laravels.received_request After LaravelS parsed Swoole\Http\Request to Illuminate\Http\Request, before Laravel's Kernel handles this request.

    // Edit file `app/Providers/EventServiceProvider.php`, add the following code into method `boot`
    // If no variable $events, you can also call Facade \Event::listen(). 
    $events->listen('laravels.received_request', function (\Illuminate\Http\Request $req, $app) {
        $req->query->set('get_key', 'hhxsv5');// Change query of request
        $req->request->set('post_key', 'hhxsv5'); // Change post of request
    });
  • laravels.generated_response After Laravel's Kernel handled the request, before LaravelS parses Illuminate\Http\Response to Swoole\Http\Response.

    // Edit file `app/Providers/EventServiceProvider.php`, add the following code into method `boot`
    // If no variable $events, you can also call Facade \Event::listen(). 
    $events->listen('laravels.generated_response', function (\Illuminate\Http\Request $req, \Symfony\Component\HttpFoundation\Response $rsp, $app) {
        $rsp->headers->set('header-key', 'hhxsv5');// Change header of response
    });

Customized asynchronous events

This feature depends on AsyncTask of Swoole, your need to set swoole.task_worker_num in config/laravels.php firstly. The performance of asynchronous event processing is influenced by number of Swoole task process, you need to set task_worker_num appropriately.

1.Create event class.

use Hhxsv5\LaravelS\Swoole\Task\Event;
class TestEvent extends Event
{
    protected $listeners = [
        // Listener list
        TestListener1::class,
        // TestListener2::class,
    ];
    private $data;
    public function __construct($data)
    {
        $this->data = $data;
    }
    public function getData()
    {
        return $this->data;
    }
}

2.Create listener class.

use Hhxsv5\LaravelS\Swoole\Task\Event;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Hhxsv5\LaravelS\Swoole\Task\Listener;
class TestListener1 extends Listener
{
    public function handle(Event $event)
    {
        \Log::info(__CLASS__ . ':handle start', [$event->getData()]);
        sleep(2);// Simulate the slow codes
        // Deliver task in CronJob, but NOT support callback finish() of task.
        // Note: Modify task_ipc_mode to 1 or 2 in config/laravels.php, see https://www.swoole.co.uk/docs/modules/swoole-server/configuration
        $ret = Task::deliver(new TestTask('task data'));
        var_dump($ret);
        // The exceptions thrown here will be caught by the upper layer and recorded in the Swoole log. Developers need to try/catch manually.
        // return false; // Stop propagating this event to subsequent listeners
    }
}

3.Fire event.

// Create instance of event and fire it, "fire" is asynchronous.
use Hhxsv5\LaravelS\Swoole\Task\Event;
$event = new TestEvent('event data');
// $event->delay(10); // Delay 10 seconds to fire event
// $event->setTries(3); // When an error occurs, try 3 times in total
$success = Event::fire($event);
var_dump($success);// Return true if sucess, otherwise false

Asynchronous task queue

This feature depends on AsyncTask of Swoole, your need to set swoole.task_worker_num in config/laravels.php firstly. The performance of task processing is influenced by number of Swoole task process, you need to set task_worker_num appropriately.

1.Create task class.

use Hhxsv5\LaravelS\Swoole\Task\Task;
class TestTask extends Task
{
    private $data;
    private $result;
    public function __construct($data)
    {
        $this->data = $data;
    }
    // The logic of task handling, run in task process, CAN NOT deliver task
    public function handle()
    {
        \Log::info(__CLASS__ . ':handle start', [$this->data]);
        sleep(2);// Simulate the slow codes
        // The exceptions thrown here will be caught by the upper layer and recorded in the Swoole log. Developers need to try/catch manually.
        $this->result = 'the result of ' . $this->data;
    }
    // Optional, finish event, the logic of after task handling, run in worker process, CAN deliver task 
    public function finish()
    {
        \Log::info(__CLASS__ . ':finish start', [$this->result]);
        Task::deliver(new TestTask2('task2 data')); // Deliver the other task
    }
}

2.Deliver task.

// Create instance of TestTask and deliver it, "deliver" is asynchronous.
use Hhxsv5\LaravelS\Swoole\Task\Task;
$task = new TestTask('task data');
// $task->delay(3);// delay 3 seconds to deliver task
// $task->setTries(3); // When an error occurs, try 3 times in total
$ret = Task::deliver($task);
var_dump($ret);// Return true if sucess, otherwise false

Millisecond cron job

Wrapper cron job base on Swoole's Millisecond Timer, replace Linux Crontab.

1.Create cron job class.

namespace App\Jobs\Timer;
use App\Tasks\TestTask;
use Swoole\Coroutine;
use Hhxsv5\LaravelS\Swoole\Task\Task;
use Hhxsv5\LaravelS\Swoole\Timer\CronJob;
class TestCronJob extends CronJob
{
    protected $i = 0;
    // !!! The `interval` and `isImmediate` of cron job can be configured in two ways(pick one of two): one is to overload the corresponding method, and the other is to pass parameters when registering cron job.
    // --- Override the corresponding method to return the configuration: begin
    public function interval()
    {
        return 1000;// Run every 1000ms
    }
    public function isImmediate()
    {
        return false;// Whether to trigger `run` immediately after setting up
    }
    // --- Override the corresponding method to return the configuration: end
    public function run()
    {
        \Log::info(__METHOD__, ['start', $this->i, microtime(true)]);
        // do something
        // sleep(1); // Swoole < 2.1
        Coroutine::sleep(1); // Swoole>=2.1 Coroutine will be automatically created for run().
        $this->i++;
        \Log::info(__METHOD__, ['end', $this->i, microtime(true)]);

        if ($this->i >= 10) { // Run 10 times only
            \Log::info(__METHOD__, ['stop', $this->i, microtime(true)]);
            $this->stop(); // Stop this cron job, but it will run again after restart/reload.
            // Deliver task in CronJob, but NOT support callback finish() of task.
            // Note: Modify task_ipc_mode to 1 or 2 in config/laravels.php, see https://www.swoole.co.uk/docs/modules/swoole-server/configuration
            $ret = Task::deliver(new TestTask('task data'));
            var_dump($ret);
        }
        // The exceptions thrown here will be caught by the upper layer and recorded in the Swoole log. Developers need to try/catch manually.
    }
}

2.Register cron job.

// Register cron jobs in file "config/laravels.php"
[
    // ...
    'timer'          => [
        'enable' => true, // Enable Timer
        'jobs'   => [ // The list of cron job
            // Enable LaravelScheduleJob to run `php artisan schedule:run` every 1 minute, replace Linux Crontab
            // \Hhxsv5\LaravelS\Illuminate\LaravelScheduleJob::class,
            // Two ways to configure parameters:
            // [\App\Jobs\Timer\TestCronJob::class, [1000, true]], // Pass in parameters when registering
            \App\Jobs\Timer\TestCronJob::class, // Override the corresponding method to return the configuration
        ],
        'max_wait_time' => 5, // Max waiting time of reloading
        // Enable the global lock to ensure that only one instance starts the timer when deploying multiple instances. This feature depends on Redis, please see https://laravel.com/docs/7.x/redis
        'global_lock'     => false,
        'global_lock_key' => config('app.name', 'Laravel'),
    ],
    // ...
];

3.Note: it will launch multiple timers when build the server cluster, so you need to make sure that launch one timer only to avoid running repetitive task.

4.LaravelS v3.4.0 starts to support the hot restart [Reload] Timer process. After LaravelS receives the SIGUSR1 signal, it waits for max_wait_time(default 5) seconds to end the process, then the Manager process will pull up the Timer process again.

5.If you only need to use minute-level scheduled tasks, it is recommended to enable Hhxsv5\LaravelS\Illuminate\LaravelScheduleJob instead of Linux Crontab, so that you can follow the coding habits of Laravel task scheduling and configure Kernel.

// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
    // runInBackground() will start a new child process to execute the task. This is asynchronous and will not affect the execution timing of other tasks.
    $schedule->command(TestCommand::class)->runInBackground()->everyMinute();
}

Automatically reload after modifying code

  • Via inotify, support Linux only.

    1.Install inotify extension.

    2.Turn on the switch in Settings.

    3.Notice: Modify the file only in Linux to receive the file change events. It's recommended to use the latest Docker. Vagrant Solution.

  • Via fswatch, support OS X/Linux/Windows.

    1.Install fswatch.

    2.Run command in your project root directory.

    # Watch current directory
    ./bin/fswatch
    # Watch app directory
    ./bin/fswatch ./app
  • Via inotifywait, support Linux.

    1.Install inotify-tools.

    2.Run command in your project root directory.

    # Watch current directory
    ./bin/inotify
    # Watch app directory
    ./bin/inotify ./app
  • When the above methods does not work, the ultimate solution: set max_request=1,worker_num=1, so that Worker process will restart after processing a request. The performance of this method is very poor, so only development environment use.

Get the instance of SwooleServer in your project

/**
 * $swoole is the instance of `Swoole\WebSocket\Server` if enable WebSocket server, otherwise `Swoole\Http\Server`
 * @var \Swoole\WebSocket\Server|\Swoole\Http\Server $swoole
 */
$swoole = app('swoole');
var_dump($swoole->stats());
$swoole->push($fd, 'Push WebSocket message');

Use SwooleTable

1.Define Table, support multiple.

All defined tables will be created before Swoole starting.

// in file "config/laravels.php"
[
    // ...
    'swoole_tables'  => [
        // Scene:bind UserId & FD in WebSocket
        'ws' => [// The Key is table name, will add suffix "Table" to avoid naming conflicts. Here defined a table named "wsTable"
            'size'   => 102400,// The max size
            'column' => [// Define the columns
                ['name' => 'value', 'type' => \Swoole\Table::TYPE_INT, 'size' => 8],
            ],
        ],
        //...Define the other tables
    ],
    // ...
];

2.Access Table: all table instances will be bound on SwooleServer, access by app('swoole')->xxxTable.

namespace App\Services;
use Hhxsv5\LaravelS\Swoole\WebSocketHandlerInterface;
use Swoole\Http\Request;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;
class WebSocketService implements WebSocketHandlerInterface
{
    /**@var \Swoole\Table $wsTable */
    private $wsTable;
    public function __construct()
    {
        $this->wsTable = app('swoole')->wsTable;
    }
    // Scene:bind UserId & FD in WebSocket
    public function onOpen(Server $server, Request $request)
    {
        // var_dump(app('swoole') === $server);// The same instance
        /**
         * Get the currently logged in user
         * This feature requires that the path to establish a WebSocket connection go through middleware such as Authenticate.
         * E.g:
         * Browser side: var ws = new WebSocket("ws://127.0.0.1:5200/ws");
         * Then the /ws route in Laravel needs to add the middleware like Authenticate.
         * Route::get('/ws', function () {
         *     // Respond any content with status code 200
         *     return 'websocket';
         * })->middleware(['auth']);
         */
        // $user = Auth::user();
        // $userId = $user ? $user->id : 0; // 0 means a guest user who is not logged in
        $userId = mt_rand(1000, 10000);
        // if (!$userId) {
        //     // Disconnect the connections of unlogged users
        //     $server->disconnect($request->fd);
        //     return;
        // }
        $this->wsTable->set('uid:' . $userId, ['value' => $request->fd]);// Bind map uid to fd
        $this->wsTable->set('fd:' . $request->fd, ['value' => $userId]);// Bind map fd to uid
        $server->push($request->fd, "Welcome to LaravelS #{$request->fd}");
    }
    public function onMessage(Server $server, Frame $frame)
    {
        // Broadcast
        foreach ($this->wsTable as $key => $row) {
            if (strpos($key, 'uid:') === 0 && $server->isEstablished($row['value'])) {
                $content = sprintf('Broadcast: new message "%s" from #%d', $frame->data, $frame->fd);
                $server->push($row['value'], $content);
            }
        }
    }
    public function onClose(Server $server, $fd, $reactorId)
    {
        $uid = $this->wsTable->get('fd:' . $fd);
        if ($uid !== false) {
            $this->wsTable->del('uid:' . $uid['value']); // Unbind uid map
        }
        $this->wsTable->del('fd:' . $fd);// Unbind fd map
        $server->push($fd, "Goodbye #{$fd}");
    }
}

Multi-port mixed protocol

For more information, please refer to Swoole Server AddListener

To make our main server support more protocols not just Http and WebSocket, we bring the feature multi-port mixed protocol of Swoole in LaravelS and name it Socket. Now, you can build TCP/UDP applications easily on top of Laravel.

  1. Create Socket handler class, and extend Hhxsv5\LaravelS\Swoole\Socket\{TcpSocket|UdpSocket|Http|WebSocket}.

    namespace App\Sockets;
    use Hhxsv5\LaravelS\Swoole\Socket\TcpSocket;
    use Swoole\Server;
    class TestTcpSocket extends TcpSocket
    {
        public function onConnect(Server $server, $fd, $reactorId)
        {
            \Log::info('New TCP connection', [$fd]);
            $server->send($fd, 'Welcome to LaravelS.');
        }
        public function onReceive(Server $server, $fd, $reactorId, $data)
        {
            \Log::info('Received data', [$fd, $data]);
            $server->send($fd, 'LaravelS: ' . $data);
            if ($data === "quit\r\n") {
                $server->send($fd, 'LaravelS: bye' . PHP_EOL);
                $server->close($fd);
            }
        }
        public function onClose(Server $server, $fd, $reactorId)
        {
            \Log::info('Close TCP connection', [$fd]);
            $server->send($fd, 'Goodbye');
        }
    }

    These Socket connections share the same worker processes with your HTTP/WebSocket connections. So it won't be a problem at all if you want to deliver tasks, use SwooleTable, even Laravel components such as DB, Eloquent and so on. At the same time, you can access Swoole\Server\Port object directly by member property swoolePort.

    public function onReceive(Server $server, $fd, $reactorId, $data)
    {
        $port = $this->swoolePort; // Get the `Swoole\Server\Port` object
    }
    namespace App\Http\Controllers;
    class TestController extends Controller
    {
        public function test()
        {
            /**@var \Swoole\Http\Server|\Swoole\WebSocket\Server $swoole */
            $swoole = app('swoole');
            // $swoole->ports: Traverse all Port objects, https://www.swoole.co.uk/docs/modules/swoole-server/multiple-ports
            $port = $swoole->ports[0]; // Get the `Swoole\Server\Port` object, $port[0] is the port of the main server
            foreach ($port->connections as $fd) { // Traverse all connections
                // $swoole->send($fd, 'Send tcp message');
                // if($swoole->isEstablished($fd)) {
                //     $swoole->push($fd, 'Send websocket message');
                // }
            }
        }
    }
  2. Register Sockets.

    // Edit `config/laravels.php`
    //...
    'sockets' => [
        [
            'host'     => '127.0.0.1',
            'port'     => 5291,
            'type'     => SWOOLE_SOCK_TCP,// Socket type: SWOOLE_SOCK_TCP/SWOOLE_SOCK_TCP6/SWOOLE_SOCK_UDP/SWOOLE_SOCK_UDP6/SWOOLE_UNIX_DGRAM/SWOOLE_UNIX_STREAM
            'settings' => [// Swoole settings:https://www.swoole.co.uk/docs/modules/swoole-server-methods#swoole_server-addlistener
                'open_eof_check' => true,
                'package_eof'    => "\r\n",
            ],
            'handler'  => \App\Sockets\TestTcpSocket::class,
            'enable'   => true, // whether to enable, default true
        ],
    ],

    About the heartbeat configuration, it can only be set on the main server and cannot be configured on Socket, but the Socket inherits the heartbeat configuration of the main server.

    For TCP socket, onConnect and onClose events will be blocked when dispatch_mode of Swoole is 1/3, so if you want to unblock these two events please set dispatch_mode to 2/4/5.

    'swoole' => [
        //...
        'dispatch_mode' => 2,
        //...
    ];
  3. Test.

  • TCP: telnet 127.0.0.1 5291

  • UDP: [Linux] echo "Hello LaravelS" > /dev/udp/127.0.0.1/5292

  1. Register example of other protocols.

    • UDP
    'sockets' => [
        [
            'host'     => '0.0.0.0',
            'port'     => 5292,
            'type'     => SWOOLE_SOCK_UDP,
            'settings' => [
                'open_eof_check' => true,
                'package_eof'    => "\r\n",
            ],
            'handler'  => \App\Sockets\TestUdpSocket::class,
        ],
    ],
    • Http
    'sockets' => [
        [
            'host'     => '0.0.0.0',
            'port'     => 5293,
            'type'     => SWOOLE_SOCK_TCP,
            'settings' => [
                'open_http_protocol' => true,
            ],
            'handler'  => \App\Sockets\TestHttp::class,
        ],
    ],
    • WebSocket: The main server must turn on WebSocket, that is, set websocket.enable to true.
    'sockets' => [
        [
            'host'     => '0.0.0.0',
            'port'     => 5294,
            'type'     => SWOOLE_SOCK_TCP,
            'settings' => [
                'open_http_protocol'      => true,
                'open_websocket_protocol' => true,
            ],
            'handler'  => \App\Sockets\TestWebSocket::class,
        ],
    ],

Coroutine

Swoole Coroutine

  • Warning: The order of code execution in the coroutine is out of order. The data of the request level should be isolated by the coroutine ID. However, there are many singleton and static attributes in Laravel/Lumen, the data between different requests will affect each other, it's Unsafe. For example, the database connection is a singleton, the same database connection shares the same PDO resource. This is fine in the synchronous blocking mode, but it does not work in the asynchronous coroutine mode. Each query needs to create different connections and maintain IO state of different connections, which requires a connection pool.

  • DO NOT enable the coroutine, only the custom process can use the coroutine.

Custom process

Support developers to create special work processes for monitoring, reporting, or other special tasks. Refer addProcess.

  1. Create Proccess class, implements CustomProcessInterface.

    namespace App\Processes;
    use App\Tasks\TestTask;
    use Hhxsv5\LaravelS\Swoole\Process\CustomProcessInterface;
    use Hhxsv5\LaravelS\Swoole\Task\Task;
    use Swoole\Coroutine;
    use Swoole\Http\Server;
    use Swoole\Process;
    class TestProcess implements CustomProcessInterface
    {
        /**
         * @var bool Quit tag for Reload updates
         */
        private static $quit = false;
    
        public static function callback(Server $swoole, Process $process)
        {
            // The callback method cannot exit. Once exited, Manager process will automatically create the process 
            while (!self::$quit) {
                \Log::info('Test process: running');
                // sleep(1); // Swoole < 2.1
                Coroutine::sleep(1); // Swoole>=2.1: Coroutine & Runtime will be automatically enabled for callback(). Pay attention to the compatibility between the components used and the coroutines. If they are not compatible, only some coroutines can be enabled, such as: \Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_TCP | SWOOLE_HOOK_SLEEP | SWOOLE_HOOK_FILE);
                 // Deliver task in custom process, but NOT support callback finish() of task.
                // Note: Modify task_ipc_mode to 1 or 2 in config/laravels.php, see https://www.swoole.co.uk/docs/modules/swoole-server/configuration
                $ret = Task::deliver(new TestTask('task data'));
                var_dump($ret);
                // The upper layer will catch the exception thrown in the callback and record it in the Swoole log, and then this process will exit. The Manager process will re-create the process after 3 seconds, so developers need to try/catch to catch the exception by themselves to avoid frequent process creation.
                // throw new \Exception('an exception');
            }
        }
        // Requirements: LaravelS >= v3.4.0 & callback() must be async non-blocking program.
        public static function onReload(Server $swoole, Process $process)
        {
            // Stop the process...
            // Then end process
            \Log::info('Test process: reloading');
            self::$quit = true;
            // $process->exit(0); // Force exit process
        }
        // Requirements: LaravelS >= v3.7.4 & callback() must be async non-blocking program.
        public static function onStop(Server $swoole, Process $process)
        {
            // Stop the process...
            // Then end process
            \Log::info('Test process: stopping');
            self::$quit = true;
            // $process->exit(0); // Force exit process
        }
    }
  2. Register TestProcess.

    // Edit `config/laravels.php`
    // ...
    'processes' => [
        'test' => [ // Key name is process name
            'class'    => \App\Processes\TestProcess::class,
            'redirect' => false, // Whether redirect stdin/stdout, true or false
            'pipe'     => 0,     // The type of pipeline, 0: no pipeline 1: SOCK_STREAM 2: SOCK_DGRAM
            'enable'   => true,  // Whether to enable, default true
            //'num'    => 3   // To create multiple processes of this class, default is 1
            //'queue'    => [ // Enable message queue as inter-process communication, configure empty array means use default parameters
            //    'msg_key'  => 0,    // The key of the message queue. Default: ftok(__FILE__, 1).
            //    'mode'     => 2,    // Communication mode, default is 2, which means contention mode
            //    'capacity' => 8192, // The length of a single message, is limited by the operating system kernel parameters. The default is 8192, and the maximum is 65536
            //],
            //'restart_interval' => 5, // After the process exits abnormally, how many seconds to wait before restarting the process, default 5 seconds
        ],
    ],
  3. Note: The callback() cannot quit. If quit, the Manager process will re-create the process.

  4. Example: Write data to a custom process.

    // config/laravels.php
    'processes' => [
        'test' => [
            'class'    => \App\Processes\TestProcess::class,
            'redirect' => false,
            'pipe'     => 1,
        ],
    ],
    // app/Processes/TestProcess.php
    public static function callback(Server $swoole, Process $process)
    {
        while ($data = $process->read()) {
            \Log::info('TestProcess: read data', [$data]);
            $process->write('TestProcess: ' . $data);
        }
    }
    // app/Http/Controllers/TestController.php
    public function testProcessWrite()
    {
        /**@var \Swoole\Process[] $process */
        $customProcesses = \Hhxsv5\LaravelS\LaravelS::getCustomProcesses();
        $process = $customProcesses['test'];
        $process->write('TestController: write data' . time());
        var_dump($process->read());
    }

Common components

Apollo

LaravelS will pull the Apollo configuration and write it to the .env file when starting. At the same time, LaravelS will start the custom process apollo to monitor the configuration and automatically reload when the configuration changes.

  1. Enable Apollo: add --enable-apollo and Apollo parameters to the startup parameters.

    php bin/laravels start --enable-apollo --apollo-server=http://127.0.0.1:8080 --apollo-app-id=LARAVEL-S-TEST
  2. Support hot updates(optional).

    // Edit `config/laravels.php`
    'processes' => Hhxsv5\LaravelS\Components\Apollo\Process::getDefinition(),
    // When there are other custom process configurations
    'processes' => [
        'test' => [
            'class'    => \App\Processes\TestProcess::class,
            'redirect' => false,
            'pipe'     => 1,
        ],
        // ...
    ] + Hhxsv5\LaravelS\Components\Apollo\Process::getDefinition(),
  3. List of available parameters.

Parameter Description Default Demo
apollo-server Apollo server URL - --apollo-server=http://127.0.0.1:8080
apollo-app-id Apollo APP ID - --apollo-app-id=LARAVEL-S-TEST
apollo-namespaces The namespace to which the APP belongs, support specify the multiple application --apollo-namespaces=application --apollo-namespaces=env
apollo-cluster The cluster to which the APP belongs default --apollo-cluster=default
apollo-client-ip IP of current instance, can also be used for grayscale publishing Local intranet IP --apollo-client-ip=10.2.1.83
apollo-pull-timeout Timeout time(seconds) when pulling configuration 5 --apollo-pull-timeout=5
apollo-backup-old-env Whether to backup the old configuration file when updating the configuration file .env false --apollo-backup-old-env

Prometheus

Support Prometheus monitoring and alarm, Grafana visually view monitoring metrics. Please refer to Docker Compose for the environment construction of Prometheus and Grafana.

  1. Require extension APCu >= 5.0.0, please install it by pecl install apcu.

  2. Copy the configuration file prometheus.php to the config directory of your project. Modify the configuration as appropriate.

    # Execute commands in the project root directory
    cp vendor/hhxsv5/laravel-s/config/prometheus.php config/

    If your project is Lumen, you also need to manually load the configuration $app->configure('prometheus'); in bootstrap/app.php.

  3. Configure global middleware: Hhxsv5\LaravelS\Components\Prometheus\RequestMiddleware::class. In order to count the request time consumption as accurately as possible, RequestMiddleware must be the first global middleware, which needs to be placed in front of other middleware.

  4. Register ServiceProvider: Hhxsv5\LaravelS\Components\Prometheus\ServiceProvider::class.

  5. Configure the CollectorProcess in config/laravels.php to collect the metrics of Swoole Worker/Task/Timer processes regularly.

    'processes' => Hhxsv5\LaravelS\Components\Prometheus\CollectorProcess::getDefinition(),
  6. Create the route to output metrics.

    use Hhxsv5\LaravelS\Components\Prometheus\Exporter;
    
    Route::get('/actuator/prometheus', function () {
        $result = app(Exporter::class)->render();
        return response($result, 200, ['Content-Type' => Exporter::REDNER_MIME_TYPE]);
    });
  7. Complete the configuration of Prometheus and start it.

    global:
      scrape_interval: 5s
      scrape_timeout: 5s
      evaluation_interval: 30s
    scrape_configs:
    - job_name: laravel-s-test
      honor_timestamps: true
      metrics_path: /actuator/prometheus
      scheme: http
      follow_redirects: true
      static_configs:
      - targets:
        - 127.0.0.1:5200 # The ip and port of the monitored service
    # Dynamically discovered using one of the supported service-discovery mechanisms
    # https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config
    # - job_name: laravels-eureka
    #   honor_timestamps: true
    #   scrape_interval: 5s
    #   metrics_path: /actuator/prometheus
    #   scheme: http
    #   follow_redirects: true
      # eureka_sd_configs:
      # - server: http://127.0.0.1:8080/eureka
      #   follow_redirects: true
      #   refresh_interval: 5s
  8. Start Grafana, then import panel json.

Grafana Dashboard

Other features

Configure Swoole events

Supported events:

Event Interface When happened
ServerStart Hhxsv5\LaravelS\Swoole\Events\ServerStartInterface Occurs when the Master process is starting, this event should not handle complex business logic, and can only do some simple work of initialization.
ServerStop Hhxsv5\LaravelS\Swoole\Events\ServerStopInterface Occurs when the server exits normally, CANNOT use async or coroutine related APIs in this event.
WorkerStart Hhxsv5\LaravelS\Swoole\Events\WorkerStartInterface Occurs after the Worker/Task process is started, and the Laravel initialization has been completed.
WorkerStop Hhxsv5\LaravelS\Swoole\Events\WorkerStopInterface Occurs after the Worker/Task process exits normally
WorkerError Hhxsv5\LaravelS\Swoole\Events\WorkerErrorInterface Occurs when an exception or fatal error occurs in the Worker/Task process

1.Create an event class to implement the corresponding interface.

namespace App\Events;
use Hhxsv5\LaravelS\Swoole\Events\ServerStartInterface;
use Swoole\Atomic;
use Swoole\Http\Server;
class ServerStartEvent implements ServerStartInterface
{
    public function __construct()
    {
    }
    public function handle(Server $server)
    {
        // Initialize a global counter (available across processes)
        $server->atomicCount = new Atomic(2233);

        // Invoked in controller: app('swoole')->atomicCount->get();
    }
}
namespace App\Events;
use Hhxsv5\LaravelS\Swoole\Events\WorkerStartInterface;
use Swoole\Http\Server;
class WorkerStartEvent implements WorkerStartInterface
{
    public function __construct()
    {
    }
    public function handle(Server $server, $workerId)
    {
        // Initialize a database connection pool
        // DatabaseConnectionPool::init();
    }
}

2.Configuration.

// Edit `config/laravels.php`
'event_handlers' => [
    'ServerStart' => [\App\Events\ServerStartEvent::class], // Trigger events in array order
    'WorkerStart' => [\App\Events\WorkerStartEvent::class],
],

Serverless

Alibaba Cloud Function Compute

Function Compute.

1.Modify bootstrap/app.php and set the storage directory. Because the project directory is read-only, the /tmp directory can only be read and written.

$app->useStoragePath(env('APP_STORAGE_PATH', '/tmp/storage'));

2.Create a shell script laravels_bootstrap and grant executable permission.

#!/usr/bin/env bash
set +e

# Create storage-related directories
mkdir -p /tmp/storage/app/public
mkdir -p /tmp/storage/framework/cache
mkdir -p /tmp/storage/framework/sessions
mkdir -p /tmp/storage/framework/testing
mkdir -p /tmp/storage/framework/views
mkdir -p /tmp/storage/logs

# Set the environment variable APP_STORAGE_PATH, please make sure it's the same as APP_STORAGE_PATH in .env
export APP_STORAGE_PATH=/tmp/storage

# Start LaravelS
php bin/laravels start

3.Configure template.xml.

ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
  laravel-s-demo:
    Type: 'Aliyun::Serverless::Service'
    Properties:
      Description: 'LaravelS Demo for Serverless'
    fc-laravel-s:
      Type: 'Aliyun::Serverless::Function'
      Properties:
        Handler: laravels.handler
        Runtime: custom
        MemorySize: 512
        Timeout: 30
        CodeUri: ./
        InstanceConcurrency: 10
        EnvironmentVariables:
          BOOTSTRAP_FILE: laravels_bootstrap

Important notices

Singleton Issue

  • Under FPM mode, singleton instances will be instantiated and recycled in every request, request start=>instantiate instance=>request end=>recycled instance.

  • Under Swoole Server, All singleton instances will be held in memory, different lifetime from FPM, request start=>instantiate instance=>request end=>do not recycle singleton instance. So need developer to maintain status of singleton instances in every request.

  • Common solutions:

    1. Write a XxxCleaner class to clean up the singleton object state. This class implements the interface Hhxsv5\LaravelS\Illuminate\Cleaners\CleanerInterface and then registers it in cleaners of laravels.php.

    2. Reset status of singleton instances by Middleware.

    3. Re-register ServiceProvider, add XxxServiceProvider into register_providers of file laravels.php. So that reinitialize singleton instances in every request Refer.

Cleaners

Configuration cleaners.

Known issues

Known issues: a package of known issues and solutions.

Debugging method

  • Logging; if you want to output to the console, you can use stderr, Log::channel('stderr')->debug('debug message').

  • Laravel Dump Server(Laravel 5.7 has been integrated by default).

Read request

Read request by Illuminate\Http\Request Object, $_ENV is readable, $_SERVER is partially readable, CANNOT USE $_GET/$_POST/$_FILES/$_COOKIE/$_REQUEST/$_SESSION/$GLOBALS.

public function form(\Illuminate\Http\Request $request)
{
    $name = $request->input('name');
    $all = $request->all();
    $sessionId = $request->cookie('sessionId');
    $photo = $request->file('photo');
    // Call getContent() to get the raw POST body, instead of file_get_contents('php://input')
    $rawContent = $request->getContent();
    //...
}

Output response

Respond by Illuminate\Http\Response Object, compatible with echo/vardump()/print_r(),CANNOT USE functions dd()/exit()/die()/header()/setcookie()/http_response_code().

public function json()
{
    return response()->json(['time' => time()])->header('header1', 'value1')->withCookie('c1', 'v1');
}

Persistent connection

Singleton connection will be resident in memory, it is recommended to turn on persistent connection for better performance.

  1. Database connection, it will reconnect automatically immediately after disconnect.
// config/database.php
'connections' => [
    'my_conn' => [
        'driver'    => 'mysql',
        'host'      => env('DB_MY_CONN_HOST', 'localhost'),
        'port'      => env('DB_MY_CONN_PORT', 3306),
        'database'  => env('DB_MY_CONN_DATABASE', 'forge'),
        'username'  => env('DB_MY_CONN_USERNAME', 'forge'),
        'password'  => env('DB_MY_CONN_PASSWORD', ''),
        'charset'   => 'utf8mb4',
        'collation' => 'utf8mb4_unicode_ci',
        'prefix'    => '',
        'strict'    => false,
        'options'   => [
            // Enable persistent connection
            \PDO::ATTR_PERSISTENT => true,
        ],
    ],
],
  1. Redis connection, it won't reconnect automatically immediately after disconnect, and will throw an exception about lost connection, reconnect next time. You need to make sure that SELECT DB correctly before operating Redis every time.
// config/database.php
'redis' => [
    'client' => env('REDIS_CLIENT', 'phpredis'), // It is recommended to use phpredis for better performance.
    'default' => [
        'host'       => env('REDIS_HOST', 'localhost'),
        'password'   => env('REDIS_PASSWORD', null),
        'port'       => env('REDIS_PORT', 6379),
        'database'   => 0,
        'persistent' => true, // Enable persistent connection
    ],
],

About memory leaks

  • Avoid using global variables. If necessary, please clean or reset them manually.

  • Infinitely appending element into static/global variable will lead to OOM(Out of Memory).

    class Test
    {
        public static $array = [];
        public static $string = '';
    }
    
    // Controller
    public function test(Request $req)
    {
        // Out of Memory
        Test::$array[] = $req->input('param1');
        Test::$string .= $req->input('param2');
    }
  • Memory leak detection method

    1. Modify config/laravels.php: worker_num=1, max_request=1000000, remember to change it back after test;

    2. Add routing /debug-memory-leak without route middleware to observe the memory changes of the Worker process;

    Route::get('/debug-memory-leak', function () {
        global $previous;
        $current = memory_get_usage();
        $stats = [
            'prev_mem' => $previous,
            'curr_mem' => $current,
            'diff_mem' => $current - $previous,
        ];
        $previous = $current;
        return $stats;
    });
    1. Start LaravelS and request /debug-memory-leak until diff_mem is less than or equal to zero; if diff_mem is always greater than zero, it means that there may be a memory leak in Global Middleware or Laravel Framework;

    2. After completing Step 3, alternately request the business routes and /debug-memory-leak (It is recommended to use ab/wrk to make a large number of requests for business routes), the initial increase in memory is normal. After a large number of requests for the business routes, if diff_mem is always greater than zero and curr_mem continues to increase, there is a high probability of memory leak; If curr_mem always changes within a certain range and does not continue to increase, there is a low probability of memory leak.

    3. If you still can't solve it, max_request is the last guarantee.

Linux kernel parameter adjustment

Linux kernel parameter adjustment

Pressure test

Pressure test

Alternatives

Sponsor

License

MIT

laravel-s's People

Contributors

abnud1 avatar blankqwq avatar bluebellx7 avatar celaraze avatar davidhhuan avatar dizys avatar drzippie avatar eleven26 avatar filakhtov avatar hellozouyou avatar hhxsv5 avatar hooklife avatar imanghafoori1 avatar lee1031 avatar louis-ni avatar m01i0ng avatar mechstud avatar meiyoufengzhengdexian avatar never615 avatar nicochenyt avatar palpalani avatar photondragon avatar reatang avatar shenstef avatar snowlyg avatar summerkk avatar to2false avatar vinhais avatar wi1dcard avatar woann avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

laravel-s's Issues

报404

怎么打log?只能用file_put_contents这个函数么?貌似也不能正确打日志,感觉调试好麻烦

nginx模式无法上传文件,一直在loading

  1. Tell us your PHP version(php -v)
PHP 7.0.20-2 (cli) (built: Jun 14 2017 05:30:04) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2017 Zend Technologies
    with Zend OPcache v7.0.20-2, Copyright (c) 1999-2017, by Zend Technologies
  1. Tell us your Swoole version(php --ri swoole)
swoole

swoole support => enabled
Version => 2.1.3
Author => tianfeng.han[email: [email protected]]
coroutine => enabled
epoll => enabled
eventfd => enabled
timerfd => enabled
signalfd => enabled
cpu affinity => enabled
spinlock => enabled
rwlock => enabled
async http/websocket client => enabled
Linux Native AIO => enabled
pcre => enabled
zlib => enabled
mutex_timedlock => enabled
pthread_barrier => enabled
futex => enabled

Directive => Local Value => Master Value
swoole.aio_thread_num => 2 => 2
swoole.display_errors => On => On
swoole.use_namespace => On => On
swoole.use_shortname => On => On
swoole.fast_serialize => Off => Off
swoole.unixsock_buffer_size => 8388608 => 8388608
  1. Tell us your Laravel/Lumen version(check composer.json & composer.lock)

"laravel/framework": "5.5.*",

  1. Detail description about this issue(error/log)

文件无法上传,一直处于loading状态,nginx是根据README里给出的配置弄的

  1. Give us a reproducible code block and steps
//TODO: Your code

请教一个关注注册laravels全局实例的问题

情景:
我的程序里面有一个服务需要加载字典,字典内容多需要加载的时间长。因此我想把服务像laravel的app一样被laravels从入口处加载,这样就可以一次加载,后面所有请求都直接调用,免去每个请求都把它实例化一次,把字典重新读取一次这么浪费时间。

问题:
那么我应该怎么把这个加载字典的工具,跟laravel的框架一起实例化加载到swoole中运行呢?

Dingo api: DataArraySerializer is not applied

  1. Tell us your PHP version(php -v)

PHP 7.2.6 (cli) (built: May 26 2018 07:45:18) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies with Xdebug v2.6.0, Copyright (c) 2002-2018, by Derick Rethans

  1. Tell us your Swoole version(php --ri swoole)

swoole support => enabled
Version => 2.2.0
Author => tianfeng.han[email: [email protected]]
coroutine => enabled
epoll => enabled
eventfd => enabled
timerfd => enabled
signalfd => enabled
cpu affinity => enabled
spinlock => enabled
rwlock => enabled
async http/websocket client => enabled
Linux Native AIO => enabled
pcre => enabled
zlib => enabled
mutex_timedlock => enabled
pthread_barrier => enabled
futex => enabled

Directive => Local Value => Master Value
swoole.aio_thread_num => 2 => 2
swoole.display_errors => On => On
swoole.use_namespace => On => On
swoole.use_shortname => On => On
swoole.fast_serialize => Off => Off
swoole.unixsock_buffer_size => 8388608 => 8388608
  1. Tell us your Laravel/Lumen version(check composer.json & composer.lock)

Lumen (5.6.3)

  1. Detail description about this issue(error/log)

If you use Dingo api from the second response the data misses the "data" wrapper set in a provider (DataArraySerializer). Even if I re register with the config, the wrapper does not come back.

  1. Give us a reproducible code block and steps

The provider where I set thhe response adapter to DataArraySerializer:

<?php

namespace App\Providers;

use League\Fractal\Manager;
use Dingo\Api\Transformer\Factory;
use Illuminate\Support\ServiceProvider;
use Dingo\Api\Transformer\Adapter\Fractal;
use League\Fractal\Serializer\DataArraySerializer;

class DingoServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    public function boot()
    {
        app(Factory::class)->setAdapter(function ($app) {
            return new Fractal(new Manager, 'include', ',');
        });

        app(Factory::class)->setAdapter(function ($app) {
            $fractal = new Manager;
            $fractal->setSerializer(new DataArraySerializer());

            return new Fractal($fractal);
        });
    }
}

I register this in the bootstrap/app.php: $app->register(App\Providers\DingoServiceProvider::class);
On the first request/response the response is wrapped inside a "data" array. The second response gives exactly the same output, but without the data array wrapping.

First response:

{"data":[{"id":1,"name":"asd, ...}]}

Second response:

[{"id":1,"name":"asd, ...}]

I tried setting the "register_providers" array, and add the boot function lines to the register method.
The problem is, that the cleaning between two requests clears something and I cannot set it back.

php7.0 artisan laravels start报错

In LaravelSCommand.php line 69:

Call to undefined function swoole_version();

我已经安装好了swoole 2.1.1,phpinfo里已经显示enable

启动的时候有未知事件监听

php7.1.14 swoole2.0.7
Warning: Swoole\Server::on(): Unknown event types[WorkerExit] in /workspace/localsite/eMonitor/vendor/hhxsv5/laravel-s/src/Swoole/Server.php on line 54

第54行
if (version_compare(\swoole_version(), '1.9.17', '>=')) {
$this->swoole->on('WorkerExit', [$this, 'onWorkerExit']);
}

swoole2.0.7 的server里是不是没有了workerExit事件,swoole文档真tm差劲

咨询一下生产环境部署代码reload的问题

生产环境是通过每次更新新tag的代码,然后修改软链接到线上环境,但是框架onWokerStart之前已经使用base_path获取了之前软连接的实际路径,导致reload不生效,是否有好的解决办法。

使用 jwt 时,auth 获取当前登录用户有问题。

  1. Tell us your PHP version(php -v)

PHP 7.1.8

  1. Tell us your Swoole version(php --ri swoole)

4.0.0-alpha

  1. Tell us your Laravel/Lumen version(check composer.json & composer.lock)

Laravel 5.6

  1. Detail description about this issue(error/log)

不是用 laravels 是一切正常的,但是用后,每运行一段时间,jwt 的接口就会出现401,jwt 的 token 是新声称的(jwt的 token 是新生成的)。
尝试过把reactor_numworker_num设置成1,没有解决问题。

  1. Give us a reproducible code block and steps

middleware

public function handle($request, Closure $next)
    {

        $admin = auth()->user();

        try {
            // ...
        } catch (\Exception $e) {
			// 每次会走进来这里
            throw new BadRoleException();
        }

        return $next($request);
    }

laravels.php

        'daemonize'          => env('LARAVELS_DAEMONIZE', true),
        'dispatch_mode'      => 1,
        'reactor_num'        => 1,
        'worker_num'         => 1,
        'task_ipc_mode'      => 3,
        'task_max_request'   => 3000,
        'task_tmpdir'        => @is_writable('/dev/shm/') ? '/dev/shm' : '/tmp',
        'message_queue_key'  => ftok(base_path('public/index.php'), 1),
        'max_request'        => 3000,
        'open_tcp_nodelay'   => true,
        'pid_file'           => storage_path('laravels.pid'),
        'log_file'           => storage_path(sprintf('logs/swoole-%s.log', date('Y-m'))),
        'log_level'          => 5,
        'document_root'      => base_path('public'),
        'buffer_output_size' => 32 * 1024 * 1024,
        'socket_buffer_size' => 128 * 1024 * 1024,
        'reload_async'       => true,
        'max_wait_time'      => 60,
        'enable_reuse_port'  => true,

兼容laravelcollective/html包FORM SESSION token的问题

通过
{!!Form::open( array( 'method' => 'POST',)) !!}
自动创建表单,会自动创建 csrf token 的字段,多用户登录后,会发现token值会混乱掉,导致无法提交表单.

同时用 {{csrf_token()}}这个手动生成的字段,token值没有问题.

同一页面观察,发现不断刷新页面,FORM自动生成的token每次都会变化,截图如下

1
刷新之后
2

WebSocket模式下静态资源的访问

我想请教下,在第一个配置(我认为是http)和websocket服务器下的配置有什么区别呢?
在websocket下配置nginx,无法访问public下的静态资源文件,配置内容是直接粘贴的。
而且在第一个配置下也是可以用websocket的呀

task method can only be used in the worker process.

  1. Tell us your PHP version(php -v)
    7.0.15

  2. Tell us your Swoole version(php --ri swoole)
    4.0.1

  3. Tell us your Laravel/Lumen version(check composer.json & composer.lock)
    Laravel Framework | 5.4.36

  4. Detail description about this issue(error/log)
    [ERROR] LaravelS: Uncaught exception 'ErrorException': [0]Swoole\Server::task(): task method can only be used in the worker process.

  5. Give us a reproducible code block and steps

use App\Tasks\ExecuteTask;
use Hhxsv5\LaravelS\Swoole\Timer\CronJob;

class Timer extends CronJob
{

    public function __construct()
    {
    }

    public function interval()
    {
        return 1000;// Run every 1 minute
    }

    public function isImmediate()
    {
        return false;
    }

    public function run()
    {
        $task = new ExecuteTask(Task::find(1));
        \Hhxsv5\LaravelS\Swoole\Task\Task::deliver($task);
    }
}

class ExecuteTask extends \Hhxsv5\LaravelS\Swoole\Task\Task
{
}

app/config/laravels.php

    'timer'              => [
        'enable' => true,
        'jobs'   => [
            \App\Jobs\Timer::class,
        ],
    ],
    'swoole'             => [
        'worker_num'         => 8,
   ]
  1. 说明
    为何这样做:想依赖swoole的秒级crontab来分发异步任务
    错误描述:swoole的timer进程如何来发起异步任务给worker进程呢,不知道有没有可以实行的办法,如果有,还望指教。
    感谢laravelS提供了封装这么好的一个swoole包

ws connection return 404

  1. Tell us your PHP version(php -v)
    7.1.14
    TODO

  2. Tell us your Swoole version(php --ri swoole)
    3.0.0-alpha
    TODO

  3. Tell us your Laravel/Lumen version(check composer.json & composer.lock)
    5.5.40
    TODO

  4. Detail description about this issue(error/log)
    image

TODO

  1. Give us a reproducible code block and steps
    My nginx config file is the same as you suggested(I used ssl cr), my js code is like this
    var ws = new WebSocket("wss://wqw.hfvi.cn/ws");
    ws.onopen = function() {
    console.log("send message");
    };

     ws.onmessage = function (evt) {
    
     };
    
     ws.onclose = function() {
    
     };
    
//TODO: Your code

事务中嵌套事务使用会报错

  1. Tell us your PHP version(php -v)
    7.2.6
    TODO

  2. Tell us your Swoole version(php --ri swoole)
    4.0.0
    TODO

  3. Tell us your Laravel/Lumen version(check composer.json & composer.lock)
    5.5.33
    TODO

  4. Detail description about this issue(error/log)
    error
    TODO

  5. Give us a reproducible code block and steps

//TODO: Your code

public function test (Request $request)
{
$data = DB::transaction(function (){
return DB::transaction(function (){
return ['a' => 1];
});
});
return $this->apiResponse(0, "success", $data);
}

开启laravels的时候swoole日志->fatal: Not a git repository (or any of the parent directories): .git

  1. Tell us your PHP version(php -v)

TODO php5.6.33 php7.0.19

  1. Tell us your Swoole version(php --ri swoole)

TODO php5.6.33->1.10.1 php7.0.19->2.0.9

  1. Tell us your Laravel/Lumen version(check composer.json & composer.lock)

TODO Lumenv5.2.5

  1. Detail description about this issue(error/log)

TODO php artisan laravels start
storage/logs/swoole-2018-06.log

fatal: Not a git repository (or any of the parent directories): .git
fatal: Not a git repository (or any of the parent directories): .git
fatal: Not a git repository (or any of the parent directories): .git

但是不影响api接口调用

  1. Give us a reproducible code block and steps
//TODO: Your code

$_SERVER中缺少HTTPS字段导致asset()函数返回的URL总是http开头

  1. Tell us your PHP version(php -v)

php 7.0.6

  1. Tell us your Swoole version(php --ri swoole)

2.1.3

  1. Tell us your Laravel/Lumen version(check composer.json & composer.lock)

Laravel 5.2.45

  1. Detail description about this issue(error/log)

站点是https的,在模板文件中引入css文件

<link rel="stylesheet" href="{{asset('/css/admin/bootstrap-multiselect.css')}}">

asset('/css/admin/bootstrap-multiselect.css')返回的URL是 http://xxxx 而不是 https://xxxx
https站点下尝试加载非https的内容,导致报错
通过追查asset()函数,最终发现是根据$_SERVER['HTTPS']的值来决定以https或以http开头生成URL

对比nginx+phpfpm,nginx+laravelS除$_SERVER['HTTPS']以外,还少了不少字段,如:
SERVER_NAME、REQUEST_SCHEME

$swoole = app('swoole'); 报错 Class swoole does not exist

  1. Tell us your PHP version(php -v)

7.2.5

  1. Tell us your Swoole version(php --ri swoole)

2.0.7

  1. Tell us your Laravel/Lumen version(check composer.json & composer.lock)

5.6.24

  1. Detail description about this issue(error/log)

在Controller中,调用 $swoole = app('swoole'); 报错 Class swoole does not exist

使用第三方库flash作为提示,与fpm运行结果不一致

  1. Tell us your PHP version(php -v)

7.1.16

  1. Tell us your Swoole version(php --ri swoole)

4.0.0-beta

  1. Tell us your Laravel/Lumen version(check composer.json & composer.lock)

5.5.40

  1. Detail description about this issue(error/log)

使用第三方库flash做表单验证提醒,当第一次提醒成功时候有一次提醒,当第二次成功的时候有两个提醒,每次会增多,刷新都没用,而在fpm下运行不会出现此结果,该库的地址https://github.com/laracasts/flash,麻烦大神帮我看看
image

  1. Give us a reproducible code block and steps
// controller
flash("添加成功")->success()->important();
return view('test');

// view
@extends('layouts.app')
@section('content')
    @include('flash::message')
@endsection

使用laravel-admin配合laravels导出文件时报错

  1. Tell us your PHP version(php -v)
    7.2.5

  2. Tell us your Swoole version(php --ri swoole)
    2.1.3

  3. Tell us your Laravel/Lumen version(check composer.json & composer.lock)
    5.6.21

  4. Detail description about this issue(error/log)
    用laravels做http服务器,在使用laravel-admin后台导出cvs文件时报错500
    laravels log会记录文件信息
    而使用nginx则会直接输出cvs文件以供下载

laravels输出格式问题吗?

  1. Give us a reproducible code block and steps

Laravel passport 配置参数污染问题

一开始是页面会莫名的退出登录,然后再次尝试登录的时候就出错了。查看日志提示是 AuthManager 的某个方法未定义。

Method attempt does not exist. {“exception”:”[object] (BadMethodCallException(code: 0): Method attempt does not exist.

这就有点奇怪了,明明在 php-fpm 下啥事都没有。更加邪门的是,这个在正式环境下才有问题,测试环境下一点问题都没有。后来仔细排查,阅读了这部分的所有相关源码之后,发现 Auth driver 加载的居然是 api 的,然而这个是 web 的路由呀。仔细追查下去,发现是 Laravel passport 在加载时改写了 config,在调用 shouldUse 方法时,如果是 api 路由,就把默认的 driver 改成他自己了,这样在 api 路由当然没问题,可老铁。。。如果处理这个请求的 Worker 后来又去请求 web 了,默认的 driver 就一直是 api 了,所以才会出现上面这种情况。

知道是什么原因了,解决就好说了,把 config 给重置下,可怎么重置也略有点蛋疼。。。

ob_* 等函数使用问题

我在业务中有使用ob_flush函数用来做持续输出,不过在laravelS无法输出,只输出到了日志文件里

        echo "<script>parent.displayMsg(\"{$msg}\");</script>";
        ob_flush();
        flush();

修改LARAVELS_INOTIFY_RELOAD为true后,不能热更新代码

  1. Tell us your PHP version(php -v)
    php7.2 + lumen

  2. Tell us your Swoole version(php --ri swoole)

3.0.0-alpha

  1. Tell us your Laravel/Lumen version(check composer.json & composer.lock)

Lumen (5.6.3)

  1. Detail description about this issue(error/log)

已经安装inotify拓展,版本为 2.0.0 但是更新代码后不生效,并且reload配置更新为true, 我是搭建在docker容器内的

  1. Give us a reproducible code block
.env
LARAVELS_INOTIFY_RELOAD=true

注销session后,再执行一次WebsocketService.php的onOpen事件,session又会回来

  1. Tell us your PHP version(php -v)

php 7.1.7

  1. Tell us your Swoole version(php --ri swoole)

swoole 2.0.12

  1. Tell us your Laravel/Lumen version(check composer.json & composer.lock)

laravel 5.6.2

  1. Detail description about this issue(error/log)

_27o 7p7tkusw5jwl9hr

1.登陆后打开websocked连接页面触发onOpen事件
pp5 g _phmp oh h5 fxo
2.注销用户登录(注销后后台首页已经不能访问了)
z54k0 nmydacq4u4rk xt1
3.重复打开websocked连接页面触发onOpen事件
4.打开后台首页(此时又能正常访问),session又有了
1 m7 306fao 3f 575

感觉是Laravel的session清除后,onOpen事件中开启的Laravel生命周期还在,刷新一下连接又恢复原来的session了

使用协程Mysql之后出现Gateway time out

| PHP | 7.1.17 |
| Swoole | 2.1.3 |
| Laravel Framework | 5.6.26

  1. Detail description about this issue(error/log)
    正常的返回数组数据之类都是没有, 正常的使用原有mysql driver也能够正常查询数据.
    当使用协程mysql, 并且按修改了配置之后, 查询就会出现504 gateway time out

upstream timed out (110: Connection timed out) while reading response header from upstream

  1. Give us a reproducible code block and steps

app.php 中替换了 databaseServiceProvider

database.php 的配置:

'mysql' => [
            'driver' => 'sw-co-mysql',
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'strict' => true,
            'engine' => null,
        ],
`

TestController
```php
public function index() {
        $res = Post::where('id',1)->get();
        return response()->json($res->toArray());
    }
`

使用
```php
DB::select('SELECT * FROM posts WHERE id=1');
`

也是一样的效果.


Nginx配置同文档一致,  使用 php artisan laravels start 启动. 

协程mysql下遇到的问题

  1. Tell us your PHP version(php -v)
    7.2.6
    TODO

  2. Tell us your Swoole version(php --ri swoole)
    4.0.0
    TODO

  3. Tell us your Laravel/Lumen version(check composer.json & composer.lock)
    5.5.33
    TODO

  4. Detail description about this issue(error/log)
    error
    TODO

  5. Give us a reproducible code block and steps

//TODO: Your code

使用协程mysql普通业务逻辑查询,大概有6人同时使用
local.ERROR: Swoole\Coroutine\MySQL::prepare(): mysql client is waiting response, cannot send new sql query. (SQL: select did from sys_district where pk_corp = 1121 and sys_district.deleted_at is null limit 1) {"exception":"[object] (Illuminate\Database\QueryException(code: 0): Swoole\Coroutine\MySQL::prepare(): mysql client is waiting response, cannot send new sql query. (SQL: select did from sys_district where pk_corp = 1121 and sys_district.deleted_at is null limit 1) at /data/nginx/pm-api/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664, ErrorException(code: 0): Swoole\Coroutine\MySQL::prepare(): mysql client is waiting response, cannot send new sql query. at /data/nginx/pm-api/vendor/hhxsv5/laravel-s/src/Illuminate/Database/CoroutineMySQL.php:23)
[stacktrace]
#0 /data/nginx/pm-api/vendor/laravel/framework/src/Illuminate/Database/Connection.php(624): Illuminate\Database\Connection->runQueryCallback('select did fr...', Array, Object(Closure))
#1 /data/nginx/pm-api/vendor/laravel/framework/src/Illuminate/Database/Connection.php(333): Illuminate\Database\Connection->run('select did fr...', Array, Object(Closure))
#2 /data/nginx/pm-api/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php(1707): Illuminate\Database\Connection->select('select did fr...', Array, true)
#3 /data/nginx/pm-api/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php(1692): Illuminate\Database\Query\Builder->runSelect()
#4 /data/nginx/pm-api/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php(479): Illuminate\Database\Query\Builder->get(Array)
#5 /data/nginx/pm-api/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php(463): Illuminate\Database\Eloquent\Builder->getModels(Array)
#6 /data/nginx/pm-api/vendor/laravel/framework/src/Illuminate/Database/Concerns/BuildsQueries.php(77): Illuminate\Database\Eloquent\Builder->get(Array)
#7 /data/nginx/pm-api/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php(445): Illuminate\Database\Eloquent\Builder->first(Array)
#8 /data/nginx/pm-api/app/Http/Middleware/CheckSign.php(37): Illuminate\Database\Eloquent\Builder->value('did')
#9 /data/nginx/pm-api/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(149): App\Http\Middleware\CheckSign->handle(Object(Illuminate\Http\Request), Object(Closure))
#10 /data/nginx/pm-api/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(53): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
"laravel-2018-07-04.log" 35410L, 6468760C
#39 /data/nginx/pm-api/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(53): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#40 /data/nginx/pm-api/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/CheckForMaintenanceMode.php(46): Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}(Object(Illuminate\Http\Request))
#41 /data/nginx/pm-api/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(149): Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode->handle(Object(Illuminate\Http\Request), Object(Closure))
#42 /data/nginx/pm-api/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php(53): Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}(Object(Illuminate\Http\Request))
#43 /data/nginx/pm-api/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(102): Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}(Object(Illuminate\Http\Request))
#44 /data/nginx/pm-api/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(151): Illuminate\Pipeline\Pipeline->then(Object(Closure))
#45 /data/nginx/pm-api/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(116): Illuminate\Foundation\Http\Kernel->sendRequestThroughRouter(Object(Illuminate\Http\Request))
#46 /data/nginx/pm-api/vendor/hhxsv5/laravel-s/src/Illuminate/Laravel.php(184): Illuminate\Foundation\Http\Kernel->handle(Object(Illuminate\Http\Request))
#47 /data/nginx/pm-api/vendor/hhxsv5/laravel-s/src/LaravelS.php(165): Hhxsv5\LaravelS\Illuminate\Laravel->handleDynamic(Object(Illuminate\Http\Request))
#48 /data/nginx/pm-api/vendor/hhxsv5/laravel-s/src/LaravelS.php(123): Hhxsv5\LaravelS\LaravelS->handleDynamicResource(Object(Illuminate\Http\Request), Object(Swoole\Http\Response))
#49 {main}
"}

Cache store [] is not defined.

我的运行环境如下:
+-------------------+------------------------------------------+
| Component | Version |
+-------------------+------------------------------------------+
| PHP | 7.2.4 |
| Swoole | 2.1.3 |
| Laravel Framework | Lumen (5.5.2) (Laravel Components 5.5.*) |
+-------------------+------------------------------------------+

config/cache.php 相关配置:

'default' => env('CACHE_DRIVER', 'redis'),

'stores' => [
    'redis' => [
        'driver' => 'redis',
        'connection' => env('CACHE_REDIS_CONNECTION', 'default'),
    ],

执行步骤:
开启websocket服务,
在postman中请求一个api接口(有启用throttle中间件),
第一次返回正常,第二次报错:
Oops! An unexpected error occurred: Cache store [] is not defined.

错误日志:
[ERROR] LaravelS: onRequest: Uncaught exception "InvalidArgumentException"([0]Cache store [] is not defined.) at /xxx/vendor/illuminate/cache/CacheManager.php:96,
#0 /xxx/vendor/illuminate/cache/CacheManager.php(80): Illuminate\Cache\CacheManager->resolve(NULL)
#1 /xxx/vendor/illuminate/cache/CacheManager.php(58): Illuminate\Cache\CacheManager->get(NULL)
#2 /xxx/vendor/illuminate/cache/CacheManager.php(69): Illuminate\Cache\CacheManager->store(NULL)
#3 /xxx/vendor/illuminate/cache/CacheServiceProvider.php(28): Illuminate\Cache\CacheManager->driver()
#4 /xxx/vendor/illuminate/container/Container.php(749): Illuminate\Cache\CacheServiceProvider->Illuminate\Cache{closure}(Object(Laravel\Lumen\Application), Array)
#5 /xxx/vendor/illuminate/container/Container.php(631): Illuminate\Container\Container->build(Object(Closure))
#6 /xxx/vendor/illuminate/container/Container.php(586): Illuminate\Container\Container->resolve('cache.store', Array)
#7 /xxx/vendor/laravel/lumen-framework/src/Application.php(230): Illuminate\Container\Container->make('cache.store', Array)
#8 /xxx/vendor/illuminate/container/Container.php(885): Laravel\Lumen\Application->make('cache.store')
#9 /xxx/vendor/illuminate/container/Container.php(813): Illuminate\Container\Container->resolveClass(Object(ReflectionParameter))
#10 /xxx/vendor/illuminate/container/Container.php(780): Illuminate\Container\Container->resolveDependencies(Array)
#11 /xxx/vendor/illuminate/container/Container.php(631): Illuminate\Container\Container->build('Tymon\JWTAuth\P...')
#12 /xxx/vendor/illuminate/container/Container.php(586): Illuminate\Container\Container->resolve('Tymon\JWTAuth\P...', Array)
#13 /xxx/vendor/laravel/lumen-framework/src/Application.php(230): Illuminate\Container\Container->make('Tymon\JWTAuth\P...', Array)
#14 /xxx/vendor/tymon/jwt-auth/src/Providers/AbstractServiceProvider.php(377): Laravel\Lumen\Application->make('Tymon\JWTAuth\P...')
#15 /xxx/vendor/tymon/jwt-auth/src/Providers/AbstractServiceProvider.php(201): Tymon\JWTAuth\Providers\AbstractServiceProvider->getConfigInstance('providers.stora...')
#16 /xxx/vendor/illuminate/container/Container.php(749): Tymon\JWTAuth\Providers\AbstractServiceProvider->Tymon\JWTAuth\Providers{closure}(Object(Laravel\Lumen\Application), Array)
#17 /xxx/vendor/illuminate/container/Container.php(631): Illuminate\Container\Container->build(Object(Closure))
#18 /xxx/vendor/illuminate/container/Container.php(586): Illuminate\Container\Container->resolve('tymon.jwt.provi...', Array)
#19 /xxx/vendor/laravel/lumen-framework/src/Application.php(230): Illuminate\Container\Container->make('tymon.jwt.provi...', Array)
#20 /xxx/vendor/illuminate/container/Container.php(503): Laravel\Lumen\Application->make('tymon.jwt.provi...')
#21 /xxx/vendor/illuminate/container/Container.php(238): Illuminate\Container\Container->rebound('tymon.jwt.provi...')
#22 /xxx/vendor/illuminate/container/Container.php(332): Illuminate\Container\Container->bind('tymon.jwt.provi...', Object(Closure), true)
#23 /xxx/vendor/tymon/jwt-auth/src/Providers/AbstractServiceProvider.php(202): Illuminate\Container\Container->singleton('tymon.jwt.provi...', Object(Closure))
#24 /xxx/vendor/tymon/jwt-auth/src/Providers/AbstractServiceProvider.php(75): Tymon\JWTAuth\Providers\AbstractServiceProvider->registerStorageProvider()
#25 /xxx/vendor/laravel/lumen-framework/src/Application.php(193): Tymon\JWTAuth\Providers\AbstractServiceProvider->register()
#26 /xxx/vendor/hhxsv5/laravel-s/src/Illuminate/Laravel.php(244): Laravel\Lumen\Application->register(Object(Tymon\JWTAuth\Providers\LumenServiceProvider), Array, true)
#27 /xxx/vendor/hhxsv5/laravel-s/src/Illuminate/Laravel.php(267): Hhxsv5\LaravelS\Illuminate\Laravel->reRegisterServiceProvider('\Tymon\JWTAuth\...')
#28 /xxx/vendor/hhxsv5/laravel-s/src/LaravelS.php(123): Hhxsv5\LaravelS\Illuminate\Laravel->cleanRequest(Object(Illuminate\Http\Request))
#29 /xxx/vendor/hhxsv5/laravel-s/src/LaravelS.php(78): Hhxsv5\LaravelS\LaravelS->handleDynamicResource(Object(Illuminate\Http\Request), Object(Swoole\Http\Response))
#30 {main}

Support Broadcasting for Echo

  1. Tell us your PHP version(php -v)

7.1.18

  1. Tell us your Swoole version(php --ri swoole)

4.0.1

  1. Tell us your Laravel/Lumen version(check composer.json & composer.lock)

TODO

  1. Detail description about this issue(error/log)

It's a question rather than an issue. We're wondering if there is out of the box support for Broadcasts to support Laravel Echo. If so, are there any instructions on configuration?

By the looks of it, it doesn't seem like this is supported out of the box. However, the tools are provided to build the implementation.

临时实现Lumen使用协程MySql客户端的方法

由于Lumen没有Laravel一样config/app.php 配置, 可以配置DatabaseServiceProvider

可以通过自定义 singleton 绑定 db来实现

在 App 下新建 一个 Application.php 文件.( 或者其他地方?)
Application 继承原有的 Laravel/Lumen/Application , 重写其中的 registerDatabaseBindings 方法, 使得其中 DatabaseServiceProvider 替换成 LaravelS 下的 DatabaseServiceProvider , 以实现跟 Laravel 中配置 config/app.php 替换一个效果.

<?php
namespace App;

use Laravel\Lumen\Application as LumenApplication;

class Application extends LumenApplication
{
    public function __construct(?string $basePath = null) {
        parent::__construct($basePath);
    }

    /**
     * Register container bindings for the application.
     *
     * @return void
     */
    protected function registerDatabaseBindings()
    {
        $this->singleton('db', function () {
            return $this->loadComponent(
                'database', [
                'Hhxsv5\LaravelS\Illuminate\Database\DatabaseServiceProvider',
                'Illuminate\Pagination\PaginationServiceProvider',
            ], 'db'
            );
        });
    }
}

在 bootstrap/app.php 中修改 Line 22 附近, 使用新建的 Application .

//$app = new Laravel\Lumen\Application(realpath(__DIR__ . '/../'));
$app = new \App\Application(realpath(__DIR__ . '/../'));

websocket fd 自增问题

  1. Tell us your PHP version(php -v)

PHP 7.2.5 (cli) (built: May 11 2018 17:15:07) ( NTS )

  1. Tell us your Swoole version(php --ri swoole)

Version => 2.1.3

  1. Tell us your Laravel/Lumen version(check composer.json & composer.lock)

Laravel version | 5.6.21

  1. Detail description about this issue(error/log)

websocket新连接fd自增总会跳过一个,比如当前fd 4,下一个连接就会变成6而不是5
是设定原因还是bug?

  1. Give us a reproducible code block and steps

[2018-06-06 11:06:10] local.INFO: New Websocket connection [2]
[2018-06-06 11:06:14] local.INFO: New Websocket connection [4]
[2018-06-06 11:06:16] local.INFO: New Websocket connection [6]

使用非默认80端口号进行集成,路由主机地址返回错误,没有正确带端口号

环境centos lnmp
PHP 版本号 7.2.4
SWOOLE版本号2.1.1
Laravel 版本号5.6

网站地址设置如果是非默认80,经过nginx + laravels处理后路由地址不带端口号,导致路由错误
var_dump(env('APP_URL'))----->http://xxxx:8081
var_dump(URL::current())------>http://xxxx

环境配置
.env
APP_URL=http://xxxx:8081

config/app.php
'url' => env('APP_URL', 'http://xxxx:8081'),

错误展示
var_dump(env('APP_URL'))----->http://xxxx:8081
var_dump(URL::current())------>http://xxxx

以上问题放弃nginx + laravels 仅使用apache提供服务则完全正常

5.1.31的laravel版本是不支持么?

如题,目前路由不起作用,还有就是,这个东西怎么设置env参数,比如用fastcgi的话是可以设置fastcgi_param这个的,意思是不写在项目里,在服务端设置

请问上传文件最大多大?怎么调整参数

  1. Tell us your PHP version(php -v)

TODO

  1. Tell us your Swoole version(php --ri swoole)

TODO

  1. Tell us your Laravel/Lumen version(check composer.json & composer.lock)

TODO

  1. Detail description about this issue(error/log)

TODO

  1. Give us a reproducible code block and steps
//TODO: Your code

Fatal error: Uncaught Error: Call to undefined method swoole_http_response::gzip()

[2018-02-07 14:54:30 *154.0] ERROR zm_deactivate_swoole (ERROR 503): Fatal error: Uncaught Error: Call to undefined method swoole_http_response::gzip() in /var/www/tanteng.me/vendor/hhxsv5/laravel-s/src/Swoole/DynamicResponse.php:13 Stack trace: #0 /var/www/tanteng.me/vendor/hhxsv5/laravel-s/src/Swoole/Response.php(54): Hhxsv5\LaravelS\Swoole\DynamicResponse->gzip() #1 /var/www/tanteng.me/vendor/hhxsv5/laravel-s/src/LaravelS.php(84): Hhxsv5\LaravelS\Swoole\Response->send(1) #2 /var/www/tanteng.me/vendor/hhxsv5/laravel-s/src/LaravelS.php(59) [2018-02-07 14:54:30 $113.0] WARNING swManager_check_exit_status: worker#0 abnormal exit, status=255, signal=0
安装了 ziplib 模块还是不行,环境 PHP7 + Swoole 最新版

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.