summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rr.yaml52
-rw-r--r--service/headers/config.go41
-rw-r--r--service/headers/service.go115
-rw-r--r--service/http/config.go61
-rw-r--r--service/http/service.go106
-rw-r--r--util/network.go4
6 files changed, 213 insertions, 166 deletions
diff --git a/.rr.yaml b/.rr.yaml
index 6565a4da..94265867 100644
--- a/.rr.yaml
+++ b/.rr.yaml
@@ -28,28 +28,6 @@ http:
# ssl private key
key: server.key
- # HTTP service provides built-in middlewares
- middlewares:
- # Middleware to handle CORS requests, https://www.w3.org/TR/cors/
- cors:
- # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
- AllowedOrigin: "*"
- # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
- allowedHeaders: "*"
- # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
- allowedMethods: "GET,POST,PUT,DELETE"
- # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
- allowCredentials: true
- # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
- exposedHeaders: "Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma"
- maxAge: 600
- # Middleware that adding Headers to the Request / Response
- headers:
- customRequestHeaders:
- "Example-Request-Header": "Value"
- customResponseHeaders:
- "X-Powered-By": "RoadRunner"
-
# HTTP service provides FastCGI as frontend
fcgi:
# FastCGI connection DSN. Supported TCP and Unix sockets.
@@ -93,6 +71,36 @@ http:
# amount of time given to worker to gracefully destruct itself.
destroyTimeout: 60
+# Additional HTTP headers and CORS control.
+headers:
+ # Middleware to handle CORS requests, https://www.w3.org/TR/cors/
+ cors:
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
+ allowedOrigin: "*"
+
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
+ allowedHeaders: "*"
+
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
+ allowedMethods: "GET,POST,PUT,DELETE"
+
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
+ allowCredentials: true
+
+ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
+ exposedHeaders: "Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma"
+
+ # Max allowed age in seconds
+ maxAge: 600
+
+ # Automatically add headers to every request passed to PHP.
+ request:
+ "Example-Request-Header": "Value"
+
+ # Automatically add headers to every response.
+ response:
+ "X-Powered-By": "RoadRunner"
+
# monitors rr server(s)
limit:
# check worker state each second
diff --git a/service/headers/config.go b/service/headers/config.go
new file mode 100644
index 00000000..bb7a6b2b
--- /dev/null
+++ b/service/headers/config.go
@@ -0,0 +1,41 @@
+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
+}
+
+// CORS 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/service.go b/service/headers/service.go
new file mode 100644
index 00000000..9060e9ff
--- /dev/null
+++ b/service/headers/service.go
@@ -0,0 +1,115 @@
+package headers
+
+import (
+ rrhttp "github.com/spiral/roadrunner/service/http"
+ "net/http"
+ "strconv"
+)
+
+const (
+ // ID contains default service name.
+ ID = "headers"
+)
+
+// Service serves static 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/http/config.go b/service/http/config.go
index e2f42626..b5b18c23 100644
--- a/service/http/config.go
+++ b/service/http/config.go
@@ -18,7 +18,11 @@ type Config struct {
// SSL defines https server options.
SSL SSLConfig
- FCGI FCGIConfig
+ // 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
@@ -30,50 +34,22 @@ type Config struct {
// Uploads configures uploads configuration.
Uploads *UploadsConfig
- // HTTP2 configuration
- HTTP2 *HTTP2Config
-
- // Middlewares
- Middlewares *MiddlewaresConfig
-
// Workers configures rr server and worker pool.
Workers *roadrunner.ServerConfig
}
-type MiddlewaresConfig struct {
- Headers *HeaderMiddlewareConfig
- CORS *CORSMiddlewareConfig
-}
-
-type CORSMiddlewareConfig struct {
- AllowedOrigin string
- AllowedMethods string
- AllowedHeaders string
- AllowCredentials *bool
- ExposedHeaders string
- MaxAge int
-}
-
-type HeaderMiddlewareConfig struct {
- CustomRequestHeaders map[string]string
- CustomResponseHeaders map[string]string
-}
-
-func (c *MiddlewaresConfig) EnableCORS() bool {
- return c.CORS != nil
-}
-
-func (c *MiddlewaresConfig) EnableHeaders() bool {
- return c.Headers.CustomRequestHeaders != nil || c.Headers.CustomResponseHeaders != nil
-}
-
+// FCGIConfig for FastCGI server.
type FCGIConfig struct {
- // Port and port to handle as http server.
+ // 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
+
+ // MaxConcurrentStreams defaults to 128.
MaxConcurrentStreams uint32
}
@@ -99,23 +75,22 @@ type SSLConfig struct {
Cert string
}
+// EnableHTTP is true when http server must run.
func (c *Config) EnableHTTP() bool {
return c.Address != ""
}
-func (c *Config) EnableMiddlewares() bool {
- return c.Middlewares != nil
-}
-
// EnableTLS returns true if rr must listen TLS connections.
func (c *Config) EnableTLS() bool {
return c.SSL.Key != "" || c.SSL.Cert != ""
}
+// EnableHTTP2 when HTTP/2 extension must be enabled (only with TSL).
func (c *Config) EnableHTTP2() bool {
return c.HTTP2.Enabled
}
+// EnableFCGI is true when FastCGI server must be enabled.
func (c *Config) EnableFCGI() bool {
return c.FCGI.Address != ""
}
@@ -130,6 +105,10 @@ func (c *Config) Hydrate(cfg service.Config) error {
c.HTTP2 = &HTTP2Config{}
}
+ if c.FCGI == nil {
+ c.FCGI = &FCGIConfig{}
+ }
+
if c.Uploads == nil {
c.Uploads = &UploadsConfig{}
}
@@ -223,6 +202,10 @@ func (c *Config) Valid() error {
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("mailformed http server address")
}
diff --git a/service/http/service.go b/service/http/service.go
index f394f6af..b3f56480 100644
--- a/service/http/service.go
+++ b/service/http/service.go
@@ -12,13 +12,12 @@ import (
"net/http"
"net/http/fcgi"
"net/url"
- "strconv"
"strings"
"sync"
)
const (
- // ID contains default svc name.
+ // ID contains default service name.
ID = "http"
// EventInitSSL thrown at moment of https initialization. SSL server passed as context.
@@ -40,7 +39,7 @@ type Service struct {
handler *Handler
http *http.Server
https *http.Server
- fcgi *http.Server
+ fcgi *http.Server
}
// Attach attaches controller. Currently only one controller is supported.
@@ -96,10 +95,6 @@ func (s *Service) Serve() error {
s.rr.Attach(s.controller)
}
- if s.cfg.EnableMiddlewares() {
- s.initMiddlewares()
- }
-
s.handler = &Handler{cfg: s.cfg, rr: s.rr}
s.handler.Listen(s.throw)
@@ -178,7 +173,7 @@ func (s *Service) Server() *roadrunner.Server {
}
func (s *Service) ListenAndServeFCGI() error {
- l, err := util.CreateListener(s.cfg.FCGI.Address);
+ l, err := util.CreateListener(s.cfg.FCGI.Address)
if err != nil {
return err
}
@@ -252,98 +247,3 @@ func (s *Service) tlsAddr(host string, forcePort bool) string {
return host
}
-
-func (s *Service) headersMiddleware(f http.HandlerFunc) http.HandlerFunc {
- // Define the http.HandlerFunc
- return func(w http.ResponseWriter, r *http.Request) {
- if s.cfg.Middlewares.Headers.CustomRequestHeaders != nil {
- for k, v := range s.cfg.Middlewares.Headers.CustomRequestHeaders {
- r.Header.Add(k, v)
- }
- }
-
- if s.cfg.Middlewares.Headers.CustomResponseHeaders != nil {
- for k, v := range s.cfg.Middlewares.Headers.CustomResponseHeaders {
- w.Header().Set(k, v)
- }
- }
-
- f(w, r)
- }
-}
-
-func handlePreflightRequest(w http.ResponseWriter, r *http.Request, options *CORSMiddlewareConfig) {
- headers := w.Header()
-
- headers.Add("Vary", "Origin")
- headers.Add("Vary", "Access-Control-Request-Method")
- headers.Add("Vary", "Access-Control-Request-Headers")
-
- if options.AllowedOrigin != "" {
- headers.Set("Access-Control-Allow-Origin", options.AllowedOrigin)
- }
-
- if options.AllowedHeaders != "" {
- headers.Set("Access-Control-Allow-Headers", options.AllowedHeaders)
- }
-
- if options.AllowedMethods != "" {
- headers.Set("Access-Control-Allow-Methods", options.AllowedMethods)
- }
-
- if options.AllowCredentials != nil {
- headers.Set("Access-Control-Allow-Credentials", strconv.FormatBool(*options.AllowCredentials))
- }
-
- if options.MaxAge > 0 {
- headers.Set("Access-Control-Max-Age", strconv.Itoa(options.MaxAge))
- }
-
- w.WriteHeader(http.StatusOK);
-}
-
-func addCORSHeaders(w http.ResponseWriter, r *http.Request, options *CORSMiddlewareConfig) {
- headers := w.Header()
-
- headers.Add("Vary", "Origin")
-
- if options.AllowedOrigin != "" {
- headers.Set("Access-Control-Allow-Origin", options.AllowedOrigin)
- }
-
- if options.AllowedHeaders != "" {
- headers.Set("Access-Control-Allow-Headers", options.AllowedHeaders)
- }
-
- if options.ExposedHeaders != "" {
- headers.Set("Access-Control-Expose-Headers", options.ExposedHeaders)
- }
-
- if options.AllowCredentials != nil {
- headers.Set("Access-Control-Allow-Credentials", strconv.FormatBool(*options.AllowCredentials))
- }
-}
-
-func (s *Service) corsMiddleware(f http.HandlerFunc) http.HandlerFunc {
- // Define the http.HandlerFunc
- return func(w http.ResponseWriter, r *http.Request) {
- if r.Method == http.MethodOptions {
- handlePreflightRequest(w, r, s.cfg.Middlewares.CORS)
- } else {
- addCORSHeaders(w, r, s.cfg.Middlewares.CORS)
- f(w, r)
- }
- }
-}
-
-func (s *Service) initMiddlewares() error {
- if s.cfg.Middlewares.EnableHeaders() {
- s.AddMiddleware(s.headersMiddleware)
- }
-
- if s.cfg.Middlewares.EnableCORS() {
- s.AddMiddleware(s.corsMiddleware)
- }
-
- return nil
-}
diff --git a/util/network.go b/util/network.go
index 4c393c37..510a97a9 100644
--- a/util/network.go
+++ b/util/network.go
@@ -1,10 +1,10 @@
package util
import (
+ "errors"
"net"
"strings"
"syscall"
- "errors"
)
func CreateListener(address string) (net.Listener, error) {
@@ -22,4 +22,4 @@ func CreateListener(address string) (net.Listener, error) {
}
return net.Listen(dsn[0], dsn[1])
-} \ No newline at end of file
+}