diff options
author | Valery Piashchynski <[email protected]> | 2020-10-13 13:55:20 +0300 |
---|---|---|
committer | Valery Piashchynski <[email protected]> | 2020-10-13 13:55:20 +0300 |
commit | 0dc44d54cfcc9dd3fa09a41136f35a9a8d26b994 (patch) | |
tree | ffcb65010bebe9f5b5436192979e64b2402a6ec0 /service/static | |
parent | 08d6b6b7f773f83b286cd48c1a0fbec9a62fb42b (diff) |
Initial commit of RR 2.0v2.0.0-alpha1
Diffstat (limited to 'service/static')
-rw-r--r-- | service/static/config.go | 82 | ||||
-rw-r--r-- | service/static/config_test.go | 45 | ||||
-rw-r--r-- | service/static/service.go | 87 | ||||
-rw-r--r-- | service/static/service_test.go | 531 |
4 files changed, 0 insertions, 745 deletions
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() -} |