summaryrefslogtreecommitdiff
path: root/service
diff options
context:
space:
mode:
Diffstat (limited to 'service')
-rw-r--r--service/env/config.go22
-rw-r--r--service/env/config_test.go41
-rw-r--r--service/env/environment.go23
-rw-r--r--service/env/service.go55
-rw-r--r--service/env/service_test.go80
-rw-r--r--service/gzip/config.go22
-rw-r--r--service/gzip/config_test.go46
-rw-r--r--service/gzip/service.go36
-rw-r--r--service/gzip/service_test.go72
-rw-r--r--service/headers/config.go41
-rw-r--r--service/headers/config_test.go30
-rw-r--r--service/headers/service.go113
-rw-r--r--service/headers/service_test.go340
-rw-r--r--service/health/config.go32
-rw-r--r--service/health/config_test.go46
-rw-r--r--service/health/service.go116
-rw-r--r--service/health/service_test.go317
-rw-r--r--service/http/attributes/attributes.go76
-rw-r--r--service/http/attributes/attributes_test.go79
-rw-r--r--service/http/config.go263
-rw-r--r--service/http/config_test.go331
-rw-r--r--service/http/constants.go6
-rw-r--r--service/http/errors.go25
-rw-r--r--service/http/errors_windows.go27
-rw-r--r--service/http/fcgi_test.go105
-rw-r--r--service/http/fixtures/server.crt15
-rw-r--r--service/http/fixtures/server.key9
-rw-r--r--service/http/h2c_test.go83
-rw-r--r--service/http/handler.go208
-rw-r--r--service/http/handler_test.go1961
-rw-r--r--service/http/parse.go147
-rw-r--r--service/http/parse_test.go52
-rw-r--r--service/http/request.go183
-rw-r--r--service/http/response.go107
-rw-r--r--service/http/response_test.go162
-rw-r--r--service/http/rpc.go34
-rw-r--r--service/http/rpc_test.go221
-rw-r--r--service/http/service.go427
-rw-r--r--service/http/service_test.go759
-rw-r--r--service/http/ssl_test.go254
-rw-r--r--service/http/uploads.go160
-rw-r--r--service/http/uploads_config.go45
-rw-r--r--service/http/uploads_config_test.go24
-rw-r--r--service/http/uploads_test.go434
-rw-r--r--service/limit/config.go48
-rw-r--r--service/limit/config_test.go51
-rw-r--r--service/limit/controller.go166
-rw-r--r--service/limit/service.go39
-rw-r--r--service/limit/service_test.go500
-rw-r--r--service/limit/state_filter.go58
-rw-r--r--service/metrics/config.go136
-rw-r--r--service/metrics/config_test.go75
-rw-r--r--service/metrics/rpc.go260
-rw-r--r--service/metrics/rpc_test.go861
-rw-r--r--service/metrics/service.go191
-rw-r--r--service/metrics/service_test.go247
-rw-r--r--service/reload/config.go72
-rw-r--r--service/reload/config_test.go63
-rw-r--r--service/reload/samefile.go9
-rw-r--r--service/reload/samefile_windows.go12
-rw-r--r--service/reload/service.go162
-rw-r--r--service/reload/service_test.go1
-rw-r--r--service/reload/watcher.go409
-rw-r--r--service/reload/watcher_test.go673
-rw-r--r--service/rpc/config.go60
-rw-r--r--service/rpc/config_test.go163
-rw-r--r--service/rpc/service.go124
-rw-r--r--service/rpc/service_test.go96
-rw-r--r--service/rpc/system.go18
-rw-r--r--service/static/config.go82
-rw-r--r--service/static/config_test.go45
-rw-r--r--service/static/service.go87
-rw-r--r--service/static/service_test.go531
73 files changed, 0 insertions, 12868 deletions
diff --git a/service/env/config.go b/service/env/config.go
deleted file mode 100644
index a7da695e..00000000
--- a/service/env/config.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package env
-
-import (
- "github.com/spiral/roadrunner/service"
-)
-
-// Config defines set of env values for RR workers.
-type Config struct {
- // values to set as worker _ENV.
- Values map[string]string
-}
-
-// Hydrate must populate Config values using given Config source. Must return error if Config is not valid.
-func (c *Config) Hydrate(cfg service.Config) error {
- return cfg.Unmarshal(&c.Values)
-}
-
-// InitDefaults allows to init blank config with pre-defined set of default values.
-func (c *Config) InitDefaults() error {
- c.Values = make(map[string]string)
- return nil
-}
diff --git a/service/env/config_test.go b/service/env/config_test.go
deleted file mode 100644
index a526990d..00000000
--- a/service/env/config_test.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package env
-
-import (
- json "github.com/json-iterator/go"
- "github.com/spiral/roadrunner/service"
- "github.com/stretchr/testify/assert"
- "testing"
-)
-
-type mockCfg struct{ cfg string }
-
-func (cfg *mockCfg) Get(name string) service.Config { return nil }
-func (cfg *mockCfg) Unmarshal(out interface{}) error {
- j := json.ConfigCompatibleWithStandardLibrary
- return j.Unmarshal([]byte(cfg.cfg), out)
-}
-
-func Test_Config_Hydrate(t *testing.T) {
- cfg := &mockCfg{`{"key":"value"}`}
- c := &Config{}
-
- assert.NoError(t, c.Hydrate(cfg))
- assert.Len(t, c.Values, 1)
-}
-
-func Test_Config_Hydrate_Empty(t *testing.T) {
- cfg := &mockCfg{`{}`}
- c := &Config{}
-
- assert.NoError(t, c.Hydrate(cfg))
- assert.Len(t, c.Values, 0)
-}
-
-func Test_Config_Defaults(t *testing.T) {
- c := &Config{}
- err := c.InitDefaults()
- if err != nil {
- t.Errorf("Test_Config_Defaults failed: error %v", err)
- }
- assert.Len(t, c.Values, 0)
-}
diff --git a/service/env/environment.go b/service/env/environment.go
deleted file mode 100644
index ab8febf7..00000000
--- a/service/env/environment.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package env
-
-// Environment aggregates list of environment variables. This interface can be used in custom implementation to drive
-// values from external sources.
-type Environment interface {
- Setter
- Getter
-
- // Copy all environment values.
- Copy(setter Setter) error
-}
-
-// Setter provides ability to set environment value.
-type Setter interface {
- // SetEnv sets or creates environment value.
- SetEnv(key, value string)
-}
-
-// Getter provides ability to set environment value.
-type Getter interface {
- // GetEnv must return list of env variables.
- GetEnv() (map[string]string, error)
-}
diff --git a/service/env/service.go b/service/env/service.go
deleted file mode 100644
index 83175b36..00000000
--- a/service/env/service.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package env
-
-// ID contains default service name.
-const ID = "env"
-
-// Service provides ability to map _ENV values from config file.
-type Service struct {
- // values is default set of values.
- values map[string]string
-}
-
-// NewService creates new env service instance for given rr version.
-func NewService(defaults map[string]string) *Service {
- s := &Service{values: defaults}
- return s
-}
-
-// Init must return configure svc and return true if svc hasStatus enabled. Must return error in case of
-// misconfiguration. Services must not be used without proper configuration pushed first.
-func (s *Service) Init(cfg *Config) (bool, error) {
- if s.values == nil {
- s.values = make(map[string]string)
- s.values["RR"] = "true"
- }
-
- for k, v := range cfg.Values {
- s.values[k] = v
- }
-
- return true, nil
-}
-
-// GetEnv must return list of env variables.
-func (s *Service) GetEnv() (map[string]string, error) {
- return s.values, nil
-}
-
-// SetEnv sets or creates environment value.
-func (s *Service) SetEnv(key, value string) {
- s.values[key] = value
-}
-
-// Copy all environment values.
-func (s *Service) Copy(setter Setter) error {
- values, err := s.GetEnv()
- if err != nil {
- return err
- }
-
- for k, v := range values {
- setter.SetEnv(k, v)
- }
-
- return nil
-}
diff --git a/service/env/service_test.go b/service/env/service_test.go
deleted file mode 100644
index 19cc03c7..00000000
--- a/service/env/service_test.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package env
-
-import (
- "github.com/stretchr/testify/assert"
- "testing"
-)
-
-func Test_NewService(t *testing.T) {
- s := NewService(map[string]string{"version": "test"})
- assert.Len(t, s.values, 1)
-}
-
-func Test_Init(t *testing.T) {
- var err error
- s := &Service{}
- _, err = s.Init(&Config{})
- if err != nil {
- t.Errorf("error during the s.Init: error %v", err)
- }
- assert.Len(t, s.values, 1)
-
- values, err := s.GetEnv()
- assert.NoError(t, err)
- assert.Equal(t, "true", values["RR"])
-}
-
-func Test_Extend(t *testing.T) {
- var err error
- s := NewService(map[string]string{"RR": "version"})
-
- _, err = s.Init(&Config{Values: map[string]string{"key": "value"}})
- if err != nil {
- t.Errorf("error during the s.Init: error %v", err)
- }
- assert.Len(t, s.values, 2)
-
- values, err := s.GetEnv()
- assert.NoError(t, err)
- assert.Len(t, values, 2)
- assert.Equal(t, "version", values["RR"])
- assert.Equal(t, "value", values["key"])
-}
-
-func Test_Set(t *testing.T) {
- var err error
- s := NewService(map[string]string{"RR": "version"})
-
- _, err = s.Init(&Config{Values: map[string]string{"key": "value"}})
- if err != nil {
- t.Errorf("error during the s.Init: error %v", err)
- }
- assert.Len(t, s.values, 2)
-
- s.SetEnv("key", "value-new")
- s.SetEnv("other", "new")
-
- values, err := s.GetEnv()
- assert.NoError(t, err)
- assert.Len(t, values, 3)
- assert.Equal(t, "version", values["RR"])
- assert.Equal(t, "value-new", values["key"])
- assert.Equal(t, "new", values["other"])
-}
-
-func Test_Copy(t *testing.T) {
- s1 := NewService(map[string]string{"RR": "version"})
- s2 := NewService(map[string]string{})
-
- s1.SetEnv("key", "value-new")
- s1.SetEnv("other", "new")
-
- assert.NoError(t, s1.Copy(s2))
-
- values, err := s2.GetEnv()
- assert.NoError(t, err)
- assert.Len(t, values, 3)
- assert.Equal(t, "version", values["RR"])
- assert.Equal(t, "value-new", values["key"])
- assert.Equal(t, "new", values["other"])
-}
diff --git a/service/gzip/config.go b/service/gzip/config.go
deleted file mode 100644
index 00ac559d..00000000
--- a/service/gzip/config.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package gzip
-
-import (
- "github.com/spiral/roadrunner/service"
-)
-
-// Config describes file location and controls access to them.
-type Config struct {
- Enable bool
-}
-
-// Hydrate must populate Config values using given Config source. Must return error if Config is not valid.
-func (c *Config) Hydrate(cfg service.Config) error {
- return cfg.Unmarshal(c)
-}
-
-// InitDefaults sets missing values to their default values.
-func (c *Config) InitDefaults() error {
- c.Enable = true
-
- return nil
-}
diff --git a/service/gzip/config_test.go b/service/gzip/config_test.go
deleted file mode 100644
index c2168166..00000000
--- a/service/gzip/config_test.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package gzip
-
-import (
- json "github.com/json-iterator/go"
- "github.com/spiral/roadrunner/service"
- "github.com/stretchr/testify/assert"
- "testing"
-)
-
-type mockCfg struct{ cfg string }
-
-func (cfg *mockCfg) Get(name string) service.Config { return nil }
-func (cfg *mockCfg) Unmarshal(out interface{}) error {
- j := json.ConfigCompatibleWithStandardLibrary
- return j.Unmarshal([]byte(cfg.cfg), out)
-}
-
-func Test_Config_Hydrate(t *testing.T) {
- cfg := &mockCfg{`{"enable": true}`}
- c := &Config{}
-
- assert.NoError(t, c.Hydrate(cfg))
-}
-
-func Test_Config_Hydrate_Error(t *testing.T) {
- cfg := &mockCfg{`{"enable": "invalid"}`}
- c := &Config{}
-
- assert.Error(t, c.Hydrate(cfg))
-}
-
-func Test_Config_Hydrate_Error2(t *testing.T) {
- cfg := &mockCfg{`{"enable": 1}`}
- c := &Config{}
-
- assert.Error(t, c.Hydrate(cfg))
-}
-
-func Test_Config_Defaults(t *testing.T) {
- c := &Config{}
- err := c.InitDefaults()
- if err != nil {
- t.Errorf("error during the InitDefaults: error %v", err)
- }
- assert.Equal(t, true, c.Enable)
-}
diff --git a/service/gzip/service.go b/service/gzip/service.go
deleted file mode 100644
index 231ba4d9..00000000
--- a/service/gzip/service.go
+++ /dev/null
@@ -1,36 +0,0 @@
-package gzip
-
-import (
- "errors"
- "github.com/NYTimes/gziphandler"
- rrhttp "github.com/spiral/roadrunner/service/http"
- "net/http"
-)
-
-// ID contains default service name.
-const ID = "gzip"
-var httpNotInitialized = errors.New("http service should be defined properly in config to use gzip")
-
-type Service struct {
- cfg *Config
-}
-
-func (s *Service) Init(cfg *Config, r *rrhttp.Service) (bool, error) {
- s.cfg = cfg
- if !s.cfg.Enable {
- return false, nil
- }
- if r == nil {
- return false, httpNotInitialized
- }
-
- r.AddMiddleware(s.middleware)
-
- return true, nil
-}
-
-func (s *Service) middleware(f http.HandlerFunc) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- gziphandler.GzipHandler(f).ServeHTTP(w, r)
- }
-}
diff --git a/service/gzip/service_test.go b/service/gzip/service_test.go
deleted file mode 100644
index 778bdacd..00000000
--- a/service/gzip/service_test.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package gzip
-
-import (
- json "github.com/json-iterator/go"
- "github.com/sirupsen/logrus"
- "github.com/sirupsen/logrus/hooks/test"
- "github.com/spiral/roadrunner/service"
- rrhttp "github.com/spiral/roadrunner/service/http"
- "github.com/stretchr/testify/assert"
- "testing"
-)
-
-type testCfg struct {
- gzip string
- httpCfg string
- target string
-}
-
-func (cfg *testCfg) Get(name string) service.Config {
- if name == rrhttp.ID {
- return &testCfg{target: cfg.httpCfg}
- }
-
- if name == ID {
- return &testCfg{target: cfg.gzip}
- }
- return nil
-}
-func (cfg *testCfg) Unmarshal(out interface{}) error {
- j := json.ConfigCompatibleWithStandardLibrary
- return j.Unmarshal([]byte(cfg.target), out)
-}
-
-func Test_Disabled(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{cfg: &Config{Enable: true}})
-
- assert.NoError(t, c.Init(&testCfg{
- httpCfg: `{
- "address": ":6029",
- "workers":{
- "command": "php ../../tests/http/client.php echo pipes",
- }
- }`,
- gzip: `{"enable":false}`,
- }))
-
- s, st := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusInactive, st)
-}
-
-// TEST bug #275
-func Test_Bug275(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- assert.Error(t, c.Init(&testCfg{
- httpCfg: "",
- gzip: `{"enable":true}`,
- }))
-
- s, st := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusInactive, st)
-}
diff --git a/service/headers/config.go b/service/headers/config.go
deleted file mode 100644
index f9af1df2..00000000
--- a/service/headers/config.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package headers
-
-import "github.com/spiral/roadrunner/service"
-
-// Config declares headers service configuration.
-type Config struct {
- // CORS settings.
- CORS *CORSConfig
-
- // Request headers to add to every payload send to PHP.
- Request map[string]string
-
- // Response headers to add to every payload generated by PHP.
- Response map[string]string
-}
-
-// CORSConfig headers configuration.
-type CORSConfig struct {
- // AllowedOrigin: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
- AllowedOrigin string
-
- // AllowedHeaders: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
- AllowedHeaders string
-
- // AllowedMethods: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
- AllowedMethods string
-
- // AllowCredentials https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
- AllowCredentials *bool
-
- // ExposeHeaders: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
- ExposedHeaders string
-
- // MaxAge of CORS headers in seconds/
- MaxAge int
-}
-
-// Hydrate service config.
-func (c *Config) Hydrate(cfg service.Config) error {
- return cfg.Unmarshal(c)
-}
diff --git a/service/headers/config_test.go b/service/headers/config_test.go
deleted file mode 100644
index 6ea02f67..00000000
--- a/service/headers/config_test.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package headers
-
-import (
- json "github.com/json-iterator/go"
- "github.com/spiral/roadrunner/service"
- "github.com/stretchr/testify/assert"
- "testing"
-)
-
-type mockCfg struct{ cfg string }
-
-func (cfg *mockCfg) Get(name string) service.Config { return nil }
-func (cfg *mockCfg) Unmarshal(out interface{}) error {
- j := json.ConfigCompatibleWithStandardLibrary
- return j.Unmarshal([]byte(cfg.cfg), out)
-}
-
-func Test_Config_Hydrate_Error1(t *testing.T) {
- cfg := &mockCfg{`{"request": {"From": "Something"}}`}
- c := &Config{}
-
- assert.NoError(t, c.Hydrate(cfg))
-}
-
-func Test_Config_Hydrate_Error2(t *testing.T) {
- cfg := &mockCfg{`{"dir": "/dir/"`}
- c := &Config{}
-
- assert.Error(t, c.Hydrate(cfg))
-}
diff --git a/service/headers/service.go b/service/headers/service.go
deleted file mode 100644
index 429219d7..00000000
--- a/service/headers/service.go
+++ /dev/null
@@ -1,113 +0,0 @@
-package headers
-
-import (
- rrhttp "github.com/spiral/roadrunner/service/http"
- "net/http"
- "strconv"
-)
-
-// ID contains default service name.
-const ID = "headers"
-
-// Service serves headers files. Potentially convert into middleware?
-type Service struct {
- // server configuration (location, forbidden files and etc)
- cfg *Config
-}
-
-// 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.
-func (s *Service) Init(cfg *Config, r *rrhttp.Service) (bool, error) {
- if r == nil {
- return false, nil
- }
-
- s.cfg = cfg
- r.AddMiddleware(s.middleware)
-
- return true, nil
-}
-
-// middleware must return true if request/response pair is handled within the middleware.
-func (s *Service) middleware(f http.HandlerFunc) http.HandlerFunc {
- // Define the http.HandlerFunc
- return func(w http.ResponseWriter, r *http.Request) {
-
- if s.cfg.Request != nil {
- for k, v := range s.cfg.Request {
- r.Header.Add(k, v)
- }
- }
-
- if s.cfg.Response != nil {
- for k, v := range s.cfg.Response {
- w.Header().Set(k, v)
- }
- }
-
- if s.cfg.CORS != nil {
- if r.Method == http.MethodOptions {
- s.preflightRequest(w, r)
- return
- }
-
- s.corsHeaders(w, r)
- }
-
- f(w, r)
- }
-}
-
-// configure OPTIONS response
-func (s *Service) preflightRequest(w http.ResponseWriter, r *http.Request) {
- headers := w.Header()
-
- headers.Add("Vary", "Origin")
- headers.Add("Vary", "Access-Control-Request-Method")
- headers.Add("Vary", "Access-Control-Request-Headers")
-
- if s.cfg.CORS.AllowedOrigin != "" {
- headers.Set("Access-Control-Allow-Origin", s.cfg.CORS.AllowedOrigin)
- }
-
- if s.cfg.CORS.AllowedHeaders != "" {
- headers.Set("Access-Control-Allow-Headers", s.cfg.CORS.AllowedHeaders)
- }
-
- if s.cfg.CORS.AllowedMethods != "" {
- headers.Set("Access-Control-Allow-Methods", s.cfg.CORS.AllowedMethods)
- }
-
- if s.cfg.CORS.AllowCredentials != nil {
- headers.Set("Access-Control-Allow-Credentials", strconv.FormatBool(*s.cfg.CORS.AllowCredentials))
- }
-
- if s.cfg.CORS.MaxAge > 0 {
- headers.Set("Access-Control-Max-Age", strconv.Itoa(s.cfg.CORS.MaxAge))
- }
-
- w.WriteHeader(http.StatusOK)
-}
-
-// configure CORS headers
-func (s *Service) corsHeaders(w http.ResponseWriter, r *http.Request) {
- headers := w.Header()
-
- headers.Add("Vary", "Origin")
-
- if s.cfg.CORS.AllowedOrigin != "" {
- headers.Set("Access-Control-Allow-Origin", s.cfg.CORS.AllowedOrigin)
- }
-
- if s.cfg.CORS.AllowedHeaders != "" {
- headers.Set("Access-Control-Allow-Headers", s.cfg.CORS.AllowedHeaders)
- }
-
- if s.cfg.CORS.ExposedHeaders != "" {
- headers.Set("Access-Control-Expose-Headers", s.cfg.CORS.ExposedHeaders)
- }
-
- if s.cfg.CORS.AllowCredentials != nil {
- headers.Set("Access-Control-Allow-Credentials", strconv.FormatBool(*s.cfg.CORS.AllowCredentials))
- }
-}
diff --git a/service/headers/service_test.go b/service/headers/service_test.go
deleted file mode 100644
index a67def02..00000000
--- a/service/headers/service_test.go
+++ /dev/null
@@ -1,340 +0,0 @@
-package headers
-
-import (
- "github.com/cenkalti/backoff/v4"
- json "github.com/json-iterator/go"
- "github.com/sirupsen/logrus"
- "github.com/sirupsen/logrus/hooks/test"
- "github.com/spiral/roadrunner/service"
- rrhttp "github.com/spiral/roadrunner/service/http"
- "github.com/stretchr/testify/assert"
- "io/ioutil"
- "net/http"
- "testing"
- "time"
-)
-
-type testCfg struct {
- httpCfg string
- headers string
- target string
-}
-
-func (cfg *testCfg) Get(name string) service.Config {
- if name == rrhttp.ID {
- return &testCfg{target: cfg.httpCfg}
- }
-
- if name == ID {
- return &testCfg{target: cfg.headers}
- }
- return nil
-}
-
-func (cfg *testCfg) Unmarshal(out interface{}) error {
- return json.Unmarshal([]byte(cfg.target), out)
-}
-
-func Test_RequestHeaders(t *testing.T) {
- bkoff := backoff.NewExponentialBackOff()
- bkoff.MaxElapsedTime = time.Second * 15
-
- err := backoff.Retry(func() error {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, &rrhttp.Service{})
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{
- headers: `{"request":{"input": "custom-header"}}`,
- httpCfg: `{
- "enable": true,
- "address": ":6078",
- "maxRequestSize": 1024,
- "workers":{
- "command": "php ../../tests/http/client.php header pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`}))
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("error during Serve: error %v", err)
- }
- }()
-
- time.Sleep(time.Millisecond * 100)
- defer c.Stop()
-
- req, err := http.NewRequest("GET", "http://localhost:6078?hello=value", nil)
- if err != nil {
- return err
- }
-
- r, err := http.DefaultClient.Do(req)
- if err != nil {
- return err
- }
-
- b, err := ioutil.ReadAll(r.Body)
- if err != nil {
- return err
- }
-
- assert.Equal(t, 200, r.StatusCode)
- assert.Equal(t, "CUSTOM-HEADER", string(b))
-
- err = r.Body.Close()
- if err != nil {
- return err
- }
-
- return nil
- }, bkoff)
-
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func Test_ResponseHeaders(t *testing.T) {
- bkoff := backoff.NewExponentialBackOff()
- bkoff.MaxElapsedTime = time.Second * 15
-
- err := backoff.Retry(func() error {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, &rrhttp.Service{})
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{
- headers: `{"response":{"output": "output-header"},"request":{"input": "custom-header"}}`,
- httpCfg: `{
- "enable": true,
- "address": ":6079",
- "maxRequestSize": 1024,
- "workers":{
- "command": "php ../../tests/http/client.php header pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`}))
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("error during the Serve: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 100)
- defer c.Stop()
-
- req, err := http.NewRequest("GET", "http://localhost:6079?hello=value", nil)
- if err != nil {
- return err
- }
-
- r, err := http.DefaultClient.Do(req)
- if err != nil {
- return err
- }
-
- assert.Equal(t, "output-header", r.Header.Get("output"))
-
- b, err := ioutil.ReadAll(r.Body)
- if err != nil {
- return err
- }
- assert.Equal(t, 200, r.StatusCode)
- assert.Equal(t, "CUSTOM-HEADER", string(b))
-
- err = r.Body.Close()
- if err != nil {
- return err
- }
-
- return nil
- }, bkoff)
-
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func TestCORS_OPTIONS(t *testing.T) {
- bkoff := backoff.NewExponentialBackOff()
- bkoff.MaxElapsedTime = time.Second * 15
-
- err := backoff.Retry(func() error {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, &rrhttp.Service{})
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{
- headers: `{
-"cors":{
- "allowedOrigin": "*",
- "allowedHeaders": "*",
- "allowedMethods": "GET,POST,PUT,DELETE",
- "allowCredentials": true,
- "exposedHeaders": "Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma",
- "maxAge": 600
-}
-}`,
- httpCfg: `{
- "enable": true,
- "address": ":16379",
- "maxRequestSize": 1024,
- "workers":{
- "command": "php ../../tests/http/client.php headers pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`}))
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("error during the Serve: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 100)
- defer c.Stop()
-
- req, err := http.NewRequest("OPTIONS", "http://localhost:16379", nil)
- if err != nil {
- return err
- }
-
- r, err := http.DefaultClient.Do(req)
- if err != nil {
- return err
- }
-
- assert.Equal(t, "true", r.Header.Get("Access-Control-Allow-Credentials"))
- assert.Equal(t, "*", r.Header.Get("Access-Control-Allow-Headers"))
- assert.Equal(t, "GET,POST,PUT,DELETE", r.Header.Get("Access-Control-Allow-Methods"))
- assert.Equal(t, "*", r.Header.Get("Access-Control-Allow-Origin"))
- assert.Equal(t, "600", r.Header.Get("Access-Control-Max-Age"))
- assert.Equal(t, "true", r.Header.Get("Access-Control-Allow-Credentials"))
-
- _, err = ioutil.ReadAll(r.Body)
- if err != nil {
- return err
- }
- assert.Equal(t, 200, r.StatusCode)
-
- err = r.Body.Close()
- if err != nil {
- return err
- }
-
- return nil
- }, bkoff)
-
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func TestCORS_Pass(t *testing.T) {
- bkoff := backoff.NewExponentialBackOff()
- bkoff.MaxElapsedTime = time.Second * 15
-
- err := backoff.Retry(func() error {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, &rrhttp.Service{})
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{
- headers: `{
-"cors":{
- "allowedOrigin": "*",
- "allowedHeaders": "*",
- "allowedMethods": "GET,POST,PUT,DELETE",
- "allowCredentials": true,
- "exposedHeaders": "Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma",
- "maxAge": 600
-}
-}`,
- httpCfg: `{
- "enable": true,
- "address": ":6672",
- "maxRequestSize": 1024,
- "workers":{
- "command": "php ../../tests/http/client.php headers pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`}))
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("error during the Serve: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 100)
- defer c.Stop()
-
- req, err := http.NewRequest("GET", "http://localhost:6672", nil)
- if err != nil {
- return err
- }
-
- r, err := http.DefaultClient.Do(req)
- if err != nil {
- return err
- }
-
- assert.Equal(t, "true", r.Header.Get("Access-Control-Allow-Credentials"))
- assert.Equal(t, "*", r.Header.Get("Access-Control-Allow-Headers"))
- assert.Equal(t, "*", r.Header.Get("Access-Control-Allow-Origin"))
- assert.Equal(t, "true", r.Header.Get("Access-Control-Allow-Credentials"))
-
- _, err = ioutil.ReadAll(r.Body)
- if err != nil {
- return err
- }
- assert.Equal(t, 200, r.StatusCode)
-
- err = r.Body.Close()
- if err != nil {
- return err
- }
-
- return nil
- }, bkoff)
-
- if err != nil {
- t.Fatal(err)
- }
-}
diff --git a/service/health/config.go b/service/health/config.go
deleted file mode 100644
index 60a52d6e..00000000
--- a/service/health/config.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package health
-
-import (
- "errors"
- "strings"
-
- "github.com/spiral/roadrunner/service"
-)
-
-// Config configures the health service
-type Config struct {
- // Address to listen on
- Address string
-}
-
-// Hydrate the config
-func (c *Config) Hydrate(cfg service.Config) error {
- if err := cfg.Unmarshal(c); err != nil {
- return err
- }
- return c.Valid()
-}
-
-// Valid validates the configuration.
-func (c *Config) Valid() error {
- // Validate the address
- if c.Address != "" && !strings.Contains(c.Address, ":") {
- return errors.New("malformed http server address")
- }
-
- return nil
-}
diff --git a/service/health/config_test.go b/service/health/config_test.go
deleted file mode 100644
index ba7d7c12..00000000
--- a/service/health/config_test.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package health
-
-import (
- json "github.com/json-iterator/go"
- "testing"
-
- "github.com/spiral/roadrunner/service"
- "github.com/stretchr/testify/assert"
-)
-
-type mockCfg struct{ cfg string }
-
-func (cfg *mockCfg) Get(name string) service.Config { return nil }
-func (cfg *mockCfg) Unmarshal(out interface{}) error {
- j := json.ConfigCompatibleWithStandardLibrary
- return j.Unmarshal([]byte(cfg.cfg), out)
-}
-
-func Test_Config_Hydrate_Error1(t *testing.T) {
- cfg := &mockCfg{`{"address": "localhost:8080"}`}
- c := &Config{}
-
- assert.NoError(t, c.Hydrate(cfg))
- assert.Equal(t, "localhost:8080", c.Address)
-}
-
-func Test_Config_Hydrate_Error2(t *testing.T) {
- cfg := &mockCfg{`{"dir": "/dir/"`}
- c := &Config{}
-
- assert.Error(t, c.Hydrate(cfg))
-}
-
-func Test_Config_Hydrate_Valid1(t *testing.T) {
- cfg := &mockCfg{`{"address": "localhost"}`}
- c := &Config{}
-
- assert.Error(t, c.Hydrate(cfg))
-}
-
-func Test_Config_Hydrate_Valid2(t *testing.T) {
- cfg := &mockCfg{`{"address": ":1111"}`}
- c := &Config{}
-
- assert.NoError(t, c.Hydrate(cfg))
-}
diff --git a/service/health/service.go b/service/health/service.go
deleted file mode 100644
index ce127340..00000000
--- a/service/health/service.go
+++ /dev/null
@@ -1,116 +0,0 @@
-package health
-
-import (
- "context"
- "fmt"
- "github.com/sirupsen/logrus"
- "net/http"
- "sync"
- "time"
-
- rrhttp "github.com/spiral/roadrunner/service/http"
-)
-
-const (
- // ID declares public service name.
- ID = "health"
- // maxHeaderSize declares max header size for prometheus server
- maxHeaderSize = 1024 * 1024 * 100 // 104MB
-)
-
-// Service to serve an endpoint for checking the health of the worker pool
-type Service struct {
- cfg *Config
- log *logrus.Logger
- mu sync.Mutex
- http *http.Server
- httpService *rrhttp.Service
-}
-
-// Init health service
-func (s *Service) Init(cfg *Config, r *rrhttp.Service, log *logrus.Logger) (bool, error) {
- // Ensure the httpService is set
- if r == nil {
- return false, nil
- }
-
- s.cfg = cfg
- s.log = log
- s.httpService = r
- return true, nil
-}
-
-// Serve the health endpoint
-func (s *Service) Serve() error {
- // Configure and start the http server
- s.mu.Lock()
- s.http = &http.Server{
- Addr: s.cfg.Address,
- Handler: s,
- IdleTimeout: time.Hour * 24,
- ReadTimeout: time.Minute * 60,
- MaxHeaderBytes: maxHeaderSize,
- ReadHeaderTimeout: time.Minute * 60,
- WriteTimeout: time.Minute * 60,
- }
- s.mu.Unlock()
-
- err := s.http.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- return err
- }
-
- return nil
-}
-
-// Stop the health endpoint
-func (s *Service) Stop() {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- if s.http != nil {
- // gracefully stop the server
- go func() {
- err := s.http.Shutdown(context.Background())
- if err != nil && err != http.ErrServerClosed {
- s.log.Error(fmt.Errorf("error shutting down the metrics server: error %v", err))
- }
- }()
- }
-}
-
-// ServeHTTP returns the health of the pool of workers
-func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- status := http.StatusOK
- if !s.isHealthy() {
- status = http.StatusInternalServerError
- }
- w.WriteHeader(status)
-}
-
-// isHealthy checks the server, pool and ensures at least one worker is active
-func (s *Service) isHealthy() bool {
- httpService := s.httpService
- if httpService == nil {
- return false
- }
-
- server := httpService.Server()
- if server == nil {
- return false
- }
-
- pool := server.Pool()
- if pool == nil {
- return false
- }
-
- // Ensure at least one worker is active
- for _, w := range pool.Workers() {
- if w.State().IsActive() {
- return true
- }
- }
-
- return false
-}
diff --git a/service/health/service_test.go b/service/health/service_test.go
deleted file mode 100644
index fc743a62..00000000
--- a/service/health/service_test.go
+++ /dev/null
@@ -1,317 +0,0 @@
-package health
-
-import (
- json "github.com/json-iterator/go"
- "io/ioutil"
- "net/http"
- "testing"
- "time"
-
- "github.com/sirupsen/logrus"
- "github.com/sirupsen/logrus/hooks/test"
- "github.com/spiral/roadrunner/service"
- rrhttp "github.com/spiral/roadrunner/service/http"
- "github.com/stretchr/testify/assert"
-)
-
-type testCfg struct {
- healthCfg string
- httpCfg string
- target string
-}
-
-func (cfg *testCfg) Get(name string) service.Config {
- if name == ID {
- return &testCfg{target: cfg.healthCfg}
- }
-
- if name == rrhttp.ID {
- return &testCfg{target: cfg.httpCfg}
- }
-
- return nil
-}
-
-func (cfg *testCfg) Unmarshal(out interface{}) error {
- j := json.ConfigCompatibleWithStandardLibrary
- err := j.Unmarshal([]byte(cfg.target), out)
- return err
-}
-
-func TestService_Serve(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, &rrhttp.Service{})
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{
- healthCfg: `{
- "address": "localhost:2116"
- }`,
- httpCfg: `{
- "address": "localhost:2115",
- "workers":{
- "command": "php ../../tests/http/client.php echo pipes",
- "pool": {"numWorkers": 1}
- }
- }`,
- }))
-
- s, status := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusOK, status)
-
- hS, httpStatus := c.Get(rrhttp.ID)
- assert.NotNil(t, hS)
- assert.Equal(t, service.StatusOK, httpStatus)
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("serve error: %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 500)
- defer c.Stop()
-
- _, res, err := get("http://localhost:2116/")
- assert.NoError(t, err)
- assert.Equal(t, http.StatusOK, res.StatusCode)
-}
-
-func TestService_Serve_DeadWorker(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, &rrhttp.Service{})
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{
- healthCfg: `{
- "address": "localhost:2117"
- }`,
- httpCfg: `{
- "address": "localhost:2118",
- "workers":{
- "command": "php ../../tests/http/slow-client.php echo pipes 1000",
- "pool": {"numWorkers": 1}
- }
- }`,
- }))
-
- s, status := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusOK, status)
-
- hS, httpStatus := c.Get(rrhttp.ID)
- assert.NotNil(t, hS)
- assert.Equal(t, service.StatusOK, httpStatus)
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("server error: %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 500)
- defer c.Stop()
-
- // Kill the worker
- httpSvc := hS.(*rrhttp.Service)
- err := httpSvc.Server().Workers()[0].Kill()
- if err != nil {
- t.Errorf("error killing the worker: error %v", err)
- }
-
- // Check health check
- _, res, err := get("http://localhost:2117/")
- assert.NoError(t, err)
- assert.Equal(t, http.StatusInternalServerError, res.StatusCode)
-}
-
-func TestService_Serve_DeadWorkerStillHealthy(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, &rrhttp.Service{})
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{
- healthCfg: `{
- "address": "localhost:2119"
- }`,
- httpCfg: `{
- "address": "localhost:2120",
- "workers":{
- "command": "php ../../tests/http/client.php echo pipes",
- "pool": {"numWorkers": 2}
- }
- }`,
- }))
-
- s, status := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusOK, status)
-
- hS, httpStatus := c.Get(rrhttp.ID)
- assert.NotNil(t, hS)
- assert.Equal(t, service.StatusOK, httpStatus)
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("serve error: %v", err)
- }
- }()
- time.Sleep(time.Second * 1)
- defer c.Stop()
-
- // Kill one of the workers
- httpSvc := hS.(*rrhttp.Service)
- err := httpSvc.Server().Workers()[0].Kill()
- if err != nil {
- t.Errorf("error killing the worker: error %v", err)
- }
-
- // Check health check
- _, res, err := get("http://localhost:2119/")
- assert.NoError(t, err)
- assert.Equal(t, http.StatusOK, res.StatusCode)
-}
-
-func TestService_Serve_NoHTTPService(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{
- healthCfg: `{
- "address": "localhost:2121"
- }`,
- }))
-
- s, status := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusInactive, status)
-}
-
-func TestService_Serve_NoServer(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- healthSvc := &Service{}
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, &rrhttp.Service{})
- c.Register(ID, healthSvc)
-
- assert.NoError(t, c.Init(&testCfg{
- healthCfg: `{
- "address": "localhost:2122"
- }`,
- httpCfg: `{
- "address": "localhost:2123",
- "workers":{
- "command": "php ../../tests/http/client.php echo pipes",
- "pool": {"numWorkers": 1}
- }
- }`,
- }))
-
- s, status := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusOK, status)
-
- hS, httpStatus := c.Get(rrhttp.ID)
- assert.NotNil(t, hS)
- assert.Equal(t, service.StatusOK, httpStatus)
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("serve error: %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 500)
- defer c.Stop()
-
- // Set the httpService to nil
- healthSvc.httpService = nil
-
- _, res, err := get("http://localhost:2122/")
- assert.NoError(t, err)
- assert.Equal(t, http.StatusInternalServerError, res.StatusCode)
-}
-
-func TestService_Serve_NoPool(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- httpSvc := &rrhttp.Service{}
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, httpSvc)
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{
- healthCfg: `{
- "address": "localhost:2124"
- }`,
- httpCfg: `{
- "address": "localhost:2125",
- "workers":{
- "command": "php ../../tests/http/client.php echo pipes",
- "pool": {"numWorkers": 1}
- }
- }`,
- }))
-
- s, status := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusOK, status)
-
- hS, httpStatus := c.Get(rrhttp.ID)
- assert.NotNil(t, hS)
- assert.Equal(t, service.StatusOK, httpStatus)
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("serve error: %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 500)
- defer c.Stop()
-
- // Stop the pool
- httpSvc.Server().Stop()
-
- _, res, err := get("http://localhost:2124/")
- assert.NoError(t, err)
- assert.Equal(t, http.StatusInternalServerError, res.StatusCode)
-}
-
-// get request and return body
-func get(url string) (string, *http.Response, error) {
- r, err := http.Get(url)
- if err != nil {
- return "", nil, err
- }
-
- b, err := ioutil.ReadAll(r.Body)
- if err != nil {
- return "", nil, err
- }
-
- err = r.Body.Close()
- if err != nil {
- return "", nil, err
- }
- return string(b), r, err
-}
diff --git a/service/http/attributes/attributes.go b/service/http/attributes/attributes.go
deleted file mode 100644
index 77d6ea69..00000000
--- a/service/http/attributes/attributes.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package attributes
-
-import (
- "context"
- "errors"
- "net/http"
-)
-
-type attrKey int
-
-const contextKey attrKey = iota
-
-type attrs map[string]interface{}
-
-func (v attrs) get(key string) interface{} {
- if v == nil {
- return ""
- }
-
- return v[key]
-}
-
-func (v attrs) set(key string, value interface{}) {
- v[key] = value
-}
-
-func (v attrs) del(key string) {
- delete(v, key)
-}
-
-// Init returns request with new context and attribute bag.
-func Init(r *http.Request) *http.Request {
- return r.WithContext(context.WithValue(r.Context(), contextKey, attrs{}))
-}
-
-// All returns all context attributes.
-func All(r *http.Request) map[string]interface{} {
- v := r.Context().Value(contextKey)
- if v == nil {
- return attrs{}
- }
-
- return v.(attrs)
-}
-
-// Get gets the value from request context. It replaces any existing
-// values.
-func Get(r *http.Request, key string) interface{} {
- v := r.Context().Value(contextKey)
- if v == nil {
- return nil
- }
-
- return v.(attrs).get(key)
-}
-
-// Set sets the key to value. It replaces any existing
-// values. Context specific.
-func Set(r *http.Request, key string, value interface{}) error {
- v := r.Context().Value(contextKey)
- if v == nil {
- return errors.New("unable to find `psr:attributes` context key")
- }
-
- v.(attrs).set(key, value)
- return nil
-}
-
-// Delete deletes values associated with attribute key.
-func (v attrs) Delete(key string) {
- if v == nil {
- return
- }
-
- v.del(key)
-}
diff --git a/service/http/attributes/attributes_test.go b/service/http/attributes/attributes_test.go
deleted file mode 100644
index 2360fd12..00000000
--- a/service/http/attributes/attributes_test.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package attributes
-
-import (
- "github.com/stretchr/testify/assert"
- "net/http"
- "testing"
-)
-
-func TestAllAttributes(t *testing.T) {
- r := &http.Request{}
- r = Init(r)
-
- err := Set(r, "key", "value")
- if err != nil {
- t.Errorf("error during the Set: error %v", err)
- }
-
- assert.Equal(t, All(r), map[string]interface{}{
- "key": "value",
- })
-}
-
-func TestAllAttributesNone(t *testing.T) {
- r := &http.Request{}
- r = Init(r)
-
- assert.Equal(t, All(r), map[string]interface{}{})
-}
-
-func TestAllAttributesNone2(t *testing.T) {
- r := &http.Request{}
-
- assert.Equal(t, All(r), map[string]interface{}{})
-}
-
-func TestGetAttribute(t *testing.T) {
- r := &http.Request{}
- r = Init(r)
-
- err := Set(r, "key", "value")
- if err != nil {
- t.Errorf("error during the Set: error %v", err)
- }
- assert.Equal(t, Get(r, "key"), "value")
-}
-
-func TestGetAttributeNone(t *testing.T) {
- r := &http.Request{}
- r = Init(r)
-
- assert.Equal(t, Get(r, "key"), nil)
-}
-
-func TestGetAttributeNone2(t *testing.T) {
- r := &http.Request{}
-
- assert.Equal(t, Get(r, "key"), nil)
-}
-
-func TestSetAttribute(t *testing.T) {
- r := &http.Request{}
- r = Init(r)
-
- err := Set(r, "key", "value")
- if err != nil {
- t.Errorf("error during the Set: error %v", err)
- }
- assert.Equal(t, Get(r, "key"), "value")
-}
-
-func TestSetAttributeNone(t *testing.T) {
- r := &http.Request{}
-
- err := Set(r, "key", "value")
- if err != nil {
- t.Errorf("error during the Set: error %v", err)
- }
- assert.Equal(t, Get(r, "key"), nil)
-}
diff --git a/service/http/config.go b/service/http/config.go
deleted file mode 100644
index 00f61652..00000000
--- a/service/http/config.go
+++ /dev/null
@@ -1,263 +0,0 @@
-package http
-
-import (
- "errors"
- "fmt"
- "github.com/spiral/roadrunner"
- "github.com/spiral/roadrunner/service"
- "net"
- "os"
- "strings"
-)
-
-// Config configures RoadRunner HTTP server.
-type Config struct {
- // Port and port to handle as http server.
- Address string
-
- // SSL defines https server options.
- SSL SSLConfig
-
- // FCGI configuration. You can use FastCGI without HTTP server.
- FCGI *FCGIConfig
-
- // HTTP2 configuration
- HTTP2 *HTTP2Config
-
- // MaxRequestSize specified max size for payload body in megabytes, set 0 to unlimited.
- MaxRequestSize int64
-
- // TrustedSubnets declare IP subnets which are allowed to set ip using X-Real-Ip and X-Forwarded-For
- TrustedSubnets []string
- cidrs []*net.IPNet
-
- // Uploads configures uploads configuration.
- Uploads *UploadsConfig
-
- // Workers configures rr server and worker pool.
- Workers *roadrunner.ServerConfig
-}
-
-// FCGIConfig for FastCGI server.
-type FCGIConfig struct {
- // Address and port to handle as http server.
- Address string
-}
-
-// HTTP2Config HTTP/2 server customizations.
-type HTTP2Config struct {
- // Enable or disable HTTP/2 extension, default enable.
- Enabled bool
-
- // H2C enables HTTP/2 over TCP
- H2C bool
-
- // MaxConcurrentStreams defaults to 128.
- MaxConcurrentStreams uint32
-}
-
-// InitDefaults sets default values for HTTP/2 configuration.
-func (cfg *HTTP2Config) InitDefaults() error {
- cfg.Enabled = true
- cfg.MaxConcurrentStreams = 128
-
- return nil
-}
-
-// SSLConfig defines https server configuration.
-type SSLConfig struct {
- // Port to listen as HTTPS server, defaults to 443.
- Port int
-
- // Redirect when enabled forces all http connections to switch to https.
- Redirect bool
-
- // Key defined private server key.
- Key string
-
- // Cert is https certificate.
- Cert string
-
- // Root CA file
- RootCA string
-}
-
-// EnableHTTP is true when http server must run.
-func (c *Config) EnableHTTP() bool {
- return c.Address != ""
-}
-
-// EnableTLS returns true if rr must listen TLS connections.
-func (c *Config) EnableTLS() bool {
- return c.SSL.Key != "" || c.SSL.Cert != "" || c.SSL.RootCA != ""
-}
-
-// EnableHTTP2 when HTTP/2 extension must be enabled (only with TSL).
-func (c *Config) EnableHTTP2() bool {
- return c.HTTP2.Enabled
-}
-
-// EnableH2C when HTTP/2 extension must be enabled on TCP.
-func (c *Config) EnableH2C() bool {
- return c.HTTP2.H2C
-}
-
-// EnableFCGI is true when FastCGI server must be enabled.
-func (c *Config) EnableFCGI() bool {
- return c.FCGI.Address != ""
-}
-
-// Hydrate must populate Config values using given Config source. Must return error if Config is not valid.
-func (c *Config) Hydrate(cfg service.Config) error {
- if c.Workers == nil {
- c.Workers = &roadrunner.ServerConfig{}
- }
-
- if c.HTTP2 == nil {
- c.HTTP2 = &HTTP2Config{}
- }
-
- if c.FCGI == nil {
- c.FCGI = &FCGIConfig{}
- }
-
- if c.Uploads == nil {
- c.Uploads = &UploadsConfig{}
- }
-
- if c.SSL.Port == 0 {
- c.SSL.Port = 443
- }
-
- err := c.HTTP2.InitDefaults()
- if err != nil {
- return err
- }
- err = c.Uploads.InitDefaults()
- if err != nil {
- return err
- }
- err = c.Workers.InitDefaults()
- if err != nil {
- return err
- }
-
- if err := cfg.Unmarshal(c); err != nil {
- return err
- }
-
- c.Workers.UpscaleDurations()
-
- if c.TrustedSubnets == nil {
- // @see https://en.wikipedia.org/wiki/Reserved_IP_addresses
- c.TrustedSubnets = []string{
- "10.0.0.0/8",
- "127.0.0.0/8",
- "172.16.0.0/12",
- "192.168.0.0/16",
- "::1/128",
- "fc00::/7",
- "fe80::/10",
- }
- }
-
- if err := c.parseCIDRs(); err != nil {
- return err
- }
-
- return c.Valid()
-}
-
-func (c *Config) parseCIDRs() error {
- for _, cidr := range c.TrustedSubnets {
- _, cr, err := net.ParseCIDR(cidr)
- if err != nil {
- return err
- }
-
- c.cidrs = append(c.cidrs, cr)
- }
-
- return nil
-}
-
-// IsTrusted if api can be trusted to use X-Real-Ip, X-Forwarded-For
-func (c *Config) IsTrusted(ip string) bool {
- if c.cidrs == nil {
- return false
- }
-
- i := net.ParseIP(ip)
- if i == nil {
- return false
- }
-
- for _, cird := range c.cidrs {
- if cird.Contains(i) {
- return true
- }
- }
-
- return false
-}
-
-// Valid validates the configuration.
-func (c *Config) Valid() error {
- if c.Uploads == nil {
- return errors.New("malformed uploads config")
- }
-
- if c.HTTP2 == nil {
- return errors.New("malformed http2 config")
- }
-
- if c.Workers == nil {
- return errors.New("malformed workers config")
- }
-
- if c.Workers.Pool == nil {
- return errors.New("malformed workers config (pool config is missing)")
- }
-
- if err := c.Workers.Pool.Valid(); err != nil {
- return err
- }
-
- if !c.EnableHTTP() && !c.EnableTLS() && !c.EnableFCGI() {
- return errors.New("unable to run http service, no method has been specified (http, https, http/2 or FastCGI)")
- }
-
- if c.Address != "" && !strings.Contains(c.Address, ":") {
- return errors.New("malformed http server address")
- }
-
- if c.EnableTLS() {
- if _, err := os.Stat(c.SSL.Key); err != nil {
- if os.IsNotExist(err) {
- return fmt.Errorf("key file '%s' does not exists", c.SSL.Key)
- }
-
- return err
- }
-
- if _, err := os.Stat(c.SSL.Cert); err != nil {
- if os.IsNotExist(err) {
- return fmt.Errorf("cert file '%s' does not exists", c.SSL.Cert)
- }
-
- return err
- }
-
- // RootCA is optional, but if provided - check it
- if c.SSL.RootCA != "" {
- if _, err := os.Stat(c.SSL.RootCA); err != nil {
- if os.IsNotExist(err) {
- return fmt.Errorf("root ca path provided, but path '%s' does not exists", c.SSL.RootCA)
- }
- return err
- }
- }
- }
-
- return nil
-}
diff --git a/service/http/config_test.go b/service/http/config_test.go
deleted file mode 100644
index d95e0995..00000000
--- a/service/http/config_test.go
+++ /dev/null
@@ -1,331 +0,0 @@
-package http
-
-import (
- json "github.com/json-iterator/go"
- "github.com/spiral/roadrunner"
- "github.com/spiral/roadrunner/service"
- "github.com/stretchr/testify/assert"
- "os"
- "testing"
- "time"
-)
-
-type mockCfg struct{ cfg string }
-
-func (cfg *mockCfg) Get(name string) service.Config { return nil }
-func (cfg *mockCfg) Unmarshal(out interface{}) error {
- j := json.ConfigCompatibleWithStandardLibrary
- return j.Unmarshal([]byte(cfg.cfg), out)
-}
-
-func Test_Config_Hydrate_Error1(t *testing.T) {
- cfg := &mockCfg{`{"address": "localhost:8080"}`}
- c := &Config{}
-
- assert.NoError(t, c.Hydrate(cfg))
-}
-
-func Test_Config_Hydrate_Error2(t *testing.T) {
- cfg := &mockCfg{`{"dir": "/dir/"`}
- c := &Config{}
-
- assert.Error(t, c.Hydrate(cfg))
-}
-
-func Test_Config_Valid(t *testing.T) {
- cfg := &Config{
- Address: ":8080",
- MaxRequestSize: 1024,
- HTTP2: &HTTP2Config{
- Enabled: true,
- },
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{".go"},
- },
- Workers: &roadrunner.ServerConfig{
- Command: "php tests/client.php echo pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: time.Second,
- DestroyTimeout: time.Second,
- },
- },
- }
-
- assert.NoError(t, cfg.Valid())
-}
-
-func Test_Trusted_Subnets(t *testing.T) {
- cfg := &Config{
- Address: ":8080",
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{".go"},
- },
- HTTP2: &HTTP2Config{
- Enabled: true,
- },
- TrustedSubnets: []string{"200.1.0.0/16"},
- Workers: &roadrunner.ServerConfig{
- Command: "php tests/client.php echo pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: time.Second,
- DestroyTimeout: time.Second,
- },
- },
- }
-
- assert.NoError(t, cfg.parseCIDRs())
-
- assert.True(t, cfg.IsTrusted("200.1.0.10"))
- assert.False(t, cfg.IsTrusted("127.0.0.0.1"))
-}
-
-func Test_Trusted_Subnets_Err(t *testing.T) {
- cfg := &Config{
- Address: ":8080",
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{".go"},
- },
- HTTP2: &HTTP2Config{
- Enabled: true,
- },
- TrustedSubnets: []string{"200.1.0.0"},
- Workers: &roadrunner.ServerConfig{
- Command: "php tests/client.php echo pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: time.Second,
- DestroyTimeout: time.Second,
- },
- },
- }
-
- assert.Error(t, cfg.parseCIDRs())
-}
-
-func Test_Config_Valid_SSL(t *testing.T) {
- cfg := &Config{
- Address: ":8080",
- SSL: SSLConfig{
- Cert: "fixtures/server.crt",
- Key: "fixtures/server.key",
- },
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{".go"},
- },
- HTTP2: &HTTP2Config{
- Enabled: true,
- },
- Workers: &roadrunner.ServerConfig{
- Command: "php tests/client.php echo pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: time.Second,
- DestroyTimeout: time.Second,
- },
- },
- }
-
- assert.Error(t, cfg.Hydrate(&testCfg{httpCfg: "{}"}))
-
- assert.NoError(t, cfg.Valid())
- assert.True(t, cfg.EnableTLS())
- assert.Equal(t, 443, cfg.SSL.Port)
-}
-
-func Test_Config_SSL_No_key(t *testing.T) {
- cfg := &Config{
- Address: ":8080",
- SSL: SSLConfig{
- Cert: "fixtures/server.crt",
- },
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{".go"},
- },
- HTTP2: &HTTP2Config{
- Enabled: true,
- },
- Workers: &roadrunner.ServerConfig{
- Command: "php tests/client.php echo pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: time.Second,
- DestroyTimeout: time.Second,
- },
- },
- }
-
- assert.Error(t, cfg.Valid())
-}
-
-func Test_Config_SSL_No_Cert(t *testing.T) {
- cfg := &Config{
- Address: ":8080",
- SSL: SSLConfig{
- Key: "fixtures/server.key",
- },
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{".go"},
- },
- HTTP2: &HTTP2Config{
- Enabled: true,
- },
- Workers: &roadrunner.ServerConfig{
- Command: "php tests/client.php echo pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: time.Second,
- DestroyTimeout: time.Second,
- },
- },
- }
-
- assert.Error(t, cfg.Valid())
-}
-
-func Test_Config_NoUploads(t *testing.T) {
- cfg := &Config{
- Address: ":8080",
- MaxRequestSize: 1024,
- HTTP2: &HTTP2Config{
- Enabled: true,
- },
- Workers: &roadrunner.ServerConfig{
- Command: "php tests/client.php echo pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: time.Second,
- DestroyTimeout: time.Second,
- },
- },
- }
-
- assert.Error(t, cfg.Valid())
-}
-
-func Test_Config_NoHTTP2(t *testing.T) {
- cfg := &Config{
- Address: ":8080",
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{".go"},
- },
- Workers: &roadrunner.ServerConfig{
- Command: "php tests/client.php echo pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 0,
- AllocateTimeout: time.Second,
- DestroyTimeout: time.Second,
- },
- },
- }
-
- assert.Error(t, cfg.Valid())
-}
-
-func Test_Config_NoWorkers(t *testing.T) {
- cfg := &Config{
- Address: ":8080",
- MaxRequestSize: 1024,
- HTTP2: &HTTP2Config{
- Enabled: true,
- },
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{".go"},
- },
- }
-
- assert.Error(t, cfg.Valid())
-}
-
-func Test_Config_NoPool(t *testing.T) {
- cfg := &Config{
- Address: ":8080",
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{".go"},
- },
- HTTP2: &HTTP2Config{
- Enabled: true,
- },
- Workers: &roadrunner.ServerConfig{
- Command: "php tests/client.php echo pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 0,
- AllocateTimeout: time.Second,
- DestroyTimeout: time.Second,
- },
- },
- }
-
- assert.Error(t, cfg.Valid())
-}
-
-func Test_Config_DeadPool(t *testing.T) {
- cfg := &Config{
- Address: ":8080",
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{".go"},
- },
- HTTP2: &HTTP2Config{
- Enabled: true,
- },
- Workers: &roadrunner.ServerConfig{
- Command: "php tests/client.php echo pipes",
- Relay: "pipes",
- },
- }
-
- assert.Error(t, cfg.Valid())
-}
-
-func Test_Config_InvalidAddress(t *testing.T) {
- cfg := &Config{
- Address: "unexpected_address",
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{".go"},
- },
- HTTP2: &HTTP2Config{
- Enabled: true,
- },
- Workers: &roadrunner.ServerConfig{
- Command: "php tests/client.php echo pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: time.Second,
- DestroyTimeout: time.Second,
- },
- },
- }
-
- assert.Error(t, cfg.Valid())
-}
diff --git a/service/http/constants.go b/service/http/constants.go
deleted file mode 100644
index a25f52a4..00000000
--- a/service/http/constants.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package http
-
-import "net/http"
-
-var http2pushHeaderKey = http.CanonicalHeaderKey("http2-push")
-var trailerHeaderKey = http.CanonicalHeaderKey("trailer")
diff --git a/service/http/errors.go b/service/http/errors.go
deleted file mode 100644
index fb8762ef..00000000
--- a/service/http/errors.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// +build !windows
-
-package http
-
-import (
- "errors"
- "net"
- "os"
- "syscall"
-)
-
-// Broken pipe
-var errEPIPE = errors.New("EPIPE(32) -> connection reset by peer")
-
-// handleWriteError just check if error was caused by aborted connection on linux
-func handleWriteError(err error) error {
- if netErr, ok2 := err.(*net.OpError); ok2 {
- if syscallErr, ok3 := netErr.Err.(*os.SyscallError); ok3 {
- if syscallErr.Err == syscall.EPIPE {
- return errEPIPE
- }
- }
- }
- return err
-}
diff --git a/service/http/errors_windows.go b/service/http/errors_windows.go
deleted file mode 100644
index 3d0ba04c..00000000
--- a/service/http/errors_windows.go
+++ /dev/null
@@ -1,27 +0,0 @@
-// +build windows
-
-package http
-
-import (
- "errors"
- "net"
- "os"
- "syscall"
-)
-
-//Software caused connection abort.
-//An established connection was aborted by the software in your host computer,
-//possibly due to a data transmission time-out or protocol error.
-var errEPIPE = errors.New("WSAECONNABORTED (10053) -> an established connection was aborted by peer")
-
-// handleWriteError just check if error was caused by aborted connection on windows
-func handleWriteError(err error) error {
- if netErr, ok2 := err.(*net.OpError); ok2 {
- if syscallErr, ok3 := netErr.Err.(*os.SyscallError); ok3 {
- if syscallErr.Err == syscall.WSAECONNABORTED {
- return errEPIPE
- }
- }
- }
- return err
-}
diff --git a/service/http/fcgi_test.go b/service/http/fcgi_test.go
deleted file mode 100644
index e68b2e7f..00000000
--- a/service/http/fcgi_test.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package http
-
-import (
- "github.com/sirupsen/logrus"
- "github.com/sirupsen/logrus/hooks/test"
- "github.com/spiral/roadrunner/service"
- "github.com/stretchr/testify/assert"
- "github.com/yookoala/gofast"
- "io/ioutil"
- "net/http/httptest"
- "testing"
- "time"
-)
-
-func Test_FCGI_Service_Echo(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{httpCfg: `{
- "fcgi": {
- "address": "tcp://0.0.0.0:6082"
- },
- "workers":{
- "command": "php ../../tests/http/client.php echo pipes",
- "pool": {"numWorkers": 1}
- }
- }`}))
-
- s, st := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusOK, st)
-
- // should do nothing
- s.(*Service).Stop()
-
- go func() { assert.NoError(t, c.Serve()) }()
- time.Sleep(time.Second * 1)
-
- fcgiConnFactory := gofast.SimpleConnFactory("tcp", "0.0.0.0:6082")
-
- fcgiHandler := gofast.NewHandler(
- gofast.BasicParamsMap(gofast.BasicSession),
- gofast.SimpleClientFactory(fcgiConnFactory, 0),
- )
-
- w := httptest.NewRecorder()
- req := httptest.NewRequest("GET", "http://site.local/?hello=world", nil)
- fcgiHandler.ServeHTTP(w, req)
-
- body, err := ioutil.ReadAll(w.Result().Body)
-
- assert.NoError(t, err)
- assert.Equal(t, 201, w.Result().StatusCode)
- assert.Equal(t, "WORLD", string(body))
- c.Stop()
-}
-
-func Test_FCGI_Service_Request_Uri(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{httpCfg: `{
- "fcgi": {
- "address": "tcp://0.0.0.0:6083"
- },
- "workers":{
- "command": "php ../../tests/http/client.php request-uri pipes",
- "pool": {"numWorkers": 1}
- }
- }`}))
-
- s, st := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusOK, st)
-
- // should do nothing
- s.(*Service).Stop()
-
- go func() { assert.NoError(t, c.Serve()) }()
- time.Sleep(time.Second * 1)
-
- fcgiConnFactory := gofast.SimpleConnFactory("tcp", "0.0.0.0:6083")
-
- fcgiHandler := gofast.NewHandler(
- gofast.BasicParamsMap(gofast.BasicSession),
- gofast.SimpleClientFactory(fcgiConnFactory, 0),
- )
-
- w := httptest.NewRecorder()
- req := httptest.NewRequest("GET", "http://site.local/hello-world", nil)
- fcgiHandler.ServeHTTP(w, req)
-
- body, err := ioutil.ReadAll(w.Result().Body)
-
- assert.NoError(t, err)
- assert.Equal(t, 200, w.Result().StatusCode)
- assert.Equal(t, "http://site.local/hello-world", string(body))
- c.Stop()
-}
diff --git a/service/http/fixtures/server.crt b/service/http/fixtures/server.crt
deleted file mode 100644
index 24d67fd7..00000000
--- a/service/http/fixtures/server.crt
+++ /dev/null
@@ -1,15 +0,0 @@
------BEGIN CERTIFICATE-----
-MIICTTCCAdOgAwIBAgIJAOKyUd+llTRKMAoGCCqGSM49BAMCMGMxCzAJBgNVBAYT
-AlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2Nv
-MRMwEQYDVQQKDApSb2FkUnVubmVyMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTgw
-OTMwMTMzNDUzWhcNMjgwOTI3MTMzNDUzWjBjMQswCQYDVQQGEwJVUzETMBEGA1UE
-CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzETMBEGA1UECgwK
-Um9hZFJ1bm5lcjESMBAGA1UEAwwJbG9jYWxob3N0MHYwEAYHKoZIzj0CAQYFK4EE
-ACIDYgAEVnbShsM+l5RR3wfWWmGhzuFGwNzKCk7i9xyobDIyBUxG/UUSfj7KKlUX
-puDnDEtF5xXcepl744CyIAYFLOXHb5WqI4jCOzG0o9f/00QQ4bQudJOdbqV910QF
-C2vb7Fxro1MwUTAdBgNVHQ4EFgQU9xUexnbB6ORKayA7Pfjzs33otsAwHwYDVR0j
-BBgwFoAU9xUexnbB6ORKayA7Pfjzs33otsAwDwYDVR0TAQH/BAUwAwEB/zAKBggq
-hkjOPQQDAgNoADBlAjEAue3HhR/MUhxoa9tSDBtOJT3FYbDQswrsdqBTz97CGKst
-e7XeZ3HMEvEXy0hGGEMhAjAqcD/4k9vViVppgWFtkk6+NFbm+Kw/QeeAiH5FgFSj
-8xQcb+b7nPwNLp3JOkXkVd4=
------END CERTIFICATE-----
diff --git a/service/http/fixtures/server.key b/service/http/fixtures/server.key
deleted file mode 100644
index 7501dd46..00000000
--- a/service/http/fixtures/server.key
+++ /dev/null
@@ -1,9 +0,0 @@
------BEGIN EC PARAMETERS-----
-BgUrgQQAIg==
------END EC PARAMETERS-----
------BEGIN EC PRIVATE KEY-----
-MIGkAgEBBDCQP8utxNbHR6xZOLAJgUhn88r6IrPqmN0MsgGJM/jePB+T9UhkmIU8
-PMm2HeScbcugBwYFK4EEACKhZANiAARWdtKGwz6XlFHfB9ZaYaHO4UbA3MoKTuL3
-HKhsMjIFTEb9RRJ+PsoqVRem4OcMS0XnFdx6mXvjgLIgBgUs5cdvlaojiMI7MbSj
-1//TRBDhtC50k51upX3XRAULa9vsXGs=
------END EC PRIVATE KEY-----
diff --git a/service/http/h2c_test.go b/service/http/h2c_test.go
deleted file mode 100644
index f17538bc..00000000
--- a/service/http/h2c_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package http
-
-import (
- "net/http"
- "testing"
- "time"
-
- "github.com/cenkalti/backoff/v4"
- "github.com/sirupsen/logrus"
- "github.com/sirupsen/logrus/hooks/test"
- "github.com/spiral/roadrunner/service"
- "github.com/stretchr/testify/assert"
-)
-
-func Test_Service_H2C(t *testing.T) {
- bkoff := backoff.NewExponentialBackOff()
- bkoff.MaxElapsedTime = time.Second * 15
-
- err := backoff.Retry(func() error {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- err := c.Init(&testCfg{httpCfg: `{
- "address": ":6029",
- "http2": {"h2c":true},
- "workers":{
- "command": "php ../../tests/http/client.php echo pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1
- }
- }
- }`})
- if err != nil {
- return err
- }
-
- s, st := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusOK, st)
-
- // should do nothing
- s.(*Service).Stop()
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("error serving: %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 100)
- defer c.Stop()
-
- req, err := http.NewRequest("PRI", "http://localhost:6029?hello=world", nil)
- if err != nil {
- return err
- }
-
- req.Header.Add("Upgrade", "h2c")
- req.Header.Add("Connection", "HTTP2-Settings")
- req.Header.Add("HTTP2-Settings", "")
-
- r, err2 := http.DefaultClient.Do(req)
- if err2 != nil {
- return err2
- }
-
- assert.Equal(t, "101 Switching Protocols", r.Status)
-
- err3 := r.Body.Close()
- if err3 != nil {
- return err3
- }
- return nil
- }, bkoff)
-
- if err != nil {
- t.Fatal(err)
- }
-}
diff --git a/service/http/handler.go b/service/http/handler.go
deleted file mode 100644
index eca05483..00000000
--- a/service/http/handler.go
+++ /dev/null
@@ -1,208 +0,0 @@
-package http
-
-import (
- "fmt"
- "net"
- "net/http"
- "strconv"
- "strings"
- "sync"
- "time"
-
- "github.com/pkg/errors"
- "github.com/sirupsen/logrus"
- "github.com/spiral/roadrunner"
-)
-
-const (
- // EventResponse thrown after the request been processed. See ErrorEvent as payload.
- EventResponse = iota + 500
-
- // EventError thrown on any non job error provided by road runner server.
- EventError
-)
-
-// ErrorEvent represents singular http error event.
-type ErrorEvent struct {
- // Request contains client request, must not be stored.
- Request *http.Request
-
- // Error - associated error, if any.
- Error error
-
- // event timings
- start time.Time
- elapsed time.Duration
-}
-
-// Elapsed returns duration of the invocation.
-func (e *ErrorEvent) Elapsed() time.Duration {
- return e.elapsed
-}
-
-// ResponseEvent represents singular http response event.
-type ResponseEvent struct {
- // Request contains client request, must not be stored.
- Request *Request
-
- // Response contains service response.
- Response *Response
-
- // event timings
- start time.Time
- elapsed time.Duration
-}
-
-// Elapsed returns duration of the invocation.
-func (e *ResponseEvent) Elapsed() time.Duration {
- return e.elapsed
-}
-
-// 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
- log *logrus.Logger
- rr *roadrunner.Server
- mul sync.Mutex
- lsn func(event int, ctx interface{})
-}
-
-// Listen attaches handler event controller.
-func (h *Handler) Listen(l func(event int, ctx interface{})) {
- h.mul.Lock()
- defer h.mul.Unlock()
-
- h.lsn = l
-}
-
-// mdwr serve using PSR-7 requests passed to underlying application. Attempts to serve static files first if enabled.
-func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- start := time.Now()
-
- // validating request size
- if h.cfg.MaxRequestSize != 0 {
- if length := r.Header.Get("content-length"); length != "" {
- if size, err := strconv.ParseInt(length, 10, 64); err != nil {
- h.handleError(w, r, err, start)
- return
- } else if size > h.cfg.MaxRequestSize*1024*1024 {
- h.handleError(w, r, errors.New("request body max size is exceeded"), start)
- return
- }
- }
- }
-
- req, err := NewRequest(r, h.cfg.Uploads)
- if err != nil {
- h.handleError(w, r, err, start)
- return
- }
-
- // proxy IP resolution
- h.resolveIP(req)
-
- req.Open(h.log)
- defer req.Close(h.log)
-
- p, err := req.Payload()
- if err != nil {
- h.handleError(w, r, err, start)
- return
- }
-
- rsp, err := h.rr.Exec(p)
- if err != nil {
- h.handleError(w, r, err, start)
- return
- }
-
- resp, err := NewResponse(rsp)
- if err != nil {
- h.handleError(w, r, err, start)
- return
- }
-
- h.handleResponse(req, resp, start)
- err = resp.Write(w)
- if err != nil {
- h.handleError(w, r, err, start)
- }
-}
-
-// handleError sends error.
-func (h *Handler) handleError(w http.ResponseWriter, r *http.Request, err error, start time.Time) {
- // if pipe is broken, there is no sense to write the header
- // in this case we just report about error
- if err == errEPIPE {
- h.throw(EventError, &ErrorEvent{Request: r, Error: err, start: start, elapsed: time.Since(start)})
- return
- }
- // ResponseWriter is ok, write the error code
- w.WriteHeader(500)
- _, err2 := w.Write([]byte(err.Error()))
- // error during the writing to the ResponseWriter
- if err2 != nil {
- // concat original error with ResponseWriter error
- h.throw(EventError, &ErrorEvent{Request: r, Error: errors.New(fmt.Sprintf("error: %v, during handle this error, ResponseWriter error occurred: %v", err, err2)), start: start, elapsed: time.Since(start)})
- return
- }
- h.throw(EventError, &ErrorEvent{Request: r, Error: err, start: start, elapsed: time.Since(start)})
-}
-
-// handleResponse triggers response event.
-func (h *Handler) handleResponse(req *Request, resp *Response, start time.Time) {
- h.throw(EventResponse, &ResponseEvent{Request: req, Response: resp, start: start, elapsed: time.Since(start)})
-}
-
-// throw invokes event handler if any.
-func (h *Handler) throw(event int, ctx interface{}) {
- h.mul.Lock()
- defer h.mul.Unlock()
-
- if h.lsn != nil {
- h.lsn(event, ctx)
- }
-}
-
-// get real ip passing multiple proxy
-func (h *Handler) resolveIP(r *Request) {
- if !h.cfg.IsTrusted(r.RemoteAddr) {
- return
- }
-
- if r.Header.Get("X-Forwarded-For") != "" {
- ips := strings.Split(r.Header.Get("X-Forwarded-For"), ",")
- ipCount := len(ips)
-
- for i := ipCount - 1; i >= 0; i-- {
- addr := strings.TrimSpace(ips[i])
- if net.ParseIP(addr) != nil {
- r.RemoteAddr = addr
- return
- }
- }
-
- return
- }
-
- // The logic here is the following:
- // In general case, we only expect X-Real-Ip header. If it exist, we get the IP addres from header and set request Remote address
- // But, if there is no X-Real-Ip header, we also trying to check CloudFlare headers
- // True-Client-IP is a general CF header in which copied information from X-Real-Ip in CF.
- // CF-Connecting-IP is an Enterprise feature and we check it last in order.
- // This operations are near O(1) because Headers struct are the map type -> type MIMEHeader map[string][]string
- if r.Header.Get("X-Real-Ip") != "" {
- r.RemoteAddr = fetchIP(r.Header.Get("X-Real-Ip"))
- return
- }
-
- if r.Header.Get("True-Client-IP") != "" {
- r.RemoteAddr = fetchIP(r.Header.Get("True-Client-IP"))
- return
- }
-
- if r.Header.Get("CF-Connecting-IP") != "" {
- r.RemoteAddr = fetchIP(r.Header.Get("CF-Connecting-IP"))
- }
-}
diff --git a/service/http/handler_test.go b/service/http/handler_test.go
deleted file mode 100644
index cb1cd728..00000000
--- a/service/http/handler_test.go
+++ /dev/null
@@ -1,1961 +0,0 @@
-package http
-
-import (
- "bytes"
- "context"
- "github.com/spiral/roadrunner"
- "github.com/stretchr/testify/assert"
- "io/ioutil"
- "mime/multipart"
- "net/http"
- "net/http/httptest"
- "net/url"
- "os"
- "runtime"
- "strings"
- "testing"
- "time"
-)
-
-// get request and return body
-func get(url string) (string, *http.Response, error) {
- r, err := http.Get(url)
- if err != nil {
- return "", nil, err
- }
- b, err := ioutil.ReadAll(r.Body)
- if err != nil {
- return "", nil, err
- }
-
- err = r.Body.Close()
- if err != nil {
- return "", nil, err
- }
- return string(b), r, err
-}
-
-// get request and return body
-func getHeader(url string, h map[string]string) (string, *http.Response, error) {
- req, err := http.NewRequest("GET", url, bytes.NewBuffer(nil))
- if err != nil {
- return "", nil, err
- }
-
- for k, v := range h {
- req.Header.Set(k, v)
- }
-
- r, err := http.DefaultClient.Do(req)
- if err != nil {
- return "", nil, err
- }
-
- b, err := ioutil.ReadAll(r.Body)
- if err != nil {
- return "", nil, err
- }
-
- err = r.Body.Close()
- if err != nil {
- return "", nil, err
- }
- return string(b), r, err
-}
-
-func TestHandler_Echo(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php echo pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8177", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- body, r, err := get("http://localhost:8177/?hello=world")
- assert.NoError(t, err)
- assert.Equal(t, 201, r.StatusCode)
- assert.Equal(t, "WORLD", body)
-}
-
-func Test_HandlerErrors(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php echo pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- wr := httptest.NewRecorder()
- rq := httptest.NewRequest("POST", "/", bytes.NewBuffer([]byte("data")))
-
- h.ServeHTTP(wr, rq)
- assert.Equal(t, 500, wr.Code)
-}
-
-func Test_Handler_JSON_error(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php echo pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- wr := httptest.NewRecorder()
- rq := httptest.NewRequest("POST", "/", bytes.NewBuffer([]byte("{sd")))
- rq.Header.Add("Content-Type", "application/json")
- rq.Header.Add("Content-Size", "3")
-
- h.ServeHTTP(wr, rq)
- assert.Equal(t, 500, wr.Code)
-}
-
-func TestHandler_Headers(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php header pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8078", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 100)
-
- req, err := http.NewRequest("GET", "http://localhost:8078?hello=world", nil)
- assert.NoError(t, err)
-
- req.Header.Add("input", "sample")
-
- r, err := http.DefaultClient.Do(req)
- assert.NoError(t, err)
- defer func() {
- err := r.Body.Close()
- if err != nil {
- t.Errorf("error during the closing Body: error %v", err)
-
- }
- }()
-
- b, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
-
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
- assert.Equal(t, "world", r.Header.Get("Header"))
- assert.Equal(t, "SAMPLE", string(b))
-}
-
-func TestHandler_Empty_User_Agent(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php user-agent pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8088", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- req, err := http.NewRequest("GET", "http://localhost:8088?hello=world", nil)
- assert.NoError(t, err)
-
- req.Header.Add("user-agent", "")
-
- r, err := http.DefaultClient.Do(req)
- assert.NoError(t, err)
- defer func() {
- err := r.Body.Close()
- if err != nil {
- t.Errorf("error during the closing Body: error %v", err)
-
- }
- }()
-
- b, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
-
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
- assert.Equal(t, "", string(b))
-}
-
-func TestHandler_User_Agent(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php user-agent pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8088", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- req, err := http.NewRequest("GET", "http://localhost:8088?hello=world", nil)
- assert.NoError(t, err)
-
- req.Header.Add("User-Agent", "go-agent")
-
- r, err := http.DefaultClient.Do(req)
- assert.NoError(t, err)
- defer func() {
- err := r.Body.Close()
- if err != nil {
- t.Errorf("error during the closing Body: error %v", err)
-
- }
- }()
-
- b, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
-
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
- assert.Equal(t, "go-agent", string(b))
-}
-
-func TestHandler_Cookies(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php cookie pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8079", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- req, err := http.NewRequest("GET", "http://localhost:8079", nil)
- assert.NoError(t, err)
-
- req.AddCookie(&http.Cookie{Name: "input", Value: "input-value"})
-
- r, err := http.DefaultClient.Do(req)
- assert.NoError(t, err)
- defer func() {
- err := r.Body.Close()
- if err != nil {
- t.Errorf("error during the closing Body: error %v", err)
-
- }
- }()
-
- b, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
-
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
- assert.Equal(t, "INPUT-VALUE", string(b))
-
- for _, c := range r.Cookies() {
- assert.Equal(t, "output", c.Name)
- assert.Equal(t, "cookie-output", c.Value)
- }
-}
-
-func TestHandler_JsonPayload_POST(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php payload pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8090", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- req, err := http.NewRequest(
- "POST",
- "http://localhost"+hs.Addr,
- bytes.NewBufferString(`{"key":"value"}`),
- )
- assert.NoError(t, err)
-
- req.Header.Add("Content-Type", "application/json")
-
- r, err := http.DefaultClient.Do(req)
- assert.NoError(t, err)
- defer func() {
- err := r.Body.Close()
- if err != nil {
- t.Errorf("error during the closing Body: error %v", err)
-
- }
- }()
-
- b, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
-
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
- assert.Equal(t, `{"value":"key"}`, string(b))
-}
-
-func TestHandler_JsonPayload_PUT(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php payload pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8081", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- req, err := http.NewRequest("PUT", "http://localhost"+hs.Addr, bytes.NewBufferString(`{"key":"value"}`))
- assert.NoError(t, err)
-
- req.Header.Add("Content-Type", "application/json")
-
- r, err := http.DefaultClient.Do(req)
- assert.NoError(t, err)
- defer func() {
- err := r.Body.Close()
- if err != nil {
- t.Errorf("error during the closing Body: error %v", err)
- }
- }()
-
- b, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
-
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
- assert.Equal(t, `{"value":"key"}`, string(b))
-}
-
-func TestHandler_JsonPayload_PATCH(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php payload pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8082", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- req, err := http.NewRequest("PATCH", "http://localhost"+hs.Addr, bytes.NewBufferString(`{"key":"value"}`))
- assert.NoError(t, err)
-
- req.Header.Add("Content-Type", "application/json")
-
- r, err := http.DefaultClient.Do(req)
- assert.NoError(t, err)
- defer func() {
- err := r.Body.Close()
- if err != nil {
- t.Errorf("error during the closing Body: error %v", err)
-
- }
- }()
-
- b, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
-
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
- assert.Equal(t, `{"value":"key"}`, string(b))
-}
-
-func TestHandler_FormData_POST(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php data pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8083", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 500)
-
- form := url.Values{}
-
- form.Add("key", "value")
- form.Add("name[]", "name1")
- form.Add("name[]", "name2")
- form.Add("name[]", "name3")
- form.Add("arr[x][y][z]", "y")
- form.Add("arr[x][y][e]", "f")
- form.Add("arr[c]p", "l")
- form.Add("arr[c]z", "")
-
- req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, strings.NewReader(form.Encode()))
- assert.NoError(t, err)
-
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
-
- r, err := http.DefaultClient.Do(req)
- assert.NoError(t, err)
- defer func() {
- err := r.Body.Close()
- if err != nil {
- t.Errorf("error during the closing Body: error %v", err)
-
- }
- }()
-
- b, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
-
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
-
- assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b))
-}
-
-func TestHandler_FormData_POST_Overwrite(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php data pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8083", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- form := url.Values{}
-
- form.Add("key", "value1")
- form.Add("key", "value2")
-
- req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, strings.NewReader(form.Encode()))
- assert.NoError(t, err)
-
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
-
- r, err := http.DefaultClient.Do(req)
- assert.NoError(t, err)
- defer func() {
- err := r.Body.Close()
- if err != nil {
- t.Errorf("error during the closing Body: error %v", err)
-
- }
- }()
-
- b, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
-
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
-
- assert.Equal(t, `{"key":"value2","arr":{"x":{"y":null}}}`, string(b))
-}
-
-func TestHandler_FormData_POST_Form_UrlEncoded_Charset(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php data pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8083", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- form := url.Values{}
-
- form.Add("key", "value")
- form.Add("name[]", "name1")
- form.Add("name[]", "name2")
- form.Add("name[]", "name3")
- form.Add("arr[x][y][z]", "y")
- form.Add("arr[x][y][e]", "f")
- form.Add("arr[c]p", "l")
- form.Add("arr[c]z", "")
-
- req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, strings.NewReader(form.Encode()))
- assert.NoError(t, err)
-
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
-
- r, err := http.DefaultClient.Do(req)
- assert.NoError(t, err)
- defer func() {
- err := r.Body.Close()
- if err != nil {
- t.Errorf("error during the closing Body: error %v", err)
-
- }
- }()
-
- b, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
-
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
-
- assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b))
-}
-
-func TestHandler_FormData_PUT(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php data pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8084", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 500)
-
- form := url.Values{}
-
- form.Add("key", "value")
- form.Add("name[]", "name1")
- form.Add("name[]", "name2")
- form.Add("name[]", "name3")
- form.Add("arr[x][y][z]", "y")
- form.Add("arr[x][y][e]", "f")
- form.Add("arr[c]p", "l")
- form.Add("arr[c]z", "")
-
- req, err := http.NewRequest("PUT", "http://localhost"+hs.Addr, strings.NewReader(form.Encode()))
- assert.NoError(t, err)
-
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
-
- r, err := http.DefaultClient.Do(req)
- assert.NoError(t, err)
- defer func() {
- err := r.Body.Close()
- if err != nil {
- t.Errorf("error during the closing Body: error %v", err)
-
- }
- }()
-
- b, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
-
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
-
- assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b))
-}
-
-func TestHandler_FormData_PATCH(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php data pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8085", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- form := url.Values{}
-
- form.Add("key", "value")
- form.Add("name[]", "name1")
- form.Add("name[]", "name2")
- form.Add("name[]", "name3")
- form.Add("arr[x][y][z]", "y")
- form.Add("arr[x][y][e]", "f")
- form.Add("arr[c]p", "l")
- form.Add("arr[c]z", "")
-
- req, err := http.NewRequest("PATCH", "http://localhost"+hs.Addr, strings.NewReader(form.Encode()))
- assert.NoError(t, err)
-
- req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
-
- r, err := http.DefaultClient.Do(req)
- assert.NoError(t, err)
- defer func() {
- err := r.Body.Close()
- if err != nil {
- t.Errorf("error during the closing Body: error %v", err)
-
- }
- }()
-
- b, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
-
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
-
- assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b))
-}
-
-func TestHandler_Multipart_POST(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php data pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8019", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- var mb bytes.Buffer
- w := multipart.NewWriter(&mb)
- err := w.WriteField("key", "value")
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("key", "value")
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("name[]", "name1")
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("name[]", "name2")
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("name[]", "name3")
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("arr[x][y][z]", "y")
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("arr[x][y][e]", "f")
-
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("arr[c]p", "l")
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("arr[c]z", "")
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.Close()
- if err != nil {
- t.Errorf("error closing the writer: error %v", err)
- }
-
- req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, &mb)
- assert.NoError(t, err)
-
- req.Header.Set("Content-Type", w.FormDataContentType())
-
- r, err := http.DefaultClient.Do(req)
- assert.NoError(t, err)
- defer func() {
- err := r.Body.Close()
- if err != nil {
- t.Errorf("error during the closing Body: error %v", err)
-
- }
- }()
-
- b, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
-
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
-
- assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b))
-}
-
-func TestHandler_Multipart_PUT(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php data pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8020", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 500)
-
- var mb bytes.Buffer
- w := multipart.NewWriter(&mb)
- err := w.WriteField("key", "value")
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("key", "value")
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("name[]", "name1")
-
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("name[]", "name2")
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("name[]", "name3")
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("arr[x][y][z]", "y")
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("arr[x][y][e]", "f")
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("arr[c]p", "l")
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("arr[c]z", "")
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.Close()
- if err != nil {
- t.Errorf("error closing the writer: error %v", err)
- }
-
- req, err := http.NewRequest("PUT", "http://localhost"+hs.Addr, &mb)
- assert.NoError(t, err)
-
- req.Header.Set("Content-Type", w.FormDataContentType())
-
- r, err := http.DefaultClient.Do(req)
- assert.NoError(t, err)
- defer func() {
- err := r.Body.Close()
- if err != nil {
- t.Errorf("error during the closing Body: error %v", err)
-
- }
- }()
-
- b, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
-
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
-
- assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b))
-}
-
-func TestHandler_Multipart_PATCH(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php data pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8021", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
-
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 500)
-
- var mb bytes.Buffer
- w := multipart.NewWriter(&mb)
- err := w.WriteField("key", "value")
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("key", "value")
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("name[]", "name1")
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("name[]", "name2")
-
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("name[]", "name3")
-
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("arr[x][y][z]", "y")
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("arr[x][y][e]", "f")
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("arr[c]p", "l")
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.WriteField("arr[c]z", "")
- if err != nil {
- t.Errorf("error writing the field: error %v", err)
- }
-
- err = w.Close()
- if err != nil {
- t.Errorf("error closing the writer: error %v", err)
- }
-
- req, err := http.NewRequest("PATCH", "http://localhost"+hs.Addr, &mb)
- assert.NoError(t, err)
-
- req.Header.Set("Content-Type", w.FormDataContentType())
-
- r, err := http.DefaultClient.Do(req)
- assert.NoError(t, err)
- defer func() {
- err := r.Body.Close()
- if err != nil {
- t.Errorf("error during the closing Body: error %v", err)
-
- }
- }()
-
- b, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
-
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
-
- assert.Equal(t, `{"arr":{"c":{"p":"l","z":""},"x":{"y":{"e":"f","z":"y"}}},"key":"value","name":["name1","name2","name3"]}`, string(b))
-}
-
-func TestHandler_Error(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php error pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8177", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- _, r, err := get("http://localhost:8177/?hello=world")
- assert.NoError(t, err)
- assert.Equal(t, 500, r.StatusCode)
-}
-
-func TestHandler_Error2(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php error2 pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8177", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- _, r, err := get("http://localhost:8177/?hello=world")
- assert.NoError(t, err)
- assert.Equal(t, 500, r.StatusCode)
-}
-
-func TestHandler_Error3(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php pid pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8177", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- b2 := &bytes.Buffer{}
- for i := 0; i < 1024*1024; i++ {
- b2.Write([]byte(" "))
- }
-
- req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, b2)
- assert.NoError(t, err)
-
- r, err := http.DefaultClient.Do(req)
- assert.NoError(t, err)
- defer func() {
- err := r.Body.Close()
- if err != nil {
- t.Errorf("error during the closing Body: error %v", err)
-
- }
- }()
-
- assert.NoError(t, err)
- assert.Equal(t, 500, r.StatusCode)
-}
-
-func TestHandler_ResponseDuration(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php echo pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8177", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- gotresp := make(chan interface{})
- h.Listen(func(event int, ctx interface{}) {
- if event == EventResponse {
- c := ctx.(*ResponseEvent)
-
- if c.Elapsed() > 0 {
- close(gotresp)
- }
- }
- })
-
- body, r, err := get("http://localhost:8177/?hello=world")
- assert.NoError(t, err)
-
- <-gotresp
-
- assert.Equal(t, 201, r.StatusCode)
- assert.Equal(t, "WORLD", body)
-}
-
-func TestHandler_ResponseDurationDelayed(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php echoDelay pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8177", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- gotresp := make(chan interface{})
- h.Listen(func(event int, ctx interface{}) {
- if event == EventResponse {
- c := ctx.(*ResponseEvent)
-
- if c.Elapsed() > time.Second {
- close(gotresp)
- }
- }
- })
-
- body, r, err := get("http://localhost:8177/?hello=world")
- assert.NoError(t, err)
-
- <-gotresp
-
- assert.Equal(t, 201, r.StatusCode)
- assert.Equal(t, "WORLD", body)
-}
-
-func TestHandler_ErrorDuration(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php error pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8177", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- goterr := make(chan interface{})
- h.Listen(func(event int, ctx interface{}) {
- if event == EventError {
- c := ctx.(*ErrorEvent)
-
- if c.Elapsed() > 0 {
- close(goterr)
- }
- }
- })
-
- _, r, err := get("http://localhost:8177/?hello=world")
- assert.NoError(t, err)
-
- <-goterr
-
- assert.Equal(t, 500, r.StatusCode)
-}
-
-func TestHandler_IP(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- TrustedSubnets: []string{
- "10.0.0.0/8",
- "127.0.0.0/8",
- "172.16.0.0/12",
- "192.168.0.0/16",
- "::1/128",
- "fc00::/7",
- "fe80::/10",
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php ip pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- err := h.cfg.parseCIDRs()
- if err != nil {
- t.Errorf("error parsing CIDRs: error %v", err)
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: "127.0.0.1:8177", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- body, r, err := get("http://127.0.0.1:8177/")
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
- assert.Equal(t, "127.0.0.1", body)
-}
-
-func TestHandler_XRealIP(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- TrustedSubnets: []string{
- "10.0.0.0/8",
- "127.0.0.0/8",
- "172.16.0.0/12",
- "192.168.0.0/16",
- "::1/128",
- "fc00::/7",
- "fe80::/10",
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php ip pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- err := h.cfg.parseCIDRs()
- if err != nil {
- t.Errorf("error parsing CIDRs: error %v", err)
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: "127.0.0.1:8177", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- body, r, err := getHeader("http://127.0.0.1:8177/", map[string]string{
- "X-Real-Ip": "200.0.0.1",
- })
-
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
- assert.Equal(t, "200.0.0.1", body)
-}
-
-func TestHandler_XForwardedFor(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- TrustedSubnets: []string{
- "10.0.0.0/8",
- "127.0.0.0/8",
- "172.16.0.0/12",
- "192.168.0.0/16",
- "100.0.0.0/16",
- "200.0.0.0/16",
- "::1/128",
- "fc00::/7",
- "fe80::/10",
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php ip pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- err := h.cfg.parseCIDRs()
- if err != nil {
- t.Errorf("error parsing CIDRs: error %v", err)
- }
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: "127.0.0.1:8177", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- body, r, err := getHeader("http://127.0.0.1:8177/", map[string]string{
- "X-Forwarded-For": "100.0.0.1, 200.0.0.1, invalid, 101.0.0.1",
- })
-
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
- assert.Equal(t, "101.0.0.1", body)
-
- body, r, err = getHeader("http://127.0.0.1:8177/", map[string]string{
- "X-Forwarded-For": "100.0.0.1, 200.0.0.1, 101.0.0.1, invalid",
- })
-
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
- assert.Equal(t, "101.0.0.1", body)
-}
-
-func TestHandler_XForwardedFor_NotTrustedRemoteIp(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- TrustedSubnets: []string{
- "10.0.0.0/8",
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php ip pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- err := h.cfg.parseCIDRs()
- if err != nil {
- t.Errorf("error parsing CIDRs: error %v", err)
- }
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: "127.0.0.1:8177", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- body, r, err := getHeader("http://127.0.0.1:8177/", map[string]string{
- "X-Forwarded-For": "100.0.0.1, 200.0.0.1, invalid, 101.0.0.1",
- })
-
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
- assert.Equal(t, "127.0.0.1", body)
-}
-
-func BenchmarkHandler_Listen_Echo(b *testing.B) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php echo pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: int64(runtime.NumCPU()),
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- err := h.rr.Start()
- if err != nil {
- b.Errorf("error starting the worker pool: error %v", err)
- }
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8177", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- b.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- b.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- bb := "WORLD"
- for n := 0; n < b.N; n++ {
- r, err := http.Get("http://localhost:8177/?hello=world")
- if err != nil {
- b.Fail()
- }
- // Response might be nil here
- if r != nil {
- br, err := ioutil.ReadAll(r.Body)
- if err != nil {
- b.Errorf("error reading Body: error %v", err)
- }
- if string(br) != bb {
- b.Fail()
- }
- err = r.Body.Close()
- if err != nil {
- b.Errorf("error closing the Body: error %v", err)
- }
- } else {
- b.Errorf("got nil response")
- }
- }
-}
diff --git a/service/http/parse.go b/service/http/parse.go
deleted file mode 100644
index 9b58d328..00000000
--- a/service/http/parse.go
+++ /dev/null
@@ -1,147 +0,0 @@
-package http
-
-import (
- "net/http"
-)
-
-// MaxLevel defines maximum tree depth for incoming request data and files.
-const MaxLevel = 127
-
-type dataTree map[string]interface{}
-type fileTree map[string]interface{}
-
-// parseData parses incoming request body into data tree.
-func parseData(r *http.Request) dataTree {
- data := make(dataTree)
- if r.PostForm != nil {
- for k, v := range r.PostForm {
- data.push(k, v)
- }
- }
-
- if r.MultipartForm != nil {
- for k, v := range r.MultipartForm.Value {
- data.push(k, v)
- }
- }
-
- return data
-}
-
-// pushes value into data tree.
-func (d dataTree) push(k string, v []string) {
- keys := fetchIndexes(k)
- if len(keys) <= MaxLevel {
- d.mount(keys, v)
- }
-}
-
-// mount mounts data tree recursively.
-func (d dataTree) mount(i []string, v []string) {
- if len(i) == 1 {
- // single value context (last element)
- d[i[0]] = v[len(v)-1]
- return
- }
-
- if len(i) == 2 && i[1] == "" {
- // non associated array of elements
- d[i[0]] = v
- return
- }
-
- if p, ok := d[i[0]]; ok {
- p.(dataTree).mount(i[1:], v)
- return
- }
-
- d[i[0]] = make(dataTree)
- d[i[0]].(dataTree).mount(i[1:], v)
-}
-
-// parse incoming dataTree request into JSON (including contentMultipart form dataTree)
-func parseUploads(r *http.Request, cfg *UploadsConfig) *Uploads {
- u := &Uploads{
- cfg: cfg,
- tree: make(fileTree),
- list: make([]*FileUpload, 0),
- }
-
- for k, v := range r.MultipartForm.File {
- files := make([]*FileUpload, 0, len(v))
- for _, f := range v {
- files = append(files, NewUpload(f))
- }
-
- u.list = append(u.list, files...)
- u.tree.push(k, files)
- }
-
- return u
-}
-
-// pushes new file upload into it's proper place.
-func (d fileTree) push(k string, v []*FileUpload) {
- keys := fetchIndexes(k)
- if len(keys) <= MaxLevel {
- d.mount(keys, v)
- }
-}
-
-// mount mounts data tree recursively.
-func (d fileTree) mount(i []string, v []*FileUpload) {
- if len(i) == 1 {
- // single value context
- d[i[0]] = v[0]
- return
- }
-
- if len(i) == 2 && i[1] == "" {
- // non associated array of elements
- d[i[0]] = v
- return
- }
-
- if p, ok := d[i[0]]; ok {
- p.(fileTree).mount(i[1:], v)
- return
- }
-
- d[i[0]] = make(fileTree)
- d[i[0]].(fileTree).mount(i[1:], v)
-}
-
-// fetchIndexes parses input name and splits it into separate indexes list.
-func fetchIndexes(s string) []string {
- var (
- pos int
- ch string
- keys = make([]string, 1)
- )
-
- for _, c := range s {
- ch = string(c)
- switch ch {
- case " ":
- // ignore all spaces
- continue
- case "[":
- pos = 1
- continue
- case "]":
- if pos == 1 {
- keys = append(keys, "")
- }
- pos = 2
- default:
- if pos == 1 || pos == 2 {
- keys = append(keys, "")
- }
-
- keys[len(keys)-1] += ch
- pos = 0
- }
- }
-
- return keys
-}
diff --git a/service/http/parse_test.go b/service/http/parse_test.go
deleted file mode 100644
index f95a3f9d..00000000
--- a/service/http/parse_test.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package http
-
-import "testing"
-
-var samples = []struct {
- in string
- out []string
-}{
- {"key", []string{"key"}},
- {"key[subkey]", []string{"key", "subkey"}},
- {"key[subkey]value", []string{"key", "subkey", "value"}},
- {"key[subkey][value]", []string{"key", "subkey", "value"}},
- {"key[subkey][value][]", []string{"key", "subkey", "value", ""}},
- {"key[subkey] [value][]", []string{"key", "subkey", "value", ""}},
- {"key [ subkey ] [ value ] [ ]", []string{"key", "subkey", "value", ""}},
-}
-
-func Test_FetchIndexes(t *testing.T) {
- for _, tt := range samples {
- t.Run(tt.in, func(t *testing.T) {
- r := fetchIndexes(tt.in)
- if !same(r, tt.out) {
- t.Errorf("got %q, want %q", r, tt.out)
- }
- })
- }
-}
-
-func BenchmarkConfig_FetchIndexes(b *testing.B) {
- for _, tt := range samples {
- for n := 0; n < b.N; n++ {
- r := fetchIndexes(tt.in)
- if !same(r, tt.out) {
- b.Fail()
- }
- }
- }
-}
-
-func same(in, out []string) bool {
- if len(in) != len(out) {
- return false
- }
-
- for i, v := range in {
- if v != out[i] {
- return false
- }
- }
-
- return true
-}
diff --git a/service/http/request.go b/service/http/request.go
deleted file mode 100644
index 8da5440f..00000000
--- a/service/http/request.go
+++ /dev/null
@@ -1,183 +0,0 @@
-package http
-
-import (
- "fmt"
- "io/ioutil"
- "net"
- "net/http"
- "net/url"
- "strings"
-
- json "github.com/json-iterator/go"
- "github.com/sirupsen/logrus"
- "github.com/spiral/roadrunner"
- "github.com/spiral/roadrunner/service/http/attributes"
-)
-
-const (
- defaultMaxMemory = 32 << 20 // 32 MB
- contentNone = iota + 900
- contentStream
- contentMultipart
- contentFormData
-)
-
-// Request maps net/http requests to PSR7 compatible structure and managed state of temporary uploaded files.
-type Request struct {
- // RemoteAddr contains ip address of client, make sure to check X-Real-Ip and X-Forwarded-For for real client address.
- RemoteAddr string `json:"remoteAddr"`
-
- // Protocol includes HTTP protocol version.
- Protocol string `json:"protocol"`
-
- // 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"`
-
- // Header contains list of request headers.
- Header http.Header `json:"headers"`
-
- // Cookies contains list of request cookies.
- Cookies map[string]string `json:"cookies"`
-
- // RawQuery contains non parsed query string (to be parsed on php end).
- RawQuery string `json:"rawQuery"`
-
- // Parsed indicates that request body has been parsed on RR end.
- Parsed bool `json:"parsed"`
-
- // Uploads contains list of uploaded files, their names, sized and associations with temporary files.
- Uploads *Uploads `json:"uploads"`
-
- // Attributes can be set by chained mdwr to safely pass value from Golang to PHP. See: GetAttribute, SetAttribute functions.
- Attributes map[string]interface{} `json:"attributes"`
-
- // request body can be parsedData or []byte
- body interface{}
-}
-
-func fetchIP(pair string) string {
- if !strings.ContainsRune(pair, ':') {
- return pair
- }
-
- addr, _, _ := net.SplitHostPort(pair)
- return addr
-}
-
-// NewRequest creates new PSR7 compatible request using net/http request.
-func NewRequest(r *http.Request, cfg *UploadsConfig) (req *Request, err error) {
- req = &Request{
- RemoteAddr: fetchIP(r.RemoteAddr),
- Protocol: r.Proto,
- Method: r.Method,
- URI: uri(r),
- Header: r.Header,
- Cookies: make(map[string]string),
- RawQuery: r.URL.RawQuery,
- Attributes: attributes.All(r),
- }
-
- for _, c := range r.Cookies() {
- if v, err := url.QueryUnescape(c.Value); err == nil {
- req.Cookies[c.Name] = v
- }
- }
-
- switch req.contentType() {
- case contentNone:
- return req, nil
-
- case contentStream:
- req.body, err = ioutil.ReadAll(r.Body)
- return req, err
-
- case contentMultipart:
- if err = r.ParseMultipartForm(defaultMaxMemory); err != nil {
- return nil, err
- }
-
- req.Uploads = parseUploads(r, cfg)
- fallthrough
- case contentFormData:
- if err = r.ParseForm(); err != nil {
- return nil, err
- }
-
- req.body = parseData(r)
- }
-
- req.Parsed = true
- return req, nil
-}
-
-// Open moves all uploaded files to temporary directory so it can be given to php later.
-func (r *Request) Open(log *logrus.Logger) {
- if r.Uploads == nil {
- return
- }
-
- r.Uploads.Open(log)
-}
-
-// Close clears all temp file uploads
-func (r *Request) Close(log *logrus.Logger) {
- if r.Uploads == nil {
- return
- }
-
- r.Uploads.Clear(log)
-}
-
-// Payload request marshaled RoadRunner payload based on PSR7 data. values encode method is JSON. Make sure to open
-// files prior to calling this method.
-func (r *Request) Payload() (p *roadrunner.Payload, err error) {
- p = &roadrunner.Payload{}
-
- j := json.ConfigCompatibleWithStandardLibrary
- if p.Context, err = j.Marshal(r); err != nil {
- return nil, err
- }
-
- if r.Parsed {
- if p.Body, err = j.Marshal(r.body); err != nil {
- return nil, err
- }
- } else if r.body != nil {
- p.Body = r.body.([]byte)
- }
-
- return p, nil
-}
-
-// contentType returns the payload content type.
-func (r *Request) contentType() int {
- if r.Method == "HEAD" || r.Method == "OPTIONS" {
- return contentNone
- }
-
- ct := r.Header.Get("content-type")
- if strings.Contains(ct, "application/x-www-form-urlencoded") {
- return contentFormData
- }
-
- if strings.Contains(ct, "multipart/form-data") {
- return contentMultipart
- }
-
- return contentStream
-}
-
-// uri fetches full uri from request in a form of string (including https scheme if TLS connection is enabled).
-func uri(r *http.Request) string {
- if r.URL.Host != "" {
- return r.URL.String()
- }
- if r.TLS != nil {
- return fmt.Sprintf("https://%s%s", r.Host, r.URL.String())
- }
-
- return fmt.Sprintf("http://%s%s", r.Host, r.URL.String())
-}
diff --git a/service/http/response.go b/service/http/response.go
deleted file mode 100644
index f34754be..00000000
--- a/service/http/response.go
+++ /dev/null
@@ -1,107 +0,0 @@
-package http
-
-import (
- "io"
- "net/http"
- "strings"
-
- json "github.com/json-iterator/go"
-
- "github.com/spiral/roadrunner"
-)
-
-
-// Response handles PSR7 response logic.
-type Response struct {
- // Status contains response status.
- Status int `json:"status"`
-
- // Header contains list of response headers.
- Headers map[string][]string `json:"headers"`
-
- // associated body payload.
- body interface{}
-}
-
-// NewResponse creates new response based on given rr payload.
-func NewResponse(p *roadrunner.Payload) (*Response, error) {
- r := &Response{body: p.Body}
- j := json.ConfigCompatibleWithStandardLibrary
- if err := j.Unmarshal(p.Context, r); err != nil {
- return nil, err
- }
-
- return r, nil
-}
-
-// Write writes response headers, status and body into ResponseWriter.
-func (r *Response) Write(w http.ResponseWriter) error {
- // INFO map is the reference type in golang
- p := handlePushHeaders(r.Headers)
- if pusher, ok := w.(http.Pusher); ok {
- for _, v := range p {
- err := pusher.Push(v, nil)
- if err != nil {
- return err
- }
- }
- }
-
- handleTrailers(r.Headers)
- for n, h := range r.Headers {
- for _, v := range h {
- w.Header().Add(n, v)
- }
- }
-
- w.WriteHeader(r.Status)
-
- if data, ok := r.body.([]byte); ok {
- _, err := w.Write(data)
- if err != nil {
- return handleWriteError(err)
- }
- }
-
- if rc, ok := r.body.(io.Reader); ok {
- if _, err := io.Copy(w, rc); err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func handlePushHeaders(h map[string][]string) []string {
- var p []string
- pushHeader, ok := h[http2pushHeaderKey]
- if !ok {
- return p
- }
-
- p = append(p, pushHeader...)
-
- delete(h, http2pushHeaderKey)
-
- return p
-}
-
-func handleTrailers(h map[string][]string) {
- trailers, ok := h[trailerHeaderKey]
- if !ok {
- return
- }
-
- for _, tr := range trailers {
- for _, n := range strings.Split(tr, ",") {
- n = strings.Trim(n, "\t ")
- if v, ok := h[n]; ok {
- h["Trailer:"+n] = v
-
- delete(h, n)
- }
- }
- }
-
- delete(h, trailerHeaderKey)
-}
diff --git a/service/http/response_test.go b/service/http/response_test.go
deleted file mode 100644
index 1f394276..00000000
--- a/service/http/response_test.go
+++ /dev/null
@@ -1,162 +0,0 @@
-package http
-
-import (
- "bytes"
- "errors"
- "net/http"
- "testing"
-
- "github.com/spiral/roadrunner"
- "github.com/stretchr/testify/assert"
-)
-
-type testWriter struct {
- h http.Header
- buf bytes.Buffer
- wroteHeader bool
- code int
- err error
- pushErr error
- pushes []string
-}
-
-func (tw *testWriter) Header() http.Header { return tw.h }
-
-func (tw *testWriter) Write(p []byte) (int, error) {
- if !tw.wroteHeader {
- tw.WriteHeader(http.StatusOK)
- }
-
- n, e := tw.buf.Write(p)
- if e == nil {
- e = tw.err
- }
-
- return n, e
-}
-
-func (tw *testWriter) WriteHeader(code int) { tw.wroteHeader = true; tw.code = code }
-
-func (tw *testWriter) Push(target string, opts *http.PushOptions) error {
- tw.pushes = append(tw.pushes, target)
-
- return tw.pushErr
-}
-
-func TestNewResponse_Error(t *testing.T) {
- r, err := NewResponse(&roadrunner.Payload{Context: []byte(`invalid payload`)})
- assert.Error(t, err)
- assert.Nil(t, r)
-}
-
-func TestNewResponse_Write(t *testing.T) {
- r, err := NewResponse(&roadrunner.Payload{
- Context: []byte(`{"headers":{"key":["value"]},"status": 301}`),
- Body: []byte(`sample body`),
- })
-
- assert.NoError(t, err)
- assert.NotNil(t, r)
-
- w := &testWriter{h: http.Header(make(map[string][]string))}
- assert.NoError(t, r.Write(w))
-
- assert.Equal(t, 301, w.code)
- assert.Equal(t, "value", w.h.Get("key"))
- assert.Equal(t, "sample body", w.buf.String())
-}
-
-func TestNewResponse_Stream(t *testing.T) {
- r, err := NewResponse(&roadrunner.Payload{
- Context: []byte(`{"headers":{"key":["value"]},"status": 301}`),
- })
-
- // r is pointer, so, it might be nil
- if r == nil {
- t.Fatal("response is nil")
- }
-
- r.body = &bytes.Buffer{}
- r.body.(*bytes.Buffer).WriteString("hello world")
-
- assert.NoError(t, err)
- assert.NotNil(t, r)
-
- w := &testWriter{h: http.Header(make(map[string][]string))}
- assert.NoError(t, r.Write(w))
-
- assert.Equal(t, 301, w.code)
- assert.Equal(t, "value", w.h.Get("key"))
- assert.Equal(t, "hello world", w.buf.String())
-}
-
-func TestNewResponse_StreamError(t *testing.T) {
- r, err := NewResponse(&roadrunner.Payload{
- Context: []byte(`{"headers":{"key":["value"]},"status": 301}`),
- })
-
- // r is pointer, so, it might be nil
- if r == nil {
- t.Fatal("response is nil")
- }
-
- r.body = &bytes.Buffer{}
- r.body.(*bytes.Buffer).WriteString("hello world")
-
- assert.NoError(t, err)
- assert.NotNil(t, r)
-
- w := &testWriter{h: http.Header(make(map[string][]string)), err: errors.New("error")}
- assert.Error(t, r.Write(w))
-}
-
-func TestWrite_HandlesPush(t *testing.T) {
- r, err := NewResponse(&roadrunner.Payload{
- Context: []byte(`{"headers":{"Http2-Push":["/test.js"],"content-type":["text/html"]},"status": 200}`),
- })
-
- assert.NoError(t, err)
- assert.NotNil(t, r)
-
- w := &testWriter{h: http.Header(make(map[string][]string))}
- assert.NoError(t, r.Write(w))
-
- assert.Nil(t, w.h["Http2-Push"])
- assert.Equal(t, []string{"/test.js"}, w.pushes)
-}
-
-func TestWrite_HandlesTrailers(t *testing.T) {
- r, err := NewResponse(&roadrunner.Payload{
- Context: []byte(`{"headers":{"Trailer":["foo, bar", "baz"],"foo":["test"],"bar":["demo"]},"status": 200}`),
- })
-
- assert.NoError(t, err)
- assert.NotNil(t, r)
-
- w := &testWriter{h: http.Header(make(map[string][]string))}
- assert.NoError(t, r.Write(w))
-
- assert.Nil(t, w.h[trailerHeaderKey])
- assert.Nil(t, w.h["foo"]) //nolint:golint,staticcheck
- assert.Nil(t, w.h["baz"]) //nolint:golint,staticcheck
-
- assert.Equal(t, "test", w.h.Get("Trailer:foo"))
- assert.Equal(t, "demo", w.h.Get("Trailer:bar"))
-}
-
-func TestWrite_HandlesHandlesWhitespacesInTrailer(t *testing.T) {
- r, err := NewResponse(&roadrunner.Payload{
- Context: []byte(
- `{"headers":{"Trailer":["foo\t,bar , baz"],"foo":["a"],"bar":["b"],"baz":["c"]},"status": 200}`),
- })
-
- assert.NoError(t, err)
- assert.NotNil(t, r)
-
- w := &testWriter{h: http.Header(make(map[string][]string))}
- assert.NoError(t, r.Write(w))
-
- assert.Equal(t, "a", w.h.Get("Trailer:foo"))
- assert.Equal(t, "b", w.h.Get("Trailer:bar"))
- assert.Equal(t, "c", w.h.Get("Trailer:baz"))
-}
diff --git a/service/http/rpc.go b/service/http/rpc.go
deleted file mode 100644
index 7b38dece..00000000
--- a/service/http/rpc.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package http
-
-import (
- "github.com/pkg/errors"
- "github.com/spiral/roadrunner/util"
-)
-
-type rpcServer struct{ svc *Service }
-
-// WorkerList contains list of workers.
-type WorkerList struct {
- // Workers is list of workers.
- Workers []*util.State `json:"workers"`
-}
-
-// Reset resets underlying RR worker pool and restarts all of it's workers.
-func (rpc *rpcServer) Reset(reset bool, r *string) error {
- if rpc.svc == nil || rpc.svc.handler == nil {
- return errors.New("http server is not running")
- }
-
- *r = "OK"
- return rpc.svc.Server().Reset()
-}
-
-// Workers returns list of active workers and their stats.
-func (rpc *rpcServer) Workers(list bool, r *WorkerList) (err error) {
- if rpc.svc == nil || rpc.svc.handler == nil {
- return errors.New("http server is not running")
- }
-
- r.Workers, err = util.ServerState(rpc.svc.Server())
- return err
-}
diff --git a/service/http/rpc_test.go b/service/http/rpc_test.go
deleted file mode 100644
index e57a8699..00000000
--- a/service/http/rpc_test.go
+++ /dev/null
@@ -1,221 +0,0 @@
-package http
-
-import (
- json "github.com/json-iterator/go"
- "github.com/sirupsen/logrus"
- "github.com/sirupsen/logrus/hooks/test"
- "github.com/spiral/roadrunner/service"
- "github.com/spiral/roadrunner/service/rpc"
- "github.com/stretchr/testify/assert"
- "os"
- "strconv"
- "testing"
- "time"
-)
-
-func Test_RPC(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rpc.ID, &rpc.Service{})
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{
- rpcCfg: `{"enable":true, "listen":"tcp://:5004"}`,
- httpCfg: `{
- "enable": true,
- "address": ":16031",
- "maxRequestSize": 1024,
- "uploads": {
- "dir": ` + tmpDir() + `,
- "forbid": []
- },
- "workers":{
- "command": "php ../../tests/http/client.php pid pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`}))
-
- s, _ := c.Get(ID)
- ss := s.(*Service)
-
- s2, _ := c.Get(rpc.ID)
- rs := s2.(*rpc.Service)
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("error during the Serve: error %v", err)
- }
- }()
-
- time.Sleep(time.Second)
-
- res, _, err := get("http://localhost:16031")
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, strconv.Itoa(*ss.rr.Workers()[0].Pid), res)
-
- cl, err := rs.Client()
- assert.NoError(t, err)
-
- r := ""
- assert.NoError(t, cl.Call("http.Reset", true, &r))
- assert.Equal(t, "OK", r)
-
- res2, _, err := get("http://localhost:16031")
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, strconv.Itoa(*ss.rr.Workers()[0].Pid), res2)
- assert.NotEqual(t, res, res2)
- c.Stop()
-}
-
-func Test_RPC_Unix(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rpc.ID, &rpc.Service{})
- c.Register(ID, &Service{})
-
- sock := `unix://` + os.TempDir() + `/rpc.unix`
- j := json.ConfigCompatibleWithStandardLibrary
- data, _ := j.Marshal(sock)
-
- assert.NoError(t, c.Init(&testCfg{
- rpcCfg: `{"enable":true, "listen":` + string(data) + `}`,
- httpCfg: `{
- "enable": true,
- "address": ":6032",
- "maxRequestSize": 1024,
- "uploads": {
- "dir": ` + tmpDir() + `,
- "forbid": []
- },
- "workers":{
- "command": "php ../../tests/http/client.php pid pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`}))
-
- s, _ := c.Get(ID)
- ss := s.(*Service)
-
- s2, _ := c.Get(rpc.ID)
- rs := s2.(*rpc.Service)
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("error during the Serve: error %v", err)
- }
- }()
-
- time.Sleep(time.Millisecond * 500)
-
- res, _, err := get("http://localhost:6032")
- if err != nil {
- c.Stop()
- t.Fatal(err)
- }
- if ss.rr.Workers() != nil && len(ss.rr.Workers()) > 0 {
- assert.Equal(t, strconv.Itoa(*ss.rr.Workers()[0].Pid), res)
- } else {
- c.Stop()
- t.Fatal("no workers initialized")
- }
-
- cl, err := rs.Client()
- if err != nil {
- c.Stop()
- t.Fatal(err)
- }
-
- r := ""
- assert.NoError(t, cl.Call("http.Reset", true, &r))
- assert.Equal(t, "OK", r)
-
- res2, _, err := get("http://localhost:6032")
- if err != nil {
- c.Stop()
- t.Fatal(err)
- }
- assert.Equal(t, strconv.Itoa(*ss.rr.Workers()[0].Pid), res2)
- assert.NotEqual(t, res, res2)
- c.Stop()
-}
-
-func Test_Workers(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rpc.ID, &rpc.Service{})
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{
- rpcCfg: `{"enable":true, "listen":"tcp://:5005"}`,
- httpCfg: `{
- "enable": true,
- "address": ":6033",
- "maxRequestSize": 1024,
- "uploads": {
- "dir": ` + tmpDir() + `,
- "forbid": []
- },
- "workers":{
- "command": "php ../../tests/http/client.php pid pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`}))
-
- s, _ := c.Get(ID)
- ss := s.(*Service)
-
- s2, _ := c.Get(rpc.ID)
- rs := s2.(*rpc.Service)
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("error during the Serve: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 500)
-
- cl, err := rs.Client()
- assert.NoError(t, err)
-
- r := &WorkerList{}
- assert.NoError(t, cl.Call("http.Workers", true, &r))
- assert.Len(t, r.Workers, 1)
-
- assert.Equal(t, *ss.rr.Workers()[0].Pid, r.Workers[0].Pid)
- c.Stop()
-}
-
-func Test_Errors(t *testing.T) {
- r := &rpcServer{nil}
-
- assert.Error(t, r.Reset(true, nil))
- assert.Error(t, r.Workers(true, nil))
-}
diff --git a/service/http/service.go b/service/http/service.go
deleted file mode 100644
index 25a10064..00000000
--- a/service/http/service.go
+++ /dev/null
@@ -1,427 +0,0 @@
-package http
-
-import (
- "context"
- "crypto/tls"
- "crypto/x509"
- "errors"
- "fmt"
- "io/ioutil"
- "net/http"
- "net/http/fcgi"
- "net/url"
- "strings"
- "sync"
-
- "github.com/sirupsen/logrus"
- "github.com/spiral/roadrunner"
- "github.com/spiral/roadrunner/service/env"
- "github.com/spiral/roadrunner/service/http/attributes"
- "github.com/spiral/roadrunner/service/rpc"
- "github.com/spiral/roadrunner/util"
- "golang.org/x/net/http2"
- "golang.org/x/net/http2/h2c"
- "golang.org/x/sys/cpu"
-)
-
-const (
- // ID contains default service name.
- ID = "http"
-
- // EventInitSSL thrown at moment of https initialization. SSL server passed as context.
- EventInitSSL = 750
-)
-
-var couldNotAppendPemError = errors.New("could not append Certs from PEM")
-
-// http middleware type.
-type middleware func(f http.HandlerFunc) http.HandlerFunc
-
-// Service manages rr, http servers.
-type Service struct {
- sync.Mutex
- sync.WaitGroup
-
- cfg *Config
- log *logrus.Logger
- cprod roadrunner.CommandProducer
- env env.Environment
- lsns []func(event int, ctx interface{})
- mdwr []middleware
-
- rr *roadrunner.Server
- controller roadrunner.Controller
- handler *Handler
-
- http *http.Server
- https *http.Server
- fcgi *http.Server
-}
-
-// Attach attaches controller. Currently only one controller is supported.
-func (s *Service) Attach(w roadrunner.Controller) {
- s.controller = w
-}
-
-// ProduceCommands changes the default command generator method
-func (s *Service) ProduceCommands(producer roadrunner.CommandProducer) {
- s.cprod = producer
-}
-
-// AddMiddleware adds new net/http mdwr.
-func (s *Service) AddMiddleware(m middleware) {
- s.mdwr = append(s.mdwr, m)
-}
-
-// AddListener attaches server event controller.
-func (s *Service) AddListener(l func(event int, ctx interface{})) {
- s.lsns = append(s.lsns, l)
-}
-
-// Init must return configure svc and return true if svc hasStatus enabled. Must return error in case of
-// misconfiguration. Services must not be used without proper configuration pushed first.
-func (s *Service) Init(cfg *Config, r *rpc.Service, e env.Environment, log *logrus.Logger) (bool, error) {
- s.cfg = cfg
- s.log = log
- s.env = e
-
- if r != nil {
- if err := r.Register(ID, &rpcServer{s}); err != nil {
- return false, err
- }
- }
-
- if !cfg.EnableHTTP() && !cfg.EnableTLS() && !cfg.EnableFCGI() {
- return false, nil
- }
-
- return true, nil
-}
-
-// Serve serves the svc.
-func (s *Service) Serve() error {
- s.Lock()
-
- if s.env != nil {
- if err := s.env.Copy(s.cfg.Workers); err != nil {
- return nil
- }
- }
-
- s.cfg.Workers.CommandProducer = s.cprod
- s.cfg.Workers.SetEnv("RR_HTTP", "true")
-
- s.rr = roadrunner.NewServer(s.cfg.Workers)
- s.rr.Listen(s.throw)
-
- if s.controller != nil {
- s.rr.Attach(s.controller)
- }
-
- s.handler = &Handler{cfg: s.cfg, rr: s.rr}
- s.handler.Listen(s.throw)
-
- if s.cfg.EnableHTTP() {
- if s.cfg.EnableH2C() {
- s.http = &http.Server{Addr: s.cfg.Address, Handler: h2c.NewHandler(s, &http2.Server{})}
- } else {
- s.http = &http.Server{Addr: s.cfg.Address, Handler: s}
- }
- }
-
- if s.cfg.EnableTLS() {
- s.https = s.initSSL()
- if s.cfg.SSL.RootCA != "" {
- err := s.appendRootCa()
- if err != nil {
- return err
- }
- }
-
- if s.cfg.EnableHTTP2() {
- if err := s.initHTTP2(); err != nil {
- return err
- }
- }
- }
-
- if s.cfg.EnableFCGI() {
- s.fcgi = &http.Server{Handler: s}
- }
-
- s.Unlock()
-
- if err := s.rr.Start(); err != nil {
- return err
- }
- defer s.rr.Stop()
-
- err := make(chan error, 3)
-
- if s.http != nil {
- go func() {
- httpErr := s.http.ListenAndServe()
- if httpErr != nil && httpErr != http.ErrServerClosed {
- err <- httpErr
- } else {
- err <- nil
- }
- }()
- }
-
- if s.https != nil {
- go func() {
- httpErr := s.https.ListenAndServeTLS(
- s.cfg.SSL.Cert,
- s.cfg.SSL.Key,
- )
-
- if httpErr != nil && httpErr != http.ErrServerClosed {
- err <- httpErr
- return
- }
- err <- nil
- }()
- }
-
- if s.fcgi != nil {
- go func() {
- httpErr := s.serveFCGI()
- if httpErr != nil && httpErr != http.ErrServerClosed {
- err <- httpErr
- return
- }
- err <- nil
- }()
- }
- return <-err
-}
-
-// Stop stops the http.
-func (s *Service) Stop() {
- s.Lock()
- defer s.Unlock()
-
- if s.fcgi != nil {
- s.Add(1)
- go func() {
- defer s.Done()
- err := s.fcgi.Shutdown(context.Background())
- if err != nil && err != http.ErrServerClosed {
- // Stop() error
- // push error from goroutines to the channel and block unil error or success shutdown or timeout
- s.log.Error(fmt.Errorf("error shutting down the fcgi server, error: %v", err))
- return
- }
- }()
- }
-
- if s.https != nil {
- s.Add(1)
- go func() {
- defer s.Done()
- err := s.https.Shutdown(context.Background())
- if err != nil && err != http.ErrServerClosed {
- s.log.Error(fmt.Errorf("error shutting down the https server, error: %v", err))
- return
- }
- }()
- }
-
- if s.http != nil {
- s.Add(1)
- go func() {
- defer s.Done()
- err := s.http.Shutdown(context.Background())
- if err != nil && err != http.ErrServerClosed {
- s.log.Error(fmt.Errorf("error shutting down the http server, error: %v", err))
- return
- }
- }()
- }
-
- s.Wait()
-}
-
-// Server returns associated rr server (if any).
-func (s *Service) Server() *roadrunner.Server {
- s.Lock()
- defer s.Unlock()
-
- return s.rr
-}
-
-// ServeHTTP handles connection using set of middleware and rr PSR-7 server.
-func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- if s.https != nil && r.TLS == nil && s.cfg.SSL.Redirect {
- target := &url.URL{
- Scheme: "https",
- Host: s.tlsAddr(r.Host, false),
- Path: r.URL.Path,
- RawQuery: r.URL.RawQuery,
- }
-
- http.Redirect(w, r, target.String(), http.StatusTemporaryRedirect)
- return
- }
-
- if s.https != nil && r.TLS != nil {
- w.Header().Add("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload")
- }
-
- r = attributes.Init(r)
-
- // chaining middleware
- f := s.handler.ServeHTTP
- for _, m := range s.mdwr {
- f = m(f)
- }
- f(w, r)
-}
-
-// append RootCA to the https server TLS config
-func (s *Service) appendRootCa() error {
- rootCAs, err := x509.SystemCertPool()
- if err != nil {
- s.throw(EventInitSSL, nil)
- return nil
- }
- if rootCAs == nil {
- rootCAs = x509.NewCertPool()
- }
-
- CA, err := ioutil.ReadFile(s.cfg.SSL.RootCA)
- if err != nil {
- s.throw(EventInitSSL, nil)
- return err
- }
-
- // should append our CA cert
- ok := rootCAs.AppendCertsFromPEM(CA)
- if !ok {
- return couldNotAppendPemError
- }
- config := &tls.Config{
- InsecureSkipVerify: false,
- RootCAs: rootCAs,
- }
- s.http.TLSConfig = config
-
- return nil
-}
-
-// Init https server
-func (s *Service) initSSL() *http.Server {
- var topCipherSuites []uint16
- var defaultCipherSuitesTLS13 []uint16
-
- hasGCMAsmAMD64 := cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
- hasGCMAsmARM64 := cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
- // Keep in sync with crypto/aes/cipher_s390x.go.
- hasGCMAsmS390X := cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && (cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
-
- hasGCMAsm := hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X
-
- if hasGCMAsm {
- // If AES-GCM hardware is provided then prioritise AES-GCM
- // cipher suites.
- topCipherSuites = []uint16{
- tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
- tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
- tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
- tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
- tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
- }
- defaultCipherSuitesTLS13 = []uint16{
- tls.TLS_AES_128_GCM_SHA256,
- tls.TLS_CHACHA20_POLY1305_SHA256,
- tls.TLS_AES_256_GCM_SHA384,
- }
- } else {
- // Without AES-GCM hardware, we put the ChaCha20-Poly1305
- // cipher suites first.
- topCipherSuites = []uint16{
- tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
- tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
- tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
- tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
- tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
- }
- defaultCipherSuitesTLS13 = []uint16{
- tls.TLS_CHACHA20_POLY1305_SHA256,
- tls.TLS_AES_128_GCM_SHA256,
- tls.TLS_AES_256_GCM_SHA384,
- }
- }
-
- DefaultCipherSuites := make([]uint16, 0, 22)
- DefaultCipherSuites = append(DefaultCipherSuites, topCipherSuites...)
- DefaultCipherSuites = append(DefaultCipherSuites, defaultCipherSuitesTLS13...)
-
- server := &http.Server{
- Addr: s.tlsAddr(s.cfg.Address, true),
- Handler: s,
- TLSConfig: &tls.Config{
- CurvePreferences: []tls.CurveID{
- tls.CurveP256,
- tls.CurveP384,
- tls.CurveP521,
- tls.X25519,
- },
- CipherSuites: DefaultCipherSuites,
- MinVersion: tls.VersionTLS12,
- PreferServerCipherSuites: true,
- },
- }
- s.throw(EventInitSSL, server)
-
- return server
-}
-
-// init http/2 server
-func (s *Service) initHTTP2() error {
- return http2.ConfigureServer(s.https, &http2.Server{
- MaxConcurrentStreams: s.cfg.HTTP2.MaxConcurrentStreams,
- })
-}
-
-// serveFCGI starts FastCGI server.
-func (s *Service) serveFCGI() error {
- l, err := util.CreateListener(s.cfg.FCGI.Address)
- if err != nil {
- return err
- }
-
- err = fcgi.Serve(l, s.fcgi.Handler)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-// throw handles service, server and pool events.
-func (s *Service) throw(event int, ctx interface{}) {
- for _, l := range s.lsns {
- l(event, ctx)
- }
-
- if event == roadrunner.EventServerFailure {
- // underlying rr server is dead
- s.Stop()
- }
-}
-
-// tlsAddr replaces listen or host port with port configured by SSL config.
-func (s *Service) tlsAddr(host string, forcePort bool) string {
- // remove current forcePort first
- host = strings.Split(host, ":")[0]
-
- if forcePort || s.cfg.SSL.Port != 443 {
- host = fmt.Sprintf("%s:%v", host, s.cfg.SSL.Port)
- }
-
- return host
-}
diff --git a/service/http/service_test.go b/service/http/service_test.go
deleted file mode 100644
index f7ee33cc..00000000
--- a/service/http/service_test.go
+++ /dev/null
@@ -1,759 +0,0 @@
-package http
-
-import (
- "github.com/cenkalti/backoff/v4"
- json "github.com/json-iterator/go"
- "github.com/sirupsen/logrus"
- "github.com/sirupsen/logrus/hooks/test"
- "github.com/spiral/roadrunner"
- "github.com/spiral/roadrunner/service"
- "github.com/spiral/roadrunner/service/env"
- "github.com/spiral/roadrunner/service/rpc"
- "github.com/stretchr/testify/assert"
- "io/ioutil"
- "net/http"
- "os"
- "testing"
- "time"
-)
-
-type testCfg struct {
- httpCfg string
- rpcCfg string
- envCfg string
- target string
-}
-
-func (cfg *testCfg) Get(name string) service.Config {
- if name == ID {
- if cfg.httpCfg == "" {
- return nil
- }
-
- return &testCfg{target: cfg.httpCfg}
- }
-
- if name == rpc.ID {
- return &testCfg{target: cfg.rpcCfg}
- }
-
- if name == env.ID {
- return &testCfg{target: cfg.envCfg}
- }
-
- return nil
-}
-func (cfg *testCfg) Unmarshal(out interface{}) error {
- j := json.ConfigCompatibleWithStandardLibrary
- return j.Unmarshal([]byte(cfg.target), out)
-}
-
-func Test_Service_NoConfig(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- err := c.Init(&testCfg{httpCfg: `{"Enable":true}`})
- assert.Error(t, err)
-
- s, st := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusInactive, st)
-}
-
-func Test_Service_Configure_Disable(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{}))
-
- s, st := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusInactive, st)
-}
-
-func Test_Service_Configure_Enable(t *testing.T) {
- bkoff := backoff.NewExponentialBackOff()
- bkoff.MaxElapsedTime = time.Second * 15
-
- err := backoff.Retry(func() error {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- err := c.Init(&testCfg{httpCfg: `{
- "enable": true,
- "address": ":8070",
- "maxRequestSize": 1024,
- "uploads": {
- "dir": ` + tmpDir() + `,
- "forbid": []
- },
- "workers":{
- "command": "php ../../tests/http/client.php echo pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`})
- if err != nil {
- return err
- }
-
- s, st := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusOK, st)
-
- return nil
- }, bkoff)
-
- if err != nil {
- t.Fatal(err)
- }
-
-}
-
-func Test_Service_Echo(t *testing.T) {
- bkoff := backoff.NewExponentialBackOff()
- bkoff.MaxElapsedTime = time.Second * 15
-
- err := backoff.Retry(func() error {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- err := c.Init(&testCfg{httpCfg: `{
- "enable": true,
- "address": ":6536",
- "maxRequestSize": 1024,
- "uploads": {
- "dir": ` + tmpDir() + `,
- "forbid": []
- },
- "workers":{
- "command": "php ../../tests/http/client.php echo pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`})
- if err != nil {
- return err
- }
-
- s, st := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusOK, st)
-
- // should do nothing
- s.(*Service).Stop()
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("serve error: %v", err)
- }
- }()
-
- time.Sleep(time.Millisecond * 100)
-
- req, err := http.NewRequest("GET", "http://localhost:6536?hello=world", nil)
- if err != nil {
- c.Stop()
- return err
- }
-
- r, err := http.DefaultClient.Do(req)
- if err != nil {
- c.Stop()
- return err
- }
- b, err := ioutil.ReadAll(r.Body)
- if err != nil {
- c.Stop()
- return err
- }
- assert.Equal(t, 201, r.StatusCode)
- assert.Equal(t, "WORLD", string(b))
-
- err = r.Body.Close()
- if err != nil {
- c.Stop()
- return err
- }
-
- c.Stop()
- return nil
- }, bkoff)
-
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func Test_Service_Env(t *testing.T) {
- bkoff := backoff.NewExponentialBackOff()
- bkoff.MaxElapsedTime = time.Second * 15
-
- err := backoff.Retry(func() error {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(env.ID, env.NewService(map[string]string{"rr": "test"}))
- c.Register(ID, &Service{})
-
- err := c.Init(&testCfg{httpCfg: `{
- "enable": true,
- "address": ":10031",
- "maxRequestSize": 1024,
- "uploads": {
- "dir": ` + tmpDir() + `,
- "forbid": []
- },
- "workers":{
- "command": "php ../../tests/http/client.php env pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`, envCfg: `{"env_key":"ENV_VALUE"}`})
- if err != nil {
- return err
- }
-
- s, st := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusOK, st)
-
- // should do nothing
- s.(*Service).Stop()
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("serve error: %v", err)
- }
- }()
-
- time.Sleep(time.Millisecond * 500)
-
- req, err := http.NewRequest("GET", "http://localhost:10031", nil)
- if err != nil {
- c.Stop()
- return err
- }
-
- r, err := http.DefaultClient.Do(req)
- if err != nil {
- c.Stop()
- return err
- }
-
- b, err := ioutil.ReadAll(r.Body)
- if err != nil {
- c.Stop()
- return err
- }
-
- assert.Equal(t, 200, r.StatusCode)
- assert.Equal(t, "ENV_VALUE", string(b))
-
- err = r.Body.Close()
- if err != nil {
- c.Stop()
- return err
- }
-
- c.Stop()
- return nil
- }, bkoff)
-
- if err != nil {
- t.Fatal(err)
- }
-
-}
-
-func Test_Service_ErrorEcho(t *testing.T) {
- bkoff := backoff.NewExponentialBackOff()
- bkoff.MaxElapsedTime = time.Second * 15
-
- err := backoff.Retry(func() error {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- err := c.Init(&testCfg{httpCfg: `{
- "enable": true,
- "address": ":6030",
- "maxRequestSize": 1024,
- "uploads": {
- "dir": ` + tmpDir() + `,
- "forbid": []
- },
- "workers":{
- "command": "php ../../tests/http/client.php echoerr pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`})
- if err != nil {
- return err
- }
-
- s, st := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusOK, st)
-
- goterr := make(chan interface{})
- s.(*Service).AddListener(func(event int, ctx interface{}) {
- if event == roadrunner.EventStderrOutput {
- if string(ctx.([]byte)) == "WORLD\n" {
- goterr <- nil
- }
- }
- })
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("serve error: %v", err)
- }
- }()
-
- time.Sleep(time.Millisecond * 500)
-
- req, err := http.NewRequest("GET", "http://localhost:6030?hello=world", nil)
- if err != nil {
- c.Stop()
- return err
- }
-
- r, err := http.DefaultClient.Do(req)
- if err != nil {
- c.Stop()
- return err
- }
-
- b, err := ioutil.ReadAll(r.Body)
- if err != nil {
- c.Stop()
- return err
- }
-
- <-goterr
-
- assert.Equal(t, 201, r.StatusCode)
- assert.Equal(t, "WORLD", string(b))
- err = r.Body.Close()
- if err != nil {
- c.Stop()
- return err
- }
-
- c.Stop()
-
- return nil
- }, bkoff)
-
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func Test_Service_Middleware(t *testing.T) {
- bkoff := backoff.NewExponentialBackOff()
- bkoff.MaxElapsedTime = time.Second * 15
-
- err := backoff.Retry(func() error {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- err := c.Init(&testCfg{httpCfg: `{
- "enable": true,
- "address": ":6032",
- "maxRequestSize": 1024,
- "uploads": {
- "dir": ` + tmpDir() + `,
- "forbid": []
- },
- "workers":{
- "command": "php ../../tests/http/client.php echo pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`})
- if err != nil {
- return err
- }
-
- s, st := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusOK, st)
-
- s.(*Service).AddMiddleware(func(f http.HandlerFunc) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path == "/halt" {
- w.WriteHeader(500)
- _, err := w.Write([]byte("halted"))
- if err != nil {
- t.Errorf("error writing the data to the http reply: error %v", err)
- }
- } else {
- f(w, r)
- }
- }
- })
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("serve error: %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 500)
-
- req, err := http.NewRequest("GET", "http://localhost:6032?hello=world", nil)
- if err != nil {
- c.Stop()
- return err
- }
-
- r, err := http.DefaultClient.Do(req)
- if err != nil {
- c.Stop()
- return err
- }
-
- b, err := ioutil.ReadAll(r.Body)
- if err != nil {
- c.Stop()
- return err
- }
-
- assert.Equal(t, 201, r.StatusCode)
- assert.Equal(t, "WORLD", string(b))
-
- err = r.Body.Close()
- if err != nil {
- c.Stop()
- return err
- }
-
- req, err = http.NewRequest("GET", "http://localhost:6032/halt", nil)
- if err != nil {
- c.Stop()
- return err
- }
-
- r, err = http.DefaultClient.Do(req)
- if err != nil {
- c.Stop()
- return err
- }
- b, err = ioutil.ReadAll(r.Body)
- if err != nil {
- c.Stop()
- return err
- }
-
- assert.Equal(t, 500, r.StatusCode)
- assert.Equal(t, "halted", string(b))
-
- err = r.Body.Close()
- if err != nil {
- c.Stop()
- return err
- }
- c.Stop()
-
- return nil
- }, bkoff)
-
- if err != nil {
- t.Fatal(err)
- }
-
-}
-
-func Test_Service_Listener(t *testing.T) {
- bkoff := backoff.NewExponentialBackOff()
- bkoff.MaxElapsedTime = time.Second * 15
-
- err := backoff.Retry(func() error {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- err := c.Init(&testCfg{httpCfg: `{
- "enable": true,
- "address": ":6033",
- "maxRequestSize": 1024,
- "uploads": {
- "dir": ` + tmpDir() + `,
- "forbid": []
- },
- "workers":{
- "command": "php ../../tests/http/client.php echo pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`})
- if err != nil {
- return err
- }
-
- s, st := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusOK, st)
-
- stop := make(chan interface{})
- s.(*Service).AddListener(func(event int, ctx interface{}) {
- if event == roadrunner.EventServerStart {
- stop <- nil
- }
- })
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("serve error: %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 500)
-
- c.Stop()
- assert.True(t, true)
-
- return nil
- }, bkoff)
-
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func Test_Service_Error(t *testing.T) {
- bkoff := backoff.NewExponentialBackOff()
- bkoff.MaxElapsedTime = time.Second * 15
-
- err := backoff.Retry(func() error {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- err := c.Init(&testCfg{httpCfg: `{
- "enable": true,
- "address": ":6034",
- "maxRequestSize": 1024,
- "uploads": {
- "dir": ` + tmpDir() + `,
- "forbid": []
- },
- "workers":{
- "command": "php ../../tests/http/client.php echo pipes",
- "relay": "---",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`})
- if err != nil {
- return err
- }
-
- // assert error
- err = c.Serve()
- if err == nil {
- return err
- }
-
- return nil
- }, bkoff)
-
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func Test_Service_Error2(t *testing.T) {
- bkoff := backoff.NewExponentialBackOff()
- bkoff.MaxElapsedTime = time.Second * 15
-
- err := backoff.Retry(func() error {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- err := c.Init(&testCfg{httpCfg: `{
- "enable": true,
- "address": ":6035",
- "maxRequestSize": 1024,
- "uploads": {
- "dir": ` + tmpDir() + `,
- "forbid": []
- },
- "workers":{
- "command": "php ../../tests/http/client.php broken pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`})
- if err != nil {
- return err
- }
-
- // assert error
- err = c.Serve()
- if err == nil {
- return err
- }
-
- return nil
- }, bkoff)
-
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func Test_Service_Error3(t *testing.T) {
- bkoff := backoff.NewExponentialBackOff()
- bkoff.MaxElapsedTime = time.Second * 15
-
- err := backoff.Retry(func() error {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- err := c.Init(&testCfg{httpCfg: `{
- "enable": true,
- "address": ":6036",
- "maxRequestSize": 1024,
- "uploads": {
- "dir": ` + tmpDir() + `,
- "forbid": []
- },
- "workers"
- "command": "php ../../tests/http/client.php broken pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`})
- // assert error
- if err == nil {
- return err
- }
-
- return nil
- }, bkoff)
-
- if err != nil {
- t.Fatal(err)
- }
-
-}
-
-func Test_Service_Error4(t *testing.T) {
- bkoff := backoff.NewExponentialBackOff()
- bkoff.MaxElapsedTime = time.Second * 15
-
- err := backoff.Retry(func() error {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- err := c.Init(&testCfg{httpCfg: `{
- "enable": true,
- "address": "----",
- "maxRequestSize": 1024,
- "uploads": {
- "dir": ` + tmpDir() + `,
- "forbid": []
- },
- "workers":{
- "command": "php ../../tests/http/client.php broken pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`})
- // assert error
- if err != nil {
- return nil
- }
-
- return err
- }, bkoff)
-
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func tmpDir() string {
- p := os.TempDir()
- j := json.ConfigCompatibleWithStandardLibrary
- r, _ := j.Marshal(p)
-
- return string(r)
-}
diff --git a/service/http/ssl_test.go b/service/http/ssl_test.go
deleted file mode 100644
index cf147be9..00000000
--- a/service/http/ssl_test.go
+++ /dev/null
@@ -1,254 +0,0 @@
-package http
-
-import (
- "crypto/tls"
- "github.com/sirupsen/logrus"
- "github.com/sirupsen/logrus/hooks/test"
- "github.com/spiral/roadrunner/service"
- "github.com/stretchr/testify/assert"
- "io/ioutil"
- "net/http"
- "testing"
- "time"
-)
-
-var sslClient = &http.Client{
- Transport: &http.Transport{
- TLSClientConfig: &tls.Config{
- InsecureSkipVerify: true,
- },
- },
-}
-
-func Test_SSL_Service_Echo(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{httpCfg: `{
- "address": ":6029",
- "ssl": {
- "port": 6900,
- "key": "fixtures/server.key",
- "cert": "fixtures/server.crt"
- },
- "workers":{
- "command": "php ../../tests/http/client.php echo pipes",
- "pool": {"numWorkers": 1}
- }
- }`}))
-
- s, st := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusOK, st)
-
- // should do nothing
- s.(*Service).Stop()
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("error during the Serve: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 500)
-
- req, err := http.NewRequest("GET", "https://localhost:6900?hello=world", nil)
- assert.NoError(t, err)
-
- r, err := sslClient.Do(req)
- assert.NoError(t, err)
-
- b, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
-
- assert.NoError(t, err)
- assert.Equal(t, 201, r.StatusCode)
- assert.Equal(t, "WORLD", string(b))
-
- err2 := r.Body.Close()
- if err2 != nil {
- t.Errorf("fail to close the Body: error %v", err2)
- }
-
- c.Stop()
-}
-
-func Test_SSL_Service_NoRedirect(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{httpCfg: `{
- "address": ":6030",
- "ssl": {
- "port": 6901,
- "key": "fixtures/server.key",
- "cert": "fixtures/server.crt"
- },
- "workers":{
- "command": "php ../../tests/http/client.php echo pipes",
- "pool": {"numWorkers": 1}
- }
- }`}))
-
- s, st := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusOK, st)
-
- // should do nothing
- s.(*Service).Stop()
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("error during the Serve: error %v", err)
- }
- }()
-
- time.Sleep(time.Millisecond * 500)
-
- req, err := http.NewRequest("GET", "http://localhost:6030?hello=world", nil)
- assert.NoError(t, err)
-
- r, err := sslClient.Do(req)
- assert.NoError(t, err)
-
- assert.Nil(t, r.TLS)
-
- b, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
-
- assert.NoError(t, err)
- assert.Equal(t, 201, r.StatusCode)
- assert.Equal(t, "WORLD", string(b))
-
- err2 := r.Body.Close()
- if err2 != nil {
- t.Errorf("fail to close the Body: error %v", err2)
- }
- c.Stop()
-}
-
-func Test_SSL_Service_Redirect(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{httpCfg: `{
- "address": ":6831",
- "ssl": {
- "port": 6902,
- "redirect": true,
- "key": "fixtures/server.key",
- "cert": "fixtures/server.crt"
- },
- "workers":{
- "command": "php ../../tests/http/client.php echo pipes",
- "pool": {"numWorkers": 1}
- }
- }`}))
-
- s, st := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusOK, st)
-
- // should do nothing
- s.(*Service).Stop()
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("error during the Serve: error %v", err)
- }
- }()
-
- time.Sleep(time.Millisecond * 500)
-
- req, err := http.NewRequest("GET", "http://localhost:6831?hello=world", nil)
- assert.NoError(t, err)
-
- r, err := sslClient.Do(req)
- assert.NoError(t, err)
- assert.NotNil(t, r.TLS)
-
- b, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
-
- assert.NoError(t, err)
- assert.Equal(t, 201, r.StatusCode)
- assert.Equal(t, "WORLD", string(b))
-
- err2 := r.Body.Close()
- if err2 != nil {
- t.Errorf("fail to close the Body: error %v", err2)
- }
- c.Stop()
-}
-
-func Test_SSL_Service_Push(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{httpCfg: `{
- "address": ":6032",
- "ssl": {
- "port": 6903,
- "redirect": true,
- "key": "fixtures/server.key",
- "cert": "fixtures/server.crt"
- },
- "workers":{
- "command": "php ../../tests/http/client.php push pipes",
- "pool": {"numWorkers": 1}
- }
- }`}))
-
- s, st := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusOK, st)
-
- // should do nothing
- s.(*Service).Stop()
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("error during the Serve: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 500)
-
- req, err := http.NewRequest("GET", "https://localhost:6903?hello=world", nil)
- assert.NoError(t, err)
-
- r, err := sslClient.Do(req)
- assert.NoError(t, err)
-
- assert.NotNil(t, r.TLS)
-
- b, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
-
- assert.Equal(t, "", r.Header.Get("Http2-Push"))
-
- assert.NoError(t, err)
- assert.Equal(t, 201, r.StatusCode)
- assert.Equal(t, "WORLD", string(b))
-
-
- err2 := r.Body.Close()
- if err2 != nil {
- t.Errorf("fail to close the Body: error %v", err2)
- }
- c.Stop()
-}
diff --git a/service/http/uploads.go b/service/http/uploads.go
deleted file mode 100644
index 39a9eaf2..00000000
--- a/service/http/uploads.go
+++ /dev/null
@@ -1,160 +0,0 @@
-package http
-
-import (
- "fmt"
- json "github.com/json-iterator/go"
- "github.com/sirupsen/logrus"
- "io"
- "io/ioutil"
- "mime/multipart"
- "os"
- "sync"
-)
-
-const (
- // UploadErrorOK - no error, the file uploaded with success.
- UploadErrorOK = 0
-
- // UploadErrorNoFile - no file was uploaded.
- UploadErrorNoFile = 4
-
- // UploadErrorNoTmpDir - missing a temporary folder.
- UploadErrorNoTmpDir = 5
-
- // UploadErrorCantWrite - failed to write file to disk.
- UploadErrorCantWrite = 6
-
- // UploadErrorExtension - forbidden file extension.
- UploadErrorExtension = 7
-)
-
-// Uploads tree manages uploaded files tree and temporary files.
-type Uploads struct {
- // associated temp directory and forbidden extensions.
- cfg *UploadsConfig
-
- // pre processed data tree for Uploads.
- tree fileTree
-
- // flat list of all file Uploads.
- list []*FileUpload
-}
-
-// MarshalJSON marshal tree tree into JSON.
-func (u *Uploads) MarshalJSON() ([]byte, error) {
- j := json.ConfigCompatibleWithStandardLibrary
- return j.Marshal(u.tree)
-}
-
-// Open moves all uploaded files to temp directory, return error in case of issue with temp directory. File errors
-// will be handled individually.
-func (u *Uploads) Open(log *logrus.Logger) {
- var wg sync.WaitGroup
- for _, f := range u.list {
- wg.Add(1)
- go func(f *FileUpload) {
- defer wg.Done()
- err := f.Open(u.cfg)
- if err != nil && log != nil {
- log.Error(fmt.Errorf("error opening the file: error %v", err))
- }
- }(f)
- }
-
- wg.Wait()
-}
-
-// Clear deletes all temporary files.
-func (u *Uploads) Clear(log *logrus.Logger) {
- for _, f := range u.list {
- if f.TempFilename != "" && exists(f.TempFilename) {
- err := os.Remove(f.TempFilename)
- if err != nil && log != nil {
- log.Error(fmt.Errorf("error removing the file: error %v", err))
- }
- }
- }
-}
-
-// FileUpload represents singular file NewUpload.
-type FileUpload struct {
- // ID contains filename specified by the client.
- Name string `json:"name"`
-
- // Mime contains mime-type provided by the client.
- Mime string `json:"mime"`
-
- // Size of the uploaded file.
- Size int64 `json:"size"`
-
- // Error indicates file upload error (if any). See http://php.net/manual/en/features.file-upload.errors.php
- Error int `json:"error"`
-
- // TempFilename points to temporary file location.
- TempFilename string `json:"tmpName"`
-
- // associated file header
- header *multipart.FileHeader
-}
-
-// NewUpload wraps net/http upload into PRS-7 compatible structure.
-func NewUpload(f *multipart.FileHeader) *FileUpload {
- return &FileUpload{
- Name: f.Filename,
- Mime: f.Header.Get("Content-Type"),
- Error: UploadErrorOK,
- header: f,
- }
-}
-
-// Open moves file content into temporary file available for PHP.
-// NOTE:
-// There is 2 deferred functions, and in case of getting 2 errors from both functions
-// error from close of temp file would be overwritten by error from the main file
-// STACK
-// DEFER FILE CLOSE (2)
-// DEFER TMP CLOSE (1)
-func (f *FileUpload) Open(cfg *UploadsConfig) (err error) {
- if cfg.Forbids(f.Name) {
- f.Error = UploadErrorExtension
- return nil
- }
-
- file, err := f.header.Open()
- if err != nil {
- f.Error = UploadErrorNoFile
- return err
- }
-
- defer func() {
- // close the main file
- err = file.Close()
- }()
-
- tmp, err := ioutil.TempFile(cfg.TmpDir(), "upload")
- if err != nil {
- // most likely cause of this issue is missing tmp dir
- f.Error = UploadErrorNoTmpDir
- return err
- }
-
- f.TempFilename = tmp.Name()
- defer func() {
- // close the temp file
- err = tmp.Close()
- }()
-
- if f.Size, err = io.Copy(tmp, file); err != nil {
- f.Error = UploadErrorCantWrite
- }
-
- return err
-}
-
-// exists if file exists.
-func exists(path string) bool {
- if _, err := os.Stat(path); os.IsNotExist(err) {
- return false
- }
- return true
-}
diff --git a/service/http/uploads_config.go b/service/http/uploads_config.go
deleted file mode 100644
index 3f655064..00000000
--- a/service/http/uploads_config.go
+++ /dev/null
@@ -1,45 +0,0 @@
-package http
-
-import (
- "os"
- "path"
- "strings"
-)
-
-// UploadsConfig describes file location and controls access to them.
-type UploadsConfig struct {
- // 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
-}
-
-// InitDefaults sets missing values to their default values.
-func (cfg *UploadsConfig) InitDefaults() error {
- cfg.Forbid = []string{".php", ".exe", ".bat"}
- return nil
-}
-
-// TmpDir returns temporary directory.
-func (cfg *UploadsConfig) TmpDir() string {
- if cfg.Dir != "" {
- return cfg.Dir
- }
-
- return os.TempDir()
-}
-
-// 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))
-
- for _, v := range cfg.Forbid {
- if ext == v {
- return true
- }
- }
-
- return false
-}
diff --git a/service/http/uploads_config_test.go b/service/http/uploads_config_test.go
deleted file mode 100644
index 2b6ceebc..00000000
--- a/service/http/uploads_config_test.go
+++ /dev/null
@@ -1,24 +0,0 @@
-package http
-
-import (
- "github.com/stretchr/testify/assert"
- "os"
- "testing"
-)
-
-func TestFsConfig_Forbids(t *testing.T) {
- cfg := UploadsConfig{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 TestFsConfig_TmpFallback(t *testing.T) {
- cfg := UploadsConfig{Dir: "test"}
- assert.Equal(t, "test", cfg.TmpDir())
-
- cfg = UploadsConfig{Dir: ""}
- assert.Equal(t, os.TempDir(), cfg.TmpDir())
-}
diff --git a/service/http/uploads_test.go b/service/http/uploads_test.go
deleted file mode 100644
index 08177c72..00000000
--- a/service/http/uploads_test.go
+++ /dev/null
@@ -1,434 +0,0 @@
-package http
-
-import (
- "bytes"
- "context"
- "crypto/md5"
- "encoding/hex"
- "fmt"
- json "github.com/json-iterator/go"
- "github.com/spiral/roadrunner"
- "github.com/stretchr/testify/assert"
- "io"
- "io/ioutil"
- "mime/multipart"
- "net/http"
- "os"
- "testing"
- "time"
-)
-
-func TestHandler_Upload_File(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php upload pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8021", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- var mb bytes.Buffer
- w := multipart.NewWriter(&mb)
-
- f := mustOpen("uploads_test.go")
- defer func() {
- err := f.Close()
- if err != nil {
- t.Errorf("failed to close a file: error %v", err)
- }
- }()
- fw, err := w.CreateFormFile("upload", f.Name())
- assert.NotNil(t, fw)
- assert.NoError(t, err)
- _, err = io.Copy(fw, f)
- if err != nil {
- t.Errorf("error copying the file: error %v", err)
- }
-
- err = w.Close()
- if err != nil {
- t.Errorf("error closing the file: error %v", err)
- }
-
- req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, &mb)
- assert.NoError(t, err)
-
- req.Header.Set("Content-Type", w.FormDataContentType())
-
- r, err := http.DefaultClient.Do(req)
- assert.NoError(t, err)
- defer func() {
- err := r.Body.Close()
- if err != nil {
- t.Errorf("error closing the Body: error %v", err)
- }
- }()
-
- b, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
-
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
-
- fs := fileString("uploads_test.go", 0, "application/octet-stream")
-
- assert.Equal(t, `{"upload":`+fs+`}`, string(b))
-}
-
-func TestHandler_Upload_NestedFile(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php upload pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8021", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- var mb bytes.Buffer
- w := multipart.NewWriter(&mb)
-
- f := mustOpen("uploads_test.go")
- defer func() {
- err := f.Close()
- if err != nil {
- t.Errorf("failed to close a file: error %v", err)
- }
- }()
- fw, err := w.CreateFormFile("upload[x][y][z][]", f.Name())
- assert.NotNil(t, fw)
- assert.NoError(t, err)
- _, err = io.Copy(fw, f)
- if err != nil {
- t.Errorf("error copying the file: error %v", err)
- }
-
- err = w.Close()
- if err != nil {
- t.Errorf("error closing the file: error %v", err)
- }
-
- req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, &mb)
- assert.NoError(t, err)
-
- req.Header.Set("Content-Type", w.FormDataContentType())
-
- r, err := http.DefaultClient.Do(req)
- assert.NoError(t, err)
- defer func() {
- err := r.Body.Close()
- if err != nil {
- t.Errorf("error closing the Body: error %v", err)
- }
- }()
-
- b, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
-
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
-
- fs := fileString("uploads_test.go", 0, "application/octet-stream")
-
- assert.Equal(t, `{"upload":{"x":{"y":{"z":[`+fs+`]}}}}`, string(b))
-}
-
-func TestHandler_Upload_File_NoTmpDir(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: "-----",
- Forbid: []string{},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php upload pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8021", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- var mb bytes.Buffer
- w := multipart.NewWriter(&mb)
-
- f := mustOpen("uploads_test.go")
- defer func() {
- err := f.Close()
- if err != nil {
- t.Errorf("failed to close a file: error %v", err)
- }
- }()
- fw, err := w.CreateFormFile("upload", f.Name())
- assert.NotNil(t, fw)
- assert.NoError(t, err)
- _, err = io.Copy(fw, f)
- if err != nil {
- t.Errorf("error copying the file: error %v", err)
- }
-
- err = w.Close()
- if err != nil {
- t.Errorf("error closing the file: error %v", err)
- }
-
- req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, &mb)
- assert.NoError(t, err)
-
- req.Header.Set("Content-Type", w.FormDataContentType())
-
- r, err := http.DefaultClient.Do(req)
- assert.NoError(t, err)
- defer func() {
- err := r.Body.Close()
- if err != nil {
- t.Errorf("error closing the Body: error %v", err)
- }
- }()
-
- b, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
-
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
-
- fs := fileString("uploads_test.go", 5, "application/octet-stream")
-
- assert.Equal(t, `{"upload":`+fs+`}`, string(b))
-}
-
-func TestHandler_Upload_File_Forbids(t *testing.T) {
- h := &Handler{
- cfg: &Config{
- MaxRequestSize: 1024,
- Uploads: &UploadsConfig{
- Dir: os.TempDir(),
- Forbid: []string{".go"},
- },
- },
- rr: roadrunner.NewServer(&roadrunner.ServerConfig{
- Command: "php ../../tests/http/client.php upload pipes",
- Relay: "pipes",
- Pool: &roadrunner.Config{
- NumWorkers: 1,
- AllocateTimeout: 10000000,
- DestroyTimeout: 10000000,
- },
- }),
- }
-
- assert.NoError(t, h.rr.Start())
- defer h.rr.Stop()
-
- hs := &http.Server{Addr: ":8021", Handler: h}
- defer func() {
- err := hs.Shutdown(context.Background())
- if err != nil {
- t.Errorf("error during the shutdown: error %v", err)
- }
- }()
-
- go func() {
- err := hs.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- t.Errorf("error listening the interface: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 10)
-
- var mb bytes.Buffer
- w := multipart.NewWriter(&mb)
-
- f := mustOpen("uploads_test.go")
- defer func() {
- err := f.Close()
- if err != nil {
- t.Errorf("failed to close a file: error %v", err)
- }
- }()
- fw, err := w.CreateFormFile("upload", f.Name())
- assert.NotNil(t, fw)
- assert.NoError(t, err)
- _, err = io.Copy(fw, f)
- if err != nil {
- t.Errorf("error copying the file: error %v", err)
- }
-
- err = w.Close()
- if err != nil {
- t.Errorf("error closing the file: error %v", err)
- }
-
- req, err := http.NewRequest("POST", "http://localhost"+hs.Addr, &mb)
- assert.NoError(t, err)
-
- req.Header.Set("Content-Type", w.FormDataContentType())
-
- r, err := http.DefaultClient.Do(req)
- assert.NoError(t, err)
- defer func() {
- err := r.Body.Close()
- if err != nil {
- t.Errorf("error closing the Body: error %v", err)
- }
- }()
-
- b, err := ioutil.ReadAll(r.Body)
- assert.NoError(t, err)
-
- assert.NoError(t, err)
- assert.Equal(t, 200, r.StatusCode)
-
- fs := fileString("uploads_test.go", 7, "application/octet-stream")
-
- assert.Equal(t, `{"upload":`+fs+`}`, string(b))
-}
-
-func Test_FileExists(t *testing.T) {
- assert.True(t, exists("uploads_test.go"))
- assert.False(t, exists("uploads_test."))
-}
-
-func mustOpen(f string) *os.File {
- r, err := os.Open(f)
- if err != nil {
- panic(err)
- }
- return r
-}
-
-type fInfo struct {
- Name string `json:"name"`
- Size int64 `json:"size"`
- Mime string `json:"mime"`
- Error int `json:"error"`
- MD5 string `json:"md5,omitempty"`
-}
-
-func fileString(f string, errNo int, mime string) string {
- s, err := os.Stat(f)
- if err != nil {
- fmt.Println(fmt.Errorf("error stat the file, error: %v", err))
- }
-
- ff, err := os.Open(f)
- if err != nil {
- fmt.Println(fmt.Errorf("error opening the file, error: %v", err))
- }
-
- defer func() {
- er := ff.Close()
- if er != nil {
- fmt.Println(fmt.Errorf("error closing the file, error: %v", er))
- }
- }()
-
- h := md5.New()
- _, err = io.Copy(h, ff)
- if err != nil {
- fmt.Println(fmt.Errorf("error copying the file, error: %v", err))
- }
-
- v := &fInfo{
- Name: s.Name(),
- Size: s.Size(),
- Error: errNo,
- Mime: mime,
- MD5: hex.EncodeToString(h.Sum(nil)),
- }
-
- if errNo != 0 {
- v.MD5 = ""
- v.Size = 0
- }
-
- j := json.ConfigCompatibleWithStandardLibrary
- r, err := j.Marshal(v)
- if err != nil {
- fmt.Println(fmt.Errorf("error marshalling fInfo, error: %v", err))
- }
- return string(r)
-
-}
diff --git a/service/limit/config.go b/service/limit/config.go
deleted file mode 100644
index 203db11b..00000000
--- a/service/limit/config.go
+++ /dev/null
@@ -1,48 +0,0 @@
-package limit
-
-import (
- "github.com/spiral/roadrunner"
- "github.com/spiral/roadrunner/service"
- "time"
-)
-
-// Config of Limit service.
-type Config struct {
- // Interval defines the update duration for underlying controllers, default 1s.
- Interval time.Duration
-
- // Services declares list of services to be watched.
- Services map[string]*controllerConfig
-}
-
-// Hydrate must populate Config values using given Config source. Must return error if Config is not valid.
-func (c *Config) Hydrate(cfg service.Config) error {
- if err := cfg.Unmarshal(c); err != nil {
- return err
- }
-
- // Always use second based definition for time durations
- if c.Interval < time.Microsecond {
- c.Interval = time.Second * time.Duration(c.Interval.Nanoseconds())
- }
-
- return nil
-}
-
-// InitDefaults sets missing values to their default values.
-func (c *Config) InitDefaults() error {
- c.Interval = time.Second
-
- return nil
-}
-
-// Controllers returns list of defined Services
-func (c *Config) Controllers(l listener) (controllers map[string]roadrunner.Controller) {
- controllers = make(map[string]roadrunner.Controller)
-
- for name, cfg := range c.Services {
- controllers[name] = &controller{lsn: l, tick: c.Interval, cfg: cfg}
- }
-
- return controllers
-}
diff --git a/service/limit/config_test.go b/service/limit/config_test.go
deleted file mode 100644
index c79836b8..00000000
--- a/service/limit/config_test.go
+++ /dev/null
@@ -1,51 +0,0 @@
-package limit
-
-import (
- json "github.com/json-iterator/go"
- "github.com/spiral/roadrunner/service"
- "github.com/stretchr/testify/assert"
- "testing"
- "time"
-)
-
-type mockCfg struct{ cfg string }
-
-func (cfg *mockCfg) Get(name string) service.Config { return nil }
-func (cfg *mockCfg) Unmarshal(out interface{}) error {
- j := json.ConfigCompatibleWithStandardLibrary
- return j.Unmarshal([]byte(cfg.cfg), out)
-}
-
-func Test_Config_Hydrate_Error1(t *testing.T) {
- cfg := &mockCfg{`{"enable: true}`}
- c := &Config{}
-
- assert.Error(t, c.Hydrate(cfg))
-}
-
-func Test_Controller_Default(t *testing.T) {
- cfg := &mockCfg{`
-{
- "services":{
- "http": {
- "TTL": 1
- }
- }
-}
-`}
- c := &Config{}
- err := c.InitDefaults()
- if err != nil {
- t.Errorf("failed to InitDefaults: error %v", err)
- }
-
- assert.NoError(t, c.Hydrate(cfg))
- assert.Equal(t, time.Second, c.Interval)
-
- list := c.Controllers(func(event int, ctx interface{}) {
- })
-
- sc := list["http"]
-
- assert.Equal(t, time.Second, sc.(*controller).tick)
-}
diff --git a/service/limit/controller.go b/service/limit/controller.go
deleted file mode 100644
index 24a158f7..00000000
--- a/service/limit/controller.go
+++ /dev/null
@@ -1,166 +0,0 @@
-package limit
-
-import (
- "fmt"
- "github.com/spiral/roadrunner"
- "github.com/spiral/roadrunner/util"
- "time"
-)
-
-const (
- // EventMaxMemory caused when worker consumes more memory than allowed.
- EventMaxMemory = iota + 8000
-
- // EventTTL thrown when worker is removed due TTL being reached. Context is rr.WorkerError
- EventTTL
-
- // EventIdleTTL triggered when worker spends too much time at rest.
- EventIdleTTL
-
- // EventExecTTL triggered when worker spends too much time doing the task (max_execution_time).
- EventExecTTL
-)
-
-// handles controller events
-type listener func(event int, ctx interface{})
-
-// defines the controller behaviour
-type controllerConfig struct {
- // MaxMemory defines maximum amount of memory allowed for worker. In megabytes.
- MaxMemory uint64
-
- // TTL defines maximum time worker is allowed to live.
- TTL int64
-
- // IdleTTL defines maximum duration worker can spend in idle mode.
- IdleTTL int64
-
- // ExecTTL defines maximum lifetime per job.
- ExecTTL int64
-}
-
-type controller struct {
- lsn listener
- tick time.Duration
- cfg *controllerConfig
-
- // list of workers which are currently working
- sw *stateFilter
-
- stop chan interface{}
-}
-
-// control the pool state
-func (c *controller) control(p roadrunner.Pool) {
- c.loadWorkers(p)
-
- now := time.Now()
-
- if c.cfg.ExecTTL != 0 {
- for _, w := range c.sw.find(
- roadrunner.StateWorking,
- now.Add(-time.Second*time.Duration(c.cfg.ExecTTL)),
- ) {
- eID := w.State().NumExecs()
- err := fmt.Errorf("max exec time reached (%vs)", c.cfg.ExecTTL)
-
- // make sure worker still on initial request
- if p.Remove(w, err) && w.State().NumExecs() == eID {
- go func() {
- err := w.Kill()
- if err != nil {
- fmt.Printf("error killing worker with PID number: %d, created: %s", w.Pid, w.Created)
- }
- }()
- c.report(EventExecTTL, w, err)
- }
- }
- }
-
- // locale workers which are in idle mode for too long
- if c.cfg.IdleTTL != 0 {
- for _, w := range c.sw.find(
- roadrunner.StateReady,
- now.Add(-time.Second*time.Duration(c.cfg.IdleTTL)),
- ) {
- err := fmt.Errorf("max idle time reached (%vs)", c.cfg.IdleTTL)
- if p.Remove(w, err) {
- c.report(EventIdleTTL, w, err)
- }
- }
- }
-}
-
-func (c *controller) loadWorkers(p roadrunner.Pool) {
- now := time.Now()
-
- for _, w := range p.Workers() {
- if w.State().Value() == roadrunner.StateInvalid {
- // skip duplicate assessment
- continue
- }
-
- s, err := util.WorkerState(w)
- if err != nil {
- continue
- }
-
- if c.cfg.TTL != 0 && now.Sub(w.Created).Seconds() >= float64(c.cfg.TTL) {
- err := fmt.Errorf("max TTL reached (%vs)", c.cfg.TTL)
- if p.Remove(w, err) {
- c.report(EventTTL, w, err)
- }
- continue
- }
-
- if c.cfg.MaxMemory != 0 && s.MemoryUsage >= c.cfg.MaxMemory*1024*1024 {
- err := fmt.Errorf("max allowed memory reached (%vMB)", c.cfg.MaxMemory)
- if p.Remove(w, err) {
- c.report(EventMaxMemory, w, err)
- }
- continue
- }
-
- // control the worker state changes
- c.sw.push(w)
- }
-
- c.sw.sync(now)
-}
-
-// throw controller event
-func (c *controller) report(event int, worker *roadrunner.Worker, caused error) {
- if c.lsn != nil {
- c.lsn(event, roadrunner.WorkerError{Worker: worker, Caused: caused})
- }
-}
-
-// Attach controller to the pool
-func (c *controller) Attach(pool roadrunner.Pool) roadrunner.Controller {
- wp := &controller{
- tick: c.tick,
- lsn: c.lsn,
- cfg: c.cfg,
- sw: newStateFilter(),
- stop: make(chan interface{}),
- }
-
- go func(wp *controller, pool roadrunner.Pool) {
- ticker := time.NewTicker(wp.tick)
- for {
- select {
- case <-ticker.C:
- wp.control(pool)
- case <-wp.stop:
- return
- }
- }
- }(wp, pool)
-
- return wp
-}
-
-// Detach controller from the pool.
-func (c *controller) Detach() {
- close(c.stop)
-}
diff --git a/service/limit/service.go b/service/limit/service.go
deleted file mode 100644
index c0b4139c..00000000
--- a/service/limit/service.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package limit
-
-import (
- "github.com/spiral/roadrunner"
- "github.com/spiral/roadrunner/service"
-)
-
-// ID defines controller service name.
-const ID = "limit"
-
-// Service to control the state of rr service inside other services.
-type Service struct {
- lsns []func(event int, ctx interface{})
-}
-
-// Init controller service
-func (s *Service) Init(cfg *Config, c service.Container) (bool, error) {
- // mount Services to designated services
- for id, watcher := range cfg.Controllers(s.throw) {
- svc, _ := c.Get(id)
- if ctrl, ok := svc.(roadrunner.Attacher); ok {
- ctrl.Attach(watcher)
- }
- }
-
- return true, nil
-}
-
-// AddListener attaches server event controller.
-func (s *Service) AddListener(l func(event int, ctx interface{})) {
- s.lsns = append(s.lsns, l)
-}
-
-// throw handles service, server and pool events.
-func (s *Service) throw(event int, ctx interface{}) {
- for _, l := range s.lsns {
- l(event, ctx)
- }
-}
diff --git a/service/limit/service_test.go b/service/limit/service_test.go
deleted file mode 100644
index b358c1c1..00000000
--- a/service/limit/service_test.go
+++ /dev/null
@@ -1,500 +0,0 @@
-package limit
-
-import (
- "fmt"
- "github.com/cenkalti/backoff/v4"
- json "github.com/json-iterator/go"
- "github.com/sirupsen/logrus"
- "github.com/sirupsen/logrus/hooks/test"
- "github.com/spiral/roadrunner/service"
- rrhttp "github.com/spiral/roadrunner/service/http"
- "github.com/stretchr/testify/assert"
- "io/ioutil"
- "net/http"
- "testing"
- "time"
-)
-
-type testCfg struct {
- httpCfg string
- limitCfg string
- target string
-}
-
-func (cfg *testCfg) Get(name string) service.Config {
- if name == rrhttp.ID {
- if cfg.httpCfg == "" {
- return nil
- }
-
- return &testCfg{target: cfg.httpCfg}
- }
-
- if name == ID {
- return &testCfg{target: cfg.limitCfg}
- }
-
- return nil
-}
-
-func (cfg *testCfg) Unmarshal(out interface{}) error {
- j := json.ConfigCompatibleWithStandardLibrary
- err := j.Unmarshal([]byte(cfg.target), out)
-
- if cl, ok := out.(*Config); ok {
- // to speed up tests
- cl.Interval = time.Millisecond
- }
-
- return err
-}
-
-func Test_Service_PidEcho(t *testing.T) {
- bkoff := backoff.NewExponentialBackOff()
- bkoff.MaxElapsedTime = time.Second * 15
-
- err := backoff.Retry(func() error {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, &rrhttp.Service{})
- c.Register(ID, &Service{})
-
- err := c.Init(&testCfg{
- httpCfg: `{
- "address": ":17029",
- "workers":{
- "command": "php ../../tests/http/client.php pid pipes",
- "pool": {"numWorkers": 1}
- }
- }`,
- limitCfg: `{
- "services": {
- "http": {
- "ttl": 1
- }
- }
- }`,
- })
- if err != nil {
- return err
- }
-
- s, _ := c.Get(rrhttp.ID)
- assert.NotNil(t, s)
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("error during the Serve: error %v", err)
- }
- }()
-
- time.Sleep(time.Millisecond * 100)
- req, err := http.NewRequest("GET", "http://localhost:17029", nil)
- if err != nil {
- return err
- }
-
- r, err := http.DefaultClient.Do(req)
- if err != nil {
- return err
- }
-
-
- b, err := ioutil.ReadAll(r.Body)
- if err != nil {
- return err
- }
-
- assert.Equal(t, getPID(s), string(b))
-
- err2 := r.Body.Close()
- if err2 != nil {
- t.Errorf("error during the body closing: error %v", err2)
- }
- c.Stop()
- return nil
-
- }, bkoff)
-
- if err != nil {
- t.Fatal(err)
- }
-
-}
-
-func Test_Service_ListenerPlusTTL(t *testing.T) {
- bkoff := backoff.NewExponentialBackOff()
- bkoff.MaxElapsedTime = time.Second * 15
-
- err := backoff.Retry(func() error {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, &rrhttp.Service{})
- c.Register(ID, &Service{})
-
- err := c.Init(&testCfg{
- httpCfg: `{
- "address": ":7030",
- "workers":{
- "command": "php ../../tests/http/client.php pid pipes",
- "pool": {"numWorkers": 1}
- }
- }`,
- limitCfg: `{
- "services": {
- "http": {
- "ttl": 1
- }
- }
- }`,
- })
- if err != nil {
- return err
- }
-
- s, _ := c.Get(rrhttp.ID)
- assert.NotNil(t, s)
-
- l, _ := c.Get(ID)
- captured := make(chan interface{})
- l.(*Service).AddListener(func(event int, ctx interface{}) {
- if event == EventTTL {
- close(captured)
- }
- })
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("error during the Serve: error %v", err)
- }
- }()
-
-
- time.Sleep(time.Millisecond * 100)
-
- lastPID := getPID(s)
-
- req, err := http.NewRequest("GET", "http://localhost:7030", nil)
- if err != nil {
- return err
- }
-
- r, err := http.DefaultClient.Do(req)
- if err != nil {
- return err
- }
-
-
- b, err := ioutil.ReadAll(r.Body)
- if err != nil {
- return err
- }
- assert.Equal(t, lastPID, string(b))
-
- <-captured
-
- // clean state
- req, err = http.NewRequest("GET", "http://localhost:7030?new", nil)
- if err != nil {
- return err
- }
-
- _, err = http.DefaultClient.Do(req)
- if err != nil {
- return err
- }
-
- assert.NotEqual(t, lastPID, getPID(s))
-
- c.Stop()
-
- err2 := r.Body.Close()
- if err2 != nil {
- t.Errorf("error during the body closing: error %v", err2)
- }
-
- return nil
- }, bkoff)
-
- if err != nil {
- t.Fatal(err)
- }
-
-}
-
-func Test_Service_ListenerPlusIdleTTL(t *testing.T) {
- bkoff := backoff.NewExponentialBackOff()
- bkoff.MaxElapsedTime = time.Second * 15
-
- err := backoff.Retry(func() error {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, &rrhttp.Service{})
- c.Register(ID, &Service{})
-
- err := c.Init(&testCfg{
- httpCfg: `{
- "address": ":7031",
- "workers":{
- "command": "php ../../tests/http/client.php pid pipes",
- "pool": {"numWorkers": 1}
- }
- }`,
- limitCfg: `{
- "services": {
- "http": {
- "idleTtl": 1
- }
- }
- }`,
- })
- if err != nil {
- return err
- }
-
- s, _ := c.Get(rrhttp.ID)
- assert.NotNil(t, s)
-
- l, _ := c.Get(ID)
- captured := make(chan interface{})
- l.(*Service).AddListener(func(event int, ctx interface{}) {
- if event == EventIdleTTL {
- close(captured)
- }
- })
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("error during the Serve: error %v", err)
- }
- }()
-
-
- time.Sleep(time.Millisecond * 100)
-
- lastPID := getPID(s)
-
- req, err := http.NewRequest("GET", "http://localhost:7031", nil)
- if err != nil {
- return err
- }
-
- r, err := http.DefaultClient.Do(req)
- if err != nil {
- return err
- }
-
- b, err := ioutil.ReadAll(r.Body)
- if err != nil {
- return err
- }
- assert.Equal(t, lastPID, string(b))
-
- <-captured
-
- // clean state
- req, err = http.NewRequest("GET", "http://localhost:7031?new", nil)
- if err != nil {
- return err
- }
-
- _, err = http.DefaultClient.Do(req)
- if err != nil {
- return err
- }
-
- assert.NotEqual(t, lastPID, getPID(s))
-
- c.Stop()
- err2 := r.Body.Close()
- if err2 != nil {
- t.Errorf("error during the body closing: error %v", err2)
- }
- return nil
- }, bkoff)
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func Test_Service_Listener_MaxExecTTL(t *testing.T) {
- bkoff := backoff.NewExponentialBackOff()
- bkoff.MaxElapsedTime = time.Second * 15
-
- err := backoff.Retry(func() error {
-
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, &rrhttp.Service{})
- c.Register(ID, &Service{})
-
- err := c.Init(&testCfg{
- httpCfg: `{
- "address": ":7032",
- "workers":{
- "command": "php ../../tests/http/client.php stuck pipes",
- "pool": {"numWorkers": 1}
- }
- }`,
- limitCfg: `{
- "services": {
- "http": {
- "execTTL": 1
- }
- }
- }`,
- })
- if err != nil {
- return err
- }
-
- s, _ := c.Get(rrhttp.ID)
- assert.NotNil(t, s)
-
- l, _ := c.Get(ID)
- captured := make(chan interface{})
- l.(*Service).AddListener(func(event int, ctx interface{}) {
- if event == EventExecTTL {
- close(captured)
- }
- })
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("error during the Serve: error %v", err)
- }
- }()
-
- time.Sleep(time.Millisecond * 100)
-
- req, err := http.NewRequest("GET", "http://localhost:7032", nil)
- if err != nil {
- return err
- }
-
- r, err := http.DefaultClient.Do(req)
- if err != nil {
- return err
- }
- assert.Equal(t, 500, r.StatusCode)
-
- <-captured
-
- c.Stop()
- return nil
- }, bkoff)
-
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func Test_Service_Listener_MaxMemoryUsage(t *testing.T) {
- bkoff := backoff.NewExponentialBackOff()
- bkoff.MaxElapsedTime = time.Second * 15
-
- err := backoff.Retry(func() error {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, &rrhttp.Service{})
- c.Register(ID, &Service{})
-
- err := c.Init(&testCfg{
- httpCfg: `{
- "address": ":7033",
- "workers":{
- "command": "php ../../tests/http/client.php memleak pipes",
- "pool": {"numWorkers": 1}
- }
- }`,
- limitCfg: `{
- "services": {
- "http": {
- "maxMemory": 10
- }
- }
- }`,
- })
- if err != nil {
- return err
- }
-
- s, _ := c.Get(rrhttp.ID)
- assert.NotNil(t, s)
-
- l, _ := c.Get(ID)
- captured := make(chan interface{})
- once := false
- l.(*Service).AddListener(func(event int, ctx interface{}) {
- if event == EventMaxMemory && !once {
- close(captured)
- once = true
- }
- })
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("error during the Serve: error %v", err)
- }
- }()
-
- time.Sleep(time.Millisecond * 100)
-
- lastPID := getPID(s)
-
- req, err := http.NewRequest("GET", "http://localhost:7033", nil)
- if err != nil {
- return err
- }
-
- for {
- select {
- case <-captured:
- _, err := http.DefaultClient.Do(req)
- if err != nil {
- c.Stop()
- t.Errorf("error during sending the http request: error %v", err)
- }
- assert.NotEqual(t, lastPID, getPID(s))
- c.Stop()
- return nil
- default:
- _, err := http.DefaultClient.Do(req)
- if err != nil {
- c.Stop()
- t.Errorf("error during sending the http request: error %v", err)
- }
- c.Stop()
- return nil
- }
- }
- }, bkoff)
-
- if err != nil {
- t.Fatal(err)
- }
-
-}
-func getPID(s interface{}) string {
- if len(s.(*rrhttp.Service).Server().Workers()) > 0 {
- w := s.(*rrhttp.Service).Server().Workers()[0]
- return fmt.Sprintf("%v", *w.Pid)
- } else {
- panic("no workers")
- }
-}
diff --git a/service/limit/state_filter.go b/service/limit/state_filter.go
deleted file mode 100644
index cd2eca94..00000000
--- a/service/limit/state_filter.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package limit
-
-import (
- "github.com/spiral/roadrunner"
- "time"
-)
-
-type stateFilter struct {
- prev map[*roadrunner.Worker]state
- next map[*roadrunner.Worker]state
-}
-
-type state struct {
- state int64
- numExecs int64
- since time.Time
-}
-
-func newStateFilter() *stateFilter {
- return &stateFilter{
- prev: make(map[*roadrunner.Worker]state),
- next: make(map[*roadrunner.Worker]state),
- }
-}
-
-// add new worker to be watched
-func (sw *stateFilter) push(w *roadrunner.Worker) {
- sw.next[w] = state{state: w.State().Value(), numExecs: w.State().NumExecs()}
-}
-
-// update worker states.
-func (sw *stateFilter) sync(t time.Time) {
- for w := range sw.prev {
- if _, ok := sw.next[w]; !ok {
- delete(sw.prev, w)
- }
- }
-
- for w, s := range sw.next {
- ps, ok := sw.prev[w]
- if !ok || ps.state != s.state || ps.numExecs != s.numExecs {
- sw.prev[w] = state{state: s.state, numExecs: s.numExecs, since: t}
- }
-
- delete(sw.next, w)
- }
-}
-
-// find all workers which spend given amount of time in a specific state.
-func (sw *stateFilter) find(state int64, since time.Time) (workers []*roadrunner.Worker) {
- for w, s := range sw.prev {
- if s.state == state && s.since.Before(since) {
- workers = append(workers, w)
- }
- }
-
- return
-}
diff --git a/service/metrics/config.go b/service/metrics/config.go
deleted file mode 100644
index c95fd940..00000000
--- a/service/metrics/config.go
+++ /dev/null
@@ -1,136 +0,0 @@
-package metrics
-
-import (
- "fmt"
- "github.com/prometheus/client_golang/prometheus"
- "github.com/spiral/roadrunner/service"
-)
-
-// Config configures metrics service.
-type Config struct {
- // Address to listen
- Address string
-
- // Collect define application specific metrics.
- Collect map[string]Collector
-}
-
-type NamedCollector struct {
- // Name of the collector
- Name string `json:"name"`
-
- // Collector structure
- Collector `json:"collector"`
-}
-
-// CollectorType represents prometheus collector types
-type CollectorType string
-
-const (
- // Histogram type
- Histogram CollectorType = "histogram"
-
- // Gauge type
- Gauge CollectorType = "gauge"
-
- // Counter type
- Counter CollectorType = "counter"
-
- // Summary type
- Summary CollectorType = "summary"
-)
-
-// Collector describes single application specific metric.
-type Collector struct {
- // Namespace of the metric.
- Namespace string `json:"namespace"`
- // Subsystem of the metric.
- Subsystem string `json:"subsystem"`
- // Collector type (histogram, gauge, counter, summary).
- Type CollectorType `json:"type"`
- // Help of collector.
- Help string `json:"help"`
- // Labels for vectorized metrics.
- Labels []string `json:"labels"`
- // Buckets for histogram metric.
- Buckets []float64 `json:"buckets"`
-}
-
-// Hydrate configuration.
-func (c *Config) Hydrate(cfg service.Config) error {
- return cfg.Unmarshal(c)
-}
-
-// register application specific metrics.
-func (c *Config) getCollectors() (map[string]prometheus.Collector, error) {
- if c.Collect == nil {
- return nil, nil
- }
-
- collectors := make(map[string]prometheus.Collector)
-
- for name, m := range c.Collect {
- var collector prometheus.Collector
- switch m.Type {
- case Histogram:
- opts := prometheus.HistogramOpts{
- Name: name,
- Namespace: m.Namespace,
- Subsystem: m.Subsystem,
- Help: m.Help,
- Buckets: m.Buckets,
- }
-
- if len(m.Labels) != 0 {
- collector = prometheus.NewHistogramVec(opts, m.Labels)
- } else {
- collector = prometheus.NewHistogram(opts)
- }
- case Gauge:
- opts := prometheus.GaugeOpts{
- Name: name,
- Namespace: m.Namespace,
- Subsystem: m.Subsystem,
- Help: m.Help,
- }
-
- if len(m.Labels) != 0 {
- collector = prometheus.NewGaugeVec(opts, m.Labels)
- } else {
- collector = prometheus.NewGauge(opts)
- }
- case Counter:
- opts := prometheus.CounterOpts{
- Name: name,
- Namespace: m.Namespace,
- Subsystem: m.Subsystem,
- Help: m.Help,
- }
-
- if len(m.Labels) != 0 {
- collector = prometheus.NewCounterVec(opts, m.Labels)
- } else {
- collector = prometheus.NewCounter(opts)
- }
- case Summary:
- opts := prometheus.SummaryOpts{
- Name: name,
- Namespace: m.Namespace,
- Subsystem: m.Subsystem,
- Help: m.Help,
- }
-
- if len(m.Labels) != 0 {
- collector = prometheus.NewSummaryVec(opts, m.Labels)
- } else {
- collector = prometheus.NewSummary(opts)
- }
- default:
- return nil, fmt.Errorf("invalid metric type `%s` for `%s`", m.Type, name)
- }
-
- collectors[name] = collector
- }
-
- return collectors, nil
-}
diff --git a/service/metrics/config_test.go b/service/metrics/config_test.go
deleted file mode 100644
index a64e9047..00000000
--- a/service/metrics/config_test.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package metrics
-
-import (
- json "github.com/json-iterator/go"
- "github.com/prometheus/client_golang/prometheus"
- "github.com/spiral/roadrunner/service"
- "github.com/stretchr/testify/assert"
- "testing"
-)
-
-type mockCfg struct{ cfg string }
-
-func (cfg *mockCfg) Get(name string) service.Config { return nil }
-func (cfg *mockCfg) Unmarshal(out interface{}) error {
- j := json.ConfigCompatibleWithStandardLibrary
- return j.Unmarshal([]byte(cfg.cfg), out)
-}
-
-func Test_Config_Hydrate_Error1(t *testing.T) {
- cfg := &mockCfg{`{"request": {"From": "Something"}}`}
- c := &Config{}
-
- assert.NoError(t, c.Hydrate(cfg))
-}
-
-func Test_Config_Hydrate_Error2(t *testing.T) {
- cfg := &mockCfg{`{"dir": "/dir/"`}
- c := &Config{}
-
- assert.Error(t, c.Hydrate(cfg))
-}
-
-func Test_Config_Metrics(t *testing.T) {
- cfg := &mockCfg{`{
-"collect":{
- "metric1":{"type": "gauge"},
- "metric2":{ "type": "counter"},
- "metric3":{"type": "summary"},
- "metric4":{"type": "histogram"}
-}
-}`}
- c := &Config{}
-
- assert.NoError(t, c.Hydrate(cfg))
-
- m, err := c.getCollectors()
- assert.NoError(t, err)
-
- assert.IsType(t, prometheus.NewGauge(prometheus.GaugeOpts{}), m["metric1"])
- assert.IsType(t, prometheus.NewCounter(prometheus.CounterOpts{}), m["metric2"])
- assert.IsType(t, prometheus.NewSummary(prometheus.SummaryOpts{}), m["metric3"])
- assert.IsType(t, prometheus.NewHistogram(prometheus.HistogramOpts{}), m["metric4"])
-}
-
-func Test_Config_MetricsVector(t *testing.T) {
- cfg := &mockCfg{`{
-"collect":{
- "metric1":{"type": "gauge","labels":["label"]},
- "metric2":{ "type": "counter","labels":["label"]},
- "metric3":{"type": "summary","labels":["label"]},
- "metric4":{"type": "histogram","labels":["label"]}
-}
-}`}
- c := &Config{}
-
- assert.NoError(t, c.Hydrate(cfg))
-
- m, err := c.getCollectors()
- assert.NoError(t, err)
-
- assert.IsType(t, prometheus.NewGaugeVec(prometheus.GaugeOpts{}, []string{}), m["metric1"])
- assert.IsType(t, prometheus.NewCounterVec(prometheus.CounterOpts{}, []string{}), m["metric2"])
- assert.IsType(t, prometheus.NewSummaryVec(prometheus.SummaryOpts{}, []string{}), m["metric3"])
- assert.IsType(t, prometheus.NewHistogramVec(prometheus.HistogramOpts{}, []string{}), m["metric4"])
-}
diff --git a/service/metrics/rpc.go b/service/metrics/rpc.go
deleted file mode 100644
index 377d6173..00000000
--- a/service/metrics/rpc.go
+++ /dev/null
@@ -1,260 +0,0 @@
-package metrics
-
-import (
- "fmt"
- "github.com/prometheus/client_golang/prometheus"
-)
-
-type rpcServer struct {
- svc *Service
-}
-
-// Metric represent single metric produced by the application.
-type Metric struct {
- // Collector name.
- Name string
-
- // Collector value.
- Value float64
-
- // Labels associated with metric. Only for vector metrics. Must be provided in a form of label values.
- Labels []string
-}
-
-// Add new metric to the designated collector.
-func (rpc *rpcServer) Add(m *Metric, ok *bool) (err error) {
- defer func() {
- if r, fail := recover().(error); fail {
- err = r
- }
- }()
-
- c := rpc.svc.Collector(m.Name)
- if c == nil {
- return fmt.Errorf("undefined collector `%s`", m.Name)
- }
-
- switch c := c.(type) {
- case prometheus.Gauge:
- c.Add(m.Value)
-
- case *prometheus.GaugeVec:
- if len(m.Labels) == 0 {
- return fmt.Errorf("required labels for collector `%s`", m.Name)
- }
-
- c.WithLabelValues(m.Labels...).Add(m.Value)
-
- case prometheus.Counter:
- c.Add(m.Value)
-
- case *prometheus.CounterVec:
- if len(m.Labels) == 0 {
- return fmt.Errorf("required labels for collector `%s`", m.Name)
- }
-
- c.WithLabelValues(m.Labels...).Add(m.Value)
-
- default:
- return fmt.Errorf("collector `%s` does not support method `Add`", m.Name)
- }
-
- // RPC, set ok to true as return value. Need by rpc.Call reply argument
- *ok = true
- return nil
-}
-
-// Sub subtract the value from the specific metric (gauge only).
-func (rpc *rpcServer) Sub(m *Metric, ok *bool) (err error) {
- defer func() {
- if r, fail := recover().(error); fail {
- err = r
- }
- }()
-
- c := rpc.svc.Collector(m.Name)
- if c == nil {
- return fmt.Errorf("undefined collector `%s`", m.Name)
- }
-
- switch c := c.(type) {
- case prometheus.Gauge:
- c.Sub(m.Value)
-
- case *prometheus.GaugeVec:
- if len(m.Labels) == 0 {
- return fmt.Errorf("required labels for collector `%s`", m.Name)
- }
-
- c.WithLabelValues(m.Labels...).Sub(m.Value)
- default:
- return fmt.Errorf("collector `%s` does not support method `Sub`", m.Name)
- }
-
- // RPC, set ok to true as return value. Need by rpc.Call reply argument
- *ok = true
- return nil
-}
-
-// Observe the value (histogram and summary only).
-func (rpc *rpcServer) Observe(m *Metric, ok *bool) (err error) {
- defer func() {
- if r, fail := recover().(error); fail {
- err = r
- }
- }()
-
- c := rpc.svc.Collector(m.Name)
- if c == nil {
- return fmt.Errorf("undefined collector `%s`", m.Name)
- }
-
- switch c := c.(type) {
- case *prometheus.SummaryVec:
- if len(m.Labels) == 0 {
- return fmt.Errorf("required labels for collector `%s`", m.Name)
- }
-
- c.WithLabelValues(m.Labels...).Observe(m.Value)
-
- case prometheus.Histogram:
- c.Observe(m.Value)
-
- case *prometheus.HistogramVec:
- if len(m.Labels) == 0 {
- return fmt.Errorf("required labels for collector `%s`", m.Name)
- }
-
- c.WithLabelValues(m.Labels...).Observe(m.Value)
- default:
- return fmt.Errorf("collector `%s` does not support method `Observe`", m.Name)
- }
-
- // RPC, set ok to true as return value. Need by rpc.Call reply argument
- *ok = true
- return nil
-}
-// Declare is used to register new collector in prometheus
-// THE TYPES ARE:
-// NamedCollector -> Collector with the name
-// bool -> RPC reply value
-// RETURNS:
-// error
-func (rpc *rpcServer) Declare(c *NamedCollector, ok *bool) (err error) {
- // MustRegister could panic, so, to return error and not shutdown whole app
- // we recover and return error
- defer func() {
- if r, fail := recover().(error); fail {
- err = r
- }
- }()
-
- if rpc.svc.Collector(c.Name) != nil {
- *ok = false
- // alternative is to return error
- // fmt.Errorf("tried to register existing collector with the name `%s`", c.Name)
- return nil
- }
-
- var collector prometheus.Collector
- switch c.Type {
- case Histogram:
- opts := prometheus.HistogramOpts{
- Name: c.Name,
- Namespace: c.Namespace,
- Subsystem: c.Subsystem,
- Help: c.Help,
- Buckets: c.Buckets,
- }
-
- if len(c.Labels) != 0 {
- collector = prometheus.NewHistogramVec(opts, c.Labels)
- } else {
- collector = prometheus.NewHistogram(opts)
- }
- case Gauge:
- opts := prometheus.GaugeOpts{
- Name: c.Name,
- Namespace: c.Namespace,
- Subsystem: c.Subsystem,
- Help: c.Help,
- }
-
- if len(c.Labels) != 0 {
- collector = prometheus.NewGaugeVec(opts, c.Labels)
- } else {
- collector = prometheus.NewGauge(opts)
- }
- case Counter:
- opts := prometheus.CounterOpts{
- Name: c.Name,
- Namespace: c.Namespace,
- Subsystem: c.Subsystem,
- Help: c.Help,
- }
-
- if len(c.Labels) != 0 {
- collector = prometheus.NewCounterVec(opts, c.Labels)
- } else {
- collector = prometheus.NewCounter(opts)
- }
- case Summary:
- opts := prometheus.SummaryOpts{
- Name: c.Name,
- Namespace: c.Namespace,
- Subsystem: c.Subsystem,
- Help: c.Help,
- }
-
- if len(c.Labels) != 0 {
- collector = prometheus.NewSummaryVec(opts, c.Labels)
- } else {
- collector = prometheus.NewSummary(opts)
- }
-
- default:
- return fmt.Errorf("unknown collector type `%s`", c.Type)
-
- }
-
- // add collector to sync.Map
- rpc.svc.collectors.Store(c.Name, collector)
- // that method might panic, we handle it by recover
- rpc.svc.MustRegister(collector)
-
- *ok = true
- return nil
-}
-
-// Set the metric value (only for gaude).
-func (rpc *rpcServer) Set(m *Metric, ok *bool) (err error) {
- defer func() {
- if r, fail := recover().(error); fail {
- err = r
- }
- }()
-
- c := rpc.svc.Collector(m.Name)
- if c == nil {
- return fmt.Errorf("undefined collector `%s`", m.Name)
- }
-
- switch c := c.(type) {
- case prometheus.Gauge:
- c.Set(m.Value)
-
- case *prometheus.GaugeVec:
- if len(m.Labels) == 0 {
- return fmt.Errorf("required labels for collector `%s`", m.Name)
- }
-
- c.WithLabelValues(m.Labels...).Set(m.Value)
-
- default:
- return fmt.Errorf("collector `%s` does not support method `Set`", m.Name)
- }
-
- // RPC, set ok to true as return value. Need by rpc.Call reply argument
- *ok = true
- return nil
-}
diff --git a/service/metrics/rpc_test.go b/service/metrics/rpc_test.go
deleted file mode 100644
index 2fc4bc32..00000000
--- a/service/metrics/rpc_test.go
+++ /dev/null
@@ -1,861 +0,0 @@
-package metrics
-
-import (
- "github.com/sirupsen/logrus"
- "github.com/sirupsen/logrus/hooks/test"
- "github.com/spiral/roadrunner/service"
- "github.com/spiral/roadrunner/service/rpc"
- "github.com/stretchr/testify/assert"
- rpc2 "net/rpc"
- "strconv"
- "testing"
- "time"
-)
-
-var port = 5004
-
-func setup(t *testing.T, metric string, portNum string) (*rpc2.Client, service.Container) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rpc.ID, &rpc.Service{})
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{
- rpcCfg: `{"enable":true, "listen":"tcp://:` + strconv.Itoa(port) + `"}`,
- metricsCfg: `{
- "address": "localhost:` + portNum + `",
- "collect":{
- ` + metric + `
- }
- }`}))
-
- // rotate ports for travis
- port++
-
- s, _ := c.Get(ID)
- assert.NotNil(t, s)
-
- s2, _ := c.Get(rpc.ID)
- rs := s2.(*rpc.Service)
-
- assert.True(t, s.(*Service).Enabled())
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("error during the Serve: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 200)
-
- client, err := rs.Client()
- assert.NoError(t, err)
- if err != nil {
- panic(err)
- }
-
- return client, c
-}
-
-func Test_Set_RPC(t *testing.T) {
- client, c := setup(
- t,
- `"user_gauge":{
- "type": "gauge"
- }`,
- "2112",
- )
- defer c.Stop()
-
- var ok bool
- assert.NoError(t, client.Call("metrics.Set", Metric{
- Name: "user_gauge",
- Value: 100.0,
- }, &ok))
- assert.True(t, ok)
-
- out, _, err := get("http://localhost:2112/metrics")
- assert.NoError(t, err)
- assert.Contains(t, out, `user_gauge 100`)
-}
-
-func Test_Set_RPC_Vector(t *testing.T) {
- client, c := setup(
- t,
- `"user_gauge":{
- "type": "gauge",
- "labels": ["type", "section"]
- }`,
- "2113",
- )
- defer c.Stop()
-
- var ok bool
- assert.NoError(t, client.Call("metrics.Set", Metric{
- Name: "user_gauge",
- Value: 100.0,
- Labels: []string{"core", "first"},
- }, &ok))
- assert.True(t, ok)
-
- out, _, err := get("http://localhost:2113/metrics")
- assert.NoError(t, err)
- assert.Contains(t, out, `user_gauge{section="first",type="core"} 100`)
-}
-
-func Test_Set_RPC_CollectorError(t *testing.T) {
- client, c := setup(
- t,
- `"user_gauge":{
- "type": "gauge",
- "labels": ["type", "section"]
- }`,
- "2114",
- )
- defer c.Stop()
-
- var ok bool
- assert.Error(t, client.Call("metrics.Set", Metric{
- Name: "user_gauge_2",
- Value: 100.0,
- Labels: []string{"missing"},
- }, &ok))
-}
-
-func Test_Set_RPC_MetricError(t *testing.T) {
- client, c := setup(
- t,
- `"user_gauge":{
- "type": "gauge",
- "labels": ["type", "section"]
- }`,
- "2115",
- )
- defer c.Stop()
-
- var ok bool
- assert.Error(t, client.Call("metrics.Set", Metric{
- Name: "user_gauge",
- Value: 100.0,
- Labels: []string{"missing"},
- }, &ok))
-}
-
-func Test_Set_RPC_MetricError_2(t *testing.T) {
- client, c := setup(
- t,
- `"user_gauge":{
- "type": "gauge",
- "labels": ["type", "section"]
- }`,
- "2116",
- )
- defer c.Stop()
-
- var ok bool
- assert.Error(t, client.Call("metrics.Set", Metric{
- Name: "user_gauge",
- Value: 100.0,
- }, &ok))
-}
-
-func Test_Set_RPC_MetricError_3(t *testing.T) {
- client, c := setup(
- t,
- `"user_gauge":{
- "type": "histogram",
- "labels": ["type", "section"]
- }`,
- "2117",
- )
- defer c.Stop()
-
- var ok bool
- assert.Error(t, client.Call("metrics.Set", Metric{
- Name: "user_gauge",
- Value: 100.0,
- }, &ok))
-}
-
-// sub
-
-func Test_Sub_RPC(t *testing.T) {
- client, c := setup(
- t,
- `"user_gauge":{
- "type": "gauge"
- }`,
- "2118",
- )
- defer c.Stop()
-
- var ok bool
- assert.NoError(t, client.Call("metrics.Add", Metric{
- Name: "user_gauge",
- Value: 100.0,
- }, &ok))
- assert.True(t, ok)
-
- assert.NoError(t, client.Call("metrics.Sub", Metric{
- Name: "user_gauge",
- Value: 10.0,
- }, &ok))
- assert.True(t, ok)
-
- out, _, err := get("http://localhost:2118/metrics")
- assert.NoError(t, err)
- assert.Contains(t, out, `user_gauge 90`)
-}
-
-func Test_Sub_RPC_Vector(t *testing.T) {
- client, c := setup(
- t,
- `"user_gauge":{
- "type": "gauge",
- "labels": ["type", "section"]
- }`,
- "2119",
- )
- defer c.Stop()
-
- var ok bool
- assert.NoError(t, client.Call("metrics.Add", Metric{
- Name: "user_gauge",
- Value: 100.0,
- Labels: []string{"core", "first"},
- }, &ok))
- assert.True(t, ok)
-
- assert.NoError(t, client.Call("metrics.Sub", Metric{
- Name: "user_gauge",
- Value: 10.0,
- Labels: []string{"core", "first"},
- }, &ok))
- assert.True(t, ok)
-
- out, _, err := get("http://localhost:2119/metrics")
- assert.NoError(t, err)
- assert.Contains(t, out, `user_gauge{section="first",type="core"} 90`)
-}
-
-func Test_Register_RPC_Histogram(t *testing.T) {
- client, c := setup(
- t,
- `"user_gauge":{
- "type": "gauge",
- "labels": ["type", "section"]
- }`,
- "2319",
- )
- defer c.Stop()
-
- var ok bool
- assert.NoError(t, client.Call("metrics.Declare", &NamedCollector{
- Name: "custom_histogram",
- Collector: Collector{
- Namespace: "test_histogram",
- Subsystem: "test_histogram",
- Type: Histogram,
- Help: "test_histogram",
- Labels: nil,
- Buckets: []float64{0.1, 0.2, 0.5},
- },
- }, &ok))
- assert.True(t, ok)
-
- var ok2 bool
- // histogram does not support Add, should be an error
- assert.Error(t, client.Call("metrics.Add", Metric{
- Name: "custom_histogram",
- }, &ok2))
- // ok should became false
- assert.False(t, ok2)
-
- out, _, err := get("http://localhost:2319/metrics")
- assert.NoError(t, err)
- assert.Contains(t, out, `TYPE test_histogram_test_histogram_custom_histogram histogram`)
-
- // check buckets
- assert.Contains(t, out, `test_histogram_test_histogram_custom_histogram_bucket{le="0.1"} 0`)
- assert.Contains(t, out, `test_histogram_test_histogram_custom_histogram_bucket{le="0.2"} 0`)
- assert.Contains(t, out, `test_histogram_test_histogram_custom_histogram_bucket{le="0.5"} 0`)
-}
-
-func Test_Register_RPC_Gauge(t *testing.T) {
- // FOR register method, setup used just to init the rpc
- client, c := setup(
- t,
- `"user_gauge":{
- "type": "gauge",
- "labels": ["type", "section"]
- }`,
- "2324",
- )
- defer c.Stop()
-
- var ok bool
- assert.NoError(t, client.Call("metrics.Declare", &NamedCollector{
- Name: "custom_gauge",
- Collector: Collector{
- Namespace: "test_gauge",
- Subsystem: "test_gauge",
- Type: Gauge,
- Help: "test_gauge",
- Labels: []string{"type", "section"},
- Buckets: nil,
- },
- }, &ok))
- assert.True(t, ok)
-
- var ok2 bool
- // Add to custom_gauge
- assert.NoError(t, client.Call("metrics.Add", Metric{
- Name: "custom_gauge",
- Value: 100.0,
- Labels: []string{"core", "first"},
- }, &ok2))
- // ok should became true
- assert.True(t, ok2)
-
- // Subtract from custom runtime metric
- var ok3 bool
- assert.NoError(t, client.Call("metrics.Sub", Metric{
- Name: "custom_gauge",
- Value: 10.0,
- Labels: []string{"core", "first"},
- }, &ok3))
- assert.True(t, ok3)
-
- out, _, err := get("http://localhost:2324/metrics")
- assert.NoError(t, err)
- assert.Contains(t, out, `test_gauge_test_gauge_custom_gauge{section="first",type="core"} 90`)
-}
-
-func Test_Register_RPC_Counter(t *testing.T) {
- // FOR register method, setup used just to init the rpc
- client, c := setup(
- t,
- `"user_gauge":{
- "type": "gauge",
- "labels": ["type", "section"]
- }`,
- "2328",
- )
- defer c.Stop()
-
- var ok bool
- assert.NoError(t, client.Call("metrics.Declare", &NamedCollector{
- Name: "custom_counter",
- Collector: Collector{
- Namespace: "test_counter",
- Subsystem: "test_counter",
- Type: Counter,
- Help: "test_counter",
- Labels: []string{"type", "section"},
- Buckets: nil,
- },
- }, &ok))
- assert.True(t, ok)
-
- var ok2 bool
- // Add to custom_counter
- assert.NoError(t, client.Call("metrics.Add", Metric{
- Name: "custom_counter",
- Value: 100.0,
- Labels: []string{"type2", "section2"},
- }, &ok2))
- // ok should became true
- assert.True(t, ok2)
-
- out, _, err := get("http://localhost:2328/metrics")
- assert.NoError(t, err)
- assert.Contains(t, out, `test_counter_test_counter_custom_counter{section="section2",type="type2"} 100`)
-}
-
-func Test_Register_RPC_Summary(t *testing.T) {
- // FOR register method, setup used just to init the rpc
- client, c := setup(
- t,
- `"user_gauge":{
- "type": "gauge",
- "labels": ["type", "section"]
- }`,
- "6666",
- )
- defer c.Stop()
-
- var ok bool
- assert.NoError(t, client.Call("metrics.Declare", &NamedCollector{
- Name: "custom_summary",
- Collector: Collector{
- Namespace: "test_summary",
- Subsystem: "test_summary",
- Type: Summary,
- Help: "test_summary",
- Labels: nil,
- Buckets: nil,
- },
- }, &ok))
- assert.True(t, ok)
-
- var ok2 bool
- // Add to custom_summary is not supported
- assert.Error(t, client.Call("metrics.Add", Metric{
- Name: "custom_summary",
- Value: 100.0,
- Labels: []string{"type22", "section22"},
- }, &ok2))
- // ok should became false
- assert.False(t, ok2)
-
- out, _, err := get("http://localhost:6666/metrics")
- assert.NoError(t, err)
- assert.Contains(t, out, `test_summary_test_summary_custom_summary_sum 0`)
- assert.Contains(t, out, `test_summary_test_summary_custom_summary_count 0`)
-}
-
-func Test_Sub_RPC_CollectorError(t *testing.T) {
- client, c := setup(
- t,
- `"user_gauge":{
- "type": "gauge",
- "labels": ["type", "section"]
- }`,
- "2120",
- )
- defer c.Stop()
-
- var ok bool
- assert.Error(t, client.Call("metrics.Sub", Metric{
- Name: "user_gauge_2",
- Value: 100.0,
- Labels: []string{"missing"},
- }, &ok))
-}
-
-func Test_Sub_RPC_MetricError(t *testing.T) {
- client, c := setup(
- t,
- `"user_gauge":{
- "type": "gauge",
- "labels": ["type", "section"]
- }`,
- "2121",
- )
- defer c.Stop()
-
- var ok bool
- assert.Error(t, client.Call("metrics.Sub", Metric{
- Name: "user_gauge",
- Value: 100.0,
- Labels: []string{"missing"},
- }, &ok))
-}
-
-func Test_Sub_RPC_MetricError_2(t *testing.T) {
- client, c := setup(
- t,
- `"user_gauge":{
- "type": "gauge",
- "labels": ["type", "section"]
- }`,
- "2122",
- )
- defer c.Stop()
-
- var ok bool
- assert.Error(t, client.Call("metrics.Sub", Metric{
- Name: "user_gauge",
- Value: 100.0,
- }, &ok))
-}
-
-func Test_Sub_RPC_MetricError_3(t *testing.T) {
- client, c := setup(
- t,
- `"user_gauge":{
- "type": "histogram",
- "labels": ["type", "section"]
- }`,
- "2123",
- )
- defer c.Stop()
-
- var ok bool
- assert.Error(t, client.Call("metrics.Sub", Metric{
- Name: "user_gauge",
- Value: 100.0,
- }, &ok))
-}
-
-// -- observe
-
-func Test_Observe_RPC(t *testing.T) {
- client, c := setup(
- t,
- `"user_histogram":{
- "type": "histogram"
- }`,
- "2124",
- )
- defer c.Stop()
-
- var ok bool
- assert.NoError(t, client.Call("metrics.Observe", Metric{
- Name: "user_histogram",
- Value: 100.0,
- }, &ok))
- assert.True(t, ok)
-
- out, _, err := get("http://localhost:2124/metrics")
- assert.NoError(t, err)
- assert.Contains(t, out, `user_histogram`)
-}
-
-func Test_Observe_RPC_Vector(t *testing.T) {
- client, c := setup(
- t,
- `"user_histogram":{
- "type": "histogram",
- "labels": ["type", "section"]
- }`,
- "2125",
- )
- defer c.Stop()
-
- var ok bool
- assert.NoError(t, client.Call("metrics.Observe", Metric{
- Name: "user_histogram",
- Value: 100.0,
- Labels: []string{"core", "first"},
- }, &ok))
- assert.True(t, ok)
-
- out, _, err := get("http://localhost:2125/metrics")
- assert.NoError(t, err)
- assert.Contains(t, out, `user_histogram`)
-}
-
-func Test_Observe_RPC_CollectorError(t *testing.T) {
- client, c := setup(
- t,
- `"user_histogram":{
- "type": "histogram",
- "labels": ["type", "section"]
- }`,
- "2126",
- )
- defer c.Stop()
-
- var ok bool
- assert.Error(t, client.Call("metrics.Observe", Metric{
- Name: "user_histogram",
- Value: 100.0,
- Labels: []string{"missing"},
- }, &ok))
-}
-
-func Test_Observe_RPC_MetricError(t *testing.T) {
- client, c := setup(
- t,
- `"user_histogram":{
- "type": "histogram",
- "labels": ["type", "section"]
- }`,
- "2127",
- )
- defer c.Stop()
-
- var ok bool
- assert.Error(t, client.Call("metrics.Observe", Metric{
- Name: "user_histogram",
- Value: 100.0,
- Labels: []string{"missing"},
- }, &ok))
-}
-
-func Test_Observe_RPC_MetricError_2(t *testing.T) {
- client, c := setup(
- t,
- `"user_histogram":{
- "type": "histogram",
- "labels": ["type", "section"]
- }`,
- "2128",
- )
- defer c.Stop()
-
- var ok bool
- assert.Error(t, client.Call("metrics.Observe", Metric{
- Name: "user_histogram",
- Value: 100.0,
- }, &ok))
-}
-
-// -- observe summary
-
-func Test_Observe2_RPC(t *testing.T) {
- client, c := setup(
- t,
- `"user_histogram":{
- "type": "summary"
- }`,
- "2129",
- )
- defer c.Stop()
-
- var ok bool
- assert.NoError(t, client.Call("metrics.Observe", Metric{
- Name: "user_histogram",
- Value: 100.0,
- }, &ok))
- assert.True(t, ok)
-
- out, _, err := get("http://localhost:2129/metrics")
- assert.NoError(t, err)
- assert.Contains(t, out, `user_histogram`)
-}
-
-func Test_Observe2_RPC_Invalid(t *testing.T) {
- client, c := setup(
- t,
- `"user_histogram":{
- "type": "summary"
- }`,
- "2130",
- )
- defer c.Stop()
-
- var ok bool
- assert.Error(t, client.Call("metrics.Observe", Metric{
- Name: "user_histogram_2",
- Value: 100.0,
- Labels: []string{"missing"},
- }, &ok))
-}
-
-func Test_Observe2_RPC_Invalid_2(t *testing.T) {
- client, c := setup(
- t,
- `"user_histogram":{
- "type": "gauge"
- }`,
- "2131",
- )
- defer c.Stop()
-
- var ok bool
- assert.Error(t, client.Call("metrics.Observe", Metric{
- Name: "user_histogram",
- Value: 100.0,
- }, &ok))
-}
-
-func Test_Observe2_RPC_Vector(t *testing.T) {
- client, c := setup(
- t,
- `"user_histogram":{
- "type": "summary",
- "labels": ["type", "section"]
- }`,
- "2132",
- )
- defer c.Stop()
-
- var ok bool
- assert.NoError(t, client.Call("metrics.Observe", Metric{
- Name: "user_histogram",
- Value: 100.0,
- Labels: []string{"core", "first"},
- }, &ok))
- assert.True(t, ok)
-
- out, _, err := get("http://localhost:2132/metrics")
- assert.NoError(t, err)
- assert.Contains(t, out, `user_histogram`)
-}
-
-func Test_Observe2_RPC_CollectorError(t *testing.T) {
- client, c := setup(
- t,
- `"user_histogram":{
- "type": "summary",
- "labels": ["type", "section"]
- }`,
- "2133",
- )
- defer c.Stop()
-
- var ok bool
- assert.Error(t, client.Call("metrics.Observe", Metric{
- Name: "user_histogram",
- Value: 100.0,
- Labels: []string{"missing"},
- }, &ok))
-}
-
-func Test_Observe2_RPC_MetricError(t *testing.T) {
- client, c := setup(
- t,
- `"user_histogram":{
- "type": "summary",
- "labels": ["type", "section"]
- }`,
- "2134",
- )
- defer c.Stop()
-
- var ok bool
- assert.Error(t, client.Call("metrics.Observe", Metric{
- Name: "user_histogram",
- Value: 100.0,
- Labels: []string{"missing"},
- }, &ok))
-}
-
-func Test_Observe2_RPC_MetricError_2(t *testing.T) {
- client, c := setup(
- t,
- `"user_histogram":{
- "type": "summary",
- "labels": ["type", "section"]
- }`,
- "2135",
- )
- defer c.Stop()
-
- var ok bool
- assert.Error(t, client.Call("metrics.Observe", Metric{
- Name: "user_histogram",
- Value: 100.0,
- }, &ok))
-}
-
-// add
-func Test_Add_RPC(t *testing.T) {
- client, c := setup(
- t,
- `"user_gauge":{
- "type": "counter"
- }`,
- "2136",
- )
- defer c.Stop()
-
- var ok bool
- assert.NoError(t, client.Call("metrics.Add", Metric{
- Name: "user_gauge",
- Value: 100.0,
- }, &ok))
- assert.True(t, ok)
-
- out, _, err := get("http://localhost:2136/metrics")
- assert.NoError(t, err)
- assert.Contains(t, out, `user_gauge 100`)
-}
-
-func Test_Add_RPC_Vector(t *testing.T) {
- client, c := setup(
- t,
- `"user_gauge":{
- "type": "counter",
- "labels": ["type", "section"]
- }`,
- "2137",
- )
- defer c.Stop()
-
- var ok bool
- assert.NoError(t, client.Call("metrics.Add", Metric{
- Name: "user_gauge",
- Value: 100.0,
- Labels: []string{"core", "first"},
- }, &ok))
- assert.True(t, ok)
-
- out, _, err := get("http://localhost:2137/metrics")
- assert.NoError(t, err)
- assert.Contains(t, out, `user_gauge{section="first",type="core"} 100`)
-}
-
-func Test_Add_RPC_CollectorError(t *testing.T) {
- client, c := setup(
- t,
- `"user_gauge":{
- "type": "counter",
- "labels": ["type", "section"]
- }`,
- "2138",
- )
- defer c.Stop()
-
- var ok bool
- assert.Error(t, client.Call("metrics.Add", Metric{
- Name: "user_gauge_2",
- Value: 100.0,
- Labels: []string{"missing"},
- }, &ok))
-
- assert.False(t, ok)
-}
-
-func Test_Add_RPC_MetricError(t *testing.T) {
- client, c := setup(
- t,
- `"user_gauge":{
- "type": "counter",
- "labels": ["type", "section"]
- }`,
- "2139",
- )
- defer c.Stop()
-
- var ok bool
- assert.Error(t, client.Call("metrics.Add", Metric{
- Name: "user_gauge",
- Value: 100.0,
- Labels: []string{"missing"},
- }, &ok))
-
- assert.False(t, ok)
-}
-
-func Test_Add_RPC_MetricError_2(t *testing.T) {
- client, c := setup(
- t,
- `"user_gauge":{
- "type": "counter",
- "labels": ["type", "section"]
- }`,
- "2140",
- )
- defer c.Stop()
-
- var ok bool
- assert.Error(t, client.Call("metrics.Add", Metric{
- Name: "user_gauge",
- Value: 100.0,
- }, &ok))
-
- assert.False(t, ok)
-}
-
-func Test_Add_RPC_MetricError_3(t *testing.T) {
- client, c := setup(
- t,
- `"user_gauge":{
- "type": "histogram",
- "labels": ["type", "section"]
- }`,
- "2141",
- )
- defer c.Stop()
-
- var ok bool
- assert.Error(t, client.Call("metrics.Add", Metric{
- Name: "user_gauge",
- Value: 100.0,
- }, &ok))
-}
diff --git a/service/metrics/service.go b/service/metrics/service.go
deleted file mode 100644
index 4656ae04..00000000
--- a/service/metrics/service.go
+++ /dev/null
@@ -1,191 +0,0 @@
-package metrics
-
-// todo: declare metric at runtime
-
-import (
- "context"
- "crypto/tls"
- "fmt"
- "net/http"
- "sync"
- "time"
-
- "github.com/prometheus/client_golang/prometheus"
- "github.com/prometheus/client_golang/prometheus/promhttp"
- "github.com/sirupsen/logrus"
- "github.com/spiral/roadrunner/service/rpc"
- "golang.org/x/sys/cpu"
-)
-
-const (
- // ID declares public service name.
- ID = "metrics"
- // maxHeaderSize declares max header size for prometheus server
- maxHeaderSize = 1024 * 1024 * 100 // 104MB
-)
-
-// Service to manage application metrics using Prometheus.
-type Service struct {
- cfg *Config
- log *logrus.Logger
- mu sync.Mutex
- http *http.Server
- collectors sync.Map
- registry *prometheus.Registry
-}
-
-// Init service.
-func (s *Service) Init(cfg *Config, r *rpc.Service, log *logrus.Logger) (bool, error) {
- s.cfg = cfg
- s.log = log
- s.registry = prometheus.NewRegistry()
-
- s.registry.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}))
- s.registry.MustRegister(prometheus.NewGoCollector())
-
- if r != nil {
- if err := r.Register(ID, &rpcServer{s}); err != nil {
- return false, err
- }
- }
-
- return true, nil
-}
-
-// Enabled indicates that server is able to collect metrics.
-func (s *Service) Enabled() bool {
- return s.cfg != nil
-}
-
-// Register new prometheus collector.
-func (s *Service) Register(c prometheus.Collector) error {
- return s.registry.Register(c)
-}
-
-// MustRegister registers new collector or fails with panic.
-func (s *Service) MustRegister(c prometheus.Collector) {
- s.registry.MustRegister(c)
-}
-
-// Serve prometheus metrics service.
-func (s *Service) Serve() error {
- // register application specific metrics
- collectors, err := s.cfg.getCollectors()
- if err != nil {
- return err
- }
-
- for name, collector := range collectors {
- if err := s.registry.Register(collector); err != nil {
- return err
- }
-
- s.collectors.Store(name, collector)
- }
-
- s.mu.Lock()
-
- var topCipherSuites []uint16
- var defaultCipherSuitesTLS13 []uint16
-
- hasGCMAsmAMD64 := cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
- hasGCMAsmARM64 := cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
- // Keep in sync with crypto/aes/cipher_s390x.go.
- hasGCMAsmS390X := cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && (cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)
-
- hasGCMAsm := hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X
-
- if hasGCMAsm {
- // If AES-GCM hardware is provided then prioritise AES-GCM
- // cipher suites.
- topCipherSuites = []uint16{
- tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
- tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
- tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
- tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
- tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
- }
- defaultCipherSuitesTLS13 = []uint16{
- tls.TLS_AES_128_GCM_SHA256,
- tls.TLS_CHACHA20_POLY1305_SHA256,
- tls.TLS_AES_256_GCM_SHA384,
- }
- } else {
- // Without AES-GCM hardware, we put the ChaCha20-Poly1305
- // cipher suites first.
- topCipherSuites = []uint16{
- tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
- tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
- tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
- tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
- tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
- tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
- }
- defaultCipherSuitesTLS13 = []uint16{
- tls.TLS_CHACHA20_POLY1305_SHA256,
- tls.TLS_AES_128_GCM_SHA256,
- tls.TLS_AES_256_GCM_SHA384,
- }
- }
-
- DefaultCipherSuites := make([]uint16, 0, 22)
- DefaultCipherSuites = append(DefaultCipherSuites, topCipherSuites...)
- DefaultCipherSuites = append(DefaultCipherSuites, defaultCipherSuitesTLS13...)
-
- s.http = &http.Server{
- Addr: s.cfg.Address,
- Handler: promhttp.HandlerFor(s.registry, promhttp.HandlerOpts{}),
- IdleTimeout: time.Hour * 24,
- ReadTimeout: time.Minute * 60,
- MaxHeaderBytes: maxHeaderSize,
- ReadHeaderTimeout: time.Minute * 60,
- WriteTimeout: time.Minute * 60,
- TLSConfig: &tls.Config{
- CurvePreferences: []tls.CurveID{
- tls.CurveP256,
- tls.CurveP384,
- tls.CurveP521,
- tls.X25519,
- },
- CipherSuites: DefaultCipherSuites,
- MinVersion: tls.VersionTLS12,
- PreferServerCipherSuites: true,
- },
- }
- s.mu.Unlock()
-
- err = s.http.ListenAndServe()
- if err != nil && err != http.ErrServerClosed {
- return err
- }
-
- return nil
-}
-
-// Stop prometheus metrics service.
-func (s *Service) Stop() {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- if s.http != nil {
- // gracefully stop server
- go func() {
- err := s.http.Shutdown(context.Background())
- if err != nil {
- // Function should be Stop() error
- s.log.Error(fmt.Errorf("error shutting down the metrics server: error %v", err))
- }
- }()
- }
-}
-
-// Collector returns application specific collector by name or nil if collector not found.
-func (s *Service) Collector(name string) prometheus.Collector {
- collector, ok := s.collectors.Load(name)
- if !ok {
- return nil
- }
-
- return collector.(prometheus.Collector)
-}
diff --git a/service/metrics/service_test.go b/service/metrics/service_test.go
deleted file mode 100644
index cdb81147..00000000
--- a/service/metrics/service_test.go
+++ /dev/null
@@ -1,247 +0,0 @@
-package metrics
-
-import (
- json "github.com/json-iterator/go"
- "github.com/prometheus/client_golang/prometheus"
- "github.com/sirupsen/logrus"
- "github.com/sirupsen/logrus/hooks/test"
- "github.com/spiral/roadrunner/service"
- "github.com/spiral/roadrunner/service/rpc"
- "github.com/stretchr/testify/assert"
- "io/ioutil"
- "net/http"
- "testing"
- "time"
-)
-
-type testCfg struct {
- rpcCfg string
- metricsCfg string
- target string
-}
-
-func (cfg *testCfg) Get(name string) service.Config {
- if name == ID {
- return &testCfg{target: cfg.metricsCfg}
- }
-
- if name == rpc.ID {
- return &testCfg{target: cfg.rpcCfg}
- }
-
- return nil
-}
-
-func (cfg *testCfg) Unmarshal(out interface{}) error {
- j := json.ConfigCompatibleWithStandardLibrary
- err := j.Unmarshal([]byte(cfg.target), out)
- return err
-}
-
-// get request and return body
-func get(url string) (string, *http.Response, error) {
- r, err := http.Get(url)
- if err != nil {
- return "", nil, err
- }
-
- b, err := ioutil.ReadAll(r.Body)
- if err != nil {
- return "", nil, err
- }
-
- err = r.Body.Close()
- if err != nil {
- return "", nil, err
- }
- return string(b), r, err
-}
-
-func TestService_Serve(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{metricsCfg: `{
- "address": "localhost:2116"
- }`}))
-
- s, _ := c.Get(ID)
- assert.NotNil(t, s)
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("error during the Serve: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 100)
- defer c.Stop()
-
- out, _, err := get("http://localhost:2116/metrics")
- assert.NoError(t, err)
-
- assert.Contains(t, out, "go_gc_duration_seconds")
-}
-
-func Test_ServiceCustomMetric(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{metricsCfg: `{
- "address": "localhost:2115"
- }`}))
-
- s, _ := c.Get(ID)
- assert.NotNil(t, s)
-
- collector := prometheus.NewGauge(prometheus.GaugeOpts{
- Name: "my_gauge",
- Help: "My gauge value",
- })
-
- assert.NoError(t, s.(*Service).Register(collector))
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("error during the Serve: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 100)
- defer c.Stop()
-
- collector.Set(100)
-
- out, _, err := get("http://localhost:2115/metrics")
- assert.NoError(t, err)
-
- assert.Contains(t, out, "my_gauge 100")
-}
-
-func Test_ServiceCustomMetricMust(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{metricsCfg: `{
- "address": "localhost:2114"
- }`}))
-
- s, _ := c.Get(ID)
- assert.NotNil(t, s)
-
- collector := prometheus.NewGauge(prometheus.GaugeOpts{
- Name: "my_gauge_2",
- Help: "My gauge value",
- })
-
- s.(*Service).MustRegister(collector)
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("error during the Serve: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 100)
- defer c.Stop()
-
- collector.Set(100)
-
- out, _, err := get("http://localhost:2114/metrics")
- assert.NoError(t, err)
-
- assert.Contains(t, out, "my_gauge_2 100")
-}
-
-func Test_ConfiguredMetric(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{metricsCfg: `{
- "address": "localhost:2113",
- "collect":{
- "user_gauge":{
- "type": "gauge"
- }
- }
- }`}))
-
- s, _ := c.Get(ID)
- assert.NotNil(t, s)
-
- assert.True(t, s.(*Service).Enabled())
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("error during the Serve: error %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 100)
- defer c.Stop()
-
- s.(*Service).Collector("user_gauge").(prometheus.Gauge).Set(100)
-
- assert.Nil(t, s.(*Service).Collector("invalid"))
-
- out, _, err := get("http://localhost:2113/metrics")
- assert.NoError(t, err)
-
- assert.Contains(t, out, "user_gauge 100")
-}
-
-func Test_ConfiguredDuplicateMetric(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{metricsCfg: `{
- "address": "localhost:2112",
- "collect":{
- "go_gc_duration_seconds":{
- "type": "gauge"
- }
- }
- }`}))
-
- s, _ := c.Get(ID)
- assert.NotNil(t, s)
-
- assert.True(t, s.(*Service).Enabled())
-
- assert.Error(t, c.Serve())
-}
-
-func Test_ConfiguredInvalidMetric(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{metricsCfg: `{
- "address": "localhost:2112",
- "collect":{
- "user_gauge":{
- "type": "invalid"
- }
- }
-
- }`}))
-
- assert.Error(t, c.Serve())
-}
diff --git a/service/reload/config.go b/service/reload/config.go
deleted file mode 100644
index efc71972..00000000
--- a/service/reload/config.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package reload
-
-import (
- "errors"
- "github.com/spiral/roadrunner"
- "github.com/spiral/roadrunner/service"
- "time"
-)
-
-// Config is a Reload configuration point.
-type Config struct {
- // Interval is a global refresh interval
- Interval time.Duration
-
- // Patterns is a global file patterns to watch. It will be applied to every directory in project
- Patterns []string
-
- // Services is set of services which would be reloaded in case of FS changes
- Services map[string]ServiceConfig
-}
-
-type ServiceConfig struct {
- // Enabled indicates that service must be watched, doest not required when any other option specified
- Enabled bool
-
- // Recursive is options to use nested files from root folder
- Recursive bool
-
- // Patterns is per-service specific files to watch
- Patterns []string
-
- // Dirs is per-service specific dirs which will be combined with Patterns
- Dirs []string
-
- // Ignore is set of files which would not be watched
- Ignore []string
-
- // service is a link to service to restart
- service *roadrunner.Controllable
-}
-
-// Hydrate must populate Config values using given Config source. Must return error if Config is not valid.
-func (c *Config) Hydrate(cfg service.Config) error {
- if err := cfg.Unmarshal(c); err != nil {
- return err
- }
-
- return nil
-}
-
-// InitDefaults sets missing values to their default values.
-func (c *Config) InitDefaults() error {
- c.Interval = time.Second
- c.Patterns = []string{".php"}
-
- return nil
-}
-
-// Valid validates the configuration.
-func (c *Config) Valid() error {
- if c.Interval < time.Second {
- return errors.New("too short interval")
- }
-
- if c.Services == nil {
- return errors.New("should add at least 1 service")
- } else if len(c.Services) == 0 {
- return errors.New("service initialized, however, no config added")
- }
-
- return nil
-}
diff --git a/service/reload/config_test.go b/service/reload/config_test.go
deleted file mode 100644
index 600975d3..00000000
--- a/service/reload/config_test.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package reload
-
-import (
- "github.com/stretchr/testify/assert"
- "testing"
- "time"
-)
-
-func Test_Config_Valid(t *testing.T) {
- services := make(map[string]ServiceConfig)
- services["test"] = ServiceConfig{
- Recursive: false,
- Patterns: nil,
- Dirs: nil,
- Ignore: nil,
- service: nil,
- }
-
- cfg := &Config{
- Interval: time.Second,
- Patterns: nil,
- Services: services,
- }
- assert.NoError(t, cfg.Valid())
-}
-
-func Test_Fake_ServiceConfig(t *testing.T) {
- services := make(map[string]ServiceConfig)
- cfg := &Config{
- Interval: time.Microsecond,
- Patterns: nil,
- Services: services,
- }
- assert.Error(t, cfg.Valid())
-}
-
-func Test_Interval(t *testing.T) {
- services := make(map[string]ServiceConfig)
- services["test"] = ServiceConfig{
- Enabled: false,
- Recursive: false,
- Patterns: nil,
- Dirs: nil,
- Ignore: nil,
- service: nil,
- }
-
- cfg := &Config{
- Interval: time.Millisecond, // should crash here
- Patterns: nil,
- Services: services,
- }
- assert.Error(t, cfg.Valid())
-}
-
-func Test_NoServiceConfig(t *testing.T) {
- cfg := &Config{
- Interval: time.Second,
- Patterns: nil,
- Services: nil,
- }
- assert.Error(t, cfg.Valid())
-}
diff --git a/service/reload/samefile.go b/service/reload/samefile.go
deleted file mode 100644
index 80df0431..00000000
--- a/service/reload/samefile.go
+++ /dev/null
@@ -1,9 +0,0 @@
-// +build !windows
-
-package reload
-
-import "os"
-
-func sameFile(fi1, fi2 os.FileInfo) bool {
- return os.SameFile(fi1, fi2)
-}
diff --git a/service/reload/samefile_windows.go b/service/reload/samefile_windows.go
deleted file mode 100644
index 5f70d327..00000000
--- a/service/reload/samefile_windows.go
+++ /dev/null
@@ -1,12 +0,0 @@
-// +build windows
-
-package reload
-
-import "os"
-
-func sameFile(fi1, fi2 os.FileInfo) bool {
- return fi1.ModTime() == fi2.ModTime() &&
- fi1.Size() == fi2.Size() &&
- fi1.Mode() == fi2.Mode() &&
- fi1.IsDir() == fi2.IsDir()
-}
diff --git a/service/reload/service.go b/service/reload/service.go
deleted file mode 100644
index 9c615e0b..00000000
--- a/service/reload/service.go
+++ /dev/null
@@ -1,162 +0,0 @@
-package reload
-
-import (
- "errors"
- "github.com/sirupsen/logrus"
- "github.com/spiral/roadrunner"
- "github.com/spiral/roadrunner/service"
- "os"
- "strings"
- "time"
-)
-
-// ID contains default service name.
-const ID = "reload"
-
-type Service struct {
- cfg *Config
- log *logrus.Logger
- watcher *Watcher
- stopc chan struct{}
-}
-
-// Init controller service
-func (s *Service) Init(cfg *Config, log *logrus.Logger, c service.Container) (bool, error) {
- if cfg == nil || len(cfg.Services) == 0 {
- return false, nil
- }
-
- s.cfg = cfg
- s.log = log
- s.stopc = make(chan struct{})
-
- var configs []WatcherConfig
-
- // mount Services to designated services
- for serviceName := range cfg.Services {
- svc, _ := c.Get(serviceName)
- if ctrl, ok := svc.(roadrunner.Controllable); ok {
- tmp := cfg.Services[serviceName]
- tmp.service = &ctrl
- cfg.Services[serviceName] = tmp
- }
- }
-
- for serviceName, config := range s.cfg.Services {
- if cfg.Services[serviceName].service == nil {
- continue
- }
- ignored, err := ConvertIgnored(config.Ignore)
- if err != nil {
- return false, err
- }
- configs = append(configs, WatcherConfig{
- serviceName: serviceName,
- recursive: config.Recursive,
- directories: config.Dirs,
- filterHooks: func(filename string, patterns []string) error {
- for i := 0; i < len(patterns); i++ {
- if strings.Contains(filename, patterns[i]) {
- return nil
- }
- }
- return ErrorSkip
- },
- files: make(map[string]os.FileInfo),
- ignored: ignored,
- filePatterns: append(config.Patterns, cfg.Patterns...),
- })
- }
-
- var err error
- s.watcher, err = NewWatcher(configs)
- if err != nil {
- return false, err
- }
-
- return true, nil
-}
-
-func (s *Service) Serve() error {
- if s.cfg.Interval < time.Second {
- return errors.New("reload interval is too fast")
- }
-
- // make a map with unique services
- // so, if we would have a 100 events from http service
- // in map we would see only 1 key and it's config
- treshholdc := make(chan struct {
- serviceConfig ServiceConfig
- service string
- }, 100)
-
- // use the same interval
- ticker := time.NewTicker(s.cfg.Interval)
-
- // drain channel in case of leaved messages
- defer func() {
- go func() {
- for range treshholdc {
-
- }
- }()
- }()
-
- go func() {
- for e := range s.watcher.Event {
- treshholdc <- struct {
- serviceConfig ServiceConfig
- service string
- }{serviceConfig: s.cfg.Services[e.service], service: e.service}
- }
- }()
-
- // map with configs by services
- updated := make(map[string]ServiceConfig, 100)
-
- go func() {
- for {
- select {
- case config := <-treshholdc:
- // replace previous value in map by more recent without adding new one
- updated[config.service] = config.serviceConfig
- // stop ticker
- ticker.Stop()
- // restart
- // logic is following:
- // if we getting a lot of events, we should't restart particular service on each of it (user doing bug move or very fast typing)
- // instead, we are resetting the ticker and wait for Interval time
- // If there is no more events, we restart service only once
- ticker = time.NewTicker(s.cfg.Interval)
- case <-ticker.C:
- if len(updated) > 0 {
- for k, v := range updated {
- sv := *v.service
- err := sv.Server().Reset()
- if err != nil {
- s.log.Error(err)
- }
- s.log.Debugf("[%s] found %v file(s) changes, reloading", k, len(updated))
- }
- // zero map
- updated = make(map[string]ServiceConfig, 100)
- }
- case <-s.stopc:
- ticker.Stop()
- return
- }
- }
- }()
-
- err := s.watcher.StartPolling(s.cfg.Interval)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *Service) Stop() {
- s.watcher.Stop()
- s.stopc <- struct{}{}
-}
diff --git a/service/reload/service_test.go b/service/reload/service_test.go
deleted file mode 100644
index 7cad4a5d..00000000
--- a/service/reload/service_test.go
+++ /dev/null
@@ -1 +0,0 @@
-package reload
diff --git a/service/reload/watcher.go b/service/reload/watcher.go
deleted file mode 100644
index 027d2d0d..00000000
--- a/service/reload/watcher.go
+++ /dev/null
@@ -1,409 +0,0 @@
-package reload
-
-import (
- "errors"
- "io/ioutil"
- "os"
- "path/filepath"
- "sync"
- "time"
-)
-
-var ErrorSkip = errors.New("file is skipped")
-var NoWalkerConfig = errors.New("should add at least one walker config, when reload is set to true")
-
-// SimpleHook is used to filter by simple criteria, CONTAINS
-type SimpleHook func(filename string, pattern []string) error
-
-// An Event describes an event that is received when files or directory
-// changes occur. It includes the os.FileInfo of the changed file or
-// directory and the type of event that's occurred and the full path of the file.
-type Event struct {
- path string
- info os.FileInfo
-
- service string // type of service, http, grpc, etc...
-}
-
-type WatcherConfig struct {
- // service name
- serviceName string
-
- // recursive or just add by singe directory
- recursive bool
-
- // directories used per-service
- directories []string
-
- // simple hook, just CONTAINS
- filterHooks func(filename string, pattern []string) error
-
- // path to file with files
- files map[string]os.FileInfo
-
- // ignored directories, used map for O(1) amortized get
- ignored map[string]struct{}
-
- // filePatterns to ignore
- filePatterns []string
-}
-
-type Watcher struct {
- // main event channel
- Event chan Event
- close chan struct{}
-
- //=============================
- mu *sync.Mutex
-
- // indicates is walker started or not
- started bool
-
- // config for each service
- // need pointer here to assign files
- watcherConfigs map[string]WatcherConfig
-}
-
-// Options is used to set Watcher Options
-type Options func(*Watcher)
-
-// NewWatcher returns new instance of File Watcher
-func NewWatcher(configs []WatcherConfig, options ...Options) (*Watcher, error) {
- w := &Watcher{
- Event: make(chan Event),
- mu: &sync.Mutex{},
-
- close: make(chan struct{}),
-
- //workingDir: workDir,
- watcherConfigs: make(map[string]WatcherConfig),
- }
-
- // add watcherConfigs by service names
- for _, v := range configs {
- w.watcherConfigs[v.serviceName] = v
- }
-
- // apply options
- for _, option := range options {
- option(w)
- }
- err := w.initFs()
- if err != nil {
- return nil, err
- }
-
- return w, nil
-}
-
-// initFs makes initial map with files
-func (w *Watcher) initFs() error {
- for srvName, config := range w.watcherConfigs {
- fileList, err := w.retrieveFileList(srvName, config)
- if err != nil {
- return err
- }
- // workaround. in golang you can't assign to map in struct field
- tmp := w.watcherConfigs[srvName]
- tmp.files = fileList
- w.watcherConfigs[srvName] = tmp
- }
- return nil
-}
-
-// ConvertIgnored is used to convert slice to map with ignored files
-func ConvertIgnored(ignored []string) (map[string]struct{}, error) {
- if len(ignored) == 0 {
- return nil, nil
- }
-
- ign := make(map[string]struct{}, len(ignored))
- for i := 0; i < len(ignored); i++ {
- abs, err := filepath.Abs(ignored[i])
- if err != nil {
- return nil, err
- }
- ign[abs] = struct{}{}
- }
-
- return ign, nil
-
-}
-
-// GetAllFiles returns all files initialized for particular company
-func (w *Watcher) GetAllFiles(serviceName string) []os.FileInfo {
- var ret []os.FileInfo
-
- for _, v := range w.watcherConfigs[serviceName].files {
- ret = append(ret, v)
- }
-
- return ret
-}
-
-// https://en.wikipedia.org/wiki/Inotify
-// SetMaxFileEvents sets max file notify events for Watcher
-// In case of file watch errors, this value can be increased system-wide
-// For linux: set --> fs.inotify.max_user_watches = 600000 (under /etc/<choose_name_here>.conf)
-// Add apply: sudo sysctl -p --system
-//func SetMaxFileEvents(events int) Options {
-// return func(watcher *Watcher) {
-// watcher.maxFileWatchEvents = events
-// }
-//
-//}
-
-// pass map from outside
-func (w *Watcher) retrieveFilesSingle(serviceName, path string) (map[string]os.FileInfo, error) {
- stat, err := os.Stat(path)
- if err != nil {
- return nil, err
- }
-
- filesList := make(map[string]os.FileInfo, 10)
- filesList[path] = stat
-
- // if it's not a dir, return
- if !stat.IsDir() {
- return filesList, nil
- }
-
- fileInfoList, err := ioutil.ReadDir(path)
- if err != nil {
- return nil, err
- }
-
- // recursive calls are slow in compare to goto
- // so, we will add files with goto pattern
-outer:
- for i := 0; i < len(fileInfoList); i++ {
- var pathToFile string
- // BCE check elimination
- // https://go101.org/article/bounds-check-elimination.html
- if len(fileInfoList) != 0 && len(fileInfoList) >= i {
- pathToFile = filepath.Join(pathToFile, fileInfoList[i].Name())
- } else {
- return nil, errors.New("file info list len")
- }
-
- // if file in ignored --> continue
- if _, ignored := w.watcherConfigs[serviceName].ignored[path]; ignored {
- continue
- }
-
- // if filename does not contain pattern --> ignore that file
- if w.watcherConfigs[serviceName].filePatterns != nil && w.watcherConfigs[serviceName].filterHooks != nil {
- err = w.watcherConfigs[serviceName].filterHooks(fileInfoList[i].Name(), w.watcherConfigs[serviceName].filePatterns)
- if err == ErrorSkip {
- continue outer
- }
- }
-
- filesList[pathToFile] = fileInfoList[i]
- }
-
- return filesList, nil
-}
-
-func (w *Watcher) StartPolling(duration time.Duration) error {
- w.mu.Lock()
- if w.started {
- w.mu.Unlock()
- return errors.New("already started")
- }
-
- w.started = true
- w.mu.Unlock()
-
- return w.waitEvent(duration)
-}
-
-// this is blocking operation
-func (w *Watcher) waitEvent(d time.Duration) error {
- ticker := time.NewTicker(d)
- for {
- select {
- case <-w.close:
- ticker.Stop()
- // just exit
- // no matter for the pollEvents
- return nil
- case <-ticker.C:
- // this is not very effective way
- // because we have to wait on Lock
- // better is to listen files in parallel, but, since that would be used in debug... TODO
- for serviceName, config := range w.watcherConfigs {
- go func(sn string, c WatcherConfig) {
- fileList, _ := w.retrieveFileList(sn, c)
- w.pollEvents(c.serviceName, fileList)
- }(serviceName, config)
- }
- }
- }
-
-}
-
-// retrieveFileList get file list for service
-func (w *Watcher) retrieveFileList(serviceName string, config WatcherConfig) (map[string]os.FileInfo, error) {
- w.mu.Lock()
- defer w.mu.Unlock()
- fileList := make(map[string]os.FileInfo)
- if config.recursive {
- // walk through directories recursively
- for _, dir := range config.directories {
- // full path is workdir/relative_path
- fullPath, err := filepath.Abs(dir)
- if err != nil {
- return nil, err
- }
- list, err := w.retrieveFilesRecursive(serviceName, fullPath)
- if err != nil {
- return nil, err
- }
-
- for k, v := range list {
- fileList[k] = v
- }
- }
- return fileList, nil
- }
-
- for _, dir := range config.directories {
- // full path is workdir/relative_path
- fullPath, err := filepath.Abs(dir)
- if err != nil {
- return nil, err
- }
-
- // list is pathToFiles with files
- list, err := w.retrieveFilesSingle(serviceName, fullPath)
- if err != nil {
- return nil, err
- }
-
- for pathToFile, file := range list {
- fileList[pathToFile] = file
- }
- }
-
- return fileList, nil
-}
-
-func (w *Watcher) retrieveFilesRecursive(serviceName, root string) (map[string]os.FileInfo, error) {
- fileList := make(map[string]os.FileInfo)
-
- return fileList, filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
- if err != nil {
- return err
- }
-
- // If path is ignored and it's a directory, skip the directory. If it's
- // ignored and it's a single file, skip the file.
- _, ignored := w.watcherConfigs[serviceName].ignored[path]
- if ignored {
- if info.IsDir() {
- // if it's dir, ignore whole
- return filepath.SkipDir
- }
- return nil
- }
-
- // if filename does not contain pattern --> ignore that file
- err = w.watcherConfigs[serviceName].filterHooks(info.Name(), w.watcherConfigs[serviceName].filePatterns)
- if err == ErrorSkip {
- return nil
- }
-
- // Add the path and it's info to the file list.
- fileList[path] = info
- return nil
- })
-}
-
-func (w *Watcher) pollEvents(serviceName string, files map[string]os.FileInfo) {
- w.mu.Lock()
- defer w.mu.Unlock()
-
- // Store create and remove events for use to check for rename events.
- creates := make(map[string]os.FileInfo)
- removes := make(map[string]os.FileInfo)
-
- // Check for removed files.
- for pth, info := range w.watcherConfigs[serviceName].files {
- if _, found := files[pth]; !found {
- removes[pth] = info
- }
- }
-
- // Check for created files, writes and chmods.
- for pth, info := range files {
- if info.IsDir() {
- continue
- }
- oldInfo, found := w.watcherConfigs[serviceName].files[pth]
- if !found {
- // A file was created.
- creates[pth] = info
- continue
- }
- if oldInfo.ModTime() != info.ModTime() {
- w.watcherConfigs[serviceName].files[pth] = info
- w.Event <- Event{
- path: pth,
- info: info,
- service: serviceName,
- }
- }
- if oldInfo.Mode() != info.Mode() {
- w.watcherConfigs[serviceName].files[pth] = info
- w.Event <- Event{
- path: pth,
- info: info,
- service: serviceName,
- }
- }
- }
-
- //Check for renames and moves.
- for path1, info1 := range removes {
- for path2, info2 := range creates {
- if sameFile(info1, info2) {
- e := Event{
- path: path2,
- info: info2,
- service: serviceName,
- }
-
- // remove initial path
- delete(w.watcherConfigs[serviceName].files, path1)
- // update with new
- w.watcherConfigs[serviceName].files[path2] = info2
-
-
- w.Event <- e
- }
- }
- }
-
- //Send all the remaining create and remove events.
- for pth, info := range creates {
- w.watcherConfigs[serviceName].files[pth] = info
- w.Event <- Event{
- path: pth,
- info: info,
- service: serviceName,
- }
- }
- for pth, info := range removes {
- delete(w.watcherConfigs[serviceName].files, pth)
- w.Event <- Event{
- path: pth,
- info: info,
- service: serviceName,
- }
- }
-}
-
-func (w *Watcher) Stop() {
- w.close <- struct{}{}
-}
diff --git a/service/reload/watcher_test.go b/service/reload/watcher_test.go
deleted file mode 100644
index 9683d2de..00000000
--- a/service/reload/watcher_test.go
+++ /dev/null
@@ -1,673 +0,0 @@
-package reload
-
-import (
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path/filepath"
- "runtime"
- "strings"
- "testing"
- "time"
-)
-
-var testServiceName = "test"
-
-// scenario
-// Create walker instance, init with default config, check that Watcher found all files from config
-func Test_Correct_Watcher_Init(t *testing.T) {
- tempDir, err := ioutil.TempDir(".", "")
- defer func() {
- err = freeResources(tempDir)
- if err != nil {
- t.Fatal(err)
- }
- }()
- if err != nil {
- t.Fatal(err)
- }
- err = ioutil.WriteFile(filepath.Join(tempDir, "file.txt"),
- []byte{}, 0755)
- if err != nil {
- t.Fatal(err)
- }
-
- wc := WatcherConfig{
- serviceName: testServiceName,
- recursive: false,
- directories: []string{tempDir},
- filterHooks: nil,
- files: make(map[string]os.FileInfo),
- ignored: nil,
- filePatterns: nil,
- }
-
- w, err := NewWatcher([]WatcherConfig{wc})
- if err != nil {
- t.Fatal(err)
- }
-
- if len(w.GetAllFiles(testServiceName)) != 2 {
- t.Fatal("incorrect directories len")
- }
-}
-
-// scenario
-// create 3 files, create walker instance
-// Start poll events
-// change file and see, if event had come to handler
-func Test_Get_FileEvent(t *testing.T) {
- tempDir, err := ioutil.TempDir(".", "")
- c := make(chan struct{})
- defer func(name string) {
- err = freeResources(name)
- if err != nil {
- c <- struct{}{}
- t.Fatal(err)
- }
- c <- struct{}{}
- }(tempDir)
-
- if err != nil {
- t.Fatal(err)
- }
- err = ioutil.WriteFile(filepath.Join(tempDir, "file1.txt"),
- []byte{}, 0755)
- if err != nil {
- t.Fatal(err)
- }
-
- err = ioutil.WriteFile(filepath.Join(tempDir, "file2.txt"),
- []byte{}, 0755)
- if err != nil {
- t.Fatal(err)
- }
-
- err = ioutil.WriteFile(filepath.Join(tempDir, "file3.txt"),
- []byte{}, 0755)
- if err != nil {
- t.Fatal(err)
- }
-
- wc := WatcherConfig{
- serviceName: testServiceName,
- recursive: false,
- directories: []string{tempDir},
- filterHooks: nil,
- files: make(map[string]os.FileInfo),
- ignored: nil,
- filePatterns: []string{"aaa", "txt"},
- }
-
- w, err := NewWatcher([]WatcherConfig{wc})
- if err != nil {
- t.Fatal(err)
- }
-
- // should be 3 files and directory
- if len(w.GetAllFiles(testServiceName)) != 4 {
- t.Fatal("incorrect directories len")
- }
-
- go limitTime(time.Second * 10, t.Name(), c)
-
- go func() {
- go func() {
- time.Sleep(time.Second)
- err2 := ioutil.WriteFile(filepath.Join(tempDir, "file2.txt"),
- []byte{1, 1, 1}, 0755)
- if err2 != nil {
- panic(err2)
- }
- runtime.Goexit()
- }()
-
- go func() {
- for e := range w.Event {
- if e.path != "file2.txt" {
- panic("didn't handle event when write file2")
- }
- w.Stop()
- }
- }()
- }()
-
- err = w.StartPolling(time.Second)
- if err != nil {
- t.Fatal(err)
- }
-}
-
-// scenario
-// create 3 files with different extensions, create walker instance
-// Start poll events
-// change file with txt extension, and see, if event had not come to handler because it was filtered
-func Test_FileExtensionFilter(t *testing.T) {
- tempDir, err := ioutil.TempDir(".", "")
- c := make(chan struct{})
- defer func(name string) {
- err = freeResources(name)
- if err != nil {
- c <- struct{}{}
- t.Fatal(err)
- }
- c <- struct{}{}
- }(tempDir)
-
- if err != nil {
- t.Fatal(err)
- }
- err = ioutil.WriteFile(filepath.Join(tempDir, "file1.aaa"),
- []byte{}, 0755)
- if err != nil {
- t.Fatal(err)
- }
-
- err = ioutil.WriteFile(filepath.Join(tempDir, "file2.bbb"),
- []byte{}, 0755)
- if err != nil {
- t.Fatal(err)
- }
-
- err = ioutil.WriteFile(filepath.Join(tempDir, "file3.txt"),
- []byte{}, 0755)
- if err != nil {
- t.Fatal(err)
- }
- wc := WatcherConfig{
- serviceName: testServiceName,
- recursive: false,
- directories: []string{tempDir},
- filterHooks: func(filename string, patterns []string) error {
- for i := 0; i < len(patterns); i++ {
- if strings.Contains(filename, patterns[i]) {
- return nil
- }
- }
- return ErrorSkip
- },
- files: make(map[string]os.FileInfo),
- ignored: nil,
- filePatterns: []string{"aaa", "bbb"},
- }
-
- w, err := NewWatcher([]WatcherConfig{wc})
- if err != nil {
- t.Fatal(err)
- }
-
- dirLen := len(w.GetAllFiles(testServiceName))
- // should be 2 files (one filtered) and directory
- if dirLen != 3 {
- t.Fatalf("incorrect directories len, len is: %d", dirLen)
- }
-
- go limitTime(time.Second * 5, t.Name(), c)
-
- go func() {
- go func() {
- err2 := ioutil.WriteFile(filepath.Join(tempDir, "file3.txt"),
- []byte{1, 1, 1}, 0755)
- if err2 != nil {
- panic(err2)
- }
-
- runtime.Goexit()
- }()
-
- go func() {
- for e := range w.Event {
- fmt.Println(e.info.Name())
- panic("handled event from filtered file")
- }
- }()
- w.Stop()
- runtime.Goexit()
- }()
-
- err = w.StartPolling(time.Second)
- if err != nil {
- t.Fatal(err)
- }
-}
-
-// nested
-// scenario
-// create dir and nested dir
-// make files with aaa, bbb and txt extensions, filter txt
-// change not filtered file, handle event
-func Test_Recursive_Support(t *testing.T) {
- tempDir, err := ioutil.TempDir(".", "")
- defer func() {
- err = freeResources(tempDir)
- if err != nil {
- t.Fatal(err)
- }
- }()
-
- nestedDir, err := ioutil.TempDir(tempDir, "nested")
- if err != nil {
- t.Fatal(err)
- }
-
- err = ioutil.WriteFile(filepath.Join(tempDir, "file1.aaa"),
- []byte{}, 0755)
- if err != nil {
- t.Fatal(err)
- }
-
- err = ioutil.WriteFile(filepath.Join(tempDir, "file2.bbb"),
- []byte{}, 0755)
- if err != nil {
- t.Fatal(err)
- }
-
- err = ioutil.WriteFile(filepath.Join(nestedDir, "file3.txt"),
- []byte{}, 0755)
- if err != nil {
- t.Fatal(err)
- }
- err = ioutil.WriteFile(filepath.Join(nestedDir, "file4.aaa"),
- []byte{}, 0755)
- if err != nil {
- t.Fatal(err)
- }
-
- wc := WatcherConfig{
- serviceName: testServiceName,
- recursive: true,
- directories: []string{tempDir},
- filterHooks: func(filename string, patterns []string) error {
- for i := 0; i < len(patterns); i++ {
- if strings.Contains(filename, patterns[i]) {
- return nil
- }
- }
- return ErrorSkip
- },
- files: make(map[string]os.FileInfo),
- ignored: nil,
- filePatterns: []string{"aaa", "bbb"},
- }
-
- w, err := NewWatcher([]WatcherConfig{wc})
- if err != nil {
- t.Fatal(err)
- }
-
- dirLen := len(w.GetAllFiles(testServiceName))
- // should be 3 files (2 from root dir, and 1 from nested), filtered txt
- if dirLen != 3 {
- t.Fatalf("incorrect directories len, len is: %d", dirLen)
- }
-
- go func() {
- // time sleep is used here because StartPolling is blocking operation
- time.Sleep(time.Second * 5)
- // change file in nested directory
- err = ioutil.WriteFile(filepath.Join(nestedDir, "file4.aaa"),
- []byte{1, 1, 1}, 0755)
- if err != nil {
- panic(err)
- }
- go func() {
- for e := range w.Event {
- if e.info.Name() != "file4.aaa" {
- panic("wrong handled event from watcher in nested dir")
- }
- w.Stop()
- }
- }()
- }()
-
- err = w.StartPolling(time.Second)
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func Test_Wrong_Dir(t *testing.T) {
- // no such file or directory
- wrongDir := "askdjfhaksdlfksdf"
-
- wc := WatcherConfig{
- serviceName: testServiceName,
- recursive: true,
- directories: []string{wrongDir},
- filterHooks: func(filename string, patterns []string) error {
- for i := 0; i < len(patterns); i++ {
- if strings.Contains(filename, patterns[i]) {
- return nil
- }
- }
- return ErrorSkip
- },
- files: make(map[string]os.FileInfo),
- ignored: nil,
- filePatterns: []string{"aaa", "bbb"},
- }
-
- _, err := NewWatcher([]WatcherConfig{wc})
- if err == nil {
- t.Fatal(err)
- }
-}
-
-func Test_Filter_Directory(t *testing.T) {
- tempDir, err := ioutil.TempDir(".", "")
- c := make(chan struct{})
- defer func(name string) {
- err = freeResources(name)
- if err != nil {
- c <- struct{}{}
- t.Fatal(err)
- }
- c <- struct{}{}
- }(tempDir)
-
- go limitTime(time.Second*10, t.Name(), c)
-
- nestedDir, err := ioutil.TempDir(tempDir, "nested")
- if err != nil {
- t.Fatal(err)
- }
-
- err = ioutil.WriteFile(filepath.Join(tempDir, "file1.aaa"),
- []byte{}, 0755)
- if err != nil {
- t.Fatal(err)
- }
-
- err = ioutil.WriteFile(filepath.Join(tempDir, "file2.bbb"),
- []byte{}, 0755)
- if err != nil {
- t.Fatal(err)
- }
-
- err = ioutil.WriteFile(filepath.Join(nestedDir, "file3.txt"),
- []byte{}, 0755)
- if err != nil {
- t.Fatal(err)
- }
- err = ioutil.WriteFile(filepath.Join(nestedDir, "file4.aaa"),
- []byte{}, 0755)
- if err != nil {
- t.Fatal(err)
- }
-
- ignored, err := ConvertIgnored([]string{nestedDir})
- if err != nil {
- t.Fatal(err)
- }
- wc := WatcherConfig{
- serviceName: testServiceName,
- recursive: true,
- directories: []string{tempDir},
- filterHooks: func(filename string, patterns []string) error {
- for i := 0; i < len(patterns); i++ {
- if strings.Contains(filename, patterns[i]) {
- return nil
- }
- }
- return ErrorSkip
- },
- files: make(map[string]os.FileInfo),
- ignored: ignored,
- filePatterns: []string{"aaa", "bbb", "txt"},
- }
-
- w, err := NewWatcher([]WatcherConfig{wc})
- if err != nil {
- t.Fatal(err)
- }
-
- dirLen := len(w.GetAllFiles(testServiceName))
- // should be 2 files (2 from root dir), filtered other
- if dirLen != 2 {
- t.Fatalf("incorrect directories len, len is: %d", dirLen)
- }
-
- go func() {
- go func() {
- err2 := ioutil.WriteFile(filepath.Join(nestedDir, "file4.aaa"),
- []byte{1, 1, 1}, 0755)
- if err2 != nil {
- panic(err2)
- }
- }()
-
- go func() {
- for e := range w.Event {
- fmt.Println("file: " + e.info.Name())
- panic("handled event from watcher in nested dir")
- }
- }()
-
- // time sleep is used here because StartPolling is blocking operation
- time.Sleep(time.Second * 5)
- w.Stop()
-
- }()
-
- err = w.StartPolling(time.Second)
- if err != nil {
- t.Fatal(err)
- }
-}
-
-// copy files from nested dir to not ignored
-// should fire an event
-func Test_Copy_Directory(t *testing.T) {
- tempDir, err := ioutil.TempDir(".", "")
- c := make(chan struct{})
- defer func() {
- err = freeResources(tempDir)
- if err != nil {
- c <- struct{}{}
- t.Fatal(err)
- }
- c <- struct{}{}
- }()
-
- nestedDir, err := ioutil.TempDir(tempDir, "nested")
- if err != nil {
- t.Fatal(err)
- }
-
- err = ioutil.WriteFile(filepath.Join(tempDir, "file1.aaa"),
- []byte{}, 0755)
- if err != nil {
- t.Fatal(err)
- }
-
- err = ioutil.WriteFile(filepath.Join(tempDir, "file2.bbb"),
- []byte{}, 0755)
- if err != nil {
- t.Fatal(err)
- }
-
- err = ioutil.WriteFile(filepath.Join(nestedDir, "file3.txt"),
- []byte{}, 0755)
- if err != nil {
- t.Fatal(err)
- }
- err = ioutil.WriteFile(filepath.Join(nestedDir, "file4.aaa"),
- []byte{}, 0755)
- if err != nil {
- t.Fatal(err)
- }
-
- ignored, err := ConvertIgnored([]string{nestedDir})
- if err != nil {
- t.Fatal(err)
- }
-
- wc := WatcherConfig{
- serviceName: testServiceName,
- recursive: true,
- directories: []string{tempDir},
- filterHooks: func(filename string, patterns []string) error {
- for i := 0; i < len(patterns); i++ {
- if strings.Contains(filename, patterns[i]) {
- return nil
- }
- }
- return ErrorSkip
- },
- files: make(map[string]os.FileInfo),
- ignored: ignored,
- filePatterns: []string{"aaa", "bbb", "txt"},
- }
-
- w, err := NewWatcher([]WatcherConfig{wc})
- if err != nil {
- t.Fatal(err)
- }
-
- dirLen := len(w.GetAllFiles(testServiceName))
- // should be 2 files (2 from root dir), filtered other
- if dirLen != 2 {
- t.Fatalf("incorrect directories len, len is: %d", dirLen)
- }
-
- go limitTime(time.Second*10, t.Name(), c)
-
- go func() {
- go func() {
- err2 := copyDir(nestedDir, filepath.Join(tempDir, "copyTo"))
- if err2 != nil {
- panic(err2)
- }
-
- // exit from current goroutine
- runtime.Goexit()
- }()
-
- go func() {
- for range w.Event {
- // here should be event, otherwise we won't stop
- w.Stop()
- }
- }()
- }()
-
- err = w.StartPolling(time.Second)
- if err != nil {
- t.Fatal(err)
- }
-}
-
-func limitTime(d time.Duration, name string, free chan struct{}) {
- go func() {
- ticket := time.NewTicker(d)
- for {
- select {
- case <-ticket.C:
- ticket.Stop()
- panic("timeout exceed, test: " + name)
- case <-free:
- ticket.Stop()
- return
- }
- }
- }()
-}
-
-func copyFile(src, dst string) (err error) {
- in, err := os.Open(src)
- if err != nil {
- return
- }
- defer in.Close()
-
- out, err := os.Create(dst)
- if err != nil {
- return
- }
- defer func() {
- if e := out.Close(); e != nil {
- err = e
- }
- }()
-
- _, err = io.Copy(out, in)
- if err != nil {
- return
- }
-
- err = out.Sync()
- if err != nil {
- return
- }
-
- si, err := os.Stat(src)
- if err != nil {
- return
- }
- err = os.Chmod(dst, si.Mode())
- if err != nil {
- return
- }
-
- return
-}
-
-func copyDir(src string, dst string) (err error) {
- src = filepath.Clean(src)
- dst = filepath.Clean(dst)
-
- si, err := os.Stat(src)
- if err != nil {
- return err
- }
- if !si.IsDir() {
- return fmt.Errorf("source is not a directory")
- }
-
- _, err = os.Stat(dst)
- if err != nil && !os.IsNotExist(err) {
- return
- }
- if err == nil {
- return fmt.Errorf("destination already exists")
- }
-
- err = os.MkdirAll(dst, si.Mode())
- if err != nil {
- return
- }
-
- entries, err := ioutil.ReadDir(src)
- if err != nil {
- return
- }
-
- for _, entry := range entries {
- srcPath := filepath.Join(src, entry.Name())
- dstPath := filepath.Join(dst, entry.Name())
-
- if entry.IsDir() {
- err = copyDir(srcPath, dstPath)
- if err != nil {
- return
- }
- } else {
- // Skip symlinks.
- if entry.Mode()&os.ModeSymlink != 0 {
- continue
- }
-
- err = copyFile(srcPath, dstPath)
- if err != nil {
- return
- }
- }
- }
-
- return
-}
-
-func freeResources(path string) error {
- return os.RemoveAll(path)
-}
diff --git a/service/rpc/config.go b/service/rpc/config.go
deleted file mode 100644
index cc492622..00000000
--- a/service/rpc/config.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package rpc
-
-import (
- "errors"
- "net"
- "strings"
-
- "github.com/spiral/roadrunner/service"
- "github.com/spiral/roadrunner/util"
-)
-
-// Config defines RPC service config.
-type Config struct {
- // Indicates if RPC connection is enabled.
- Enable bool
-
- // Listen string
- Listen string
-}
-
-// Hydrate must populate Config values using given Config source. Must return error if Config is not valid.
-func (c *Config) Hydrate(cfg service.Config) error {
- if err := cfg.Unmarshal(c); err != nil {
- return err
- }
-
- return c.Valid()
-}
-
-// InitDefaults allows to init blank config with pre-defined set of default values.
-func (c *Config) InitDefaults() error {
- c.Enable = true
- c.Listen = "tcp://127.0.0.1:6001"
-
- return nil
-}
-
-// Valid returns nil if config is valid.
-func (c *Config) Valid() error {
- if dsn := strings.Split(c.Listen, "://"); len(dsn) != 2 {
- return errors.New("invalid socket DSN (tcp://:6001, unix://file.sock)")
- }
-
- return nil
-}
-
-// Listener creates new rpc socket Listener.
-func (c *Config) Listener() (net.Listener, error) {
- return util.CreateListener(c.Listen)
-}
-
-// Dialer creates rpc socket Dialer.
-func (c *Config) Dialer() (net.Conn, error) {
- dsn := strings.Split(c.Listen, "://")
- if len(dsn) != 2 {
- return nil, errors.New("invalid socket DSN (tcp://:6001, unix://file.sock)")
- }
-
- return net.Dial(dsn[0], dsn[1])
-}
diff --git a/service/rpc/config_test.go b/service/rpc/config_test.go
deleted file mode 100644
index 1ecd71b3..00000000
--- a/service/rpc/config_test.go
+++ /dev/null
@@ -1,163 +0,0 @@
-package rpc
-
-import (
- json "github.com/json-iterator/go"
- "github.com/spiral/roadrunner/service"
- "github.com/stretchr/testify/assert"
- "testing"
-)
-
-type testCfg struct{ cfg string }
-
-func (cfg *testCfg) Get(name string) service.Config { return nil }
-func (cfg *testCfg) Unmarshal(out interface{}) error {
- j := json.ConfigCompatibleWithStandardLibrary
- return j.Unmarshal([]byte(cfg.cfg), out)
-}
-
-func Test_Config_Hydrate(t *testing.T) {
- cfg := &testCfg{`{"enable": true, "listen": "tcp://:18001"}`}
- c := &Config{}
-
- assert.NoError(t, c.Hydrate(cfg))
-}
-
-func Test_Config_Hydrate_Error(t *testing.T) {
- cfg := &testCfg{`{"enable": true, "listen": "invalid"}`}
- c := &Config{}
-
- assert.Error(t, c.Hydrate(cfg))
-}
-
-func Test_Config_Hydrate_Error2(t *testing.T) {
- cfg := &testCfg{`{"enable": true, "listen": "invalid"`}
- c := &Config{}
-
- assert.Error(t, c.Hydrate(cfg))
-}
-
-func TestConfig_Listener(t *testing.T) {
- cfg := &Config{Listen: "tcp://:18001"}
-
- ln, err := cfg.Listener()
- assert.NoError(t, err)
- assert.NotNil(t, ln)
- defer func() {
- err := ln.Close()
- if err != nil {
- t.Errorf("error closing the listener: error %v", err)
- }
- }()
-
- assert.Equal(t, "tcp", ln.Addr().Network())
- assert.Equal(t, "0.0.0.0:18001", ln.Addr().String())
-}
-
-func TestConfig_ListenerUnix(t *testing.T) {
- cfg := &Config{Listen: "unix://file.sock"}
-
- ln, err := cfg.Listener()
- assert.NoError(t, err)
- assert.NotNil(t, ln)
- defer func() {
- err := ln.Close()
- if err != nil {
- t.Errorf("error closing the listener: error %v", err)
- }
- }()
-
- assert.Equal(t, "unix", ln.Addr().Network())
- assert.Equal(t, "file.sock", ln.Addr().String())
-}
-
-func Test_Config_Error(t *testing.T) {
- cfg := &Config{Listen: "uni:unix.sock"}
- ln, err := cfg.Listener()
- assert.Nil(t, ln)
- assert.Error(t, err)
- assert.Equal(t, "invalid DSN (tcp://:6001, unix://file.sock)", err.Error())
-}
-
-func Test_Config_ErrorMethod(t *testing.T) {
- cfg := &Config{Listen: "xinu://unix.sock"}
-
- ln, err := cfg.Listener()
- assert.Nil(t, ln)
- assert.Error(t, err)
-}
-
-func TestConfig_Dialer(t *testing.T) {
- cfg := &Config{Listen: "tcp://:18001"}
-
- ln, _ := cfg.Listener()
- defer func() {
- err := ln.Close()
- if err != nil {
- t.Errorf("error closing the listener: error %v", err)
- }
- }()
-
- conn, err := cfg.Dialer()
- assert.NoError(t, err)
- assert.NotNil(t, conn)
- defer func() {
- err := conn.Close()
- if err != nil {
- t.Errorf("error closing the connection: error %v", err)
- }
- }()
-
- assert.Equal(t, "tcp", conn.RemoteAddr().Network())
- assert.Equal(t, "127.0.0.1:18001", conn.RemoteAddr().String())
-}
-
-func TestConfig_DialerUnix(t *testing.T) {
- cfg := &Config{Listen: "unix://file.sock"}
-
- ln, _ := cfg.Listener()
- defer func() {
- err := ln.Close()
- if err != nil {
- t.Errorf("error closing the listener: error %v", err)
- }
- }()
-
- conn, err := cfg.Dialer()
- assert.NoError(t, err)
- assert.NotNil(t, conn)
- defer func() {
- err := conn.Close()
- if err != nil {
- t.Errorf("error closing the connection: error %v", err)
- }
- }()
-
- assert.Equal(t, "unix", conn.RemoteAddr().Network())
- assert.Equal(t, "file.sock", conn.RemoteAddr().String())
-}
-
-func Test_Config_DialerError(t *testing.T) {
- cfg := &Config{Listen: "uni:unix.sock"}
- ln, err := cfg.Dialer()
- assert.Nil(t, ln)
- assert.Error(t, err)
- assert.Equal(t, "invalid socket DSN (tcp://:6001, unix://file.sock)", err.Error())
-}
-
-func Test_Config_DialerErrorMethod(t *testing.T) {
- cfg := &Config{Listen: "xinu://unix.sock"}
-
- ln, err := cfg.Dialer()
- assert.Nil(t, ln)
- assert.Error(t, err)
-}
-
-func Test_Config_Defaults(t *testing.T) {
- c := &Config{}
- err := c.InitDefaults()
- if err != nil {
- t.Errorf("error during the InitDefaults: error %v", err)
- }
- assert.Equal(t, true, c.Enable)
- assert.Equal(t, "tcp://127.0.0.1:6001", c.Listen)
-}
diff --git a/service/rpc/service.go b/service/rpc/service.go
deleted file mode 100644
index 7a649f1b..00000000
--- a/service/rpc/service.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package rpc
-
-import (
- "errors"
- "github.com/spiral/goridge/v2"
- "github.com/spiral/roadrunner/service"
- "github.com/spiral/roadrunner/service/env"
- "net/rpc"
- "sync"
-)
-
-// ID contains default service name.
-const ID = "rpc"
-
-// Service is RPC service.
-type Service struct {
- cfg *Config
- stop chan interface{}
- rpc *rpc.Server
- mu sync.Mutex
- serving bool
-}
-
-// Init rpc service. Must return true if service is enabled.
-func (s *Service) Init(cfg *Config, c service.Container, env env.Environment) (bool, error) {
- if !cfg.Enable {
- return false, nil
- }
-
- s.cfg = cfg
- s.rpc = rpc.NewServer()
-
- if env != nil {
- env.SetEnv("RR_RPC", cfg.Listen)
- }
-
- if err := s.Register("system", &systemService{c}); err != nil {
- return false, err
- }
-
- return true, nil
-}
-
-// Serve serves the service.
-func (s *Service) Serve() error {
- if s.rpc == nil {
- return errors.New("RPC service is not configured")
- }
-
- s.mu.Lock()
- s.serving = true
- s.stop = make(chan interface{})
- s.mu.Unlock()
-
- ln, err := s.cfg.Listener()
- if err != nil {
- return err
- }
- defer ln.Close()
-
- go func() {
- for {
- select {
- case <-s.stop:
- return
- default:
- conn, err := ln.Accept()
- if err != nil {
- continue
- }
-
- go s.rpc.ServeCodec(goridge.NewCodec(conn))
- }
- }
- }()
-
- <-s.stop
-
- s.mu.Lock()
- s.serving = false
- s.mu.Unlock()
-
- return nil
-}
-
-// Stop stops the service.
-func (s *Service) Stop() {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- if s.serving {
- close(s.stop)
- }
-}
-
-// Register publishes in the server the set of methods of the
-// receiver value that satisfy the following conditions:
-// - exported method of exported type
-// - two arguments, both of exported type
-// - the second argument is a pointer
-// - one return value, of type error
-// It returns an error if the receiver is not an exported type or has
-// no suitable methods. It also logs the error using package log.
-func (s *Service) Register(name string, svc interface{}) error {
- if s.rpc == nil {
- return errors.New("RPC service is not configured")
- }
-
- return s.rpc.RegisterName(name, svc)
-}
-
-// Client creates new RPC client.
-func (s *Service) Client() (*rpc.Client, error) {
- if s.cfg == nil {
- return nil, errors.New("RPC service is not configured")
- }
-
- conn, err := s.cfg.Dialer()
- if err != nil {
- return nil, err
- }
-
- return rpc.NewClientWithCodec(goridge.NewClientCodec(conn)), nil
-}
diff --git a/service/rpc/service_test.go b/service/rpc/service_test.go
deleted file mode 100644
index 51c1b337..00000000
--- a/service/rpc/service_test.go
+++ /dev/null
@@ -1,96 +0,0 @@
-package rpc
-
-import (
- "github.com/spiral/roadrunner/service"
- "github.com/spiral/roadrunner/service/env"
- "github.com/stretchr/testify/assert"
- "testing"
- "time"
-)
-
-type testService struct{}
-
-func (ts *testService) Echo(msg string, r *string) error { *r = msg; return nil }
-
-func Test_Disabled(t *testing.T) {
- s := &Service{}
- ok, err := s.Init(&Config{Enable: false}, service.NewContainer(nil), nil)
-
- assert.NoError(t, err)
- assert.False(t, ok)
-}
-
-func Test_RegisterNotConfigured(t *testing.T) {
- s := &Service{}
- assert.Error(t, s.Register("test", &testService{}))
-
- client, err := s.Client()
- assert.Nil(t, client)
- assert.Error(t, err)
- assert.Error(t, s.Serve())
-}
-
-func Test_Enabled(t *testing.T) {
- s := &Service{}
- ok, err := s.Init(&Config{Enable: true, Listen: "tcp://localhost:9008"}, service.NewContainer(nil), nil)
-
- assert.NoError(t, err)
- assert.True(t, ok)
-}
-
-func Test_StopNonServing(t *testing.T) {
- s := &Service{}
- ok, err := s.Init(&Config{Enable: true, Listen: "tcp://localhost:9008"}, service.NewContainer(nil), nil)
-
- assert.NoError(t, err)
- assert.True(t, ok)
- s.Stop()
-}
-
-func Test_Serve_Errors(t *testing.T) {
- s := &Service{}
- ok, err := s.Init(&Config{Enable: true, Listen: "malformed"}, service.NewContainer(nil), nil)
- assert.NoError(t, err)
- assert.True(t, ok)
-
- assert.Error(t, s.Serve())
-
- client, err := s.Client()
- assert.Nil(t, client)
- assert.Error(t, err)
-}
-
-func Test_Serve_Client(t *testing.T) {
- s := &Service{}
- ok, err := s.Init(&Config{Enable: true, Listen: "tcp://localhost:9018"}, service.NewContainer(nil), nil)
- assert.NoError(t, err)
- assert.True(t, ok)
-
- defer s.Stop()
-
- assert.NoError(t, s.Register("test", &testService{}))
-
- go func() { assert.NoError(t, s.Serve()) }()
- time.Sleep(time.Second)
-
- client, err := s.Client()
- assert.NotNil(t, client)
- assert.NoError(t, err)
-
- var resp string
- assert.NoError(t, client.Call("test.Echo", "hello world", &resp))
- assert.Equal(t, "hello world", resp)
- assert.NoError(t, client.Close())
-}
-
-func TestSetEnv(t *testing.T) {
- s := &Service{}
- e := env.NewService(map[string]string{})
- ok, err := s.Init(&Config{Enable: true, Listen: "tcp://localhost:9018"}, service.NewContainer(nil), e)
-
- assert.NoError(t, err)
- assert.True(t, ok)
-
- v, _ := e.GetEnv()
- assert.Equal(t, "tcp://localhost:9018", v["RR_RPC"])
-}
diff --git a/service/rpc/system.go b/service/rpc/system.go
deleted file mode 100644
index ffba3782..00000000
--- a/service/rpc/system.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package rpc
-
-import "github.com/spiral/roadrunner/service"
-
-// systemService service controls rr server.
-type systemService struct {
- c service.Container
-}
-
-// Detach the underlying c.
-func (s *systemService) Stop(stop bool, r *string) error {
- if stop {
- s.c.Stop()
- }
- *r = "OK"
-
- return nil
-}
diff --git a/service/static/config.go b/service/static/config.go
deleted file mode 100644
index 3ca20a83..00000000
--- a/service/static/config.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package static
-
-import (
- "fmt"
- "github.com/spiral/roadrunner/service"
- "os"
- "path"
- "strings"
-)
-
-// Config describes file location and controls access to them.
-type Config struct {
- // 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
-
- // Always specifies list of extensions which must always be served by static
- // service, even if file not found.
- Always []string
-
- // Request headers to add to every static.
- Request map[string]string
-
- // Response headers to add to every static.
- Response map[string]string
-}
-
-// Hydrate must populate Config values using given Config source. Must return error if Config is not valid.
-func (c *Config) Hydrate(cfg service.Config) error {
- if err := cfg.Unmarshal(c); err != nil {
- return err
- }
-
- return c.Valid()
-}
-
-// Valid returns nil if config is valid.
-func (c *Config) Valid() error {
- st, err := os.Stat(c.Dir)
- if err != nil {
- if os.IsNotExist(err) {
- return fmt.Errorf("root directory '%s' does not exists", c.Dir)
- }
-
- return err
- }
-
- if !st.IsDir() {
- return fmt.Errorf("invalid root directory '%s'", c.Dir)
- }
-
- return nil
-}
-
-// AlwaysForbid must return true if file extension is not allowed for the upload.
-func (c *Config) AlwaysForbid(filename string) bool {
- ext := strings.ToLower(path.Ext(filename))
-
- for _, v := range c.Forbid {
- if ext == v {
- return true
- }
- }
-
- return false
-}
-
-// AlwaysServe must indicate that file is expected to be served by static service.
-func (c *Config) AlwaysServe(filename string) bool {
- ext := strings.ToLower(path.Ext(filename))
-
- for _, v := range c.Always {
- if ext == v {
- return true
- }
- }
-
- return false
-}
diff --git a/service/static/config_test.go b/service/static/config_test.go
deleted file mode 100644
index 8bf0d372..00000000
--- a/service/static/config_test.go
+++ /dev/null
@@ -1,45 +0,0 @@
-package static
-
-import (
- json "github.com/json-iterator/go"
- "github.com/spiral/roadrunner/service"
- "github.com/stretchr/testify/assert"
- "testing"
-)
-
-type mockCfg struct{ cfg string }
-
-func (cfg *mockCfg) Get(name string) service.Config { return nil }
-func (cfg *mockCfg) Unmarshal(out interface{}) error {
- j := json.ConfigCompatibleWithStandardLibrary
- return j.Unmarshal([]byte(cfg.cfg), out)
-}
-
-func Test_Config_Hydrate(t *testing.T) {
- cfg := &mockCfg{`{"dir": "./", "request":{"foo": "bar"}, "response":{"xxx": "yyy"}}`}
- c := &Config{}
-
- assert.NoError(t, c.Hydrate(cfg))
-}
-
-func Test_Config_Hydrate_Error(t *testing.T) {
- cfg := &mockCfg{`{"enable": true,"dir": "/dir/"}`}
- c := &Config{}
-
- assert.Error(t, c.Hydrate(cfg))
-}
-
-func TestConfig_Forbids(t *testing.T) {
- cfg := Config{Forbid: []string{".php"}}
-
- assert.True(t, cfg.AlwaysForbid("index.php"))
- assert.True(t, cfg.AlwaysForbid("index.PHP"))
- assert.True(t, cfg.AlwaysForbid("phpadmin/index.bak.php"))
- assert.False(t, cfg.AlwaysForbid("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
deleted file mode 100644
index 95b99860..00000000
--- a/service/static/service.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package static
-
-import (
- rrhttp "github.com/spiral/roadrunner/service/http"
- "net/http"
- "path"
-)
-
-// ID contains default service name.
-const ID = "static"
-
-// Service serves static files. Potentially convert into middleware?
-type Service struct {
- // server configuration (location, forbidden files and etc)
- cfg *Config
-
- // root is initiated http directory
- root http.Dir
-}
-
-// 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.
-func (s *Service) Init(cfg *Config, r *rrhttp.Service) (bool, error) {
- if r == nil {
- return false, nil
- }
-
- s.cfg = cfg
- s.root = http.Dir(s.cfg.Dir)
- r.AddMiddleware(s.middleware)
-
- return true, nil
-}
-
-// middleware must return true if request/response pair is handled within the middleware.
-func (s *Service) middleware(f http.HandlerFunc) http.HandlerFunc {
- // Define the http.HandlerFunc
- return func(w http.ResponseWriter, r *http.Request) {
- if s.cfg.Request != nil {
- for k, v := range s.cfg.Request {
- r.Header.Add(k, v)
- }
- }
-
- if s.cfg.Response != nil {
- for k, v := range s.cfg.Response {
- w.Header().Set(k, v)
- }
- }
-
- if !s.handleStatic(w, r) {
- f(w, r)
- }
- }
-}
-
-func (s *Service) handleStatic(w http.ResponseWriter, r *http.Request) bool {
- fPath := path.Clean(r.URL.Path)
-
- if s.cfg.AlwaysForbid(fPath) {
- return false
- }
-
- f, err := s.root.Open(fPath)
- if err != nil {
- if s.cfg.AlwaysServe(fPath) {
- w.WriteHeader(404)
- return true
- }
-
- return false
- }
- defer f.Close()
-
- d, err := f.Stat()
- if err != nil {
- return false
- }
-
- // do not serve directories
- if d.IsDir() {
- return false
- }
-
- http.ServeContent(w, r, d.Name(), d.ModTime(), f)
- return true
-}
diff --git a/service/static/service_test.go b/service/static/service_test.go
deleted file mode 100644
index 842662c9..00000000
--- a/service/static/service_test.go
+++ /dev/null
@@ -1,531 +0,0 @@
-package static
-
-import (
- "bytes"
- json "github.com/json-iterator/go"
- "github.com/sirupsen/logrus"
- "github.com/sirupsen/logrus/hooks/test"
- "github.com/spiral/roadrunner/service"
- rrhttp "github.com/spiral/roadrunner/service/http"
- "github.com/stretchr/testify/assert"
- "io"
- "io/ioutil"
- "net/http"
- "os"
- "testing"
- "time"
-)
-
-type testCfg struct {
- httpCfg string
- static string
- target string
-}
-
-func (cfg *testCfg) Get(name string) service.Config {
- if name == rrhttp.ID {
- return &testCfg{target: cfg.httpCfg}
- }
-
- if name == ID {
- return &testCfg{target: cfg.static}
- }
- return nil
-}
-func (cfg *testCfg) Unmarshal(out interface{}) error {
- j := json.ConfigCompatibleWithStandardLibrary
- return j.Unmarshal([]byte(cfg.target), out)
-}
-
-func Test_Files(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, &rrhttp.Service{})
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{
- static: `{"enable":true, "dir":"../../tests", "forbid":[]}`,
- httpCfg: `{
- "enable": true,
- "address": ":8029",
- "maxRequestSize": 1024,
- "uploads": {
- "dir": ` + tmpDir() + `,
- "forbid": []
- },
- "workers":{
- "command": "php ../../tests/http/client.php pid pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`}))
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("serve error: %v", err)
- }
- }()
-
- time.Sleep(time.Second)
-
-
- b, _, _ := get("http://localhost:8029/sample.txt")
- assert.Equal(t, "sample", b)
- c.Stop()
-}
-
-func Test_Disabled(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{
- static: `{"enable":true, "dir":"../../tests", "forbid":[]}`,
- }))
-
- s, st := c.Get(ID)
- assert.NotNil(t, s)
- assert.Equal(t, service.StatusInactive, st)
-}
-
-func Test_Files_Disable(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, &rrhttp.Service{})
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{
- static: `{"enable":false, "dir":"../../tests", "forbid":[".php"]}`,
- httpCfg: `{
- "enable": true,
- "address": ":8030",
- "maxRequestSize": 1024,
- "uploads": {
- "dir": ` + tmpDir() + `,
- "forbid": []
- },
- "workers":{
- "command": "php ../../tests/http/client.php echo pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`}))
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("serve error: %v", err)
- }
- }()
-
- time.Sleep(time.Second)
-
- b, _, err := get("http://localhost:8030/client.php?hello=world")
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, "WORLD", b)
- c.Stop()
-}
-
-func Test_Files_Error(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, &rrhttp.Service{})
- c.Register(ID, &Service{})
-
- assert.Error(t, c.Init(&testCfg{
- static: `{"enable":true, "dir":"dir/invalid", "forbid":[".php"]}`,
- httpCfg: `{
- "enable": true,
- "address": ":8031",
- "maxRequestSize": 1024,
- "uploads": {
- "dir": ` + tmpDir() + `,
- "forbid": []
- },
- "workers":{
- "command": "php ../../tests/http/client.php echo pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`}))
-}
-
-func Test_Files_Error2(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, &rrhttp.Service{})
- c.Register(ID, &Service{})
-
- assert.Error(t, c.Init(&testCfg{
- static: `{"enable":true, "dir":"dir/invalid", "forbid":[".php"]`,
- httpCfg: `{
- "enable": true,
- "address": ":8032",
- "maxRequestSize": 1024,
- "uploads": {
- "dir": ` + tmpDir() + `,
- "forbid": []
- },
- "workers":{
- "command": "php ../../tests/http/client.php echo pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`}))
-}
-
-func Test_Files_Forbid(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, &rrhttp.Service{})
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{
- static: `{"enable":true, "dir":"../../tests", "forbid":[".php"]}`,
- httpCfg: `{
- "enable": true,
- "address": ":8033",
- "maxRequestSize": 1024,
- "uploads": {
- "dir": ` + tmpDir() + `,
- "forbid": []
- },
- "workers":{
- "command": "php ../../tests/http/client.php echo pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`}))
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("serve error: %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 500)
-
- b, _, err := get("http://localhost:8033/client.php?hello=world")
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, "WORLD", b)
- c.Stop()
-}
-
-func Test_Files_Always(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, &rrhttp.Service{})
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{
- static: `{"enable":true, "dir":"../../tests", "forbid":[".php"], "always":[".ico"]}`,
- httpCfg: `{
- "enable": true,
- "address": ":8034",
- "maxRequestSize": 1024,
- "uploads": {
- "dir": ` + tmpDir() + `,
- "forbid": []
- },
- "workers":{
- "command": "php ../../tests/http/client.php echo pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`}))
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("serve error: %v", err)
- }
- }()
-
- time.Sleep(time.Millisecond * 500)
-
- _, r, err := get("http://localhost:8034/favicon.ico")
- if err != nil {
- t.Fatal(err)
- }
- assert.Equal(t, 404, r.StatusCode)
- c.Stop()
-}
-
-func Test_Files_NotFound(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, &rrhttp.Service{})
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{
- static: `{"enable":true, "dir":"../../tests", "forbid":[".php"]}`,
- httpCfg: `{
- "enable": true,
- "address": ":8035",
- "maxRequestSize": 1024,
- "uploads": {
- "dir": ` + tmpDir() + `,
- "forbid": []
- },
- "workers":{
- "command": "php ../../tests/http/client.php echo pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`}))
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("serve error: %v", err)
- }
- }()
-
- time.Sleep(time.Millisecond * 500)
-
- b, _, _ := get("http://localhost:8035/client.XXX?hello=world")
- assert.Equal(t, "WORLD", b)
- c.Stop()
-}
-
-func Test_Files_Dir(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, &rrhttp.Service{})
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{
- static: `{"enable":true, "dir":"../../tests", "forbid":[".php"]}`,
- httpCfg: `{
- "enable": true,
- "address": ":8036",
- "maxRequestSize": 1024,
- "uploads": {
- "dir": ` + tmpDir() + `,
- "forbid": []
- },
- "workers":{
- "command": "php ../../tests/http/client.php echo pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`}))
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("serve error: %v", err)
- }
- }()
- time.Sleep(time.Millisecond * 500)
-
- b, _, _ := get("http://localhost:8036/http?hello=world")
- assert.Equal(t, "WORLD", b)
- c.Stop()
-}
-
-func Test_Files_NotForbid(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, &rrhttp.Service{})
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{
- static: `{"enable":true, "dir":"../../tests", "forbid":[]}`,
- httpCfg: `{
- "enable": true,
- "address": ":8037",
- "maxRequestSize": 1024,
- "uploads": {
- "dir": ` + tmpDir() + `,
- "forbid": []
- },
- "workers":{
- "command": "php ../../tests/http/client.php pid pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`}))
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("serve error: %v", err)
- }
- }()
-
- time.Sleep(time.Millisecond * 500)
-
- b, _, _ := get("http://localhost:8037/client.php")
- assert.Equal(t, all("../../tests/client.php"), b)
- assert.Equal(t, all("../../tests/client.php"), b)
- c.Stop()
-}
-
-func TestStatic_Headers(t *testing.T) {
- logger, _ := test.NewNullLogger()
- logger.SetLevel(logrus.DebugLevel)
-
- c := service.NewContainer(logger)
- c.Register(rrhttp.ID, &rrhttp.Service{})
- c.Register(ID, &Service{})
-
- assert.NoError(t, c.Init(&testCfg{
- static: `{"enable":true, "dir":"../../tests", "forbid":[], "request":{"input": "custom-header"}, "response":{"output": "output-header"}}`,
- httpCfg: `{
- "enable": true,
- "address": ":8037",
- "maxRequestSize": 1024,
- "uploads": {
- "dir": ` + tmpDir() + `,
- "forbid": []
- },
- "workers":{
- "command": "php ../../tests/http/client.php pid pipes",
- "relay": "pipes",
- "pool": {
- "numWorkers": 1,
- "allocateTimeout": 10000000,
- "destroyTimeout": 10000000
- }
- }
- }`}))
-
- go func() {
- err := c.Serve()
- if err != nil {
- t.Errorf("serve error: %v", err)
- }
- }()
-
- time.Sleep(time.Millisecond * 500)
-
- req, err := http.NewRequest("GET", "http://localhost:8037/client.php", nil)
- if err != nil {
- t.Fatal(err)
- }
-
- resp, err := http.DefaultClient.Do(req)
- if err != nil {
- t.Fatal(err)
- }
-
- if resp.Header.Get("Output") != "output-header" {
- t.Fatal("can't find output header in response")
- }
-
-
- b, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- t.Fatal(err)
- }
-
- assert.Equal(t, all("../../tests/client.php"), string(b))
- assert.Equal(t, all("../../tests/client.php"), string(b))
- c.Stop()
-}
-
-func get(url string) (string, *http.Response, error) {
- r, err := http.Get(url)
- if err != nil {
- return "", nil, err
- }
-
- b, err := ioutil.ReadAll(r.Body)
- if err != nil {
- return "", nil, err
- }
-
- err = r.Body.Close()
- if err != nil {
- return "", nil, err
- }
-
- return string(b), r, err
-}
-
-func tmpDir() string {
- p := os.TempDir()
- j := json.ConfigCompatibleWithStandardLibrary
- r, _ := j.Marshal(p)
-
- return string(r)
-}
-
-func all(fn string) string {
- f, _ := os.Open(fn)
-
- b := &bytes.Buffer{}
- _, err := io.Copy(b, f)
- if err != nil {
- return ""
- }
-
- err = f.Close()
- if err != nil {
- return ""
- }
-
- return b.String()
-}