diff options
author | Wolfy-J <[email protected]> | 2018-09-30 17:58:16 +0300 |
---|---|---|
committer | Wolfy-J <[email protected]> | 2018-09-30 17:58:16 +0300 |
commit | 734fab795eb5ee396ee76955c9ddadc4f3b09112 (patch) | |
tree | cec3a313fe207c3f91cd142d6d37202ef4db9494 | |
parent | 6122fca108c20984732c969fb1ba53cce5b3c44a (diff) |
https and http2 support
-rw-r--r-- | cmd/rr/.rr.yaml | 20 | ||||
-rw-r--r-- | server_config.go | 1 | ||||
-rw-r--r-- | service/env/service.go | 11 | ||||
-rw-r--r-- | service/http/config.go | 48 | ||||
-rw-r--r-- | service/http/fixtures/server.crt | 15 | ||||
-rw-r--r-- | service/http/fixtures/server.key | 9 | ||||
-rw-r--r-- | service/http/handler.go | 2 | ||||
-rw-r--r-- | service/http/response.go | 6 | ||||
-rw-r--r-- | service/http/rpc.go | 4 | ||||
-rw-r--r-- | service/http/service.go | 91 | ||||
-rw-r--r-- | service/rpc/service.go | 12 | ||||
-rw-r--r-- | service/static/config.go | 7 | ||||
-rw-r--r-- | service/static/service.go | 2 |
13 files changed, 170 insertions, 58 deletions
diff --git a/cmd/rr/.rr.yaml b/cmd/rr/.rr.yaml index 401a42da..c428a787 100644 --- a/cmd/rr/.rr.yaml +++ b/cmd/rr/.rr.yaml @@ -18,6 +18,19 @@ http: # http host to listen. address: 0.0.0.0:8080 + ssl: + # custom https port (default 443) + port: 443 + + # force redirect to https connection + redirect: true + + # ssl cert + cert: server.crt + + # ssl private key + key: server.key + # max POST request size, including file uploads in MB. maxRequest: 200 @@ -31,7 +44,7 @@ http: # php worker command. command: "php psr-worker.php pipes" - # connection method (pipes, tcp://:9000, unix://socket.unix). + # connection method (pipes, tcp://:9000, unix://socket.unix). default "pipes" relay: "pipes" # worker pool configuration. @@ -48,11 +61,8 @@ http: # amount of time given to worker to gracefully destruct itself. destroyTimeout: 60 -# static file serving. +# static file serving. remove this section to disable static file serving. static: - # serve http static files - enable: true - # root directory for static file (http would not serve .php and .htaccess files). dir: "public" diff --git a/server_config.go b/server_config.go index 936744a1..db13382b 100644 --- a/server_config.go +++ b/server_config.go @@ -75,6 +75,7 @@ func (cfg *ServerConfig) makeCommand() func() *exec.Cmd { var cmd = strings.Split(cfg.Command, " ") return func() *exec.Cmd { cmd := exec.Command(cmd[0], cmd[1:]...) + cmd.Env = append(os.Environ(), fmt.Sprintf("RR_RELAY=%s", cfg.Relay)) cmd.Env = append(os.Environ(), cfg.env...) return cmd } diff --git a/service/env/service.go b/service/env/service.go index 41e70bee..4d1327d4 100644 --- a/service/env/service.go +++ b/service/env/service.go @@ -1,12 +1,7 @@ package env -const ( - // ID contains default service name. - ID = "env" - - // rrKey contains default env key to indicate than php running in RR mode. - rrKey = "rr" -) +// ID contains default service name. +const ID = "env" // Service provides ability to map _ENV values from config file. type Service struct { @@ -25,7 +20,7 @@ func NewService(defaults map[string]string) *Service { func (s *Service) Init(cfg *Config) (bool, error) { if s.values == nil { s.values = make(map[string]string) - s.values[rrKey] = "yes" + s.values["RR"] = "true" } for k, v := range cfg.Values { diff --git a/service/http/config.go b/service/http/config.go index b11d807c..d9adf727 100644 --- a/service/http/config.go +++ b/service/http/config.go @@ -5,13 +5,30 @@ import ( "github.com/spiral/roadrunner" "github.com/spiral/roadrunner/service" "strings" + "os" + "fmt" ) // Config configures RoadRunner HTTP server. type Config struct { - // Address and port to handle as http server. + // Port and port to handle as http server. Address string + // SSL defines https server options. + SSL struct { + // Port to listen as HTTPS server, defaults to 443. + Port int + + // Redirect when enabled forces all http connections to switch to https. + Redirect bool + + // Key defined private server key. + Key string + + // Cert is https certificate. + Cert string + } + // MaxRequest specified max size for payload body in megabytes, set 0 to unlimited. MaxRequest int64 @@ -22,6 +39,11 @@ type Config struct { Workers *roadrunner.ServerConfig } +// EnableTLS returns true if rr must listen TLS connections. +func (c *Config) EnableTLS() bool { + return c.SSL.Key != "" || c.SSL.Cert != "" +} + // 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 c.Workers == nil { @@ -32,6 +54,10 @@ func (c *Config) Hydrate(cfg service.Config) error { c.Uploads = &UploadsConfig{} } + if c.SSL.Port == 0 { + c.SSL.Port = 443 + } + c.Uploads.InitDefaults() c.Workers.InitDefaults() @@ -67,7 +93,25 @@ func (c *Config) Valid() error { } if !strings.Contains(c.Address, ":") { - return errors.New("mailformed server address") + return errors.New("mailformed http server address") + } + + if c.EnableTLS() { + if _, err := os.Stat(c.SSL.Key); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("key file '%s' does not exists", c.SSL.Key) + } + + return err + } + + if _, err := os.Stat(c.SSL.Cert); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("cert file '%s' does not exists", c.SSL.Key) + } + + return err + } } return nil diff --git a/service/http/fixtures/server.crt b/service/http/fixtures/server.crt new file mode 100644 index 00000000..24d67fd7 --- /dev/null +++ b/service/http/fixtures/server.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICTTCCAdOgAwIBAgIJAOKyUd+llTRKMAoGCCqGSM49BAMCMGMxCzAJBgNVBAYT +AlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2Nv +MRMwEQYDVQQKDApSb2FkUnVubmVyMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTgw +OTMwMTMzNDUzWhcNMjgwOTI3MTMzNDUzWjBjMQswCQYDVQQGEwJVUzETMBEGA1UE +CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzETMBEGA1UECgwK +Um9hZFJ1bm5lcjESMBAGA1UEAwwJbG9jYWxob3N0MHYwEAYHKoZIzj0CAQYFK4EE +ACIDYgAEVnbShsM+l5RR3wfWWmGhzuFGwNzKCk7i9xyobDIyBUxG/UUSfj7KKlUX +puDnDEtF5xXcepl744CyIAYFLOXHb5WqI4jCOzG0o9f/00QQ4bQudJOdbqV910QF +C2vb7Fxro1MwUTAdBgNVHQ4EFgQU9xUexnbB6ORKayA7Pfjzs33otsAwHwYDVR0j +BBgwFoAU9xUexnbB6ORKayA7Pfjzs33otsAwDwYDVR0TAQH/BAUwAwEB/zAKBggq +hkjOPQQDAgNoADBlAjEAue3HhR/MUhxoa9tSDBtOJT3FYbDQswrsdqBTz97CGKst +e7XeZ3HMEvEXy0hGGEMhAjAqcD/4k9vViVppgWFtkk6+NFbm+Kw/QeeAiH5FgFSj +8xQcb+b7nPwNLp3JOkXkVd4= +-----END CERTIFICATE----- diff --git a/service/http/fixtures/server.key b/service/http/fixtures/server.key new file mode 100644 index 00000000..7501dd46 --- /dev/null +++ b/service/http/fixtures/server.key @@ -0,0 +1,9 @@ +-----BEGIN EC PARAMETERS----- +BgUrgQQAIg== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDCQP8utxNbHR6xZOLAJgUhn88r6IrPqmN0MsgGJM/jePB+T9UhkmIU8 +PMm2HeScbcugBwYFK4EEACKhZANiAARWdtKGwz6XlFHfB9ZaYaHO4UbA3MoKTuL3 +HKhsMjIFTEb9RRJ+PsoqVRem4OcMS0XnFdx6mXvjgLIgBgUs5cdvlaojiMI7MbSj +1//TRBDhtC50k51upX3XRAULa9vsXGs= +-----END EC PRIVATE KEY----- diff --git a/service/http/handler.go b/service/http/handler.go index f719c751..d7521959 100644 --- a/service/http/handler.go +++ b/service/http/handler.go @@ -110,7 +110,7 @@ func (h *Handler) handleResponse(req *Request, resp *Response) { h.throw(EventResponse, &ResponseEvent{Request: req, Response: resp}) } -// throw invokes event srv if any. +// throw invokes event handler if any. func (h *Handler) throw(event int, ctx interface{}) { h.mul.Lock() defer h.mul.Unlock() diff --git a/service/http/response.go b/service/http/response.go index d43f514d..4902ee70 100644 --- a/service/http/response.go +++ b/service/http/response.go @@ -31,9 +31,9 @@ func NewResponse(p *roadrunner.Payload) (*Response, error) { // 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) + for n, h := range r.Headers { + for _, v := range h { + w.Header().Add(n, v) } } diff --git a/service/http/rpc.go b/service/http/rpc.go index 08b3f262..3390a93d 100644 --- a/service/http/rpc.go +++ b/service/http/rpc.go @@ -15,7 +15,7 @@ type WorkerList struct { // Reset resets underlying RR worker pool and restarts all of it's workers. func (rpc *rpcServer) Reset(reset bool, r *string) error { - if rpc.svc == nil || rpc.svc.srv == nil { + if rpc.svc == nil || rpc.svc.handler == nil { return errors.New("http server is not running") } @@ -25,7 +25,7 @@ func (rpc *rpcServer) Reset(reset bool, r *string) error { // Workers returns list of active workers and their stats. func (rpc *rpcServer) Workers(list bool, r *WorkerList) (err error) { - if rpc.svc == nil || rpc.svc.srv == nil { + if rpc.svc == nil || rpc.svc.handler == nil { return errors.New("http server is not running") } diff --git a/service/http/service.go b/service/http/service.go index bb75a2c0..bea71d25 100644 --- a/service/http/service.go +++ b/service/http/service.go @@ -9,14 +9,18 @@ import ( "net/http" "sync" "sync/atomic" + "golang.org/x/net/http2" + "strings" + "fmt" + "net/url" ) const ( // ID contains default svc name. ID = "http" - // httpKey indicates to php process that it's running under http service - httpKey = "rr_http" + // EventInitSSL thrown at moment of https initialization. SSL server passed as context. + EventInitSSL = 750 ) // http middleware type. @@ -31,8 +35,9 @@ type Service struct { mu sync.Mutex rr *roadrunner.Server stopping int32 - srv *Handler + handler *Handler http *http.Server + https *http.Server } // AddMiddleware adds new net/http mdwr. @@ -71,28 +76,35 @@ func (s *Service) Serve() error { s.cfg.Workers.SetEnv(k, v) } - s.cfg.Workers.SetEnv(httpKey, "true") + s.cfg.Workers.SetEnv("RR_HTTP", "true") } - rr := roadrunner.NewServer(s.cfg.Workers) + s.rr = roadrunner.NewServer(s.cfg.Workers) + s.rr.Listen(s.throw) - s.rr = rr - s.srv = &Handler{cfg: s.cfg, rr: s.rr} - s.http = &http.Server{Addr: s.cfg.Address} + s.handler = &Handler{cfg: s.cfg, rr: s.rr} + s.handler.Listen(s.throw) - s.rr.Listen(s.listener) - s.srv.Listen(s.listener) + s.http = &http.Server{Addr: s.cfg.Address, Handler: s} - s.http.Handler = s + if s.cfg.EnableTLS() { + s.https = s.initSSL() + } s.mu.Unlock() - if err := rr.Start(); err != nil { + if err := s.rr.Start(); err != nil { return err } - defer rr.Stop() + defer s.rr.Stop() + + err := make(chan error, 2) + go func() { err <- s.http.ListenAndServe() }() + if s.https != nil { + go func() { err <- s.https.ListenAndServeTLS(s.cfg.SSL.Cert, s.cfg.SSL.Key) }() + } - return s.http.ListenAndServe() + return <-err } // Stop stops the svc. @@ -108,23 +120,50 @@ func (s *Service) Stop() { return } - s.http.Shutdown(context.Background()) + if s.https != nil { + go s.https.Shutdown(context.Background()) + } + + go s.http.Shutdown(context.Background()) } -// mdwr handles connection using set of mdwr and rr PSR-7 server. +// ServeHTTP handles connection using set of middleware and rr PSR-7 server. func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if s.https != nil && r.TLS == nil && s.cfg.SSL.Redirect { + target := &url.URL{ + Scheme: "https", + Host: s.tlsAddr(r.Host, false), + Path: r.URL.Path, + RawQuery: r.URL.RawQuery, + } + + http.Redirect(w, r, target.String(), http.StatusTemporaryRedirect) + return + } + r = attributes.Init(r) - // chaining mdwr - f := s.srv.ServeHTTP + // chaining middleware + f := s.handler.ServeHTTP for _, m := range s.mdwr { f = m(f) } f(w, r) } -// listener handles service, server and pool events. -func (s *Service) listener(event int, ctx interface{}) { +// Init https server. +func (s *Service) initSSL() *http.Server { + server := &http.Server{Addr: s.tlsAddr(s.cfg.Address, true), Handler: s} + s.throw(EventInitSSL, server) + + // Enable HTTP/2 support by default + http2.ConfigureServer(server, &http2.Server{}) + + return server +} + +// throw handles service, server and pool events. +func (s *Service) throw(event int, ctx interface{}) { for _, l := range s.lsns { l(event, ctx) } @@ -134,3 +173,15 @@ func (s *Service) listener(event int, ctx interface{}) { s.Stop() } } + +// tlsAddr replaces listen or host port with port configured by SSL config. +func (s *Service) tlsAddr(host string, forcePort bool) string { + // remove current forcePort first + host = strings.Split(host, ":")[0] + + if forcePort || s.cfg.SSL.Port != 443 { + host = fmt.Sprintf("%s:%v", host, s.cfg.SSL.Port) + } + + return host +} diff --git a/service/rpc/service.go b/service/rpc/service.go index 3ea6c5fc..0b957976 100644 --- a/service/rpc/service.go +++ b/service/rpc/service.go @@ -8,14 +8,8 @@ import ( "sync" ) -const ( - // ID contains default service name. - ID = "rpc" - - // rrKey defines environment key to be used to store information about - // rpc server connection. - envKey = "rr_rpc" -) +// ID contains default service name. +const ID = "rpc" // Service is RPC service. type Service struct { @@ -36,7 +30,7 @@ func (s *Service) Init(cfg *Config, env env.Environment) (bool, error) { s.rpc = rpc.NewServer() if env != nil { - env.SetEnv(envKey, cfg.Listen) + env.SetEnv("RR_RPC", cfg.Listen) } return true, nil diff --git a/service/static/config.go b/service/static/config.go index be0ac3ed..5df7b013 100644 --- a/service/static/config.go +++ b/service/static/config.go @@ -10,9 +10,6 @@ import ( // 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 @@ -32,10 +29,6 @@ func (c *Config) Hydrate(cfg service.Config) error { // Valid returns nil if config is valid. func (c *Config) Valid() error { - if !c.Enable { - return nil - } - st, err := os.Stat(c.Dir) if err != nil { if os.IsNotExist(err) { diff --git a/service/static/service.go b/service/static/service.go index 98d8313c..0c101ceb 100644 --- a/service/static/service.go +++ b/service/static/service.go @@ -22,7 +22,7 @@ type Service struct { // 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 *rrttp.Service) (bool, error) { - if !cfg.Enable || r == nil { + if r == nil { return false, nil } |