summaryrefslogtreecommitdiff
path: root/plugins/http
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/http')
-rw-r--r--plugins/http/config/http.go21
-rw-r--r--plugins/http/config/static.go58
-rw-r--r--plugins/http/plugin.go306
-rw-r--r--plugins/http/serve.go242
-rw-r--r--plugins/http/static/etag.go71
-rw-r--r--plugins/http/static/static.go88
-rw-r--r--plugins/http/worker_handler/constants.go (renamed from plugins/http/constants.go)2
-rw-r--r--plugins/http/worker_handler/errors.go (renamed from plugins/http/errors.go)2
-rw-r--r--plugins/http/worker_handler/errors_windows.go (renamed from plugins/http/errors_windows.go)2
-rw-r--r--plugins/http/worker_handler/handler.go (renamed from plugins/http/handler.go)2
-rw-r--r--plugins/http/worker_handler/parse.go (renamed from plugins/http/parse.go)2
-rw-r--r--plugins/http/worker_handler/request.go (renamed from plugins/http/request.go)2
-rw-r--r--plugins/http/worker_handler/response.go (renamed from plugins/http/response.go)2
-rw-r--r--plugins/http/worker_handler/uploads.go (renamed from plugins/http/uploads.go)2
14 files changed, 552 insertions, 250 deletions
diff --git a/plugins/http/config/http.go b/plugins/http/config/http.go
index 8b63395f..59735e2e 100644
--- a/plugins/http/config/http.go
+++ b/plugins/http/config/http.go
@@ -33,6 +33,9 @@ type HTTP struct {
// Uploads configures uploads configuration.
Uploads *Uploads `mapstructure:"uploads"`
+ // static configuration
+ Static *Static `mapstructure:"static"`
+
// Pool configures worker pool.
Pool *poolImpl.Config `mapstructure:"pool"`
@@ -100,6 +103,16 @@ func (c *HTTP) InitDefaults() error {
c.SSLConfig.Address = "127.0.0.1:443"
}
+ // static files
+ if c.Static != nil {
+ if c.Static.Pattern == "" {
+ c.Static.Pattern = "/static/"
+ }
+ if c.Static.Dir == "" {
+ c.Static.Dir = "."
+ }
+ }
+
err := c.HTTP2Config.InitDefaults()
if err != nil {
return err
@@ -176,5 +189,13 @@ func (c *HTTP) Valid() error {
}
}
+ // validate static
+ if c.Static != nil {
+ err := c.Static.Valid()
+ if err != nil {
+ return errors.E(op, err)
+ }
+ }
+
return nil
}
diff --git a/plugins/http/config/static.go b/plugins/http/config/static.go
new file mode 100644
index 00000000..4b7b3a9b
--- /dev/null
+++ b/plugins/http/config/static.go
@@ -0,0 +1,58 @@
+package config
+
+import (
+ "os"
+
+ "github.com/spiral/errors"
+)
+
+// Static describes file location and controls access to them.
+type Static struct {
+ // Dir contains name of directory to control access to.
+ // Default - "."
+ Dir string
+
+ // HTTP pattern, where to serve static files
+ // for example - `/static/`, `/my-files/static/`, etc
+ // Default - /static/
+ Pattern string
+
+ // CalculateEtag can be true/false and used to calculate etag for the static
+ CalculateEtag bool `mapstructure:"calculate_etag"`
+
+ // Weak etag `W/`
+ Weak bool
+
+ // forbid specifies list of file extensions which are forbidden for access.
+ // example: .php, .exe, .bat, .htaccess and etc.
+ Forbid []string
+
+ // Allow specifies list of file extensions which are allowed for access.
+ // example: .php, .exe, .bat, .htaccess and etc.
+ Allow []string
+
+ // Request headers to add to every static.
+ Request map[string]string
+
+ // Response headers to add to every static.
+ Response map[string]string
+}
+
+// Valid returns nil if config is valid.
+func (c *Static) Valid() error {
+ const op = errors.Op("static_plugin_valid")
+ st, err := os.Stat(c.Dir)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return errors.E(op, errors.Errorf("root directory '%s' does not exists", c.Dir))
+ }
+
+ return err
+ }
+
+ if !st.IsDir() {
+ return errors.E(op, errors.Errorf("invalid root directory '%s'", c.Dir))
+ }
+
+ return nil
+}
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])
- }
- }
-}
diff --git a/plugins/http/serve.go b/plugins/http/serve.go
new file mode 100644
index 00000000..338d4339
--- /dev/null
+++ b/plugins/http/serve.go
@@ -0,0 +1,242 @@
+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 (s *Plugin) serveHTTP(errCh chan error) {
+ if s.http == nil {
+ return
+ }
+ const op = errors.Op("http_plugin_serve_http")
+
+ if len(s.mdwr) > 0 {
+ 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")
+ if len(s.mdwr) > 0 {
+ 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")
+
+ if len(s.mdwr) > 0 {
+ applyMiddlewares(s.https, 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
+ }
+}
+
+func (s *Plugin) redirect(w http.ResponseWriter, r *http.Request) {
+ target := &url.URL{
+ Scheme: HTTPSScheme,
+ // 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 := os.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) {
+ for i := len(order) - 1; i >= 0; 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])
+ }
+ }
+}
diff --git a/plugins/http/static/etag.go b/plugins/http/static/etag.go
new file mode 100644
index 00000000..5d41cc53
--- /dev/null
+++ b/plugins/http/static/etag.go
@@ -0,0 +1,71 @@
+package static
+
+import (
+ "hash/crc32"
+ "io"
+ "net/http"
+ "os"
+ "unsafe"
+
+ httpConfig "github.com/spiral/roadrunner/v2/plugins/http/config"
+)
+
+const etag string = "Etag"
+
+// weak Etag prefix
+var weakPrefix = []byte(`W/`)
+
+// CRC32 table
+var crc32q = crc32.MakeTable(0x48D90782)
+
+func SetEtag(cfg *httpConfig.Static, f *os.File, w http.ResponseWriter) {
+ // read the file content
+ body, err := io.ReadAll(f)
+ if err != nil {
+ return
+ }
+
+ // skip for 0 body
+ if len(body) == 0 {
+ return
+ }
+
+ // preallocate
+ calculatedEtag := make([]byte, 0, 64)
+
+ // write weak
+ if cfg.Weak {
+ calculatedEtag = append(calculatedEtag, weakPrefix...)
+ }
+
+ calculatedEtag = append(calculatedEtag, '"')
+ calculatedEtag = appendUint(calculatedEtag, uint32(len(body)))
+ calculatedEtag = append(calculatedEtag, '-')
+ calculatedEtag = appendUint(calculatedEtag, crc32.Checksum(body, crc32q))
+ calculatedEtag = append(calculatedEtag, '"')
+
+ w.Header().Set(etag, byteToSrt(calculatedEtag))
+}
+
+// appendUint appends n to dst and returns the extended dst.
+func appendUint(dst []byte, n uint32) []byte {
+ var b [20]byte
+ buf := b[:]
+ i := len(buf)
+ var q uint32
+ for n >= 10 {
+ i--
+ q = n / 10
+ buf[i] = '0' + byte(n-q*10)
+ n = q
+ }
+ i--
+ buf[i] = '0' + byte(n)
+
+ dst = append(dst, buf[i:]...)
+ return dst
+}
+
+func byteToSrt(b []byte) string {
+ return *(*string)(unsafe.Pointer(&b))
+}
diff --git a/plugins/http/static/static.go b/plugins/http/static/static.go
new file mode 100644
index 00000000..d0278466
--- /dev/null
+++ b/plugins/http/static/static.go
@@ -0,0 +1,88 @@
+package static
+
+import (
+ "io/fs"
+ "net/http"
+ "path/filepath"
+ "strings"
+
+ httpConfig "github.com/spiral/roadrunner/v2/plugins/http/config"
+)
+
+type ExtensionFilter struct {
+ allowed map[string]struct{}
+ forbidden map[string]struct{}
+}
+
+func NewExtensionFilter(allow, forbid []string) *ExtensionFilter {
+ ef := &ExtensionFilter{
+ allowed: make(map[string]struct{}, len(allow)),
+ forbidden: make(map[string]struct{}, len(forbid)),
+ }
+
+ for i := 0; i < len(forbid); i++ {
+ // skip empty lines
+ if forbid[i] == "" {
+ continue
+ }
+ ef.forbidden[forbid[i]] = struct{}{}
+ }
+
+ for i := 0; i < len(allow); i++ {
+ // skip empty lines
+ if allow[i] == "" {
+ continue
+ }
+ ef.allowed[allow[i]] = struct{}{}
+ }
+
+ // check if any forbidden items presented in the allowed
+ // if presented, delete such items from allowed
+ for k := range ef.allowed {
+ if _, ok := ef.forbidden[k]; ok {
+ delete(ef.allowed, k)
+ }
+ }
+
+ return ef
+}
+
+type FileSystem struct {
+ ef *ExtensionFilter
+ // embedded
+ http.FileSystem
+}
+
+// Open wrapper around http.FileSystem Open method, name here is the name of the
+func (f FileSystem) Open(name string) (http.File, error) {
+ file, err := f.FileSystem.Open(name)
+ if err != nil {
+ return nil, err
+ }
+
+ fstat, err := file.Stat()
+ if err != nil {
+ return nil, fs.ErrNotExist
+ }
+
+ if fstat.IsDir() {
+ return nil, fs.ErrPermission
+ }
+
+ ext := strings.ToLower(filepath.Ext(fstat.Name()))
+ if _, ok := f.ef.forbidden[ext]; ok {
+ return nil, fs.ErrPermission
+ }
+
+ // if file extension is allowed, append it to the FileInfo slice
+ if _, ok := f.ef.allowed[ext]; ok {
+ return file, nil
+ }
+
+ return nil, fs.ErrNotExist
+}
+
+// FS is a constructor for the http.FileSystem
+func FS(config *httpConfig.Static) http.FileSystem {
+ return FileSystem{NewExtensionFilter(config.Allow, config.Forbid), http.Dir(config.Dir)}
+}
diff --git a/plugins/http/constants.go b/plugins/http/worker_handler/constants.go
index c3d5c589..3355d9c2 100644
--- a/plugins/http/constants.go
+++ b/plugins/http/worker_handler/constants.go
@@ -1,4 +1,4 @@
-package http
+package handler
import "net/http"
diff --git a/plugins/http/errors.go b/plugins/http/worker_handler/errors.go
index 5889aa76..5fa8e64e 100644
--- a/plugins/http/errors.go
+++ b/plugins/http/worker_handler/errors.go
@@ -1,6 +1,6 @@
// +build !windows
-package http
+package handler
import (
"errors"
diff --git a/plugins/http/errors_windows.go b/plugins/http/worker_handler/errors_windows.go
index 3d0ba04c..390cc7d1 100644
--- a/plugins/http/errors_windows.go
+++ b/plugins/http/worker_handler/errors_windows.go
@@ -1,6 +1,6 @@
// +build windows
-package http
+package handler
import (
"errors"
diff --git a/plugins/http/handler.go b/plugins/http/worker_handler/handler.go
index d3c928aa..be53fc12 100644
--- a/plugins/http/handler.go
+++ b/plugins/http/worker_handler/handler.go
@@ -1,4 +1,4 @@
-package http
+package handler
import (
"net"
diff --git a/plugins/http/parse.go b/plugins/http/worker_handler/parse.go
index 780e1279..2790da2a 100644
--- a/plugins/http/parse.go
+++ b/plugins/http/worker_handler/parse.go
@@ -1,4 +1,4 @@
-package http
+package handler
import (
"net/http"
diff --git a/plugins/http/request.go b/plugins/http/worker_handler/request.go
index a1398819..178bc827 100644
--- a/plugins/http/request.go
+++ b/plugins/http/worker_handler/request.go
@@ -1,4 +1,4 @@
-package http
+package handler
import (
"fmt"
diff --git a/plugins/http/response.go b/plugins/http/worker_handler/response.go
index 17049ce1..1763d304 100644
--- a/plugins/http/response.go
+++ b/plugins/http/worker_handler/response.go
@@ -1,4 +1,4 @@
-package http
+package handler
import (
"io"
diff --git a/plugins/http/uploads.go b/plugins/http/worker_handler/uploads.go
index f9f8e1c8..e695000e 100644
--- a/plugins/http/uploads.go
+++ b/plugins/http/worker_handler/uploads.go
@@ -1,4 +1,4 @@
-package http
+package handler
import (
"github.com/spiral/roadrunner/v2/plugins/http/config"