summaryrefslogtreecommitdiff
path: root/http/server.go
diff options
context:
space:
mode:
Diffstat (limited to 'http/server.go')
-rw-r--r--http/server.go129
1 files changed, 129 insertions, 0 deletions
diff --git a/http/server.go b/http/server.go
new file mode 100644
index 00000000..b0d0b56a
--- /dev/null
+++ b/http/server.go
@@ -0,0 +1,129 @@
+package http
+
+import (
+ "github.com/spiral/roadrunner"
+ "net/http"
+ "strings"
+ "path"
+ "github.com/sirupsen/logrus"
+ "os"
+ "path/filepath"
+ "encoding/json"
+)
+
+var (
+ excludeFiles = []string{".php", ".htaccess"}
+)
+
+// Configures http rr
+type Config struct {
+ // ServeStatic enables static file serving from desired root directory.
+ ServeStatic bool
+
+ // Root directory, required when ServeStatic set to true.
+ Root string
+}
+
+// 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 data (if any) - todo: do we need to do that?.
+type Server struct {
+ cfg Config
+ root http.Dir
+ rr *roadrunner.Server
+}
+
+// NewServer returns new instance of Server PSR7 server.
+func NewServer(cfg Config, server *roadrunner.Server) *Server {
+ h := &Server{cfg: cfg, rr: server}
+ if cfg.ServeStatic {
+ h.root = http.Dir(h.cfg.Root)
+ }
+
+ return h
+}
+
+// ServeHTTP serve using PSR-7 requests passed to underlying application.
+func (svr *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) () {
+ if svr.cfg.ServeStatic && svr.serveStatic(w, r) {
+ // server always attempt to serve static files first
+ return
+ }
+
+ req, err := ParseRequest(r)
+ if err != nil {
+ w.WriteHeader(500)
+ w.Write([]byte(err.Error())) //todo: better errors
+ return
+ }
+ defer req.Close()
+
+ rsp, err := svr.rr.Exec(req.Payload())
+ if err != nil {
+ w.WriteHeader(500)
+ w.Write([]byte(err.Error())) //todo: better errors
+ return
+ }
+
+ resp := &Response{}
+ if err = json.Unmarshal(rsp.Context, resp); err != nil {
+ w.WriteHeader(500)
+ w.Write([]byte(err.Error())) //todo: better errors
+ return
+ }
+
+ resp.Write(w)
+ w.Write(rsp.Body)
+}
+
+// serveStatic attempts to serve static file and returns true in case of success, will return false in case if file not
+// found, not allowed or on read error.
+func (svr *Server) serveStatic(w http.ResponseWriter, r *http.Request) bool {
+ fpath := r.URL.Path
+ if !strings.HasPrefix(fpath, "/") {
+ fpath = "/" + fpath
+ }
+ fpath = path.Clean(fpath)
+
+ if svr.excluded(fpath) {
+ logrus.Warningf("attempt to access forbidden file %s", fpath)
+ return false
+ }
+
+ f, err := svr.root.Open(fpath)
+ if err != nil {
+ if !os.IsNotExist(err) {
+ // rr or access error
+ logrus.Error(err)
+ }
+
+ return false
+ }
+ defer f.Close()
+
+ d, err := f.Stat()
+ if err != nil {
+ // rr error
+ logrus.Error(err)
+ return false
+ }
+
+ if d.IsDir() {
+ // we are not serving directories
+ return false
+ }
+
+ http.ServeContent(w, r, d.Name(), d.ModTime(), f)
+ return true
+}
+
+// excluded returns true if file has forbidden extension.
+func (svr *Server) excluded(path string) bool {
+ ext := strings.ToLower(filepath.Ext(path))
+ for _, exl := range excludeFiles {
+ if ext == exl {
+ return true
+ }
+ }
+
+ return false
+}