summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWolfy-J <[email protected]>2018-09-30 17:58:16 +0300
committerWolfy-J <[email protected]>2018-09-30 17:58:16 +0300
commit734fab795eb5ee396ee76955c9ddadc4f3b09112 (patch)
treecec3a313fe207c3f91cd142d6d37202ef4db9494
parent6122fca108c20984732c969fb1ba53cce5b3c44a (diff)
https and http2 support
-rw-r--r--cmd/rr/.rr.yaml20
-rw-r--r--server_config.go1
-rw-r--r--service/env/service.go11
-rw-r--r--service/http/config.go48
-rw-r--r--service/http/fixtures/server.crt15
-rw-r--r--service/http/fixtures/server.key9
-rw-r--r--service/http/handler.go2
-rw-r--r--service/http/response.go6
-rw-r--r--service/http/rpc.go4
-rw-r--r--service/http/service.go91
-rw-r--r--service/rpc/service.go12
-rw-r--r--service/static/config.go7
-rw-r--r--service/static/service.go2
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
}