diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | CHANGELOG.md | 2 | ||||
-rw-r--r-- | Makefile | 12 | ||||
-rw-r--r-- | README.md | 133 | ||||
-rwxr-xr-x | build.sh | 50 | ||||
-rw-r--r-- | cmd/rr/.rr.yaml | 2 | ||||
-rw-r--r-- | cmd/rr/cmd/root.go | 6 | ||||
-rw-r--r-- | cmd/rr/cmd/version.go | 6 | ||||
-rw-r--r-- | cmd/rr/debug/debugger.go (renamed from cmd/rr/debug/listener.go) | 28 | ||||
-rw-r--r-- | cmd/rr/main.go | 2 | ||||
-rw-r--r-- | config.go | 6 | ||||
-rw-r--r-- | go.mod | 30 | ||||
-rw-r--r-- | server.go | 8 | ||||
-rw-r--r-- | server_config.go | 2 | ||||
-rw-r--r-- | service/http/config.go | 2 | ||||
-rw-r--r-- | service/http/handler.go | 10 | ||||
-rw-r--r-- | service/http/request.go | 6 | ||||
-rw-r--r-- | service/http/service.go | 1 | ||||
-rw-r--r-- | service/http/uploads.go | 13 | ||||
-rw-r--r-- | service/http/uploads_config.go | 2 | ||||
-rw-r--r-- | service/service.go | 2 | ||||
-rw-r--r-- | service/static/config.go | 2 | ||||
-rw-r--r-- | static_pool.go | 2 |
23 files changed, 237 insertions, 94 deletions
@@ -1,6 +1,4 @@ -coverage.txt -.idea .idea/* composer.lock vendor/ -bin/
\ No newline at end of file +builds/
\ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 73989e34..e04d02b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,5 +19,5 @@ v1.0.0 (14.06.2018) - less dependencies - yaml/json configs (thx viper) - CLI application server -- middlewares and event listeners +- middleware and event listeners support - psr7 library for php @@ -1,6 +1,16 @@ +all: + @./build.sh +build: + @./build.sh all +clean: + rm -rf rr +install: all + cp rr /usr/local/bin/rr +uninstall: + rm -f /usr/local/bin/rr test: go test -v -race -cover go test -v -race -cover ./service go test -v -race -cover ./service/rpc go test -v -race -cover ./service/http - go test -v -race -cover ./service/static
\ No newline at end of file + go test -v -race -cover ./service/static @@ -7,13 +7,14 @@ RoadRunner [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/spiral/roadrunner/badges/quality-score.png)](https://scrutinizer-ci.com/g/spiral/roadrunner/?branch=master) [![Codecov](https://codecov.io/gh/spiral/roadrunner/branch/master/graph/badge.svg)](https://codecov.io/gh/spiral/roadrunner/) -High-Performance PSR-7 PHP application server, load balancer and process manager. +RoadRunner is an open source (MIT licensed), high-performance PSR-7 PHP application server, load balancer and process manager. +It supports service model with ability to extend it's functionality on a project basis. Features: -------- - PSR-7 HTTP server (file uploads, error handling, static files, hot reload, middlewares, event listeners) - extendable service model (plus PHP compatible RPC server) -- no external services, drop-in (based on [Goridge](https://github.com/spiral/goridge)) +- no external PHP dependencies, drop-in (based on [Goridge](https://github.com/spiral/goridge)) - load balancer, process manager and task pipeline - frontend agnostic (queue, REST, PSR-7, async php, etc) - works over TCP, unix sockets and standard pipes @@ -26,33 +27,32 @@ Features: - very fast (~250k rpc calls per second on Ryzen 1700X over 16 threads) - works on Windows -Installation: +Getting Started: -------- -``` -$ go get github.com/spiral/roadrunner -$ composer require spiral/roadrunner -``` -Usage: ------- +#### Getting RoadRunner +The easiest way to get the latest RoadRunner version is to use one of the pre-built release binaries which are available for +OSX, Linux, FreeBSD, and Windows. Instructions for using these binaries are on the GitHub [releases page](https://github.com/spiral/roadrunner/releases). + +#### Building RoadRunner: +RoadRunner can be compiled on Linux, OSX, Windows and other 64 bit environments as the only requirement is Go 1.8+ itself. + +To build: ``` -$ cd cmd -$ cd rr -$ go build && go install -$ cp .rr.yaml path/to/the/project +$ make ``` -> TODO: To be updated with build scripts! +To test: ``` -$ rr serve -v +$ make test ``` -Example [worker](https://github.com/spiral/roadrunner/blob/master/php-src/tests/http/client.php). +Using RoadRunner: +-------- -Example config: ---------------- +In order to use RoadRunner you only have to place `.rr.yaml` file in a root of your php project: ```yaml # rpc bus allows php application and external clients to talk to rr services. @@ -95,16 +95,16 @@ http: # maximum jobs per worker, 0 - unlimited. maxJobs: 0 - # for how long pool should attempt to allocate free worker (request timeout). In nanoseconds for now :( - allocateTimeout: 600000000 + # for how long pool should attempt to allocate free worker (request timeout). Nanoseconds atm. + allocateTimeout: 1000000000 - # amount of time given to worker to gracefully destruct itself. In nanoseconds for now :( - destroyTimeout: 600000000 + # amount of time given to worker to gracefully destruct itself. Nanoseconds atm. + destroyTimeout: 1000000000 # static file serving. static: # serve http static files - enable: false + enable: true # root directory for static file (http would not serve .php and .htaccess files). dir: "public" @@ -113,23 +113,76 @@ static: forbid: [".php", ".htaccess"] ``` -Examples: +Where `psr-worker.php`: + +```php +$psr7 = new RoadRunner\PSR7Client(new RoadRunner\Worker($relay)); + +while ($req = $psr7->acceptRequest()) { + try { + $resp = new \Zend\Diactoros\Response() + $resp->getBody()->write("hello world"); + + $psr7->respond($resp); + } catch (\Throwable $e) { + $psr7->getWorker()->error((string)$e); + } +} +``` + +> Check how to init relay [here](./php-src/tests/client.php). + +Working with RoadRunner service: -------- +RoadRunner application can be started by calling simple command from the root of your PHP application. + +``` +$ rr serve +``` + +You can also run RR in debug mode to view all incoming requests. + +``` +$ rr serve -d +``` + +You can force RR service to reload it's http workers. + +``` +$ rr http:reset +``` + +> You can attach this command as file watcher in your IDE. + +To view status of all active workers in interactive mode. + +``` +$ rr http:workers -i +``` + +Standalone Usage: +-------- +You can also use RoadRunner as library in order to drive your application without any additional protocol at top of it. + ```go -p, err := rr.NewPool( - func() *exec.Cmd { return exec.Command("php", "worker.php", "pipes") }, - rr.NewPipeFactory(), - rr.Config{ - NumWorkers: uint64(runtime.NumCPU()), - AllocateTimeout: time.Second, - DestroyTimeout: time.Second, - }, -) -defer p.Destroy() - -rsp, err := p.Exec(&rr.Payload{Body: []byte("hello")}) +srv := NewServer( + &ServerConfig{ + Command: "php client.php echo pipes", + Relay: "pipes", + Pool: &Config{ + NumWorkers: int64(runtime.NumCPU()), + AllocateTimeout: time.Second, + DestroyTimeout: time.Second, + }, + }) +defer srv.Stop() + +srv.Start() + +res, err := srv.Exec(&Payload{Body: []byte("hello")}) ``` + ```php <?php /** @@ -149,13 +202,9 @@ while ($body = $rr->receive($context)) { } } ``` -> Check how to init relay [here](./php-src/tests/client.php). More examples can be found in tests. +> Check how to init relay [here](./php-src/tests/client.php). -Testing: --------- -``` -$ make test -``` +You can find more examples in tests and `php-src` directory. License: -------- diff --git a/build.sh b/build.sh new file mode 100755 index 00000000..fd84fc79 --- /dev/null +++ b/build.sh @@ -0,0 +1,50 @@ +#!/bin/bash +set -e + +cd $(dirname "${BASH_SOURCE[0]}") +OD="$(pwd)" + +# Pushes application version into the build information. +RR_VERSION=1.0.1 + +# Hardcode some values to the core package +LDFLAGS="$LDFLAGS -X github.com/spiral/roadrunner/cmd/rr/cmd.Version=${RR_VERSION}" +LDFLAGS="$LDFLAGS -X github.com/spiral/roadrunner/cmd/rr/cmd.BuildTime=$(date +%FT%T%z)" + +build(){ + echo Packaging $1 Build + bdir=roadrunner-${RR_VERSION}-$2-$3 + rm -rf builds/$bdir && mkdir -p builds/$bdir + GOOS=$2 GOARCH=$3 ./build.sh + + if [ "$2" == "windows" ]; then + mv rr builds/$bdir/rr.exe + else + mv rr builds/$bdir + fi + + cp README.md builds/$bdir + cp CHANGELOG.md builds/$bdir + cp LICENSE builds/$bdir + cd builds + + if [ "$2" == "linux" ]; then + tar -zcf $bdir.tar.gz $bdir + else + zip -r -q $bdir.zip $bdir + fi + + rm -rf $bdir + cd .. +} + +if [ "$1" == "all" ]; then + rm -rf builds/ + build "Windows" "windows" "amd64" + build "Mac" "darwin" "amd64" + build "Linux" "linux" "amd64" + build "FreeBSD" "freebsd" "amd64" + exit +fi + +CGO_ENABLED=0 go build -ldflags "$LDFLAGS -extldflags '-static'" -o "$OD/rr" cmd/rr/main.go
\ No newline at end of file diff --git a/cmd/rr/.rr.yaml b/cmd/rr/.rr.yaml index ab0f3e7f..775cd6c3 100644 --- a/cmd/rr/.rr.yaml +++ b/cmd/rr/.rr.yaml @@ -47,7 +47,7 @@ http: # static file serving. static: # serve http static files - enable: false + enable: true # root directory for static file (http would not serve .php and .htaccess files). dir: "public" diff --git a/cmd/rr/cmd/root.go b/cmd/rr/cmd/root.go index b1f3ea9a..1a21cfc9 100644 --- a/cmd/rr/cmd/root.go +++ b/cmd/rr/cmd/root.go @@ -45,7 +45,11 @@ var ( Use: "rr", SilenceErrors: true, SilenceUsage: true, - Short: utils.Sprintf("<green>RoadRunner, PHP Application Server.</reset>"), + Short: utils.Sprintf( + "<green>RoadRunner, PHP Application Server:</reset>\nVersion: <yellow+hb>%s</reset>, %s", + Version, + BuildTime, + ), } ) diff --git a/cmd/rr/cmd/version.go b/cmd/rr/cmd/version.go new file mode 100644 index 00000000..5edb7543 --- /dev/null +++ b/cmd/rr/cmd/version.go @@ -0,0 +1,6 @@ +package cmd + +var ( + Version = "1.0.0" // Placeholder for the version + BuildTime = "development" // Placeholder for the build time +) diff --git a/cmd/rr/debug/listener.go b/cmd/rr/debug/debugger.go index f137b06f..0621285b 100644 --- a/cmd/rr/debug/listener.go +++ b/cmd/rr/debug/debugger.go @@ -7,32 +7,32 @@ import ( "github.com/spiral/roadrunner/service/http" ) -// Listener provide debug callback for system events. With colors! -type listener struct{ logger *logrus.Logger } - -// NewListener creates new debug listener. -func NewListener(logger *logrus.Logger) *listener { - return &listener{logger} +// Listener creates new debug listener. +func Listener(logger *logrus.Logger) func(event int, ctx interface{}) { + return (&debugger{logger}).listener } -// Listener listens to http events and generates nice looking output. -func (s *listener) Listener(event int, ctx interface{}) { +// listener provide debug callback for system events. With colors! +type debugger struct{ logger *logrus.Logger } + +// listener listens to http events and generates nice looking output. +func (s *debugger) listener(event int, ctx interface{}) { // http events switch event { case http.EventResponse: log := ctx.(*http.Event) - s.logger.Info(utils.Sprintf("%s <white+hb>%s</reset> %s", statusColor(log.Status), log.Method, log.Uri)) + s.logger.Info(utils.Sprintf("%s <white+hb>%s</reset> %s", statusColor(log.Status), log.Method, log.URI)) case http.EventError: log := ctx.(*http.Event) if _, ok := log.Error.(roadrunner.JobError); ok { - s.logger.Info(utils.Sprintf("%s <white+hb>%s</reset> %s", statusColor(log.Status), log.Method, log.Uri)) + s.logger.Info(utils.Sprintf("%s <white+hb>%s</reset> %s", statusColor(log.Status), log.Method, log.URI)) } else { s.logger.Info(utils.Sprintf( "%s <white+hb>%s</reset> %s <red>%s</reset>", statusColor(log.Status), log.Method, - log.Uri, + log.URI, log.Error, )) } @@ -70,12 +70,6 @@ func (s *listener) Listener(event int, ctx interface{}) { } } -// Serve serves. -func (s *listener) Serve() error { return nil } - -// Stop stops the Listener. -func (s *listener) Stop() {} - func statusColor(status int) string { if status < 300 { return utils.Sprintf("<green>%v</reset>", status) diff --git a/cmd/rr/main.go b/cmd/rr/main.go index 40f191c6..4ab2fbe2 100644 --- a/cmd/rr/main.go +++ b/cmd/rr/main.go @@ -55,7 +55,7 @@ func main() { cobra.OnInitialize(func() { if debugMode { service, _ := rr.Container.Get(http.ID) - service.(*http.Service).AddListener(debug.NewListener(rr.Logger).Listener) + service.(*http.Service).AddListener(debug.Listener(rr.Logger)) } }) @@ -18,14 +18,14 @@ type Config struct { // AllocateTimeout defines for how long pool will be waiting for a worker to // be freed to handle the task. - AllocateTimeout time.Duration //todo: to milleseconds? + AllocateTimeout time.Duration // DestroyTimeout defines for how long pool should be waiting for worker to // properly stop, if timeout reached worker will be killed. - DestroyTimeout time.Duration //todo: to milleseconds? + DestroyTimeout time.Duration } -// Reconfigure returns error if cfg not valid +// Valid returns error if config not valid. func (cfg *Config) Valid() error { if cfg.NumWorkers == 0 { return fmt.Errorf("pool.NumWorkers must be set") @@ -0,0 +1,30 @@ +module github.com/spiral/roadrunner + +require ( + github.com/buger/goterm v0.0.0-20180423150900-6d19e6a8df12 + github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e + github.com/fsnotify/fsnotify v1.4.7 + github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce + github.com/magiconair/properties v1.8.0 + github.com/mattn/go-colorable v0.0.9 + github.com/mattn/go-isatty v0.0.3 + github.com/mattn/go-runewidth v0.0.2 + github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b + github.com/mitchellh/mapstructure v0.0.0-20180511142126-bb74f1db0675 + github.com/olekukonko/tablewriter v0.0.0-20180506121414-d4647c9c7a84 + github.com/pelletier/go-toml v1.2.0 + github.com/pkg/errors v0.8.0 + github.com/shirou/gopsutil v0.0.0-20180613084040-c23bcca55e77 + github.com/sirupsen/logrus v1.0.5 + github.com/spf13/afero v1.1.1 + github.com/spf13/cast v1.2.0 + github.com/spf13/cobra v0.0.3 + github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec + github.com/spf13/pflag v1.0.1 + github.com/spf13/viper v1.0.2 + github.com/spiral/goridge v1.0.4 + golang.org/x/crypto v0.0.0-20180614221331-a8fb68e7206f + golang.org/x/sys v0.0.0-20180615093615-8014b7b116a6 + golang.org/x/text v0.3.0 + gopkg.in/yaml.v2 v2.2.1 +) @@ -7,10 +7,10 @@ import ( ) const ( - // EventPoolConstruct triggered when server creates new pool. + // EventServerStart triggered when server creates new pool. EventServerStart = iota + 200 - // EventPoolConstruct triggered when server creates new pool. + // EventServerStop triggered when server creates new pool. EventServerStop // EventServerFailure triggered when server is unable to replace dead pool. @@ -23,7 +23,7 @@ const ( EventPoolDestruct ) -// Service manages pool creation and swapping. +// Server manages pool creation and swapping. type Server struct { // configures server, pool, cmd creation and factory. cfg *ServerConfig @@ -50,7 +50,7 @@ func NewServer(cfg *ServerConfig) *Server { return &Server{cfg: cfg} } -// AddListener attaches server event watcher. +// Listen attaches server event watcher. func (s *Server) Listen(l func(event int, ctx interface{})) { s.mul.Lock() defer s.mul.Unlock() diff --git a/server_config.go b/server_config.go index c6567fc3..ecd7dd2b 100644 --- a/server_config.go +++ b/server_config.go @@ -8,7 +8,7 @@ import ( "time" ) -// Server config combines factory, pool and cmd configurations. +// ServerConfig config combines factory, pool and cmd configurations. type ServerConfig struct { // Command includes command strings with all the parameters, example: "php worker.php pipes". Command string diff --git a/service/http/config.go b/service/http/config.go index de791f45..fb4574b1 100644 --- a/service/http/config.go +++ b/service/http/config.go @@ -6,7 +6,7 @@ import ( "strings" ) -// Configures RoadRunner HTTP server. +// Config configures RoadRunner HTTP server. type Config struct { // Enable enables http svc. Enable bool diff --git a/service/http/handler.go b/service/http/handler.go index 2c7c1fad..6f2617b1 100644 --- a/service/http/handler.go +++ b/service/http/handler.go @@ -21,8 +21,8 @@ type Event struct { // Method of the request. Method string - // Uri requested by the client. - Uri string + // URI requested by the client. + URI string // Status is response status. Status int @@ -40,7 +40,7 @@ type Handler struct { lsn func(event int, ctx interface{}) } -// AddListener attaches pool event watcher. +// Listen attaches handler event watcher. func (h *Handler) Listen(l func(event int, ctx interface{})) { h.mul.Lock() defer h.mul.Unlock() @@ -99,7 +99,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // handleError sends error. func (h *Handler) handleError(w http.ResponseWriter, r *http.Request, err error) { - h.throw(EventError, &Event{Method: r.Method, Uri: uri(r), Status: 500, Error: err}) + h.throw(EventError, &Event{Method: r.Method, URI: uri(r), Status: 500, Error: err}) w.WriteHeader(500) w.Write([]byte(err.Error())) @@ -107,7 +107,7 @@ func (h *Handler) handleError(w http.ResponseWriter, r *http.Request, err error) // handleResponse triggers response event. func (h *Handler) handleResponse(req *Request, resp *Response) { - h.throw(EventResponse, &Event{Method: req.Method, Uri: req.Uri, Status: resp.Status}) + h.throw(EventResponse, &Event{Method: req.Method, URI: req.URI, Status: resp.Status}) } // throw invokes event srv if any. diff --git a/service/http/request.go b/service/http/request.go index 2e8ae090..3082eeb5 100644 --- a/service/http/request.go +++ b/service/http/request.go @@ -26,8 +26,8 @@ type Request struct { // Method contains name of HTTP method used for the request. Method string `json:"method"` - // Uri contains full request Uri with scheme and query. - Uri string `json:"uri"` + // URI contains full request URI with scheme and query. + URI string `json:"uri"` // Headers contains list of request headers. Headers http.Header `json:"headers"` @@ -53,7 +53,7 @@ func NewRequest(r *http.Request, cfg *UploadsConfig) (req *Request, err error) { req = &Request{ Protocol: r.Proto, Method: r.Method, - Uri: uri(r), + URI: uri(r), Headers: r.Header, Cookies: make(map[string]string), RawQuery: r.URL.RawQuery, diff --git a/service/http/service.go b/service/http/service.go index 7a7354fb..3d200845 100644 --- a/service/http/service.go +++ b/service/http/service.go @@ -27,6 +27,7 @@ type Service struct { http *http.Server } +// AddMiddleware adds new net/http middleware. func (s *Service) AddMiddleware(m middleware) { s.mdws = append(s.mdws, m) } diff --git a/service/http/uploads.go b/service/http/uploads.go index 4607dea4..9b205f00 100644 --- a/service/http/uploads.go +++ b/service/http/uploads.go @@ -10,23 +10,23 @@ import ( ) const ( - // There is no error, the file uploaded with success. + // UploadErrorOK - no error, the file uploaded with success. UploadErrorOK = 0 - // No file was uploaded. + // UploadErrorNoFile - no file was uploaded. UploadErrorNoFile = 4 - // Missing a temporary folder. + // UploadErrorNoTmpDir - missing a temporary folder. UploadErrorNoTmpDir = 5 - // Failed to write file to disk. + // UploadErrorCantWrite - failed to write file to disk. UploadErrorCantWrite = 6 - // Forbid file extension. + // UploadErrorExtension - forbidden file extension. UploadErrorExtension = 7 ) -// tree manages uploaded files tree and temporary files. +// Uploads tree manages uploaded files tree and temporary files. type Uploads struct { // associated temp directory and forbidden extensions. cfg *UploadsConfig @@ -99,6 +99,7 @@ func NewUpload(f *multipart.FileHeader) *FileUpload { } } +// Open moves file content into temporary file available for PHP. func (f *FileUpload) Open(cfg *UploadsConfig) error { if cfg.Forbids(f.Name) { f.Error = UploadErrorExtension diff --git a/service/http/uploads_config.go b/service/http/uploads_config.go index 148ebba3..e90d9b70 100644 --- a/service/http/uploads_config.go +++ b/service/http/uploads_config.go @@ -25,7 +25,7 @@ func (cfg *UploadsConfig) TmpDir() string { return os.TempDir() } -// Forbid must return true if file extension is not allowed for the upload. +// Forbids must return true if file extension is not allowed for the upload. func (cfg *UploadsConfig) Forbids(filename string) bool { ext := strings.ToLower(path.Ext(filename)) diff --git a/service/service.go b/service/service.go index 6ddcda41..6cd12b51 100644 --- a/service/service.go +++ b/service/service.go @@ -2,7 +2,7 @@ package service import "sync" -// svc provides high level functionality for road runner svc. +// Service provides high level functionality for road runner modules. type Service interface { // Init must return configure service and return true if service hasStatus enabled. Must return error in case of // misconfiguration. Services must not be used without proper configuration pushed first. diff --git a/service/static/config.go b/service/static/config.go index d55fcd66..1020b8cd 100644 --- a/service/static/config.go +++ b/service/static/config.go @@ -20,7 +20,7 @@ type Config struct { Forbid []string } -// Forbid must return true if file extension is not allowed for the upload. +// Forbids must return true if file extension is not allowed for the upload. func (cfg *Config) Forbids(filename string) bool { ext := strings.ToLower(path.Ext(filename)) diff --git a/static_pool.go b/static_pool.go index 975ddbe4..b3e4f488 100644 --- a/static_pool.go +++ b/static_pool.go @@ -77,7 +77,7 @@ func NewPool(cmd func() *exec.Cmd, factory Factory, cfg Config) (*StaticPool, er return p, nil } -// AddListener attaches pool event watcher. +// Listen attaches pool event watcher. func (p *StaticPool) Listen(l func(event int, ctx interface{})) { p.mul.Lock() defer p.mul.Unlock() |