diff options
author | Wolfy-J <[email protected]> | 2019-06-24 13:35:04 +0300 |
---|---|---|
committer | Wolfy-J <[email protected]> | 2019-06-24 13:35:04 +0300 |
commit | 5dc7dd6b231ccea05ffbec0df47ecaa866192308 (patch) | |
tree | 7959b582590bfbe15205480e64de2f14de2d6b47 | |
parent | 464baf2eb7bd87ed80332280e8f73faa2d495746 (diff) |
polishing fastcgi integration, polishing headers service (splitted from http)
-rw-r--r-- | .rr.yaml | 52 | ||||
-rw-r--r-- | service/headers/config.go | 41 | ||||
-rw-r--r-- | service/headers/service.go | 115 | ||||
-rw-r--r-- | service/http/config.go | 61 | ||||
-rw-r--r-- | service/http/service.go | 106 | ||||
-rw-r--r-- | util/network.go | 4 |
6 files changed, 213 insertions, 166 deletions
@@ -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 +} |