diff options
author | Valery Piashchynski <[email protected]> | 2020-11-15 13:41:19 +0300 |
---|---|---|
committer | Valery Piashchynski <[email protected]> | 2020-11-15 13:41:19 +0300 |
commit | d890eee1742dc3a0a1c787f7e65d40b1e81a94db (patch) | |
tree | b84dcf3c594d2f80ce6f71fae4300e5bfa63e1a1 /plugins/http/plugin.go | |
parent | 5260639a7e95d4d4a36b46fd4b19f665a0a60ef9 (diff) |
Remove old rr imports
Diffstat (limited to 'plugins/http/plugin.go')
-rw-r--r-- | plugins/http/plugin.go | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/plugins/http/plugin.go b/plugins/http/plugin.go new file mode 100644 index 00000000..7c46c814 --- /dev/null +++ b/plugins/http/plugin.go @@ -0,0 +1,423 @@ +package http + +import ( + "context" + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/http/fcgi" + "net/url" + "strings" + "sync" + + "github.com/spiral/roadrunner/v2/interfaces/log" + "github.com/spiral/roadrunner/v2/plugins/config" + "github.com/spiral/roadrunner/v2/plugins/rpc" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" + "golang.org/x/sys/cpu" +) + +const ( + // ID contains default service name. + ID = "http" + + // EventInitSSL thrown at moment of https initialization. SSL server passed as context. + EventInitSSL = 750 +) + +var couldNotAppendPemError = errors.New("could not append Certs from PEM") + +// http middleware type. +type middleware func(f http.HandlerFunc) http.HandlerFunc + +// Service manages rr, http servers. +type Service struct { + sync.Mutex + sync.WaitGroup + + cfg *Config + log *logrus.Logger + cprod roadrunner.CommandProducer + env env.Environment + lsns []func(event int, ctx interface{}) + mdwr []middleware + + rr *roadrunner.Server + controller roadrunner.Controller + handler *Handler + + http *http.Server + https *http.Server + fcgi *http.Server +} + +// Attach attaches controller. Currently only one controller is supported. +func (s *Service) Attach(w roadrunner.Controller) { + s.controller = w +} + +// ProduceCommands changes the default command generator method +func (s *Service) ProduceCommands(producer roadrunner.CommandProducer) { + s.cprod = producer +} + +// AddMiddleware adds new net/http mdwr. +func (s *Service) AddMiddleware(m middleware) { + s.mdwr = append(s.mdwr, m) +} + +// AddListener attaches server event controller. +func (s *Service) AddListener(l func(event int, ctx interface{})) { + s.lsns = append(s.lsns, l) +} + +// Init must return configure svc and return true if svc 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.Configurer, r rpc.Plugin, log log.Logger) (bool, error) { + s.cfg = cfg + s.log = log + + //if r != nil { + // if err := r.Register(ID, &rpcServer{s}); err != nil { + // return false, err + // } + //} + // + //if !cfg.EnableHTTP() && !cfg.EnableTLS() && !cfg.EnableFCGI() { + // return false, nil + //} + + return true, nil +} + +// Serve serves the svc. +func (s *Service) Serve() error { + s.Lock() + + if s.env != nil { + if err := s.env.Copy(s.cfg.Workers); err != nil { + return nil + } + } + + s.cfg.Workers.CommandProducer = s.cprod + s.cfg.Workers.SetEnv("RR_HTTP", "true") + + s.rr = roadrunner.NewServer(s.cfg.Workers) + s.rr.Listen(s.throw) + + if s.controller != nil { + s.rr.Attach(s.controller) + } + + s.handler = &Handler{cfg: s.cfg, rr: s.rr} + s.handler.Listen(s.throw) + + if s.cfg.EnableHTTP() { + if s.cfg.EnableH2C() { + s.http = &http.Server{Addr: s.cfg.Address, Handler: h2c.NewHandler(s, &http2.Server{})} + } else { + s.http = &http.Server{Addr: s.cfg.Address, Handler: s} + } + } + + if s.cfg.EnableTLS() { + s.https = s.initSSL() + if s.cfg.SSL.RootCA != "" { + err := s.appendRootCa() + if err != nil { + return err + } + } + + if s.cfg.EnableHTTP2() { + if err := s.initHTTP2(); err != nil { + return err + } + } + } + + if s.cfg.EnableFCGI() { + s.fcgi = &http.Server{Handler: s} + } + + s.Unlock() + + if err := s.rr.Start(); err != nil { + return err + } + defer s.rr.Stop() + + err := make(chan error, 3) + + if s.http != nil { + go func() { + httpErr := s.http.ListenAndServe() + if httpErr != nil && httpErr != http.ErrServerClosed { + err <- httpErr + } else { + err <- nil + } + }() + } + + if s.https != nil { + go func() { + httpErr := s.https.ListenAndServeTLS( + s.cfg.SSL.Cert, + s.cfg.SSL.Key, + ) + + if httpErr != nil && httpErr != http.ErrServerClosed { + err <- httpErr + return + } + err <- nil + }() + } + + if s.fcgi != nil { + go func() { + httpErr := s.serveFCGI() + if httpErr != nil && httpErr != http.ErrServerClosed { + err <- httpErr + return + } + err <- nil + }() + } + return <-err +} + +// Stop stops the http. +func (s *Service) Stop() { + s.Lock() + defer s.Unlock() + + if s.fcgi != nil { + s.Add(1) + go func() { + defer s.Done() + err := s.fcgi.Shutdown(context.Background()) + if err != nil && err != http.ErrServerClosed { + // Stop() error + // push error from goroutines to the channel and block unil error or success shutdown or timeout + s.log.Error(fmt.Errorf("error shutting down the fcgi server, error: %v", err)) + return + } + }() + } + + if s.https != nil { + s.Add(1) + go func() { + defer s.Done() + err := s.https.Shutdown(context.Background()) + if err != nil && err != http.ErrServerClosed { + s.log.Error(fmt.Errorf("error shutting down the https server, error: %v", err)) + return + } + }() + } + + if s.http != nil { + s.Add(1) + go func() { + defer s.Done() + err := s.http.Shutdown(context.Background()) + if err != nil && err != http.ErrServerClosed { + s.log.Error(fmt.Errorf("error shutting down the http server, error: %v", err)) + return + } + }() + } + + s.Wait() +} + +// Server returns associated rr server (if any). +func (s *Service) Server() *roadrunner.Server { + s.Lock() + defer s.Unlock() + + return s.rr +} + +// 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 + } + + if s.https != nil && r.TLS != nil { + w.Header().Add("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload") + } + + r = attributes.Init(r) + + // chaining middleware + f := s.handler.ServeHTTP + for _, m := range s.mdwr { + f = m(f) + } + f(w, r) +} + +// append RootCA to the https server TLS config +func (s *Service) appendRootCa() error { + rootCAs, err := x509.SystemCertPool() + if err != nil { + s.throw(EventInitSSL, nil) + return nil + } + if rootCAs == nil { + rootCAs = x509.NewCertPool() + } + + CA, err := ioutil.ReadFile(s.cfg.SSL.RootCA) + if err != nil { + s.throw(EventInitSSL, nil) + return err + } + + // should append our CA cert + ok := rootCAs.AppendCertsFromPEM(CA) + if !ok { + return couldNotAppendPemError + } + config := &tls.Config{ + InsecureSkipVerify: false, + RootCAs: rootCAs, + } + s.http.TLSConfig = config + + return nil +} + +// Init https server +func (s *Service) initSSL() *http.Server { + var topCipherSuites []uint16 + var defaultCipherSuitesTLS13 []uint16 + + hasGCMAsmAMD64 := cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ + hasGCMAsmARM64 := cpu.ARM64.HasAES && cpu.ARM64.HasPMULL + // Keep in sync with crypto/aes/cipher_s390x.go. + hasGCMAsmS390X := cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && (cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM) + + hasGCMAsm := hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X + + if hasGCMAsm { + // If AES-GCM hardware is provided then prioritise AES-GCM + // cipher suites. + topCipherSuites = []uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + } + defaultCipherSuitesTLS13 = []uint16{ + tls.TLS_AES_128_GCM_SHA256, + tls.TLS_CHACHA20_POLY1305_SHA256, + tls.TLS_AES_256_GCM_SHA384, + } + } else { + // Without AES-GCM hardware, we put the ChaCha20-Poly1305 + // cipher suites first. + topCipherSuites = []uint16{ + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + } + defaultCipherSuitesTLS13 = []uint16{ + tls.TLS_CHACHA20_POLY1305_SHA256, + tls.TLS_AES_128_GCM_SHA256, + tls.TLS_AES_256_GCM_SHA384, + } + } + + DefaultCipherSuites := make([]uint16, 0, 22) + DefaultCipherSuites = append(DefaultCipherSuites, topCipherSuites...) + DefaultCipherSuites = append(DefaultCipherSuites, defaultCipherSuitesTLS13...) + + server := &http.Server{ + Addr: s.tlsAddr(s.cfg.Address, true), + Handler: s, + TLSConfig: &tls.Config{ + CurvePreferences: []tls.CurveID{ + tls.CurveP256, + tls.CurveP384, + tls.CurveP521, + tls.X25519, + }, + CipherSuites: DefaultCipherSuites, + MinVersion: tls.VersionTLS12, + PreferServerCipherSuites: true, + }, + } + s.throw(EventInitSSL, server) + + return server +} + +// init http/2 server +func (s *Service) initHTTP2() error { + return http2.ConfigureServer(s.https, &http2.Server{ + MaxConcurrentStreams: s.cfg.HTTP2.MaxConcurrentStreams, + }) +} + +// serveFCGI starts FastCGI server. +func (s *Service) serveFCGI() error { + l, err := util.CreateListener(s.cfg.FCGI.Address) + if err != nil { + return err + } + + err = fcgi.Serve(l, s.fcgi.Handler) + if err != nil { + return err + } + + return nil +} + +// throw handles service, server and pool events. +func (s *Service) throw(event int, ctx interface{}) { + for _, l := range s.lsns { + l(event, ctx) + } + + if event == roadrunner.EventServerFailure { + // underlying rr server is dead + 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 +} |