diff --git a/README.md b/README.md index e69de29..ed09113 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,2 @@ +# Introduction +Proxima is a small reverse-proxy intended to guard the popular [Imaginary](https://github.com/h2non/imaginary) service. It's intended as a public facing frontend for Imaginary. \ No newline at end of file diff --git a/handlers/pathstrip.go b/handlers/pathstrip.go new file mode 100644 index 0000000..a30e4b7 --- /dev/null +++ b/handlers/pathstrip.go @@ -0,0 +1,22 @@ +package handlers + +import ( + "net/http" + + "strings" + + "github.com/go-kit/kit/log" +) + +// NewPathStrip strips the path from the request URL, paths always start with a /. +func NewPathStrip(_ log.Logger, path string) func(h http.Handler) http.Handler { + return func(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.HasPrefix(r.URL.Path, path) { + r.URL.Path = strings.TrimPrefix(r.URL.Path, path) + } + + h.ServeHTTP(w, r) + }) + } +} diff --git a/handlers/ratelimiter.go b/handlers/ratelimiter.go index 4147985..735c394 100644 --- a/handlers/ratelimiter.go +++ b/handlers/ratelimiter.go @@ -8,7 +8,7 @@ import ( "github.com/juju/ratelimit" ) -func NewRateLimitHandler(b *ratelimit.Bucket, l log.Logger) func(h http.Handler) http.Handler { +func NewRateLimitHandler(l log.Logger, b *ratelimit.Bucket) func(h http.Handler) http.Handler { return func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { d := b.Take(1) diff --git a/main.go b/main.go index f600c1e..20dd3de 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,8 @@ import ( "fmt" + stdlog "log" + "github.com/Dynom/proxima/handlers" "github.com/go-kit/kit/log" "github.com/juju/ratelimit" @@ -21,16 +23,12 @@ var ( allowedImaginaryParams argumentList allowedImaginaryActions argumentList imaginaryURL string + pathSegmentToStrip string listenPort int64 bucketRate float64 bucketSize int64 Version = "dev" - logger = log.With( - log.NewLogfmtLogger(os.Stderr), - "ts", log.DefaultTimestampUTC, - "caller", log.DefaultCaller, - ) ) func init() { @@ -42,17 +40,25 @@ func init() { flag.Int64Var(&listenPort, "listen-port", 8080, "Port to listen on") flag.Float64Var(&bucketRate, "bucket-rate", 20, "Rate limiter bucket fill rate (req/s)") flag.Int64Var(&bucketSize, "bucket-size", 500, "Rate limiter bucket size (burst capacity)") + flag.StringVar(&pathSegmentToStrip, "root-path-strip", "", "A section of the (left most) path to strip (e.g.: \"/static\"). Start with a /.") } func main() { flag.Parse() + logger := log.With( + log.NewLogfmtLogger(os.Stderr), + "ts", log.DefaultTimestampUTC, + "caller", log.DefaultCaller, + ) + logger.Log( "msg", "Starting.", "version", Version, "allowed_hosts", allowedHosts.String(), "allowed_params", allowedImaginaryParams.String(), "allowed_actions", allowedImaginaryActions.String(), + "path_to_strip", pathSegmentToStrip, "imaginary_backend", imaginaryURL, ) @@ -62,20 +68,11 @@ func main() { } rlBucket := ratelimit.NewBucketWithRate(bucketRate, bucketSize) - - proxy := httputil.NewSingleHostReverseProxy(rURL) - proxy.Transport = &http.Transport{ - DisableCompression: true, - DisableKeepAlives: false, - IdleConnTimeout: 5 * time.Minute, - MaxIdleConns: 10000, - MaxIdleConnsPerHost: 10000, - ResponseHeaderTimeout: 10 * time.Second, - } + proxy := newProxy(logger, rURL) s := &http.Server{ Addr: fmt.Sprintf(":%d", listenPort), - Handler: decorateHandler(proxy, rlBucket), + Handler: decorateHandler(logger, proxy, rlBucket), ReadHeaderTimeout: 2 * time.Second, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, @@ -86,18 +83,33 @@ func main() { s.ListenAndServe() } +func newProxy(l log.Logger, backend *url.URL) *httputil.ReverseProxy { + proxy := httputil.NewSingleHostReverseProxy(backend) + proxy.ErrorLog = stdlog.New(log.NewStdlibAdapter(l), "", stdlog.LstdFlags) + proxy.Transport = &http.Transport{ + DisableCompression: true, + DisableKeepAlives: false, + IdleConnTimeout: 5 * time.Minute, + MaxIdleConns: 10000, + MaxIdleConnsPerHost: 10000, + ResponseHeaderTimeout: 10 * time.Second, + } + + return proxy +} + type httpHandler func(h http.Handler) http.Handler -func decorateHandler(h http.Handler, b *ratelimit.Bucket) http.Handler { +func decorateHandler(l log.Logger, h http.Handler, b *ratelimit.Bucket) http.Handler { decorators := []httpHandler{ - handlers.NewValidateURLParameter(logger, allowedHosts), + handlers.NewValidateURLParameter(l, allowedHosts), } if len(allowedImaginaryParams) > 0 { decorators = append( decorators, handlers.NewAllowedParams( - logger, + l, allowedImaginaryParams, )) } @@ -106,16 +118,25 @@ func decorateHandler(h http.Handler, b *ratelimit.Bucket) http.Handler { decorators = append( decorators, handlers.NewAllowedActions( - logger, + l, allowedImaginaryActions, )) } + if pathSegmentToStrip != "" { + decorators = append( + decorators, + handlers.NewPathStrip( + l, + pathSegmentToStrip, + )) + } + // Defining early needed handlers last decorators = append( decorators, handlers.NewIgnoreFaviconRequests(), - handlers.NewRateLimitHandler(b, logger), + handlers.NewRateLimitHandler(l, b), ) var handler http.Handler = h