Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RR2] Headers plugin #421

Merged
merged 7 commits into from
Dec 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 16 additions & 15 deletions .github/workflows/ci-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,26 +65,27 @@ jobs:

- name: Run golang tests
run: |
go test -v -race . -tags=debug -coverprofile=lib.txt -covermode=atomic
go test -v -race ./plugins/rpc -tags=debug -coverprofile=rpc_config.txt -covermode=atomic
go test -v -race ./plugins/rpc/tests -tags=debug -coverprofile=rpc.txt -covermode=atomic
go test -v -race ./plugins/config/tests -tags=debug -coverprofile=plugin_config.txt -covermode=atomic
go test -v -race ./plugins/logger/tests -tags=debug -coverprofile=logger.txt -covermode=atomic
go test -v -race ./plugins/server/tests -tags=debug -coverprofile=server.txt -covermode=atomic
go test -v -race ./plugins/metrics/tests -tags=debug -coverprofile=metrics.txt -covermode=atomic
go test -v -race ./plugins/informer/tests -tags=debug -coverprofile=informer.txt -covermode=atomic
go test -v -race ./plugins/resetter/tests -tags=debug -coverprofile=informer.txt -covermode=atomic
go test -v -race ./plugins/http/attributes -tags=debug -coverprofile=attributes.txt -covermode=atomic
go test -v -race ./plugins/http/tests -tags=debug -coverprofile=http_tests.txt -covermode=atomic
go test -v -race ./plugins/gzip/tests -tags=debug -coverprofile=gzip.txt -covermode=atomic
go test -v -race -cover ./plugins/static/tests -tags=debug -coverprofile=static.txt -covermode=atomic
go test -v -race -cover ./plugins/static -tags=debug -coverprofile=static_root.txt -covermode=atomic
go test -v -race -cover -tags=debug -coverprofile=lib.txt -covermode=atomic .
go test -v -race -cover -tags=debug -coverprofile=rpc_config.txt -covermode=atomic ./plugins/rpc
go test -v -race -cover -tags=debug -coverprofile=rpc.txt -covermode=atomic ./plugins/rpc/tests
go test -v -race -cover -tags=debug -coverprofile=plugin_config.txt -covermode=atomic ./plugins/config/tests
go test -v -race -cover -tags=debug -coverprofile=logger.txt -covermode=atomic ./plugins/logger/tests
go test -v -race -cover -tags=debug -coverprofile=server.txt -covermode=atomic ./plugins/server/tests
go test -v -race -cover -tags=debug -coverprofile=metrics.txt -covermode=atomic ./plugins/metrics/tests
go test -v -race -cover -tags=debug -coverprofile=informer.txt -covermode=atomic ./plugins/informer/tests
go test -v -race -cover -tags=debug -coverprofile=informer.txt -covermode=atomic ./plugins/resetter/tests
go test -v -race -cover -tags=debug -coverprofile=attributes.txt -covermode=atomic ./plugins/http/attributes
go test -v -race -cover -tags=debug -coverprofile=http_tests.txt -covermode=atomic ./plugins/http/tests
go test -v -race -cover -tags=debug -coverprofile=gzip.txt -covermode=atomic ./plugins/gzip/tests
go test -v -race -cover -tags=debug -coverprofile=static.txt -covermode=atomic ./plugins/static/tests
go test -v -race -cover -tags=debug -coverprofile=static_root.txt -covermode=atomic ./plugins/static
go test -v -race -cover -tags=debug -coverprofile=headers.txt -covermode=atomic ./plugins/headers/tests

- name: Run code coverage
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: static.txt, static_root.txt, gzip.txt, lib.txt, rpc_config.txt, rpc.txt, plugin_config.txt, logger.txt, server.txt, metrics.txt, informer.txt attributes.txt http_tests.txt
files: headers.txt, static.txt, static_root.txt, gzip.txt, lib.txt, rpc_config.txt, rpc.txt, plugin_config.txt, logger.txt, server.txt, metrics.txt, informer.txt attributes.txt http_tests.txt
flags: unittests
name: codecov-umbrella
fail_ci_if_error: false
Expand Down
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ run:
- plugins/http/tests/rpc_test_old.go
- plugins/http/tests/config_test.go
- plugins/static/tests/static_plugin_test.go
- plugins/headers/tests/old.go
linters:
disable-all: true
enable:
Expand Down
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@ test:
go test -v -race -cover ./plugins/http/tests -tags=debug
go test -v -race -cover ./plugins/gzip/tests -tags=debug
go test -v -race -cover ./plugins/static/tests -tags=debug
go test -v -race -cover ./plugins/static -tags=debug
go test -v -race -cover ./plugins/static -tags=debug
go test -v -race -cover ./plugins/headers/tests -tags=debug

test_headers:
go test -v -race -cover ./plugins/headers/tests -tags=debug
4 changes: 2 additions & 2 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
coverage:
status:
project: off
patch: off
project: true
patch: false
4 changes: 2 additions & 2 deletions plugins/gzip/tests/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
)

func TestGzipPlugin(t *testing.T) {
cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel), endure.Visualize(endure.StdOut, ""))
cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
assert.NoError(t, err)

cfg := &config.Viper{
Expand Down Expand Up @@ -102,7 +102,7 @@ func headerCheck(t *testing.T) {
}

func TestMiddlewareNotExist(t *testing.T) {
cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel), endure.Visualize(endure.StdOut, ""))
cont, err := endure.NewContainer(nil, endure.SetLogLevel(endure.DebugLevel))
assert.NoError(t, err)

cfg := &config.Viper{
Expand Down
36 changes: 36 additions & 0 deletions plugins/headers/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package headers

// Config declares headers service configuration.
type Config struct {
Headers struct {
// CORS settings.
CORS *CORSConfig

// Request headers to add to every payload send to PHP.
Request map[string]string

// Response headers to add to every payload generated by PHP.
Response map[string]string
}
}

// CORSConfig headers configuration.
type CORSConfig struct {
// AllowedOrigin: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
AllowedOrigin string

// AllowedHeaders: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
AllowedHeaders string

// AllowedMethods: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
AllowedMethods string

// AllowCredentials https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
AllowCredentials *bool

// ExposeHeaders: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
ExposedHeaders string

// MaxAge of CORS headers in seconds/
MaxAge int
}
117 changes: 117 additions & 0 deletions plugins/headers/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package headers

import (
"net/http"
"strconv"

"github.com/spiral/errors"
"github.com/spiral/roadrunner/v2/plugins/config"
)

// ID contains default service name.
const PluginName = "headers"
const RootPluginName = "http"

// Service serves headers files. Potentially convert into middleware?
type Plugin struct {
// server configuration (location, forbidden files and etc)
cfg *Config
}

// 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 *Plugin) Init(cfg config.Configurer) error {
const op = errors.Op("headers plugin init")
err := cfg.UnmarshalKey(RootPluginName, &s.cfg)
if err != nil {
return errors.E(op, errors.Disabled, err)
}

return nil
}

// middleware must return true if request/response pair is handled within the middleware.
func (s *Plugin) Middleware(next http.Handler) http.HandlerFunc {
// Define the http.HandlerFunc
return func(w http.ResponseWriter, r *http.Request) {
if s.cfg.Headers.Request != nil {
for k, v := range s.cfg.Headers.Request {
r.Header.Add(k, v)
}
}

if s.cfg.Headers.Response != nil {
for k, v := range s.cfg.Headers.Response {
w.Header().Set(k, v)
}
}

if s.cfg.Headers.CORS != nil {
if r.Method == http.MethodOptions {
s.preflightRequest(w)
return
}
s.corsHeaders(w)
}

next.ServeHTTP(w, r)
}
}

func (s *Plugin) Name() string {
return PluginName
}

// configure OPTIONS response
func (s *Plugin) preflightRequest(w http.ResponseWriter) {
headers := w.Header()

headers.Add("Vary", "Origin")
headers.Add("Vary", "Access-Control-Request-Method")
headers.Add("Vary", "Access-Control-Request-Headers")

if s.cfg.Headers.CORS.AllowedOrigin != "" {
headers.Set("Access-Control-Allow-Origin", s.cfg.Headers.CORS.AllowedOrigin)
}

if s.cfg.Headers.CORS.AllowedHeaders != "" {
headers.Set("Access-Control-Allow-Headers", s.cfg.Headers.CORS.AllowedHeaders)
}

if s.cfg.Headers.CORS.AllowedMethods != "" {
headers.Set("Access-Control-Allow-Methods", s.cfg.Headers.CORS.AllowedMethods)
}

if s.cfg.Headers.CORS.AllowCredentials != nil {
headers.Set("Access-Control-Allow-Credentials", strconv.FormatBool(*s.cfg.Headers.CORS.AllowCredentials))
}

if s.cfg.Headers.CORS.MaxAge > 0 {
headers.Set("Access-Control-Max-Age", strconv.Itoa(s.cfg.Headers.CORS.MaxAge))
}

w.WriteHeader(http.StatusOK)
}

// configure CORS headers
func (s *Plugin) corsHeaders(w http.ResponseWriter) {
headers := w.Header()

headers.Add("Vary", "Origin")

if s.cfg.Headers.CORS.AllowedOrigin != "" {
headers.Set("Access-Control-Allow-Origin", s.cfg.Headers.CORS.AllowedOrigin)
}

if s.cfg.Headers.CORS.AllowedHeaders != "" {
headers.Set("Access-Control-Allow-Headers", s.cfg.Headers.CORS.AllowedHeaders)
}

if s.cfg.Headers.CORS.ExposedHeaders != "" {
headers.Set("Access-Control-Expose-Headers", s.cfg.Headers.CORS.ExposedHeaders)
}

if s.cfg.Headers.CORS.AllowCredentials != nil {
headers.Set("Access-Control-Allow-Credentials", strconv.FormatBool(*s.cfg.Headers.CORS.AllowCredentials))
}
}
37 changes: 37 additions & 0 deletions plugins/headers/tests/configs/.rr-cors-headers.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
server:
command: "php ../../../tests/http/client.php headers pipes"
user: ""
group: ""
env:
"RR_HTTP": "true"
relay: "pipes"
relayTimeout: "20s"

http:
debug: true
address: 127.0.0.1:22855
maxRequestSize: 1024
middleware: [ "headers" ]
uploads:
forbid: [ ".php", ".exe", ".bat" ]
trustedSubnets: [ "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" ]
# Additional HTTP headers and CORS control.
headers:
cors:
allowedOrigin: "*"
allowedHeaders: "*"
allowedMethods: "GET,POST,PUT,DELETE"
allowCredentials: true
exposedHeaders: "Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma"
maxAge: 600
request:
"input": "custom-header"
response:
"output": "output-header"
pool:
numWorkers: 2
maxJobs: 0
allocateTimeout: 60s
destroyTimeout: 60s


37 changes: 37 additions & 0 deletions plugins/headers/tests/configs/.rr-headers-init.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
server:
command: "php ../../../tests/http/client.php echo pipes"
user: ""
group: ""
env:
"RR_HTTP": "true"
relay: "pipes"
relayTimeout: "20s"

http:
debug: true
address: 127.0.0.1:33453
maxRequestSize: 1024
middleware: [ "headers" ]
uploads:
forbid: [ ".php", ".exe", ".bat" ]
trustedSubnets: [ "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" ]
# Additional HTTP headers and CORS control.
headers:
cors:
allowedOrigin: "*"
allowedHeaders: "*"
allowedMethods: "GET,POST,PUT,DELETE"
allowCredentials: true
exposedHeaders: "Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma"
maxAge: 600
request:
"Example-Request-Header": "Value"
response:
"X-Powered-By": "RoadRunner"
pool:
numWorkers: 2
maxJobs: 0
allocateTimeout: 60s
destroyTimeout: 60s


30 changes: 30 additions & 0 deletions plugins/headers/tests/configs/.rr-req-headers.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
server:
command: "php ../../../tests/http/client.php header pipes"
user: ""
group: ""
env:
"RR_HTTP": "true"
relay: "pipes"
relayTimeout: "20s"

http:
debug: true
address: 127.0.0.1:22655
maxRequestSize: 1024
middleware: [ "headers" ]
uploads:
forbid: [ ".php", ".exe", ".bat" ]
trustedSubnets: [ "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" ]
# Additional HTTP headers and CORS control.
headers:
request:
"input": "custom-header"
response:
"output": "output-header"
pool:
numWorkers: 2
maxJobs: 0
allocateTimeout: 60s
destroyTimeout: 60s


30 changes: 30 additions & 0 deletions plugins/headers/tests/configs/.rr-res-headers.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
server:
command: "php ../../../tests/http/client.php header pipes"
user: ""
group: ""
env:
"RR_HTTP": "true"
relay: "pipes"
relayTimeout: "20s"

http:
debug: true
address: 127.0.0.1:22455
maxRequestSize: 1024
middleware: [ "headers" ]
uploads:
forbid: [ ".php", ".exe", ".bat" ]
trustedSubnets: [ "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" ]
# Additional HTTP headers and CORS control.
headers:
request:
"input": "custom-header"
response:
"output": "output-header"
pool:
numWorkers: 2
maxJobs: 0
allocateTimeout: 60s
destroyTimeout: 60s


Loading