summaryrefslogtreecommitdiff
path: root/plugins/http
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/http')
-rw-r--r--plugins/http/attributes/attributes.go89
-rw-r--r--plugins/http/config/fcgi.go7
-rw-r--r--plugins/http/config/http.go187
-rw-r--r--plugins/http/config/http2.go28
-rw-r--r--plugins/http/config/ip.go26
-rw-r--r--plugins/http/config/ssl.go84
-rw-r--r--plugins/http/config/ssl_config_test.go116
-rw-r--r--plugins/http/config/uploads_config.go46
-rw-r--r--plugins/http/metrics.go92
-rw-r--r--plugins/http/plugin.go412
-rw-r--r--plugins/http/serve.go254
11 files changed, 0 insertions, 1341 deletions
diff --git a/plugins/http/attributes/attributes.go b/plugins/http/attributes/attributes.go
deleted file mode 100644
index 243b6c78..00000000
--- a/plugins/http/attributes/attributes.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package attributes
-
-import (
- "context"
- "errors"
- "net/http"
-)
-
-// contextKey is a value for use with context.WithValue. It's used as
-// a pointer fits an interface{} without allocation.
-type contextKey struct {
- name string
-}
-
-func (k *contextKey) String() string { return k.name }
-
-var (
- // PsrContextKey is a context key. It can be used in the http attributes
- PsrContextKey = &contextKey{"psr_attributes"}
-)
-
-type attrs map[string]interface{}
-
-func (v attrs) get(key string) interface{} {
- if v == nil {
- return ""
- }
-
- return v[key]
-}
-
-func (v attrs) set(key string, value interface{}) {
- v[key] = value
-}
-
-func (v attrs) del(key string) {
- delete(v, key)
-}
-
-// Init returns request with new context and attribute bag.
-func Init(r *http.Request) *http.Request {
- // do not overwrite psr attributes
- if val := r.Context().Value(PsrContextKey); val == nil {
- return r.WithContext(context.WithValue(r.Context(), PsrContextKey, attrs{}))
- }
- return r
-}
-
-// All returns all context attributes.
-func All(r *http.Request) map[string]interface{} {
- v := r.Context().Value(PsrContextKey)
- if v == nil {
- return attrs{}
- }
-
- return v.(attrs)
-}
-
-// Get gets the value from request context. It replaces any existing
-// values.
-func Get(r *http.Request, key string) interface{} {
- v := r.Context().Value(PsrContextKey)
- if v == nil {
- return nil
- }
-
- return v.(attrs).get(key)
-}
-
-// Set sets the key to value. It replaces any existing
-// values. Context specific.
-func Set(r *http.Request, key string, value interface{}) error {
- v := r.Context().Value(PsrContextKey)
- if v == nil {
- return errors.New("unable to find `psr:attributes` context key")
- }
-
- v.(attrs).set(key, value)
- return nil
-}
-
-// Delete deletes values associated with attribute key.
-func (v attrs) Delete(key string) {
- if v == nil {
- return
- }
-
- v.del(key)
-}
diff --git a/plugins/http/config/fcgi.go b/plugins/http/config/fcgi.go
deleted file mode 100644
index 3d4acbe1..00000000
--- a/plugins/http/config/fcgi.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package config
-
-// FCGI for FastCGI server.
-type FCGI struct {
- // Address and port to handle as http server.
- Address string
-}
diff --git a/plugins/http/config/http.go b/plugins/http/config/http.go
deleted file mode 100644
index f06adc49..00000000
--- a/plugins/http/config/http.go
+++ /dev/null
@@ -1,187 +0,0 @@
-package config
-
-import (
- "net"
- "runtime"
- "strings"
- "time"
-
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/pkg/pool"
-)
-
-// HTTP configures RoadRunner HTTP server.
-type HTTP struct {
- // Host and port to handle as http server.
- Address string
-
- // InternalErrorCode used to override default 500 (InternalServerError) http code
- InternalErrorCode uint64 `mapstructure:"internal_error_code"`
-
- // SSLConfig defines https server options.
- SSLConfig *SSL `mapstructure:"ssl"`
-
- // FCGIConfig configuration. You can use FastCGI without HTTP server.
- FCGIConfig *FCGI `mapstructure:"fcgi"`
-
- // HTTP2Config configuration
- HTTP2Config *HTTP2 `mapstructure:"http2"`
-
- // MaxRequestSize specified max size for payload body in megabytes, set 0 to unlimited.
- MaxRequestSize uint64 `mapstructure:"max_request_size"`
-
- // TrustedSubnets declare IP subnets which are allowed to set ip using X-Real-Ip and X-Forwarded-For
- TrustedSubnets []string `mapstructure:"trusted_subnets"`
-
- // Uploads configures uploads configuration.
- Uploads *Uploads `mapstructure:"uploads"`
-
- // Pool configures worker pool.
- Pool *pool.Config `mapstructure:"pool"`
-
- // Env is environment variables passed to the http pool
- Env map[string]string
-
- // List of the middleware names (order will be preserved)
- Middleware []string
-
- // slice of net.IPNet
- Cidrs Cidrs
-}
-
-// EnableHTTP is true when http server must run.
-func (c *HTTP) EnableHTTP() bool {
- return c.Address != ""
-}
-
-// EnableTLS returns true if pool must listen TLS connections.
-func (c *HTTP) EnableTLS() bool {
- return c.SSLConfig.Key != "" || c.SSLConfig.Cert != ""
-}
-
-// EnableH2C when HTTP/2 extension must be enabled on TCP.
-func (c *HTTP) EnableH2C() bool {
- return c.HTTP2Config.H2C
-}
-
-// EnableFCGI is true when FastCGI server must be enabled.
-func (c *HTTP) EnableFCGI() bool {
- return c.FCGIConfig.Address != ""
-}
-
-// InitDefaults must populate HTTP values using given HTTP source. Must return error if HTTP is not valid.
-func (c *HTTP) InitDefaults() error {
- if c.Pool == nil {
- // default pool
- c.Pool = &pool.Config{
- Debug: false,
- NumWorkers: uint64(runtime.NumCPU()),
- MaxJobs: 0,
- AllocateTimeout: time.Second * 60,
- DestroyTimeout: time.Second * 60,
- Supervisor: nil,
- }
- }
-
- if c.InternalErrorCode == 0 {
- c.InternalErrorCode = 500
- }
-
- if c.HTTP2Config == nil {
- c.HTTP2Config = &HTTP2{}
- }
-
- if c.FCGIConfig == nil {
- c.FCGIConfig = &FCGI{}
- }
-
- if c.Uploads == nil {
- c.Uploads = &Uploads{}
- }
-
- if c.SSLConfig == nil {
- c.SSLConfig = &SSL{}
- }
-
- if c.SSLConfig.Address == "" {
- c.SSLConfig.Address = "127.0.0.1:443"
- }
-
- err := c.HTTP2Config.InitDefaults()
- if err != nil {
- return err
- }
- err = c.Uploads.InitDefaults()
- if err != nil {
- return err
- }
-
- if c.TrustedSubnets == nil {
- // @see https://en.wikipedia.org/wiki/Reserved_IP_addresses
- c.TrustedSubnets = []string{
- "10.0.0.0/8",
- "127.0.0.0/8",
- "172.16.0.0/12",
- "192.168.0.0/16",
- "::1/128",
- "fc00::/7",
- "fe80::/10",
- }
- }
-
- cidrs, err := ParseCIDRs(c.TrustedSubnets)
- if err != nil {
- return err
- }
- c.Cidrs = cidrs
-
- return c.Valid()
-}
-
-// ParseCIDRs parse IPNet addresses and return slice of its
-func ParseCIDRs(subnets []string) (Cidrs, error) {
- c := make(Cidrs, 0, len(subnets))
- for _, cidr := range subnets {
- _, cr, err := net.ParseCIDR(cidr)
- if err != nil {
- return nil, err
- }
-
- c = append(c, cr)
- }
-
- return c, nil
-}
-
-// Valid validates the configuration.
-func (c *HTTP) Valid() error {
- const op = errors.Op("validation")
- if c.Uploads == nil {
- return errors.E(op, errors.Str("malformed uploads config"))
- }
-
- if c.HTTP2Config == nil {
- return errors.E(op, errors.Str("malformed http2 config"))
- }
-
- if c.Pool == nil {
- return errors.E(op, "malformed pool config")
- }
-
- if !c.EnableHTTP() && !c.EnableTLS() && !c.EnableFCGI() {
- return errors.E(op, errors.Str("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.E(op, errors.Str("malformed http server address"))
- }
-
- if c.EnableTLS() {
- err := c.SSLConfig.Valid()
- if err != nil {
- return errors.E(op, err)
- }
- }
-
- return nil
-}
diff --git a/plugins/http/config/http2.go b/plugins/http/config/http2.go
deleted file mode 100644
index b1e109e9..00000000
--- a/plugins/http/config/http2.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package config
-
-// HTTP2 HTTP/2 server customizations.
-type HTTP2 struct {
- // h2cHandler is a Handler which implements h2c by hijacking the HTTP/1 traffic
- // that should be h2c traffic. There are two ways to begin a h2c connection
- // (RFC 7540 Section 3.2 and 3.4): (1) Starting with Prior Knowledge - this
- // works by starting an h2c connection with a string of bytes that is valid
- // HTTP/1, but unlikely to occur in practice and (2) Upgrading from HTTP/1 to
- // h2c - this works by using the HTTP/1 Upgrade header to request an upgrade to
- // h2c. When either of those situations occur we hijack the HTTP/1 connection,
- // convert it to a HTTP/2 connection and pass the net.Conn to http2.ServeConn.
-
- // H2C enables HTTP/2 over TCP
- H2C bool
-
- // MaxConcurrentStreams defaults to 128.
- MaxConcurrentStreams uint32 `mapstructure:"max_concurrent_streams"`
-}
-
-// InitDefaults sets default values for HTTP/2 configuration.
-func (cfg *HTTP2) InitDefaults() error {
- if cfg.MaxConcurrentStreams == 0 {
- cfg.MaxConcurrentStreams = 128
- }
-
- return nil
-}
diff --git a/plugins/http/config/ip.go b/plugins/http/config/ip.go
deleted file mode 100644
index c4981f74..00000000
--- a/plugins/http/config/ip.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package config
-
-import "net"
-
-// Cidrs is a slice of IPNet addresses
-type Cidrs []*net.IPNet
-
-// IsTrusted checks if the ip address exists in the provided in the config addresses
-func (c *Cidrs) IsTrusted(ip string) bool {
- if len(*c) == 0 {
- return false
- }
-
- i := net.ParseIP(ip)
- if i == nil {
- return false
- }
-
- for _, cird := range *c {
- if cird.Contains(i) {
- return true
- }
- }
-
- return false
-}
diff --git a/plugins/http/config/ssl.go b/plugins/http/config/ssl.go
deleted file mode 100644
index 0e3c0caf..00000000
--- a/plugins/http/config/ssl.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package config
-
-import (
- "os"
- "strconv"
- "strings"
-
- "github.com/spiral/errors"
-)
-
-// SSL defines https server configuration.
-type SSL struct {
- // Address to listen as HTTPS server, defaults to 0.0.0.0:443.
- Address string
-
- // 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
-
- // Root CA file
- RootCA string `mapstructure:"root_ca"`
-
- // internal
- host string
- Port int
-}
-
-func (s *SSL) Valid() error {
- const op = errors.Op("ssl_valid")
-
- parts := strings.Split(s.Address, ":")
- switch len(parts) {
- // :443 form
- // 127.0.0.1:443 form
- // use 0.0.0.0 as host and 443 as port
- case 2:
- if parts[0] == "" {
- s.host = "127.0.0.1"
- } else {
- s.host = parts[0]
- }
-
- port, err := strconv.Atoi(parts[1])
- if err != nil {
- return errors.E(op, err)
- }
- s.Port = port
- default:
- return errors.E(op, errors.Errorf("unknown format, accepted format is [:<port> or <host>:<port>], provided: %s", s.Address))
- }
-
- if _, err := os.Stat(s.Key); err != nil {
- if os.IsNotExist(err) {
- return errors.E(op, errors.Errorf("key file '%s' does not exists", s.Key))
- }
-
- return err
- }
-
- if _, err := os.Stat(s.Cert); err != nil {
- if os.IsNotExist(err) {
- return errors.E(op, errors.Errorf("cert file '%s' does not exists", s.Cert))
- }
-
- return err
- }
-
- // RootCA is optional, but if provided - check it
- if s.RootCA != "" {
- if _, err := os.Stat(s.RootCA); err != nil {
- if os.IsNotExist(err) {
- return errors.E(op, errors.Errorf("root ca path provided, but path '%s' does not exists", s.RootCA))
- }
- return err
- }
- }
-
- return nil
-}
diff --git a/plugins/http/config/ssl_config_test.go b/plugins/http/config/ssl_config_test.go
deleted file mode 100644
index 8f6cf40e..00000000
--- a/plugins/http/config/ssl_config_test.go
+++ /dev/null
@@ -1,116 +0,0 @@
-package config
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestSSL_Valid1(t *testing.T) {
- conf := &SSL{
- Address: "",
- Redirect: false,
- Key: "",
- Cert: "",
- RootCA: "",
- host: "",
- Port: 0,
- }
-
- err := conf.Valid()
- assert.Error(t, err)
-}
-
-func TestSSL_Valid2(t *testing.T) {
- conf := &SSL{
- Address: ":hello",
- Redirect: false,
- Key: "",
- Cert: "",
- RootCA: "",
- host: "",
- Port: 0,
- }
-
- err := conf.Valid()
- assert.Error(t, err)
-}
-
-func TestSSL_Valid3(t *testing.T) {
- conf := &SSL{
- Address: ":555",
- Redirect: false,
- Key: "",
- Cert: "",
- RootCA: "",
- host: "",
- Port: 0,
- }
-
- err := conf.Valid()
- assert.Error(t, err)
-}
-
-func TestSSL_Valid4(t *testing.T) {
- conf := &SSL{
- Address: ":555",
- Redirect: false,
- Key: "../../../tests/plugins/http/fixtures/server.key",
- Cert: "../../../tests/plugins/http/fixtures/server.crt",
- RootCA: "",
- host: "",
- // private
- Port: 0,
- }
-
- err := conf.Valid()
- assert.NoError(t, err)
-}
-
-func TestSSL_Valid5(t *testing.T) {
- conf := &SSL{
- Address: "a:b:c",
- Redirect: false,
- Key: "../../../tests/plugins/http/fixtures/server.key",
- Cert: "../../../tests/plugins/http/fixtures/server.crt",
- RootCA: "",
- host: "",
- // private
- Port: 0,
- }
-
- err := conf.Valid()
- assert.Error(t, err)
-}
-
-func TestSSL_Valid6(t *testing.T) {
- conf := &SSL{
- Address: ":",
- Redirect: false,
- Key: "../../../tests/plugins/http/fixtures/server.key",
- Cert: "../../../tests/plugins/http/fixtures/server.crt",
- RootCA: "",
- host: "",
- // private
- Port: 0,
- }
-
- err := conf.Valid()
- assert.Error(t, err)
-}
-
-func TestSSL_Valid7(t *testing.T) {
- conf := &SSL{
- Address: "127.0.0.1:555:1",
- Redirect: false,
- Key: "../../../tests/plugins/http/fixtures/server.key",
- Cert: "../../../tests/plugins/http/fixtures/server.crt",
- RootCA: "",
- host: "",
- // private
- Port: 0,
- }
-
- err := conf.Valid()
- assert.Error(t, err)
-}
diff --git a/plugins/http/config/uploads_config.go b/plugins/http/config/uploads_config.go
deleted file mode 100644
index 5edb0ab7..00000000
--- a/plugins/http/config/uploads_config.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package config
-
-import (
- "os"
- "path"
- "strings"
-)
-
-// Uploads describes file location and controls access to them.
-type Uploads struct {
- // Dir contains name of directory to control access to.
- Dir string
-
- // Forbid specifies list of file extensions which are forbidden for access.
- // Example: .php, .exe, .bat, .htaccess and etc.
- Forbid []string
-}
-
-// InitDefaults sets missing values to their default values.
-func (cfg *Uploads) InitDefaults() error {
- cfg.Forbid = []string{".php", ".exe", ".bat"}
- cfg.Dir = os.TempDir()
- return nil
-}
-
-// TmpDir returns temporary directory.
-func (cfg *Uploads) TmpDir() string {
- if cfg.Dir != "" {
- return cfg.Dir
- }
-
- return os.TempDir()
-}
-
-// Forbids must return true if file extension is not allowed for the upload.
-func (cfg *Uploads) Forbids(filename string) bool {
- ext := strings.ToLower(path.Ext(filename))
-
- for _, v := range cfg.Forbid {
- if ext == v {
- return true
- }
- }
-
- return false
-}
diff --git a/plugins/http/metrics.go b/plugins/http/metrics.go
deleted file mode 100644
index d7a9110b..00000000
--- a/plugins/http/metrics.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package http
-
-import (
- "strconv"
-
- "github.com/prometheus/client_golang/prometheus"
- handler "github.com/spiral/roadrunner/v2/pkg/worker_handler"
-)
-
-func (p *Plugin) MetricsCollector() []prometheus.Collector {
- // p - implements Exporter interface (workers)
- // other - request duration and count
- return []prometheus.Collector{p, p.requestsExporter.requestDuration, p.requestsExporter.requestCounter}
-}
-
-func (p *Plugin) metricsCallback(event interface{}) {
- switch e := event.(type) {
- case handler.ResponseEvent:
- p.requestsExporter.requestCounter.With(prometheus.Labels{
- "status": strconv.Itoa(e.Response.Status),
- }).Inc()
-
- p.requestsExporter.requestDuration.With(prometheus.Labels{
- "status": strconv.Itoa(e.Response.Status),
- }).Observe(e.Elapsed().Seconds())
- case handler.ErrorEvent:
- p.requestsExporter.requestCounter.With(prometheus.Labels{
- "status": "500",
- }).Inc()
-
- p.requestsExporter.requestDuration.With(prometheus.Labels{
- "status": "500",
- }).Observe(e.Elapsed().Seconds())
- }
-}
-
-type workersExporter struct {
- wm *prometheus.Desc
- workersMemory uint64
-}
-
-func newWorkersExporter() *workersExporter {
- return &workersExporter{
- wm: prometheus.NewDesc("rr_http_workers_memory_bytes", "Memory usage by HTTP workers.", nil, nil),
- workersMemory: 0,
- }
-}
-
-func (p *Plugin) Describe(d chan<- *prometheus.Desc) {
- // send description
- d <- p.workersExporter.wm
-}
-
-func (p *Plugin) Collect(ch chan<- prometheus.Metric) {
- // get the copy of the processes
- workers := p.Workers()
-
- // cumulative RSS memory in bytes
- var cum uint64
-
- // collect the memory
- for i := 0; i < len(workers); i++ {
- cum += workers[i].MemoryUsage
- }
-
- // send the values to the prometheus
- ch <- prometheus.MustNewConstMetric(p.workersExporter.wm, prometheus.GaugeValue, float64(cum))
-}
-
-type requestsExporter struct {
- requestCounter *prometheus.CounterVec
- requestDuration *prometheus.HistogramVec
-}
-
-func newRequestsExporter() *requestsExporter {
- return &requestsExporter{
- requestCounter: prometheus.NewCounterVec(
- prometheus.CounterOpts{
- Name: "rr_http_request_total",
- Help: "Total number of handled http requests after server restart.",
- },
- []string{"status"},
- ),
- requestDuration: prometheus.NewHistogramVec(
- prometheus.HistogramOpts{
- Name: "rr_http_request_duration_seconds",
- Help: "HTTP request duration.",
- },
- []string{"status"},
- ),
- }
-}
diff --git a/plugins/http/plugin.go b/plugins/http/plugin.go
deleted file mode 100644
index dc887f87..00000000
--- a/plugins/http/plugin.go
+++ /dev/null
@@ -1,412 +0,0 @@
-package http
-
-import (
- "context"
- "fmt"
- "log"
- "net/http"
- "sync"
-
- endure "github.com/spiral/endure/pkg/container"
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/pkg/pool"
- "github.com/spiral/roadrunner/v2/pkg/state/process"
- "github.com/spiral/roadrunner/v2/pkg/worker"
- handler "github.com/spiral/roadrunner/v2/pkg/worker_handler"
- "github.com/spiral/roadrunner/v2/plugins/config"
- "github.com/spiral/roadrunner/v2/plugins/http/attributes"
- httpConfig "github.com/spiral/roadrunner/v2/plugins/http/config"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- "github.com/spiral/roadrunner/v2/plugins/server"
- "github.com/spiral/roadrunner/v2/plugins/status"
- "golang.org/x/net/http2"
- "golang.org/x/net/http2/h2c"
-)
-
-const (
- // PluginName declares plugin name.
- PluginName = "http"
-
- // RrMode RR_HTTP env variable key (internal) if the HTTP presents
- RrMode = "RR_MODE"
-
- HTTPSScheme = "https"
-)
-
-// Middleware interface
-type Middleware interface {
- Middleware(f http.Handler) http.Handler
-}
-
-type middleware map[string]Middleware
-
-// Plugin manages pool, http servers. The main http plugin structure
-type Plugin struct {
- sync.RWMutex
-
- // plugins
- server server.Server
- log logger.Logger
- // stdlog passed to the http/https/fcgi servers to log their internal messages
- stdLog *log.Logger
-
- // http configuration
- cfg *httpConfig.HTTP `mapstructure:"http"`
-
- // middlewares to chain
- mdwr middleware
-
- // Pool which attached to all servers
- pool pool.Pool
-
- // servers RR handler
- handler *handler.Handler
-
- // metrics
- workersExporter *workersExporter
- requestsExporter *requestsExporter
-
- // servers
- http *http.Server
- https *http.Server
- fcgi *http.Server
-}
-
-// 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 (p *Plugin) Init(cfg config.Configurer, rrLogger logger.Logger, server server.Server) error {
- const op = errors.Op("http_plugin_init")
- if !cfg.Has(PluginName) {
- return errors.E(op, errors.Disabled)
- }
-
- err := cfg.UnmarshalKey(PluginName, &p.cfg)
- if err != nil {
- return errors.E(op, err)
- }
-
- err = p.cfg.InitDefaults()
- if err != nil {
- return errors.E(op, err)
- }
-
- // rr logger (via plugin)
- p.log = rrLogger
- // use time and date in UTC format
- p.stdLog = log.New(logger.NewStdAdapter(p.log), "http_plugin: ", log.Ldate|log.Ltime|log.LUTC)
-
- p.mdwr = make(map[string]Middleware)
-
- if !p.cfg.EnableHTTP() && !p.cfg.EnableTLS() && !p.cfg.EnableFCGI() {
- return errors.E(op, errors.Disabled)
- }
-
- // init if nil
- if p.cfg.Env == nil {
- p.cfg.Env = make(map[string]string)
- }
-
- // initialize workersExporter
- p.workersExporter = newWorkersExporter()
- // initialize requests exporter
- p.requestsExporter = newRequestsExporter()
-
- p.cfg.Env[RrMode] = "http"
- p.server = server
-
- return nil
-}
-
-func (p *Plugin) logCallback(event interface{}) {
- if ev, ok := event.(handler.ResponseEvent); ok {
- p.log.Debug(fmt.Sprintf("%d %s %s", ev.Response.Status, ev.Request.Method, ev.Request.URI),
- "remote", ev.Request.RemoteAddr,
- "elapsed", ev.Elapsed().String(),
- )
- }
-}
-
-// Serve serves the svc.
-func (p *Plugin) Serve() chan error {
- errCh := make(chan error, 2)
- // run whole process in the goroutine
- go func() {
- // protect http initialization
- p.Lock()
- p.serve(errCh)
- p.Unlock()
- }()
-
- return errCh
-}
-
-func (p *Plugin) serve(errCh chan error) {
- var err error
- const op = errors.Op("http_plugin_serve")
- p.pool, err = p.server.NewWorkerPool(context.Background(), &pool.Config{
- Debug: p.cfg.Pool.Debug,
- NumWorkers: p.cfg.Pool.NumWorkers,
- MaxJobs: p.cfg.Pool.MaxJobs,
- AllocateTimeout: p.cfg.Pool.AllocateTimeout,
- DestroyTimeout: p.cfg.Pool.DestroyTimeout,
- Supervisor: p.cfg.Pool.Supervisor,
- }, p.cfg.Env, p.logCallback)
- if err != nil {
- errCh <- errors.E(op, err)
- return
- }
-
- p.handler, err = handler.NewHandler(
- p.cfg.MaxRequestSize,
- p.cfg.InternalErrorCode,
- *p.cfg.Uploads,
- p.cfg.Cidrs,
- p.pool,
- )
- if err != nil {
- errCh <- errors.E(op, err)
- return
- }
-
- p.handler.AddListener(p.logCallback, p.metricsCallback)
-
- if p.cfg.EnableHTTP() {
- if p.cfg.EnableH2C() {
- p.http = &http.Server{
- Handler: h2c.NewHandler(p, &http2.Server{}),
- ErrorLog: p.stdLog,
- }
- } else {
- p.http = &http.Server{
- Handler: p,
- ErrorLog: p.stdLog,
- }
- }
- }
-
- if p.cfg.EnableTLS() {
- p.https = p.initSSL()
- if p.cfg.SSLConfig.RootCA != "" {
- err = p.appendRootCa()
- if err != nil {
- errCh <- errors.E(op, err)
- return
- }
- }
-
- // if HTTP2Config not nil
- if p.cfg.HTTP2Config != nil {
- if err := p.initHTTP2(); err != nil {
- errCh <- errors.E(op, err)
- return
- }
- }
- }
-
- if p.cfg.EnableFCGI() {
- p.fcgi = &http.Server{Handler: p, ErrorLog: p.stdLog}
- }
-
- // start http, https and fcgi servers if requested in the config
- go func() {
- p.serveHTTP(errCh)
- }()
-
- go func() {
- p.serveHTTPS(errCh)
- }()
-
- go func() {
- p.serveFCGI(errCh)
- }()
-}
-
-// Stop stops the http.
-func (p *Plugin) Stop() error {
- p.Lock()
- defer p.Unlock()
-
- if p.fcgi != nil {
- err := p.fcgi.Shutdown(context.Background())
- if err != nil && err != http.ErrServerClosed {
- p.log.Error("fcgi shutdown", "error", err)
- }
- }
-
- if p.https != nil {
- err := p.https.Shutdown(context.Background())
- if err != nil && err != http.ErrServerClosed {
- p.log.Error("https shutdown", "error", err)
- }
- }
-
- if p.http != nil {
- err := p.http.Shutdown(context.Background())
- if err != nil && err != http.ErrServerClosed {
- p.log.Error("http shutdown", "error", err)
- }
- }
-
- // check for safety
- if p.pool != nil {
- p.pool.Destroy(context.Background())
- }
-
- return nil
-}
-
-// ServeHTTP handles connection using set of middleware and pool PSR-7 server.
-func (p *Plugin) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- defer func() {
- err := r.Body.Close()
- if err != nil {
- p.log.Error("body close", "error", err)
- }
- }()
- if headerContainsUpgrade(r) {
- http.Error(w, "server does not support upgrade header", http.StatusInternalServerError)
- return
- }
-
- if p.https != nil && r.TLS == nil && p.cfg.SSLConfig.Redirect {
- p.redirect(w, r)
- return
- }
-
- if p.https != nil && r.TLS != nil {
- w.Header().Add("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload")
- }
-
- r = attributes.Init(r)
- // protect the case, when user sendEvent Reset and we are replacing handler with pool
- p.RLock()
- p.handler.ServeHTTP(w, r)
- p.RUnlock()
-}
-
-// Workers returns slice with the process states for the workers
-func (p *Plugin) Workers() []*process.State {
- p.RLock()
- defer p.RUnlock()
-
- workers := p.workers()
-
- ps := make([]*process.State, 0, len(workers))
- for i := 0; i < len(workers); i++ {
- state, err := process.WorkerProcessState(workers[i])
- if err != nil {
- return nil
- }
- ps = append(ps, state)
- }
-
- return ps
-}
-
-// internal
-func (p *Plugin) workers() []worker.BaseProcess {
- return p.pool.Workers()
-}
-
-// Name returns endure.Named interface implementation
-func (p *Plugin) Name() string {
- return PluginName
-}
-
-// Reset destroys the old pool and replaces it with new one, waiting for old pool to die
-func (p *Plugin) Reset() error {
- p.Lock()
- defer p.Unlock()
- const op = errors.Op("http_plugin_reset")
- p.log.Info("HTTP plugin got restart request. Restarting...")
- p.pool.Destroy(context.Background())
- p.pool = nil
-
- var err error
- p.pool, err = p.server.NewWorkerPool(context.Background(), &pool.Config{
- Debug: p.cfg.Pool.Debug,
- NumWorkers: p.cfg.Pool.NumWorkers,
- MaxJobs: p.cfg.Pool.MaxJobs,
- AllocateTimeout: p.cfg.Pool.AllocateTimeout,
- DestroyTimeout: p.cfg.Pool.DestroyTimeout,
- Supervisor: p.cfg.Pool.Supervisor,
- }, p.cfg.Env, p.logCallback)
- if err != nil {
- return errors.E(op, err)
- }
-
- p.log.Info("HTTP workers Pool successfully restarted")
-
- p.handler, err = handler.NewHandler(
- p.cfg.MaxRequestSize,
- p.cfg.InternalErrorCode,
- *p.cfg.Uploads,
- p.cfg.Cidrs,
- p.pool,
- )
-
- if err != nil {
- return errors.E(op, err)
- }
-
- p.log.Info("HTTP handler listeners successfully re-added")
- p.handler.AddListener(p.logCallback, p.metricsCallback)
-
- p.log.Info("HTTP plugin successfully restarted")
- return nil
-}
-
-// Collects collecting http middlewares
-func (p *Plugin) Collects() []interface{} {
- return []interface{}{
- p.AddMiddleware,
- }
-}
-
-// AddMiddleware is base requirement for the middleware (name and Middleware)
-func (p *Plugin) AddMiddleware(name endure.Named, m Middleware) {
- p.mdwr[name.Name()] = m
-}
-
-// Status return status of the particular plugin
-func (p *Plugin) Status() status.Status {
- p.RLock()
- defer p.RUnlock()
-
- workers := p.workers()
- for i := 0; i < len(workers); i++ {
- if workers[i].State().IsActive() {
- return status.Status{
- Code: http.StatusOK,
- }
- }
- }
- // if there are no workers, threat this as error
- return status.Status{
- Code: http.StatusServiceUnavailable,
- }
-}
-
-// Ready return readiness status of the particular plugin
-func (p *Plugin) Ready() status.Status {
- p.RLock()
- defer p.RUnlock()
-
- workers := p.workers()
- for i := 0; i < len(workers); i++ {
- // If state of the worker is ready (at least 1)
- // we assume, that plugin's worker pool is ready
- if workers[i].State().Value() == worker.StateReady {
- return status.Status{
- Code: http.StatusOK,
- }
- }
- }
- // if there are no workers, threat this as no content error
- return status.Status{
- Code: http.StatusServiceUnavailable,
- }
-}
-
-// Available interface implementation
-func (p *Plugin) Available() {}
diff --git a/plugins/http/serve.go b/plugins/http/serve.go
deleted file mode 100644
index 6d3f2228..00000000
--- a/plugins/http/serve.go
+++ /dev/null
@@ -1,254 +0,0 @@
-package http
-
-import (
- "crypto/tls"
- "crypto/x509"
- "fmt"
- "net/http"
- "net/http/fcgi"
- "net/url"
- "os"
- "strings"
-
- "github.com/spiral/errors"
- "github.com/spiral/roadrunner/v2/plugins/logger"
- "github.com/spiral/roadrunner/v2/utils"
- "golang.org/x/net/http2"
- "golang.org/x/sys/cpu"
-)
-
-func (p *Plugin) serveHTTP(errCh chan error) {
- if p.http == nil {
- return
- }
- const op = errors.Op("serveHTTP")
-
- if len(p.mdwr) > 0 {
- applyMiddlewares(p.http, p.mdwr, p.cfg.Middleware, p.log)
- }
- l, err := utils.CreateListener(p.cfg.Address)
- if err != nil {
- errCh <- errors.E(op, err)
- return
- }
-
- err = p.http.Serve(l)
- if err != nil && err != http.ErrServerClosed {
- errCh <- errors.E(op, err)
- return
- }
-}
-
-func (p *Plugin) serveHTTPS(errCh chan error) {
- if p.https == nil {
- return
- }
- const op = errors.Op("serveHTTPS")
- if len(p.mdwr) > 0 {
- applyMiddlewares(p.https, p.mdwr, p.cfg.Middleware, p.log)
- }
- l, err := utils.CreateListener(p.cfg.SSLConfig.Address)
- if err != nil {
- errCh <- errors.E(op, err)
- return
- }
-
- err = p.https.ServeTLS(
- l,
- p.cfg.SSLConfig.Cert,
- p.cfg.SSLConfig.Key,
- )
-
- if err != nil && err != http.ErrServerClosed {
- errCh <- errors.E(op, err)
- return
- }
-}
-
-// serveFCGI starts FastCGI server.
-func (p *Plugin) serveFCGI(errCh chan error) {
- if p.fcgi == nil {
- return
- }
- const op = errors.Op("serveFCGI")
-
- if len(p.mdwr) > 0 {
- applyMiddlewares(p.fcgi, p.mdwr, p.cfg.Middleware, p.log)
- }
-
- l, err := utils.CreateListener(p.cfg.FCGIConfig.Address)
- if err != nil {
- errCh <- errors.E(op, err)
- return
- }
-
- err = fcgi.Serve(l, p.fcgi.Handler)
- if err != nil && err != http.ErrServerClosed {
- errCh <- errors.E(op, err)
- return
- }
-}
-
-func (p *Plugin) redirect(w http.ResponseWriter, r *http.Request) {
- target := &url.URL{
- Scheme: HTTPSScheme,
- // host or host:port
- Host: p.tlsAddr(r.Host, false),
- Path: r.URL.Path,
- RawQuery: r.URL.RawQuery,
- }
-
- http.Redirect(w, r, target.String(), http.StatusPermanentRedirect)
-}
-
-// https://golang.org/pkg/net/http/#Hijacker
-//go:inline
-func headerContainsUpgrade(r *http.Request) bool {
- if _, ok := r.Header["Upgrade"]; ok {
- return true
- }
- return false
-}
-
-// append RootCA to the https server TLS config
-func (p *Plugin) appendRootCa() error {
- const op = errors.Op("http_plugin_append_root_ca")
- rootCAs, err := x509.SystemCertPool()
- if err != nil {
- return nil
- }
- if rootCAs == nil {
- rootCAs = x509.NewCertPool()
- }
-
- CA, err := os.ReadFile(p.cfg.SSLConfig.RootCA)
- if err != nil {
- return err
- }
-
- // should append our CA cert
- ok := rootCAs.AppendCertsFromPEM(CA)
- if !ok {
- return errors.E(op, errors.Str("could not append Certs from PEM"))
- }
- // disable "G402 (CWE-295): TLS MinVersion too low. (Confidence: HIGH, Severity: HIGH)"
- // #nosec G402
- cfg := &tls.Config{
- InsecureSkipVerify: false,
- RootCAs: rootCAs,
- }
- p.http.TLSConfig = cfg
-
- return nil
-}
-
-// Init https server
-func (p *Plugin) 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 priorities 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...)
-
- sslServer := &http.Server{
- Addr: p.tlsAddr(p.cfg.Address, true),
- Handler: p,
- ErrorLog: p.stdLog,
- TLSConfig: &tls.Config{
- CurvePreferences: []tls.CurveID{
- tls.CurveP256,
- tls.CurveP384,
- tls.CurveP521,
- tls.X25519,
- },
- CipherSuites: DefaultCipherSuites,
- MinVersion: tls.VersionTLS12,
- PreferServerCipherSuites: true,
- },
- }
-
- return sslServer
-}
-
-// init http/2 server
-func (p *Plugin) initHTTP2() error {
- return http2.ConfigureServer(p.https, &http2.Server{
- MaxConcurrentStreams: p.cfg.HTTP2Config.MaxConcurrentStreams,
- })
-}
-
-// tlsAddr replaces listen or host port with port configured by SSLConfig config.
-func (p *Plugin) tlsAddr(host string, forcePort bool) string {
- // remove current forcePort first
- host = strings.Split(host, ":")[0]
-
- if forcePort || p.cfg.SSLConfig.Port != 443 {
- host = fmt.Sprintf("%s:%v", host, p.cfg.SSLConfig.Port)
- }
-
- return host
-}
-
-// static plugin name
-const static string = "static"
-
-func applyMiddlewares(server *http.Server, middlewares map[string]Middleware, order []string, log logger.Logger) {
- for i := len(order) - 1; i >= 0; i-- {
- // set static last in the row
- if order[i] == static {
- continue
- }
- if mdwr, ok := middlewares[order[i]]; ok {
- server.Handler = mdwr.Middleware(server.Handler)
- } else {
- log.Warn("requested middleware does not exist", "requested", order[i])
- }
- }
-
- // set static if exists
- if mdwr, ok := middlewares[static]; ok {
- server.Handler = mdwr.Middleware(server.Handler)
- }
-}