summaryrefslogtreecommitdiff
path: root/plugins/http/plugin.go
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/http/plugin.go')
-rw-r--r--plugins/http/plugin.go306
1 files changed, 64 insertions, 242 deletions
diff --git a/plugins/http/plugin.go b/plugins/http/plugin.go
index 01bd243f..2b1dec89 100644
--- a/plugins/http/plugin.go
+++ b/plugins/http/plugin.go
@@ -2,14 +2,11 @@ package http
import (
"context"
- "crypto/tls"
- "crypto/x509"
"fmt"
- "io/ioutil"
"log"
"net/http"
- "net/http/fcgi"
- "net/url"
+ "os"
+ "path/filepath"
"strings"
"sync"
@@ -22,29 +19,28 @@ import (
"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/http/static"
+ handler "github.com/spiral/roadrunner/v2/plugins/http/worker_handler"
"github.com/spiral/roadrunner/v2/plugins/logger"
"github.com/spiral/roadrunner/v2/plugins/server"
"github.com/spiral/roadrunner/v2/plugins/status"
- "github.com/spiral/roadrunner/v2/utils"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
- "golang.org/x/sys/cpu"
)
const (
// PluginName declares plugin name.
PluginName = "http"
- // RR_HTTP env variable key (internal) if the HTTP presents
- RR_MODE = "RR_MODE" //nolint:golint,stylecheck
+ // RrMode RR_HTTP env variable key (internal) if the HTTP presents
+ RrMode = "RR_MODE"
- // HTTPS_SCHEME
- HTTPS_SCHEME = "https" //nolint:golint,stylecheck
+ HTTPSScheme = "https"
)
// Middleware interface
type Middleware interface {
- Middleware(f http.Handler) http.HandlerFunc
+ Middleware(f http.Handler) http.Handler
}
type middleware map[string]Middleware
@@ -59,7 +55,9 @@ type Plugin struct {
// 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
@@ -67,7 +65,7 @@ type Plugin struct {
pool pool.Pool
// servers RR handler
- handler *Handler
+ handler *handler.Handler
// servers
http *http.Server
@@ -109,14 +107,14 @@ func (s *Plugin) Init(cfg config.Configurer, rrLogger logger.Logger, server serv
s.cfg.Env = make(map[string]string)
}
- s.cfg.Env[RR_MODE] = "http"
+ s.cfg.Env[RrMode] = "http"
s.server = server
return nil
}
func (s *Plugin) logCallback(event interface{}) {
- if ev, ok := event.(ResponseEvent); ok {
+ if ev, ok := event.(handler.ResponseEvent); ok {
s.log.Debug(fmt.Sprintf("%d %s %s", ev.Response.Status, ev.Request.Method, ev.Request.URI),
"remote", ev.Request.RemoteAddr,
"elapsed", ev.Elapsed().String(),
@@ -138,7 +136,7 @@ func (s *Plugin) Serve() chan error {
return errCh
}
-func (s *Plugin) serve(errCh chan error) {
+func (s *Plugin) serve(errCh chan error) { //nolint:gocognit
var err error
const op = errors.Op("http_plugin_serve")
s.pool, err = s.server.NewWorkerPool(context.Background(), pool.Config{
@@ -154,7 +152,7 @@ func (s *Plugin) serve(errCh chan error) {
return
}
- s.handler, err = NewHandler(
+ s.handler, err = handler.NewHandler(
s.cfg.MaxRequestSize,
*s.cfg.Uploads,
s.cfg.Cidrs,
@@ -167,11 +165,56 @@ func (s *Plugin) serve(errCh chan error) {
s.handler.AddListener(s.logCallback)
+ // Create new HTTP Multiplexer
+ mux := http.NewServeMux()
+
+ // if we have static, handler here, create a fileserver
+ if s.cfg.Static != nil {
+ h := http.FileServer(static.FS(s.cfg.Static))
+ // Static files handler
+ mux.HandleFunc(s.cfg.Static.Pattern, func(w http.ResponseWriter, r *http.Request) {
+ if s.cfg.Static.Request != nil {
+ for k, v := range s.cfg.Static.Request {
+ r.Header.Add(k, v)
+ }
+ }
+
+ if s.cfg.Static.Response != nil {
+ for k, v := range s.cfg.Static.Response {
+ w.Header().Set(k, v)
+ }
+ }
+
+ // calculate etag for the resource
+ if s.cfg.Static.CalculateEtag {
+ // do not allow paths like ../../resource
+ // only specified folder and resources in it
+ // https://lgtm.com/rules/1510366186013/
+ if strings.Contains(r.URL.Path, "..") {
+ w.WriteHeader(http.StatusForbidden)
+ return
+ }
+ f, errS := os.Open(filepath.Join(s.cfg.Static.Dir, r.URL.Path))
+ if errS != nil {
+ s.log.Warn("error opening file to calculate the Etag", "provided path", r.URL.Path)
+ }
+
+ // Set etag value to the ResponseWriter
+ static.SetEtag(s.cfg.Static, f, w)
+ }
+
+ h.ServeHTTP(w, r)
+ })
+ }
+
+ // handle main route
+ mux.HandleFunc("/", s.ServeHTTP)
+
if s.cfg.EnableHTTP() {
if s.cfg.EnableH2C() {
- s.http = &http.Server{Handler: h2c.NewHandler(s, &http2.Server{}), ErrorLog: s.stdLog}
+ s.http = &http.Server{Handler: h2c.NewHandler(mux, &http2.Server{}), ErrorLog: s.stdLog}
} else {
- s.http = &http.Server{Handler: s, ErrorLog: s.stdLog}
+ s.http = &http.Server{Handler: mux, ErrorLog: s.stdLog}
}
}
@@ -195,7 +238,7 @@ func (s *Plugin) serve(errCh chan error) {
}
if s.cfg.EnableFCGI() {
- s.fcgi = &http.Server{Handler: s, ErrorLog: s.stdLog}
+ s.fcgi = &http.Server{Handler: mux, ErrorLog: s.stdLog}
}
// start http, https and fcgi servers if requested in the config
@@ -212,72 +255,6 @@ func (s *Plugin) serve(errCh chan error) {
}()
}
-func (s *Plugin) serveHTTP(errCh chan error) {
- if s.http == nil {
- return
- }
-
- const op = errors.Op("http_plugin_serve_http")
- applyMiddlewares(s.http, s.mdwr, s.cfg.Middleware, s.log)
- l, err := utils.CreateListener(s.cfg.Address)
- if err != nil {
- errCh <- errors.E(op, err)
- return
- }
-
- err = s.http.Serve(l)
- if err != nil && err != http.ErrServerClosed {
- errCh <- errors.E(op, err)
- return
- }
-}
-
-func (s *Plugin) serveHTTPS(errCh chan error) {
- if s.https == nil {
- return
- }
-
- const op = errors.Op("http_plugin_serve_https")
- applyMiddlewares(s.https, s.mdwr, s.cfg.Middleware, s.log)
- l, err := utils.CreateListener(s.cfg.SSLConfig.Address)
- if err != nil {
- errCh <- errors.E(op, err)
- return
- }
-
- err = s.https.ServeTLS(
- l,
- s.cfg.SSLConfig.Cert,
- s.cfg.SSLConfig.Key,
- )
-
- if err != nil && err != http.ErrServerClosed {
- errCh <- errors.E(op, err)
- return
- }
-}
-
-// serveFCGI starts FastCGI server.
-func (s *Plugin) serveFCGI(errCh chan error) {
- if s.fcgi == nil {
- return
- }
-
- const op = errors.Op("http_plugin_serve_fcgi")
- applyMiddlewares(s.fcgi, s.mdwr, s.cfg.Middleware, s.log)
- l, err := utils.CreateListener(s.cfg.FCGIConfig.Address)
- if err != nil {
- errCh <- errors.E(op, err)
- return
- }
-
- err = fcgi.Serve(l, s.fcgi.Handler)
- if err != nil && err != http.ErrServerClosed {
- errCh <- errors.E(op, err)
- return
- }
-}
-
// Stop stops the http.
func (s *Plugin) Stop() error {
s.Lock()
@@ -395,7 +372,7 @@ func (s *Plugin) Reset() error {
s.log.Info("HTTP workers Pool successfully restarted")
- s.handler, err = NewHandler(
+ s.handler, err = handler.NewHandler(
s.cfg.MaxRequestSize,
*s.cfg.Uploads,
s.cfg.Cidrs,
@@ -463,158 +440,3 @@ func (s *Plugin) Ready() status.Status {
Code: http.StatusServiceUnavailable,
}
}
-
-func (s *Plugin) redirect(w http.ResponseWriter, r *http.Request) {
- target := &url.URL{
- Scheme: HTTPS_SCHEME,
- // host or host:port
- Host: s.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 (s *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 := ioutil.ReadFile(s.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,
- }
- s.http.TLSConfig = cfg
-
- return nil
-}
-
-// Init https server
-func (s *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: s.tlsAddr(s.cfg.Address, true),
- Handler: s,
- ErrorLog: s.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 (s *Plugin) initHTTP2() error {
- return http2.ConfigureServer(s.https, &http2.Server{
- MaxConcurrentStreams: s.cfg.HTTP2Config.MaxConcurrentStreams,
- })
-}
-
-// tlsAddr replaces listen or host port with port configured by SSLConfig config.
-func (s *Plugin) tlsAddr(host string, forcePort bool) string {
- // remove current forcePort first
- host = strings.Split(host, ":")[0]
-
- if forcePort || s.cfg.SSLConfig.Port != 443 {
- host = fmt.Sprintf("%s:%v", host, s.cfg.SSLConfig.Port)
- }
-
- return host
-}
-
-func applyMiddlewares(server *http.Server, middlewares map[string]Middleware, order []string, log logger.Logger) {
- if len(middlewares) == 0 {
- return
- }
- for i := 0; i < len(order); i++ {
- if mdwr, ok := middlewares[order[i]]; ok {
- server.Handler = mdwr.Middleware(server.Handler)
- } else {
- log.Warn("requested middleware does not exist", "requested", order[i])
- }
- }
-}