summaryrefslogtreecommitdiff
path: root/service
diff options
context:
space:
mode:
authorWolfy-J <[email protected]>2018-06-10 17:06:06 +0300
committerWolfy-J <[email protected]>2018-06-10 17:06:06 +0300
commit232aa8f3c20a060e556ab431467f4f7b3f83bfbf (patch)
treea9dacbc142020cabae6a0708733aadb7e789aea5 /service
parent3fe85e9d92f5f98337e8f7fd9a14e6b66b9694bd (diff)
http service
Diffstat (limited to 'service')
-rw-r--r--service/http/config.go34
-rw-r--r--service/http/parse.go147
-rw-r--r--service/http/request.go137
-rw-r--r--service/http/response.go54
-rw-r--r--service/http/rpc.go78
-rw-r--r--service/http/server.go84
-rw-r--r--service/http/service.go103
-rw-r--r--service/http/uploads.go130
-rw-r--r--service/http/uploads_config.go29
-rw-r--r--service/http/uploads_config_test.go15
-rw-r--r--service/rpc/config.go35
-rw-r--r--service/rpc/config_test.go109
-rw-r--r--service/rpc/service.go122
-rw-r--r--service/rpc/service_test.go95
-rw-r--r--service/static/config.go52
-rw-r--r--service/static/config_test.go21
-rw-r--r--service/static/service.go120
17 files changed, 0 insertions, 1365 deletions
diff --git a/service/http/config.go b/service/http/config.go
deleted file mode 100644
index efcaae30..00000000
--- a/service/http/config.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package http
-
-import (
- "github.com/spiral/roadrunner"
- "fmt"
-)
-
-// Configures RoadRunner HTTP server.
-type Config struct {
- // Enable enables http service.
- Enable bool
-
- // Host and port to handle as http server.
- Host, Port string
-
- // MaxRequest specified max size for payload body in bytes, set 0 to unlimited.
- MaxRequest int64
-
- // Uploads configures uploads configuration.
- Uploads *UploadsConfig
-
- // Workers configures roadrunner server and worker pool.
- Workers *roadrunner.ServerConfig
-}
-
-// Valid validates the configuration.
-func (cfg *Config) Valid() error {
- return nil
-}
-
-// httpAddr returns prepared http listen address.
-func (cfg *Config) httpAddr() string {
- return fmt.Sprintf("%s:%v", cfg.Host, cfg.Port)
-}
diff --git a/service/http/parse.go b/service/http/parse.go
deleted file mode 100644
index fe8361d6..00000000
--- a/service/http/parse.go
+++ /dev/null
@@ -1,147 +0,0 @@
-package http
-
-import (
- "strings"
- "net/http"
- "os"
-)
-
-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, error) {
- data := make(dataTree)
- for k, v := range r.PostForm {
- data.push(k, v)
- }
-
- for k, v := range r.MultipartForm.Value {
- data.push(k, v)
- }
-
- return data, nil
-}
-
-// pushes value into data tree.
-func (d dataTree) push(k string, v []string) {
- if len(v) == 0 {
- // skip empty values
- return
- }
-
- indexes := make([]string, 0)
- for _, index := range strings.Split(k, "[") {
- indexes = append(indexes, strings.Trim(index, "]"))
- }
-
- if len(indexes) <= maxLevel {
- d.mount(indexes, v)
- }
-}
-
-// mount mounts data tree recursively.
-func (d dataTree) mount(i []string, v []string) {
- if len(v) == 0 {
- return
- }
-
- 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.(dataTree).mount(i[1:], v)
- }
-
- d[i[0]] = make(dataTree)
- d[i[0]].(dataTree).mount(i[1:], v)
-}
-
-// parse incoming dataTree request into JSON (including multipart form dataTree)
-func parseUploads(r *http.Request, cfg *UploadsConfig) (*Uploads, error) {
- 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, nil
-}
-
-// exists if file exists.
-func exists(path string) bool {
- _, err := os.Stat(path)
- if err == nil {
- return true
- }
-
- if os.IsNotExist(err) {
- return false
- }
-
- return false
-}
-
-// pushes new file upload into it's proper place.
-func (d fileTree) push(k string, v []*FileUpload) {
- if len(v) == 0 {
- // skip empty values
- return
- }
-
- indexes := make([]string, 0)
- for _, index := range strings.Split(k, "[") {
- indexes = append(indexes, strings.Trim(index, "]"))
- }
-
- if len(indexes) <= maxLevel {
- d.mount(indexes, v)
- }
-}
-
-// mount mounts data tree recursively.
-func (d fileTree) mount(i []string, v []*FileUpload) {
- if len(v) == 0 {
- return
- }
-
- 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)
- }
-
- d[i[0]] = make(fileTree)
- d[i[0]].(fileTree).mount(i[1:], v)
-}
diff --git a/service/http/request.go b/service/http/request.go
deleted file mode 100644
index c7304c8d..00000000
--- a/service/http/request.go
+++ /dev/null
@@ -1,137 +0,0 @@
-package http
-
-import (
- "encoding/json"
- "fmt"
- "github.com/spiral/roadrunner"
- "io/ioutil"
- "net/http"
- "strings"
-)
-
-const (
- defaultMaxMemory = 32 << 20 // 32 MB
-)
-
-// Request maps net/http requests to PSR7 compatible structure and managed state of temporary uploaded files.
-type Request struct {
- // 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"`
-
- // Headers contains list of request headers.
- Headers 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"`
-
- // request body can be parsedData or []byte
- body interface{}
-}
-
-// NewRequest creates new PSR7 compatible request using net/http request.
-func NewRequest(r *http.Request, cfg *UploadsConfig) (req *Request, err error) {
- req = &Request{
- Protocol: r.Proto,
- Method: r.Method,
- Uri: uri(r),
- Headers: r.Header,
- Cookies: make(map[string]string),
- RawQuery: r.URL.RawQuery,
- }
-
- for _, c := range r.Cookies() {
- req.Cookies[c.Name] = c.Value
- }
-
- if !req.parsable() {
- req.body, err = ioutil.ReadAll(r.Body)
- return req, err
- }
-
- if err = r.ParseMultipartForm(defaultMaxMemory); err != nil {
- return nil, err
- }
-
- if req.body, err = parseData(r); err != nil {
- return nil, err
- }
-
- if req.Uploads, err = parseUploads(r, cfg); err != nil {
- return nil, err
- }
-
- 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() error {
- if r.Uploads == nil {
- return nil
- }
-
- return r.Uploads.Open()
-}
-
-// Close clears all temp file uploads
-func (r *Request) Close() {
- if r.Uploads == nil {
- return
- }
-
- r.Uploads.Clear()
-}
-
-// Payload request marshaled RoadRunner payload based on PSR7 data. Default 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{}
-
- if p.Context, err = json.Marshal(r); err != nil {
- return nil, err
- }
-
- if r.Parsed {
- if p.Body, err = json.Marshal(r.body); err != nil {
- return nil, err
- }
- } else if r.body != nil {
- p.Body = r.body.([]byte)
- }
-
- return p, nil
-}
-
-// parsable returns true if request payload can be parsed (POST dataTree, file tree).
-func (r *Request) parsable() bool {
- if r.Method != "POST" && r.Method != "PUT" && r.Method != "PATCH" {
- return false
- }
-
- ct := r.Headers.Get("content-type")
- return strings.Contains(ct, "multipart/form-data") || ct == "application/x-www-form-urlencoded"
-}
-
-// 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.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 dd092353..00000000
--- a/service/http/response.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package http
-
-import (
- "encoding/json"
- "github.com/spiral/roadrunner"
- "net/http"
- "io"
-)
-
-// Response handles PSR7 response logic.
-type Response struct {
- // Status contains response status.
- Status int `json:"status"`
-
- // Headers contains list of response headers.
- Headers map[string][]string `json:"headers"`
-
- // associated body payload.
- body interface{}
-}
-
-// NewResponse creates new response based on given roadrunner payload.
-func NewResponse(p *roadrunner.Payload) (*Response, error) {
- r := &Response{body: p.Body}
- if err := json.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 {
- for k, v := range r.Headers {
- for _, h := range v {
- w.Header().Add(k, h)
-
- }
- }
-
- w.WriteHeader(r.Status)
-
- if data, ok := r.body.([]byte); ok {
- w.Write(data)
- }
-
- if rc, ok := r.body.(io.Reader); ok {
- if _, err := io.Copy(w, rc); err != nil {
- return err
- }
- }
-
- return nil
-}
diff --git a/service/http/rpc.go b/service/http/rpc.go
deleted file mode 100644
index 673ff2bb..00000000
--- a/service/http/rpc.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package http
-
-import (
- "github.com/sirupsen/logrus"
- "github.com/spiral/roadrunner/_____/utils"
- "github.com/pkg/errors"
-)
-
-type rpcServer struct {
- service *Service
-}
-
-// WorkerList contains list of workers.
-type WorkerList struct {
- // Workers is list of workers.
- Workers []utils.Worker `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.service.srv == nil {
- return errors.New("no http server")
- }
-
- logrus.Info("http: restarting worker pool")
- *r = "OK"
-
- err := rpc.service.srv.rr.Reset()
- if err != nil {
- logrus.Errorf("http: %s", err)
- }
-
- return err
-}
-
-// Workers returns list of active workers and their stats.
-func (rpc *rpcServer) Workers(list bool, r *WorkerList) error {
- if rpc.service.srv == nil {
- return errors.New("no http server")
- }
-
- r.Workers = utils.FetchWorkers(rpc.service.srv.rr)
- return nil
-}
-
-// Worker provides information about specific worker.
-type Worker struct {
- // Pid contains process id.
- Pid int `json:"pid"`
-
- // Status of the worker.
- Status string `json:"status"`
-
- // Number of worker executions.
- NumExecs uint64 `json:"numExecs"`
-
- // Created is unix nano timestamp of worker creation time.
- Created int64 `json:"created"`
-
- // Updated is unix nano timestamp of last worker execution.
- Updated int64 `json:"updated"`
-}
-
-// FetchWorkers fetches list of workers from RR Server.
-func FetchWorkers(srv *roadrunner.Server) (result []Worker) {
- for _, w := range srv.Workers() {
- state := w.State()
- result = append(result, Worker{
- Pid: *w.Pid,
- Status: state.String(),
- NumExecs: state.NumExecs(),
- Created: w.Created.UnixNano(),
- Updated: state.Updated().UnixNano(),
- })
- }
-
- return
-} \ No newline at end of file
diff --git a/service/http/server.go b/service/http/server.go
deleted file mode 100644
index 178980a7..00000000
--- a/service/http/server.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package http
-
-import (
- "net/http"
- "strconv"
- "github.com/spiral/roadrunner"
- "github.com/pkg/errors"
-)
-
-// Server 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 Server struct {
- cfg *Config
- listener func(event int, ctx interface{})
- rr *roadrunner.Server
-}
-
-// Listen attaches pool event watcher.
-func (s *Server) Listen(l func(event int, ctx interface{})) {
- s.listener = l
-}
-
-// Handle serve using PSR-7 requests passed to underlying application. Attempts to serve static files first if enabled.
-func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- // validating request size
- if s.cfg.MaxRequest != 0 {
- if length := r.Header.Get("content-length"); length != "" {
- if size, err := strconv.ParseInt(length, 10, 64); err != nil {
- s.handleError(w, r, err)
- return
- } else if size > s.cfg.MaxRequest {
- s.handleError(w, r, errors.New("request body max size is exceeded"))
- return
- }
- }
- }
-
- req, err := NewRequest(r, s.cfg.Uploads)
- if err != nil {
- s.handleError(w, r, err)
- return
- }
-
- if err = req.Open(); err != nil {
- s.handleError(w, r, err)
- return
- }
- defer req.Close()
-
- p, err := req.Payload()
- if err != nil {
- s.handleError(w, r, err)
- return
- }
-
- rsp, err := s.rr.Exec(p)
- if err != nil {
- s.handleError(w, r, err)
- return
- }
-
- resp, err := NewResponse(rsp)
- if err != nil {
- s.handleError(w, r, err)
- return
- }
-
- resp.Write(w)
-}
-
-// handleError sends error
-func (s *Server) handleError(w http.ResponseWriter, r *http.Request, err error) {
- s.throw(2332323, err)
-
- w.WriteHeader(500)
- w.Write([]byte(err.Error()))
-}
-
-// throw invokes event srv if any.
-func (s *Server) throw(event int, ctx interface{}) {
- if s.listener != nil {
- s.listener(event, ctx)
- }
-}
diff --git a/service/http/service.go b/service/http/service.go
deleted file mode 100644
index c31c4a47..00000000
--- a/service/http/service.go
+++ /dev/null
@@ -1,103 +0,0 @@
-package http
-
-import (
- "net/http"
- "github.com/spiral/roadrunner/service"
- "context"
- "github.com/spiral/roadrunner"
-)
-
-// Name contains default service name.
-const Name = "http"
-
-type Middleware interface {
- // Handle must return true if request/response pair is handled withing the middleware.
- Handle(w http.ResponseWriter, r *http.Request) bool
-}
-
-// Service manages rr, http servers.
-type Service struct {
- cfg *Config
- listener func(event int, ctx interface{})
- middleware []Middleware
- rr *roadrunner.Server
- srv *Server
- http *http.Server
-}
-
-func (s *Service) Add(m Middleware) {
- s.middleware = append(s.middleware, m)
-}
-
-// Listen attaches server event watcher.
-func (s *Service) Listen(o func(event int, ctx interface{})) {
- s.listener = o
-}
-
-// Configure must return configure service and return true if service hasStatus enabled. Must return error in case of
-// misconfiguration. Services must not be used without proper configuration pushed first.
-func (s *Service) Configure(cfg service.Config, c service.Container) (bool, error) {
- config := &Config{}
- if err := cfg.Unmarshal(config); err != nil {
- return false, err
- }
-
- if !config.Enable {
- return false, nil
- }
-
- if err := config.Valid(); err != nil {
- return false, err
- }
-
- s.cfg = config
- return true, nil
-}
-
-// Serve serves the service.
-func (s *Service) Serve() error {
- rr := roadrunner.NewServer(s.cfg.Workers)
- if err := rr.Start(); err != nil {
- return err
- }
- defer s.rr.Stop()
-
- s.rr = rr
- s.srv = &Server{cfg: s.cfg, rr: s.rr}
- s.http = &http.Server{Addr: s.cfg.httpAddr()}
-
- s.rr.Listen(s.listener)
- s.srv.Listen(s.listener)
-
- if len(s.middleware) == 0 {
- s.http.Handler = s.srv
- } else {
- s.http.Handler = s
- }
-
- if err := s.http.ListenAndServe(); err != nil {
- return err
- }
-
- return nil
-}
-
-// Stop stops the service.
-func (s *Service) Stop() {
- if s.http == nil {
- return
- }
-
- s.http.Shutdown(context.Background())
-}
-
-// Handle handles connection using set of middleware and rr PSR-7 server.
-func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- for _, m := range s.middleware {
- if m.Handle(w, r) {
- return
- }
- }
-
- s.srv.ServeHTTP(w, r)
-}
diff --git a/service/http/uploads.go b/service/http/uploads.go
deleted file mode 100644
index 62167a6c..00000000
--- a/service/http/uploads.go
+++ /dev/null
@@ -1,130 +0,0 @@
-package http
-
-import (
- "encoding/json"
- "os"
- "sync"
- "mime/multipart"
- "io/ioutil"
- "io"
-)
-
-const (
- // There is no error, the file uploaded with success.
- UploadErrorOK = 0
-
- // No file was uploaded.
- UploadErrorNoFile = 4
-
- // Missing a temporary folder.
- UploadErrorNoTmpDir = 5
-
- // Failed to write file to disk.
- UploadErrorCantWrite = 6
-
- // Forbid file extension.
- UploadErrorExtension = 7
-)
-
-// 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) {
- return json.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() error {
- var wg sync.WaitGroup
- for _, f := range u.list {
- wg.Add(1)
- go func(f *FileUpload) {
- defer wg.Done()
- f.Open(u.cfg)
- }(f)
- }
-
- wg.Wait()
- return nil
-}
-
-// Clear deletes all temporary files.
-func (u *Uploads) Clear() {
- for _, f := range u.list {
- if f.TempFilename != "" && exists(f.TempFilename) {
- os.Remove(f.TempFilename)
- }
- }
-}
-
-// FileUpload represents singular file NewUpload.
-type FileUpload struct {
- // Name contains filename specified by the client.
- Name string `json:"name"`
-
- // MimeType contains mime-type provided by the client.
- MimeType string `json:"type"`
-
- // 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,
- MimeType: f.Header.Get("Content-Type"),
- Error: UploadErrorOK,
- header: f,
- }
-}
-
-func (f *FileUpload) Open(cfg *UploadsConfig) 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 file.Close()
-
- tmp, err := ioutil.TempFile(cfg.Dir, "upload")
- if err != nil {
- // most likely cause of this issue is missing tmp dir
- f.Error = UploadErrorNoTmpDir
- return err
- }
-
- f.TempFilename = tmp.Name()
- defer tmp.Close()
-
- if f.Size, err = io.Copy(tmp, file); err != nil {
- f.Error = UploadErrorCantWrite
- }
-
- return err
-}
diff --git a/service/http/uploads_config.go b/service/http/uploads_config.go
deleted file mode 100644
index ac80723f..00000000
--- a/service/http/uploads_config.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package http
-
-import (
- "strings"
- "path"
-)
-
-// 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
-}
-
-// Forbid 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 e2de97f2..00000000
--- a/service/http/uploads_config_test.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package http
-
-import (
- "testing"
- "github.com/stretchr/testify/assert"
-)
-
-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"))
-}
diff --git a/service/rpc/config.go b/service/rpc/config.go
deleted file mode 100644
index 8a34752a..00000000
--- a/service/rpc/config.go
+++ /dev/null
@@ -1,35 +0,0 @@
-package rpc
-
-import (
- "errors"
- "net"
- "strings"
-)
-
-type config struct {
- // Indicates if RPC connection is enabled.
- Enable bool
-
- // Listen string
- Listen string
-}
-
-// listener creates new rpc socket listener.
-func (cfg *config) listener() (net.Listener, error) {
- dsn := strings.Split(cfg.Listen, "://")
- if len(dsn) != 2 {
- return nil, errors.New("invalid socket DSN (tcp://:6001, unix://rpc.sock)")
- }
-
- return net.Listen(dsn[0], dsn[1])
-}
-
-// dialer creates rpc socket dialer.
-func (cfg *config) dialer() (net.Conn, error) {
- dsn := strings.Split(cfg.Listen, "://")
- if len(dsn) != 2 {
- return nil, errors.New("invalid socket DSN (tcp://:6001, unix://rpc.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 a953e30e..00000000
--- a/service/rpc/config_test.go
+++ /dev/null
@@ -1,109 +0,0 @@
-package rpc
-
-import (
- "github.com/stretchr/testify/assert"
- "runtime"
- "testing"
-)
-
-func TestConfig_Listener(t *testing.T) {
- cfg := &config{Listen: "tcp://:18001"}
-
- ln, err := cfg.listener()
- assert.NoError(t, err)
- assert.NotNil(t, ln)
- defer ln.Close()
-
- assert.Equal(t, "tcp", ln.Addr().Network())
- assert.Equal(t, "[::]:18001", ln.Addr().String())
-}
-
-func TestConfig_ListenerUnix(t *testing.T) {
- if runtime.GOOS == "windows" {
- t.Skip("not supported on " + runtime.GOOS)
- }
-
- cfg := &config{Listen: "unix://rpc.sock"}
-
- ln, err := cfg.listener()
- assert.NoError(t, err)
- assert.NotNil(t, ln)
- defer ln.Close()
-
- assert.Equal(t, "unix", ln.Addr().Network())
- assert.Equal(t, "rpc.sock", ln.Addr().String())
-}
-
-func Test_Config_Error(t *testing.T) {
- if runtime.GOOS == "windows" {
- t.Skip("not supported on " + runtime.GOOS)
- }
-
- cfg := &config{Listen: "uni:unix.sock"}
- ln, err := cfg.listener()
- assert.Nil(t, ln)
- assert.Error(t, err)
- assert.Equal(t, "invalid socket DSN (tcp://:6001, unix://rpc.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, err := cfg.listener()
- defer ln.Close()
-
- conn, err := cfg.dialer()
- assert.NoError(t, err)
- assert.NotNil(t, conn)
- defer conn.Close()
-
- assert.Equal(t, "tcp", conn.RemoteAddr().Network())
- assert.Equal(t, "127.0.0.1:18001", conn.RemoteAddr().String())
-}
-
-func TestConfig_DialerUnix(t *testing.T) {
- if runtime.GOOS == "windows" {
- t.Skip("not supported on " + runtime.GOOS)
- }
-
- cfg := &config{Listen: "unix://rpc.sock"}
-
- ln, err := cfg.listener()
- defer ln.Close()
-
- conn, err := cfg.dialer()
- assert.NoError(t, err)
- assert.NotNil(t, conn)
- defer conn.Close()
-
- assert.Equal(t, "unix", conn.RemoteAddr().Network())
- assert.Equal(t, "rpc.sock", conn.RemoteAddr().String())
-}
-
-func Test_Config_DialerError(t *testing.T) {
- if runtime.GOOS == "windows" {
- t.Skip("not supported on " + runtime.GOOS)
- }
-
- 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://rpc.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)
-}
diff --git a/service/rpc/service.go b/service/rpc/service.go
deleted file mode 100644
index ce1e3351..00000000
--- a/service/rpc/service.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package rpc
-
-import (
- "errors"
- "github.com/spiral/goridge"
- "github.com/spiral/roadrunner/service"
- "net/rpc"
- "sync"
-)
-
-// Name contains default service name.
-const Name = "rpc"
-
-// Service is RPC service.
-type Service struct {
- cfg *config
- stop chan interface{}
- rpc *rpc.Server
-
- mu sync.Mutex
- serving bool
-}
-
-// Configure must return configure service and return true if service hasStatus enabled. Must return error in case of
-// misconfiguration. Services must not be used without proper configuration pushed first.
-func (s *Service) Configure(cfg service.Config, reg service.Container) (enabled bool, err error) {
- config := &config{}
- if err := cfg.Unmarshal(config); err != nil {
- return false, err
- }
-
- if !config.Enable {
- return false, nil
- }
-
- s.cfg = config
- s.rpc = rpc.NewServer()
-
- 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:
- break
- 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, rcvr interface{}) error {
- if s.rpc == nil {
- return errors.New("RPC service is not configured")
- }
-
- return s.rpc.RegisterName(name, rcvr)
-}
-
-// 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 a57ce1bd..00000000
--- a/service/rpc/service_test.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package rpc
-
-import (
- "encoding/json"
- "github.com/spiral/roadrunner/service"
- "github.com/stretchr/testify/assert"
- "testing"
-)
-
-type testService struct{}
-
-func (ts *testService) Echo(msg string, r *string) error { *r = msg; return nil }
-
-type testCfg struct{ cfg string }
-
-func (cfg *testCfg) Get(name string) service.Config { return nil }
-func (cfg *testCfg) Unmarshal(out interface{}) error { return json.Unmarshal([]byte(cfg.cfg), out) }
-
-func Test_ConfigError(t *testing.T) {
- s := &Service{}
- ok, err := s.Configure(&testCfg{`{"enable":false`}, nil)
-
- assert.Error(t, err)
- assert.False(t, ok)
-}
-
-func Test_Disabled(t *testing.T) {
- s := &Service{}
- ok, err := s.Configure(&testCfg{`{"enable":false}`}, 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.Configure(&testCfg{`{"enable":true, "listen":"tcp://localhost:9008"}`}, nil)
-
- assert.NoError(t, err)
- assert.True(t, ok)
-}
-
-func Test_StopNonServing(t *testing.T) {
- s := &Service{}
- ok, err := s.Configure(&testCfg{`{"enable":true, "listen":"tcp://localhost:9008"}`}, nil)
-
- assert.NoError(t, err)
- assert.True(t, ok)
- s.Stop()
-}
-
-func Test_Serve_Errors(t *testing.T) {
- s := &Service{}
- ok, err := s.Configure(&testCfg{`{"enable":true, "listen":"mailformed"}`}, 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.Configure(&testCfg{`{"enable":true, "listen":"tcp://localhost:9008"}`}, 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()) }()
-
- client, err := s.Client()
- assert.NotNil(t, client)
- assert.NoError(t, err)
- defer client.Close()
-
- var resp string
- assert.NoError(t, client.Call("test.Echo", "hello world", &resp))
- assert.Equal(t, "hello world", resp)
-}
diff --git a/service/static/config.go b/service/static/config.go
deleted file mode 100644
index 2a1f6c13..00000000
--- a/service/static/config.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package static
-
-import (
- "strings"
- "path"
- "os"
- "github.com/pkg/errors"
-)
-
-// Config describes file location and controls access to them.
-type Config struct {
- // Enables StaticFile service.
- Enable bool
-
- // Dir contains name of directory to control access to.
- Dir string
-
- // Forbid specifies list of file extensions which are forbidden for access.
- // Example: .php, .exe, .bat, .htaccess and etc.
- Forbid []string
-}
-
-// Forbid must return true if file extension is not allowed for the upload.
-func (cfg *Config) Forbids(filename string) bool {
- ext := strings.ToLower(path.Ext(filename))
-
- for _, v := range cfg.Forbid {
- if ext == v {
- return true
- }
- }
-
- return false
-}
-
-// Valid validates existence of directory.
-func (cfg *Config) Valid() error {
- st, err := os.Stat(cfg.Dir)
- if err != nil {
- if os.IsNotExist(err) {
- return errors.New("root directory does not exists")
- }
-
- return err
- }
-
- if !st.IsDir() {
- return errors.New("invalid root directory")
- }
-
- return nil
-}
diff --git a/service/static/config_test.go b/service/static/config_test.go
deleted file mode 100644
index ce31348a..00000000
--- a/service/static/config_test.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package static
-
-import (
- "testing"
- "github.com/stretchr/testify/assert"
-)
-
-func TestConfig_Forbids(t *testing.T) {
- cfg := Config{Forbid: []string{".php"}}
-
- assert.True(t, cfg.Forbids("index.php"))
- assert.True(t, cfg.Forbids("index.PHP"))
- assert.True(t, cfg.Forbids("phpadmin/index.bak.php"))
- assert.False(t, cfg.Forbids("index.html"))
-}
-
-func TestConfig_Valid(t *testing.T) {
- assert.NoError(t, (&Config{Dir: "./"}).Valid())
- assert.Error(t, (&Config{Dir: "./config.go"}).Valid())
- assert.Error(t, (&Config{Dir: "./dir/"}).Valid())
-}
diff --git a/service/static/service.go b/service/static/service.go
deleted file mode 100644
index 916c18a2..00000000
--- a/service/static/service.go
+++ /dev/null
@@ -1,120 +0,0 @@
-package static
-
-import (
- "github.com/sirupsen/logrus"
- "net/http"
- "os"
- "path"
- "strings"
- rrttp "github.com/spiral/roadrunner/service/http"
- "github.com/spiral/roadrunner/service"
-)
-
-// Name contains default service name.
-const Name = "static-server"
-
-// Service serves static files. Potentially convert into middleware?
-type Service struct {
- // Logger is associated debug and error logger. Can be empty.
- Logger *logrus.Logger
-
- // server configuration (location, forbidden files and etc)
- cfg *Config
-
- // root is initiated http directory
- root http.Dir
-
- // let's service stay running
- done chan interface{}
-}
-
-// Configure must return configure service and return true if service hasStatus enabled. Must return error in case of
-// misconfiguration. Services must not be used without proper configuration pushed first.
-func (s *Service) Configure(cfg service.Config, c service.Container) (enabled bool, err error) {
- config := &Config{}
- if err := cfg.Unmarshal(config); err != nil {
- return false, err
- }
-
- if !config.Enable {
- return false, nil
- }
-
- if err := config.Valid(); err != nil {
- return false, err
- }
-
- s.cfg = config
- s.root = http.Dir(s.cfg.Dir)
-
- // registering as middleware
- if h, ok := c.Get(rrttp.Name); ok >= service.StatusConfigured {
- if h, ok := h.(*rrttp.Service); ok {
- h.Add(s)
- }
- } else {
- if s.Logger != nil {
- s.Logger.Warningf("no http service found")
- }
- }
-
- return true, nil
-}
-
-// Serve serves the service.
-func (s *Service) Serve() error {
- s.done = make(chan interface{})
- <-s.done
-
- return nil
-}
-
-// Stop stops the service.
-func (s *Service) Stop() {
- //todo: this is not safe (TODO CHECK IT?)
- close(s.done)
-}
-
-// Handle must return true if request/response pair is handled withing the middleware.
-func (s *Service) Handle(w http.ResponseWriter, r *http.Request) bool {
- fPath := r.URL.Path
- if !strings.HasPrefix(fPath, "/") {
- fPath = "/" + fPath
- }
- fPath = path.Clean(fPath)
-
- if s.cfg.Forbids(fPath) {
- if s.Logger != nil {
- s.Logger.Warningf("attempt to access forbidden file %s", fPath)
- }
- return false
- }
-
- f, err := s.root.Open(fPath)
- if err != nil {
- if !os.IsNotExist(err) {
- if s.Logger != nil {
- s.Logger.Error(err)
- }
- }
-
- return false
- }
- defer f.Close()
-
- d, err := f.Stat()
- if err != nil {
- if s.Logger != nil {
- s.Logger.Error(err)
- }
- return false
- }
-
- // do not Handle directories
- if d.IsDir() {
- return false
- }
-
- http.ServeContent(w, r, d.Name(), d.ModTime(), f)
- return true
-}