diff options
author | Valery Piashchynski <[email protected]> | 2021-09-10 15:21:06 +0300 |
---|---|---|
committer | Valery Piashchynski <[email protected]> | 2021-09-10 15:21:06 +0300 |
commit | 8b70fb48b2b0a9451d9b82a17ac2f4cd8a1f561e (patch) | |
tree | 852cef5775c326f62dac96e8b1f80ef9b75962c2 /docs/library | |
parent | 183d0ac682b57f285c9193492e50310046422184 (diff) |
Add docs folder
Signed-off-by: Valery Piashchynski <[email protected]>
Diffstat (limited to 'docs/library')
-rw-r--r-- | docs/library/aws-lambda.md | 262 | ||||
-rw-r--r-- | docs/library/event-listeners.md | 65 | ||||
-rw-r--r-- | docs/library/standalone-usage.md | 48 |
3 files changed, 375 insertions, 0 deletions
diff --git a/docs/library/aws-lambda.md b/docs/library/aws-lambda.md new file mode 100644 index 00000000..3fb0a5c0 --- /dev/null +++ b/docs/library/aws-lambda.md @@ -0,0 +1,262 @@ +# AWS Lambda +RoadRunner can run PHP as AWS Lambda function. + +### Installation +Prior to the function deployment, you must compile or download PHP binary files to run your application. There are multiple projects available for such goal: + +- https://github.com/araines/serverless-php +- https://github.com/stechstudio/php-lambda (includes pre-built versions of PHP binaries) + +Place PHP binaries in a `bin/` folder of your project. + +### PHP Worker +PHP worker does not require any specific configuration to run inside Lambda function. We can use default snippet with internal counter to demonstrate how workers are being reused: + +```php +<?php +/** + * @var Goridge\RelayInterface $relay + */ +use Spiral\Goridge; +use Spiral\RoadRunner; + +ini_set('display_errors', 'stderr'); +require __DIR__ . "/vendor/autoload.php"; + +$worker = new RoadRunner\Worker(new Goridge\StreamRelay(STDIN, STDOUT)); +$psr7 = new RoadRunner\Http\PSR7Worker( + $worker, + new \Nyholm\Psr7\Factory\Psr17Factory(), + new \Nyholm\Psr7\Factory\Psr17Factory(), + new \Nyholm\Psr7\Factory\Psr17Factory() +); + +while ($req = $psr7->waitRequest()) { + try { + $resp = new \Nyholm\Psr7\Response(); + $resp->getBody()->write(str_repeat("hello world", 1000)); + + $psr7->respond($resp); + } catch (\Throwable $e) { + $psr7->getWorker()->error((string)$e); + } + +``` + +Name this file `handler.php` and put it into the root of your project. Make sure to run `composer require spiral/roadrunner`. + +### Application +We can create a simple application to demonstrate how it works: +1. You need 3 files, main.go with the `Endure` container: + +```golang +import ( + _ "embed" + "log" + "os" + "os/signal" + "sync" + "syscall" + "time" + + endure "github.com/spiral/endure/pkg/container" + "github.com/spiral/roadrunner/v2/plugins/config" + "github.com/spiral/roadrunner/v2/plugins/logger" + "github.com/spiral/roadrunner/v2/plugins/server" +) + +//go:embed .rr.yaml +var rrYaml []byte + +func main() { + _ = os.Setenv("PATH", os.Getenv("PATH")+":"+os.Getenv("LAMBDA_TASK_ROOT")) + _ = os.Setenv("LD_LIBRARY_PATH", "./lib:/lib64:/usr/lib64") + + cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.ErrorLevel)) + if err != nil { + log.Fatal(err) + } + + cfg := &config.Viper{} + cfg.CommonConfig = &config.General{GracefulTimeout: time.Second * 30} + cfg.ReadInCfg = rrYaml + cfg.Type = "yaml" + + // only 4 plugins needed here + // 1. Server which should provide pool to us + // 2. Our mini plugin, which expose this pool for us + // 3. Logger + // 4. Configurer + err = cont.RegisterAll( + cfg, + &logger.ZapLogger{}, + &Plugin{}, + &server.Plugin{}, + ) + if err != nil { + log.Fatal(err) + } + + err = cont.Init() + if err != nil { + log.Fatal(err) + } + + ch, err := cont.Serve() + if err != nil { + log.Fatal(err) + } + + sig := make(chan os.Signal, 1) + signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) + + wg := &sync.WaitGroup{} + wg.Add(1) + + go func() { + defer wg.Done() + for { + select { + case e := <-ch: + log.Println(e.Error.Error()) + case <-sig: + err = cont.Stop() + if err != nil { + log.Println(err) + } + return + } + } + }() + + wg.Wait() +} +``` + +2. And `Plugin` for the RR: +```golang +package main + +import ( + "context" + "sync" + "unsafe" + + "github.com/aws/aws-lambda-go/lambda" + "github.com/spiral/roadrunner/v2/pkg/payload" + "github.com/spiral/roadrunner/v2/pkg/pool" + "github.com/spiral/roadrunner/v2/plugins/logger" + "github.com/spiral/roadrunner/v2/plugins/server" +) + +type Plugin struct { + sync.Mutex + log logger.Logger + srv server.Server + wrkPool pool.Pool +} + +func (p *Plugin) Init(srv server.Server, log logger.Logger) error { + var err error + p.srv = srv + p.log = log + return err +} + +func (p *Plugin) Serve() chan error { + errCh := make(chan error, 1) + p.Lock() + defer p.Unlock() + var err error + + p.wrkPool, err = p.srv.NewWorkerPool(context.Background(), pool.Config{ + Debug: false, + NumWorkers: 1, + MaxJobs: 0, + AllocateTimeout: 0, + DestroyTimeout: 0, + Supervisor: &pool.SupervisorConfig{ + WatchTick: 0, + TTL: 0, + IdleTTL: 0, + ExecTTL: 0, + MaxWorkerMemory: 0, + }, + }, nil, nil) + + go func() { + // register handler + lambda.Start(p.handler()) + }() + + if err != nil { + errCh <- err + } + return errCh +} + +func (p *Plugin) Stop() error { + p.Lock() + defer p.Unlock() + + if p.wrkPool != nil { + p.wrkPool.Destroy(context.Background()) + } + return nil +} + +func (p *Plugin) handler() func(pld string) (string, error) { + return func(pld string) (string, error) { + data := fastConvert(pld) + // execute on worker pool + if p.wrkPool == nil { + // or any error + return "", nil + } + exec, err := p.wrkPool.Exec(payload.Payload{ + Context: nil, + Body: data, + }) + if err != nil { + return "", err + } + return exec.String(), nil + } +} + +// reinterpret_cast conversion cast from string to []byte +// unsafe +func fastConvert(d string) []byte { + return *(*[]byte)(unsafe.Pointer(&d)) +} +``` +3. Config file, which can be embedded into the binary with `embed` import: +```yaml +server: + command: "php handler.php" + user: "" + group: "" + env: + - SOME_KEY: "SOME_VALUE" + - SOME_KEY2: "SOME_VALUE2" + relay: pipes + relay_timeout: 60s +``` +Here you can use full advantage of the RR2, you can include any plugin here and configure it with the embedded config (within reasonable limits). + +To build and package your lambda function run: + +``` +$ GOOS=linux GOARCH=amd64 go build -o main main.go +$ zip main.zip * -r +``` + +You can now upload and invoke your handler using simple string event. + +## Notes +There are multiple notes you have to acknowledge. + +- start with 1 worker per lambda function in order to control your memory usage. +- make sure to include env variables listed in the code to properly resolve the location of PHP binary and it's dependencies. +- avoid database connections without concurrency limit +- avoid database connections diff --git a/docs/library/event-listeners.md b/docs/library/event-listeners.md new file mode 100644 index 00000000..612d12bd --- /dev/null +++ b/docs/library/event-listeners.md @@ -0,0 +1,65 @@ +# Event Listeners +RoadRunner server exposes the way to handle internal events using custom event listeners, we can demonstrate how to display console message each time HTTP server responds: + +```golang +func main() { + rr.Logger.Formatter = &logrus.TextFormatter{ForceColors: true} + + rr.Container.Register(env.ID, env.NewService(map[string]string{"rr": rr.Version})) + + rr.Container.Register(rpc.ID, &rpc.Service{}) + rr.Container.Register(http.ID, &http.Service{}) + rr.Container.Register(static.ID, &static.Service{}) + + // add event listener to http server + svc, _ := Container.Get(http.ID) + svc.(*http.Service).AddListener(myListener) + + // you can register additional commands using cmd.CLI + rr.Execute() +} +``` + +Where `myListener` is: + +```golang +import ( + "github.com/sirupsen/logrus" + rrhttp "github.com/spiral/roadrunner/service/http" +) + +func myListener(event int, ctx interface{}) { + switch event { + case rrhttp.EventResponse: + e := ctx.(*rrhttp.ResponseEvent) + logrus.Info( + "%s %v %s %s", + e.Request.RemoteAddr, + e.Response.Status, + e.Request.Method, + e.Request.URI, + ) + case rrhttp.EventError: + e := ctx.(*rrhttp.ErrorEvent) + + if _, ok := e.Error.(roadrunner.JobError); ok { + logrus.Info( + "%v %s %s", + 500, + e.Request.Method, + e.Request.URI, + ) + } else { + logrus.Info( + "%v %s %s %s", + 500, + e.Request.Method, + e.Request.URI, + e.Error, + ) + } + } +} +``` + +You can find a list of available events [here](https://godoc.org/github.com/spiral/roadrunner). diff --git a/docs/library/standalone-usage.md b/docs/library/standalone-usage.md new file mode 100644 index 00000000..04209c95 --- /dev/null +++ b/docs/library/standalone-usage.md @@ -0,0 +1,48 @@ +# Standalone Usage +You can use RoadRunner library as the component, without bringing the whole application into your project. + +In order to create PHP worker pool on Golang: + +```golang +srv := roadrunner.NewServer( + &roadrunner.ServerConfig{ + Command: "php client.php echo pipes", + Relay: "pipes", + Pool: &roadrunner.Config{ + NumWorkers: int64(runtime.NumCPU()), + AllocateTimeout: time.Second, + DestroyTimeout: time.Second, + }, + }) +defer srv.Stop() + +srv.Start() + +res, err := srv.Exec(&roadrunner.Payload{Body: []byte("hello")}) +``` + +Worker (echo) structure would look like: + +```php +<?php +/** + * @var Goridge\RelayInterface $relay + */ +use Spiral\Goridge; +use Spiral\RoadRunner; + +ini_set('display_errors', 'stderr'); +require 'vendor/autoload.php'; + +$rr = new RoadRunner\Worker(new Spiral\Goridge\StreamRelay(STDIN, STDOUT)); + +while ($body = $rr->receive($context)) { + try { + $rr->send((string)$body, (string)$context); + } catch (\Throwable $e) { + $rr->error((string)$e); + } +} +``` + +Make sure to run `go get github.com/spiral/roadrunner` and `composer require spiral/roadrunner` to load necessary dependencies. |