summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--_____/http/config.go88
-rw-r--r--_____/http/service.go80
-rw-r--r--cmd/rr/main.go14
-rw-r--r--cmd/rr/utils/size.go28
-rw-r--r--cmd/rr/utils/utils_test.go12
-rw-r--r--service/container.go13
-rw-r--r--service/http/config.go34
-rw-r--r--service/http/fs_config.go (renamed from http/fs_config.go)0
-rw-r--r--service/http/fs_config_test.go (renamed from http/fs_config_test.go)0
-rw-r--r--service/http/handler.go71
-rw-r--r--service/http/parse.go (renamed from http/parse.go)0
-rw-r--r--service/http/request.go (renamed from _____/http/request.go)0
-rw-r--r--service/http/response.go (renamed from http/response.go)0
-rw-r--r--service/http/rpc.go (renamed from _____/http/rpc.go)0
-rw-r--r--service/http/service.go86
-rw-r--r--service/http/uploads.go (renamed from http/uploads.go)0
-rw-r--r--service/rpc/config.go (renamed from rpc/config.go)0
-rw-r--r--service/rpc/config_test.go (renamed from rpc/config_test.go)0
-rw-r--r--service/rpc/service.go (renamed from rpc/service.go)11
-rw-r--r--service/rpc/service_test.go (renamed from rpc/service_test.go)0
-rw-r--r--service/service.go (renamed from service/entry.go)13
-rw-r--r--service/static/config.go52
-rw-r--r--service/static/config_test.go21
-rw-r--r--service/static/service.go120
24 files changed, 415 insertions, 228 deletions
diff --git a/_____/http/config.go b/_____/http/config.go
deleted file mode 100644
index bd8cec5e..00000000
--- a/_____/http/config.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package http
-
-import (
- "fmt"
- "github.com/spiral/roadrunner/service"
- "github.com/spiral/roadrunner/_____/utils"
- "os"
- "path"
- "strings"
- "github.com/spiral/roadrunner/http"
-)
-
-// Configures RoadRunner HTTP server.
-type Config struct {
- // serve enables static file serving from desired root directory.
- ServeStatic bool
-
- Static *http.FsConfig
-
- // Root directory, required when serve set to true.
- Root string
-
- // Dir contains name of temporary directory to store uploaded files passed to underlying PHP process.
- TmpDir string
-
- // MaxRequest specified max size for payload body in bytes, set 0 to unlimited.
- MaxRequest int64
-
- // Forbid specifies list of file extensions which are forbidden for uploads.
- // Example: .php, .exe, .bat, .htaccess and etc.
- ForbidUploads []string
-}
-
-// Forbid must return true if file extension is not allowed for the upload.
-func (cfg Config) Forbidden(filename string) bool {
- ext := strings.ToLower(path.Ext(filename))
-
- for _, v := range cfg.ForbidUploads {
- if ext == v {
- return true
- }
- }
-
- return false
-}
-
-type serviceConfig struct {
- Enabled bool
- Host string
- Port string
- MaxRequest string
- Static struct {
- Serve bool
- Root string
- }
-
- Uploads struct {
- TmpDir string
- Forbid []string
- }
-
- Pool service.PoolConfig
-
- //todo: verbose ?
-}
-
-func (cfg *serviceConfig) httpAddr() string {
- return fmt.Sprintf("%s:%v", cfg.Host, cfg.Port)
-}
-
-func (cfg *serviceConfig) httpConfig() *Config {
- tmpDir := cfg.Uploads.TmpDir
- if tmpDir == "" {
- tmpDir = os.TempDir()
- }
-
- return &Config{
- ServeStatic: cfg.Static.Serve,
- Root: cfg.Static.Root,
- TmpDir: tmpDir,
- MaxRequest: utils.ParseSize(cfg.MaxRequest),
- ForbidUploads: cfg.Uploads.Forbid,
- }
-}
-
-func (cfg *serviceConfig) Valid() error {
- return nil
-}
diff --git a/_____/http/service.go b/_____/http/service.go
deleted file mode 100644
index 008aeab8..00000000
--- a/_____/http/service.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package http
-
-import (
- "context"
- "github.com/sirupsen/logrus"
- "github.com/spiral/roadrunner/service"
- "net/http"
- "github.com/spiral/roadrunner"
-)
-
-const ServiceName = "http"
-
-type Service struct {
- cfg *serviceConfig
- http *http.Server
- srv *Server
-}
-
-func (s *Service) Name() string {
- return ServiceName
-}
-
-func (s *Service) Configure(cfg service.Config) (bool, error) {
- config := &serviceConfig{}
- if err := cfg.Unmarshal(config); err != nil {
- return false, err
- }
-
- if !config.Enabled {
- return false, nil
- }
-
- if err := config.Valid(); err != nil {
- return false, err
- }
-
- s.cfg = config
- return true, nil
-}
-
-func (s *Service) RPC() interface{} {
- return &rpcServer{s}
-}
-
-func (s *Service) Serve() error {
- logrus.Debugf("http: started")
- defer logrus.Debugf("http: stopped")
-
- rr, term, err := s.cfg.Pool.NewServer()
- if err != nil {
- return err
- }
- defer term()
-
- //todo: remove
- rr.Observe(func(event int, ctx interface{}) {
- switch event {
- case roadrunner.EventPoolError:
- logrus.Error(ctx)
- case roadrunner.EventWorkerError:
- logrus.Errorf("%s: %s", ctx.(roadrunner.WorkerError).Worker, ctx.(roadrunner.WorkerError).Error())
- }
- })
-
- s.srv = NewServer(s.cfg.httpConfig(), rr)
- s.http = &http.Server{
- Addr: s.cfg.httpAddr(),
- Handler: s.srv,
- }
-
- if err := s.http.ListenAndServe(); err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *Service) Stop() error {
- return s.http.Shutdown(context.Background())
-}
diff --git a/cmd/rr/main.go b/cmd/rr/main.go
index 625e879c..ea16a99a 100644
--- a/cmd/rr/main.go
+++ b/cmd/rr/main.go
@@ -24,7 +24,11 @@ package main
import (
rr "github.com/spiral/roadrunner/cmd/rr/cmd"
- "github.com/spiral/roadrunner/rpc"
+
+ // services (plugins)
+ "github.com/spiral/roadrunner/service/http"
+ "github.com/spiral/roadrunner/service/rpc"
+ "github.com/spiral/roadrunner/service/static"
// cli plugins
_ "github.com/spiral/roadrunner/cmd/rr/http"
@@ -32,7 +36,13 @@ import (
func main() {
// provides ability to make local connection to services
- rr.Container.Register("rpc", new(rpc.Service))
+ rr.Container.Register(rpc.Name, new(rpc.Service))
+
+ // http serving
+ rr.Container.Register(http.Name, new(http.Service))
+
+ // serving static files
+ rr.Container.Register(static.Name, new(static.Service))
// you can register additional commands using cmd.CLI
rr.Execute()
diff --git a/cmd/rr/utils/size.go b/cmd/rr/utils/size.go
deleted file mode 100644
index 176cc9e1..00000000
--- a/cmd/rr/utils/size.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package utils
-
-import (
- "strconv"
- "strings"
-)
-
-func ParseSize(size string) int64 {
- if len(size) == 0 {
- return 0
- }
-
- s, err := strconv.Atoi(size[:len(size)-1])
- if err != nil {
- return 0
- }
-
- switch strings.ToLower(size[len(size)-1:]) {
- case "k", "kb":
- return int64(s * 1024)
- case "m", "mb":
- return int64(s * 1024 * 1024)
- case "g", "gb":
- return int64(s * 1024 * 1024 * 1024)
- }
-
- return 0
-}
diff --git a/cmd/rr/utils/utils_test.go b/cmd/rr/utils/utils_test.go
deleted file mode 100644
index f67b2a10..00000000
--- a/cmd/rr/utils/utils_test.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package utils
-
-import (
- "testing"
- "github.com/magiconair/properties/assert"
-)
-
-func TestUtils(t *testing.T) {
- assert.Equal(t, int64(1024), ParseSize("1K"))
- assert.Equal(t, int64(1024*1024), ParseSize("1M"))
- assert.Equal(t, int64(2*1024*1024*1024), ParseSize("2G"))
-}
diff --git a/service/container.go b/service/container.go
index bf712092..3395cd86 100644
--- a/service/container.go
+++ b/service/container.go
@@ -39,19 +39,6 @@ type Container interface {
Stop()
}
-// svc provides high level functionality for road runner svc.
-type Service interface {
- // Configure 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.
- Configure(cfg Config, c Container) (enabled bool, err error)
-
- // Serve serves svc.
- Serve() error
-
- // Close setStopped svc svc.
- Stop()
-}
-
type container struct {
log logrus.FieldLogger
mu sync.Mutex
diff --git a/service/http/config.go b/service/http/config.go
new file mode 100644
index 00000000..b828eb08
--- /dev/null
+++ b/service/http/config.go
@@ -0,0 +1,34 @@
+package http
+
+import (
+ "github.com/spiral/roadrunner"
+ "fmt"
+)
+
+// Configures RoadRunner HTTP server.
+type Config struct {
+ // Enable enables http service.
+ Enable bool
+
+ // Host and port to handle as http server.
+ Host, Port string
+
+ // MaxRequest specified max size for payload body in bytes, set 0 to unlimited.
+ MaxRequest int64
+
+ // Uploads configures uploads configuration.
+ Uploads *FsConfig
+
+ // Workers configures roadrunner server and worker pool.
+ Workers *roadrunner.ServerConfig
+}
+
+// Valid validates the configuration.
+func (cfg *Config) Valid() error {
+ return nil
+}
+
+// httpAddr returns prepared http listen address.
+func (cfg *Config) httpAddr() string {
+ return fmt.Sprintf("%s:%v", cfg.Host, cfg.Port)
+}
diff --git a/http/fs_config.go b/service/http/fs_config.go
index de5b1389..de5b1389 100644
--- a/http/fs_config.go
+++ b/service/http/fs_config.go
diff --git a/http/fs_config_test.go b/service/http/fs_config_test.go
index 05f568e5..05f568e5 100644
--- a/http/fs_config_test.go
+++ b/service/http/fs_config_test.go
diff --git a/service/http/handler.go b/service/http/handler.go
new file mode 100644
index 00000000..1319200c
--- /dev/null
+++ b/service/http/handler.go
@@ -0,0 +1,71 @@
+package http
+
+import (
+ "net/http"
+ "strconv"
+ "github.com/sirupsen/logrus"
+ "github.com/spiral/roadrunner"
+ "github.com/pkg/errors"
+)
+
+// Handler serves http connections to underlying PHP application using PSR-7 protocol. Context will include request headers,
+// parsed files and query, payload will include parsed form dataTree (if any).
+type Handler struct {
+ cfg *Config
+ rr *roadrunner.Server
+}
+
+// Handle serve using PSR-7 requests passed to underlying application. Attempts to serve static files first if enabled.
+func (h *Handler) Handle(w http.ResponseWriter, r *http.Request) {
+ // validating request size
+ if h.cfg.MaxRequest != 0 {
+ if length := r.Header.Get("content-length"); length != "" {
+ if size, err := strconv.ParseInt(length, 10, 64); err != nil {
+ h.sendError(w, r, err)
+ return
+ } else if size > h.cfg.MaxRequest {
+ h.sendError(w, r, errors.New("request body max size is exceeded"))
+ return
+ }
+ }
+ }
+
+ req, err := NewRequest(r)
+ if err != nil {
+ h.sendError(w, r, err)
+ return
+ }
+
+ if err = req.Open(h.cfg); err != nil {
+ h.sendError(w, r, err)
+ return
+ }
+ defer req.Close()
+
+ p, err := req.Payload()
+ if err != nil {
+ h.sendError(w, r, err)
+ return
+ }
+
+ rsp, err := h.rr.Exec(p)
+ if err != nil {
+ h.sendError(w, r, err)
+ return
+ }
+
+ resp, err := NewResponse(rsp)
+ if err != nil {
+ h.sendError(w, r, err)
+ return
+ }
+
+ resp.Write(w)
+}
+
+// sendError sends error
+func (h *Handler) sendError(w http.ResponseWriter, r *http.Request, err error) {
+ logrus.Errorf("http: %s", err)
+ w.WriteHeader(500)
+ w.Write([]byte(err.Error()))
+}
diff --git a/http/parse.go b/service/http/parse.go
index 898f39a1..898f39a1 100644
--- a/http/parse.go
+++ b/service/http/parse.go
diff --git a/_____/http/request.go b/service/http/request.go
index fd483744..fd483744 100644
--- a/_____/http/request.go
+++ b/service/http/request.go
diff --git a/http/response.go b/service/http/response.go
index dd092353..dd092353 100644
--- a/http/response.go
+++ b/service/http/response.go
diff --git a/_____/http/rpc.go b/service/http/rpc.go
index 673ff2bb..673ff2bb 100644
--- a/_____/http/rpc.go
+++ b/service/http/rpc.go
diff --git a/service/http/service.go b/service/http/service.go
new file mode 100644
index 00000000..5a0d4c16
--- /dev/null
+++ b/service/http/service.go
@@ -0,0 +1,86 @@
+package http
+
+import (
+ "net/http"
+ "github.com/spiral/roadrunner/service"
+ "context"
+ "github.com/spiral/roadrunner"
+)
+
+// Name contains default service name.
+const Name = "http"
+
+type Middleware interface {
+ // Handle must return true if request/response pair is handled withing the middleware.
+ Handle(w http.ResponseWriter, r *http.Request) bool
+}
+
+// Service manages rr, http servers.
+type Service struct {
+ middleware []Middleware
+ cfg *Config
+ rr *roadrunner.Server
+ handler *Handler
+ http *http.Server
+}
+
+func (s *Service) Add(m Middleware) {
+ s.middleware = append(s.middleware, m)
+}
+
+// Configure 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.
+func (s *Service) Configure(cfg service.Config, c service.Container) (bool, error) {
+ config := &Config{}
+ if err := cfg.Unmarshal(config); err != nil {
+ return false, err
+ }
+
+ if !config.Enable {
+ return false, nil
+ }
+
+ if err := config.Valid(); err != nil {
+ return false, err
+ }
+
+ s.cfg = config
+ return true, nil
+}
+
+// Serve serves the service.
+func (s *Service) Serve() error {
+ rr := roadrunner.NewServer(s.cfg.Workers)
+ if err := rr.Start(); err != nil {
+ return err
+ }
+ defer s.rr.Stop()
+
+ // todo: observer
+
+ s.rr = rr
+ s.handler = &Handler{cfg: s.cfg, rr: s.rr}
+ s.http = &http.Server{Addr: s.cfg.httpAddr(), Handler: s}
+
+ if err := s.http.ListenAndServe(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Stop stops the service.
+func (s *Service) Stop() {
+ s.http.Shutdown(context.Background())
+}
+
+// Handle handles connection using set of middleware and rr PSR-7 server.
+func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ for _, m := range s.middleware {
+ if m.Handle(w, r) {
+ return
+ }
+ }
+
+ s.handler.Handle(w, r)
+}
diff --git a/http/uploads.go b/service/http/uploads.go
index cdd3e52c..cdd3e52c 100644
--- a/http/uploads.go
+++ b/service/http/uploads.go
diff --git a/rpc/config.go b/service/rpc/config.go
index 8a34752a..8a34752a 100644
--- a/rpc/config.go
+++ b/service/rpc/config.go
diff --git a/rpc/config_test.go b/service/rpc/config_test.go
index a953e30e..a953e30e 100644
--- a/rpc/config_test.go
+++ b/service/rpc/config_test.go
diff --git a/rpc/service.go b/service/rpc/service.go
index 516f341a..ce1e3351 100644
--- a/rpc/service.go
+++ b/service/rpc/service.go
@@ -8,7 +8,8 @@ import (
"sync"
)
-// todo: improved logging
+// Name contains default service name.
+const Name = "rpc"
// Service is RPC service.
type Service struct {
@@ -20,8 +21,8 @@ type Service struct {
serving bool
}
-// Configure must return configure service and return true if service is enabled. Must return error in case of
-// misconfiguration.
+// Configure 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.
func (s *Service) Configure(cfg service.Config, reg service.Container) (enabled bool, err error) {
config := &config{}
if err := cfg.Unmarshal(config); err != nil {
@@ -38,7 +39,7 @@ func (s *Service) Configure(cfg service.Config, reg service.Container) (enabled
return true, nil
}
-// Serve serves Service.
+// Serve serves the service.
func (s *Service) Serve() error {
if s.rpc == nil {
return errors.New("RPC service is not configured")
@@ -80,7 +81,7 @@ func (s *Service) Serve() error {
return nil
}
-// Close stop Service Service.
+// Stop stops the service.
func (s *Service) Stop() {
s.mu.Lock()
defer s.mu.Unlock()
diff --git a/rpc/service_test.go b/service/rpc/service_test.go
index a57ce1bd..a57ce1bd 100644
--- a/rpc/service_test.go
+++ b/service/rpc/service_test.go
diff --git a/service/entry.go b/service/service.go
index cf44349c..4c159fe1 100644
--- a/service/entry.go
+++ b/service/service.go
@@ -2,6 +2,19 @@ package service
import "sync"
+// svc provides high level functionality for road runner svc.
+type Service interface {
+ // Configure 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.
+ Configure(cfg Config, c Container) (enabled bool, err error)
+
+ // Serve serves.
+ Serve() error
+
+ // Stop stops the service.
+ Stop()
+}
+
const (
// StatusUndefined when service bus can not find the service.
StatusUndefined = iota
diff --git a/service/static/config.go b/service/static/config.go
new file mode 100644
index 00000000..2a1f6c13
--- /dev/null
+++ b/service/static/config.go
@@ -0,0 +1,52 @@
+package static
+
+import (
+ "strings"
+ "path"
+ "os"
+ "github.com/pkg/errors"
+)
+
+// Config describes file location and controls access to them.
+type Config struct {
+ // Enables StaticFile service.
+ Enable bool
+
+ // Dir contains name of directory to control access to.
+ Dir string
+
+ // Forbid specifies list of file extensions which are forbidden for access.
+ // Example: .php, .exe, .bat, .htaccess and etc.
+ Forbid []string
+}
+
+// Forbid 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))
+
+ for _, v := range cfg.Forbid {
+ if ext == v {
+ return true
+ }
+ }
+
+ return false
+}
+
+// Valid validates existence of directory.
+func (cfg *Config) Valid() error {
+ st, err := os.Stat(cfg.Dir)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return errors.New("root directory does not exists")
+ }
+
+ return err
+ }
+
+ if !st.IsDir() {
+ return errors.New("invalid root directory")
+ }
+
+ return nil
+}
diff --git a/service/static/config_test.go b/service/static/config_test.go
new file mode 100644
index 00000000..ce31348a
--- /dev/null
+++ b/service/static/config_test.go
@@ -0,0 +1,21 @@
+package static
+
+import (
+ "testing"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestConfig_Forbids(t *testing.T) {
+ cfg := Config{Forbid: []string{".php"}}
+
+ assert.True(t, cfg.Forbids("index.php"))
+ assert.True(t, cfg.Forbids("index.PHP"))
+ assert.True(t, cfg.Forbids("phpadmin/index.bak.php"))
+ assert.False(t, cfg.Forbids("index.html"))
+}
+
+func TestConfig_Valid(t *testing.T) {
+ assert.NoError(t, (&Config{Dir: "./"}).Valid())
+ assert.Error(t, (&Config{Dir: "./config.go"}).Valid())
+ assert.Error(t, (&Config{Dir: "./dir/"}).Valid())
+}
diff --git a/service/static/service.go b/service/static/service.go
new file mode 100644
index 00000000..916c18a2
--- /dev/null
+++ b/service/static/service.go
@@ -0,0 +1,120 @@
+package static
+
+import (
+ "github.com/sirupsen/logrus"
+ "net/http"
+ "os"
+ "path"
+ "strings"
+ rrttp "github.com/spiral/roadrunner/service/http"
+ "github.com/spiral/roadrunner/service"
+)
+
+// Name contains default service name.
+const Name = "static-server"
+
+// Service serves static files. Potentially convert into middleware?
+type Service struct {
+ // Logger is associated debug and error logger. Can be empty.
+ Logger *logrus.Logger
+
+ // server configuration (location, forbidden files and etc)
+ cfg *Config
+
+ // root is initiated http directory
+ root http.Dir
+
+ // let's service stay running
+ done chan interface{}
+}
+
+// Configure 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.
+func (s *Service) Configure(cfg service.Config, c service.Container) (enabled bool, err error) {
+ config := &Config{}
+ if err := cfg.Unmarshal(config); err != nil {
+ return false, err
+ }
+
+ if !config.Enable {
+ return false, nil
+ }
+
+ if err := config.Valid(); err != nil {
+ return false, err
+ }
+
+ s.cfg = config
+ s.root = http.Dir(s.cfg.Dir)
+
+ // registering as middleware
+ if h, ok := c.Get(rrttp.Name); ok >= service.StatusConfigured {
+ if h, ok := h.(*rrttp.Service); ok {
+ h.Add(s)
+ }
+ } else {
+ if s.Logger != nil {
+ s.Logger.Warningf("no http service found")
+ }
+ }
+
+ return true, nil
+}
+
+// Serve serves the service.
+func (s *Service) Serve() error {
+ s.done = make(chan interface{})
+ <-s.done
+
+ return nil
+}
+
+// Stop stops the service.
+func (s *Service) Stop() {
+ //todo: this is not safe (TODO CHECK IT?)
+ close(s.done)
+}
+
+// Handle must return true if request/response pair is handled withing the middleware.
+func (s *Service) Handle(w http.ResponseWriter, r *http.Request) bool {
+ fPath := r.URL.Path
+ if !strings.HasPrefix(fPath, "/") {
+ fPath = "/" + fPath
+ }
+ fPath = path.Clean(fPath)
+
+ if s.cfg.Forbids(fPath) {
+ if s.Logger != nil {
+ s.Logger.Warningf("attempt to access forbidden file %s", fPath)
+ }
+ return false
+ }
+
+ f, err := s.root.Open(fPath)
+ if err != nil {
+ if !os.IsNotExist(err) {
+ if s.Logger != nil {
+ s.Logger.Error(err)
+ }
+ }
+
+ return false
+ }
+ defer f.Close()
+
+ d, err := f.Stat()
+ if err != nil {
+ if s.Logger != nil {
+ s.Logger.Error(err)
+ }
+ return false
+ }
+
+ // do not Handle directories
+ if d.IsDir() {
+ return false
+ }
+
+ http.ServeContent(w, r, d.Name(), d.ModTime(), f)
+ return true
+}