diff --git a/README.md b/README.md index 8bc71ae..4023eae 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,8 @@ Reproxy returns 502 (Bad Gateway) error in case if request doesn't match to any Each option can be provided in two forms: command line or environment key:value. Some command line options have a short form, like `-l localhost:8080` and all of them have the long form, i.e `--listen=localhost:8080`. The environment key (name) listed for each option as a suffix, i.e. `[$LISTEN]`. +All size options support unit suffixes, i.e. 10K (or 10k) for kilobytes, 16M (or 16m) for megabytes, 10G (or 10g) for gigabytes. LAck of any suffix (i.e. 1024) means bytes. + Some options are repeatable, in this case you may pass it multiple times with command line, or comma-separated in env. For example `--ssl.fqdn` is such an option and can be passed as `--ssl.fqdn=a1.example.com --ssl.fqdn=a2.example.com` or as env `SSL_ACME_FQDN=a1.example.com,a2.example.com` This is the list of all options supporting multiple elements: @@ -219,12 +221,13 @@ This is the list of all options supporting multiple elements: - `docker.exclude` (`DOCKER_EXCLUDE`) - `static.rule` (`$STATIC_RULES`) + ## All Application Options ``` Application Options: -l, --listen= listen on host:port (default: 0.0.0.0:8080/8443 under docker, 127.0.0.1:80/443 without) [$LISTEN] - -m, --max= max request size (default: 64000) [$MAX_SIZE] + -m, --max= max request size (default: 64K) [$MAX_SIZE] -g, --gzip enable gz compression [$GZIP] -x, --header= proxy headers [$HEADER] --signature enable reproxy signature headers [$SIGNATURE] @@ -248,7 +251,7 @@ logger: --logger.stdout enable stdout logging [$LOGGER_STDOUT] --logger.enabled enable access and error rotated logs [$LOGGER_ENABLED] --logger.file= location of access log (default: access.log) [$LOGGER_FILE] - --logger.max-size= maximum size in megabytes before it gets rotated (default: 100) [$LOGGER_MAX_SIZE] + --logger.max-size= maximum size in megabytes before it gets rotated (default: 100M) [$LOGGER_MAX_SIZE] --logger.max-backups= maximum number of old log files to retain (default: 10) [$LOGGER_MAX_BACKUPS] docker: diff --git a/app/main.go b/app/main.go index b68530f..7975a24 100644 --- a/app/main.go +++ b/app/main.go @@ -6,9 +6,11 @@ import ( "fmt" "io" "io/ioutil" + "math" "net/http" "os" "os/signal" + "strconv" "strings" "syscall" "time" @@ -26,7 +28,7 @@ import ( var opts struct { Listen string `short:"l" long:"listen" env:"LISTEN" description:"listen on host:port (default: 0.0.0.0:8080/8443 under docker, 127.0.0.1:80/443 without)"` - MaxSize int64 `short:"m" long:"max" env:"MAX_SIZE" default:"64000" description:"max request size"` + MaxSize string `short:"m" long:"max" env:"MAX_SIZE" default:"64K" description:"max request size"` GzipEnabled bool `short:"g" long:"gzip" env:"GZIP" description:"enable gz compression"` ProxyHeaders []string `short:"x" long:"header" env:"HEADER" description:"proxy headers" env-delim:","` @@ -50,7 +52,7 @@ var opts struct { StdOut bool `long:"stdout" env:"STDOUT" description:"enable stdout logging"` Enabled bool `long:"enabled" env:"ENABLED" description:"enable access and error rotated logs"` FileName string `long:"file" env:"FILE" default:"access.log" description:"location of access log"` - MaxSize int `long:"max-size" env:"MAX_SIZE" default:"100" description:"maximum size in megabytes before it gets rotated"` + MaxSize string `long:"max-size" env:"MAX_SIZE" default:"100M" description:"maximum size before it gets rotated"` MaxBackups int `long:"max-backups" env:"MAX_BACKUPS" default:"10" description:"maximum number of old log files to retain"` } `group:"logger" namespace:"logger" env-namespace:"LOGGER"` @@ -167,7 +169,11 @@ func run() error { return fmt.Errorf("failed to make config of ssl server params: %w", sslErr) } - accessLog := makeAccessLogWriter() + accessLog, alErr := makeAccessLogWriter() + if alErr != nil { + return fmt.Errorf("failed to access log: %w", sslErr) + } + defer func() { if logErr := accessLog.Close(); logErr != nil { log.Printf("[WARN] can't close access log, %v", logErr) @@ -204,11 +210,16 @@ func run() error { addr := listenAddress(opts.Listen, opts.SSL.Type) log.Printf("[DEBUG] listen address %s", addr) + maxBodySize, perr := sizeParse(opts.MaxSize) + if perr != nil { + return fmt.Errorf("failed to convert MaxSize: %w", err) + } + px := &proxy.Http{ Version: revision, Matcher: svc, Address: addr, - MaxBodySize: opts.MaxSize, + MaxBodySize: int64(maxBodySize), AssetsLocation: opts.Assets.Location, AssetsWebRoot: opts.Assets.WebRoot, CacheControl: cacheControl, @@ -326,18 +337,26 @@ func makeErrorReporter() (proxy.Reporter, error) { return result, nil } -func makeAccessLogWriter() (accessLog io.WriteCloser) { +func makeAccessLogWriter() (accessLog io.WriteCloser, err error) { if !opts.Logger.Enabled { - return nopWriteCloser{ioutil.Discard} + return nopWriteCloser{ioutil.Discard}, nil } - log.Printf("[INFO] logger enabled for %s", opts.Logger.FileName) + + maxSize, perr := sizeParse(opts.Logger.MaxSize) + if perr != nil { + return nil, fmt.Errorf("can't parse logger MaxSize: %w", perr) + } + + maxSize = maxSize / 1048576 + + log.Printf("[INFO] logger enabled for %s, max size %dM", opts.Logger.FileName, maxSize) return &lumberjack.Logger{ Filename: opts.Logger.FileName, - MaxSize: opts.Logger.MaxSize, + MaxSize: int(maxSize), // in MB MaxBackups: opts.Logger.MaxBackups, Compress: true, LocalTime: true, - } + }, nil } // listenAddress sets default to 127.0.0.0:8080/80443 and, if detected REPROXY_IN_DOCKER env, to 0.0.0.0:80/443 @@ -374,6 +393,22 @@ func redirHTTPPort(port int) int { return 80 } +func sizeParse(inp string) (uint64, error) { + if inp == "" { + return 0, errors.New("empty value") + } + for i, sfx := range []string{"k", "m", "g", "t"} { + if strings.HasSuffix(inp, strings.ToUpper(sfx)) || strings.HasSuffix(inp, strings.ToLower(sfx)) { + val, err := strconv.Atoi(inp[:len(inp)-1]) + if err != nil { + return 0, fmt.Errorf("can't parse %s: %w", inp, err) + } + return uint64(float64(val) * math.Pow(float64(1024), float64(i+1))), nil + } + } + return strconv.ParseUint(inp, 10, 64) +} + type nopWriteCloser struct{ io.Writer } func (n nopWriteCloser) Close() error { return nil } diff --git a/app/main_test.go b/app/main_test.go index 823ce3e..393e2bb 100644 --- a/app/main_test.go +++ b/app/main_test.go @@ -233,3 +233,38 @@ func Test_redirHTTPPort(t *testing.T) { }) } } + +func Test_sizeParse(t *testing.T) { + + tbl := []struct { + inp string + res uint64 + err bool + }{ + {"1000", 1000, false}, + {"0", 0, false}, + {"", 0, true}, + {"10K", 10240, false}, + {"1k", 1024, false}, + {"14m", 14 * 1024 * 1024, false}, + {"7G", 7 * 1024 * 1024 * 1024, false}, + {"170g", 170 * 1024 * 1024 * 1024, false}, + {"17T", 17 * 1024 * 1024 * 1024 * 1024, false}, + {"123aT", 0, true}, + {"123a", 0, true}, + {"123.45", 0, true}, + } + + for i, tt := range tbl { + t.Run(strconv.Itoa(i), func(t *testing.T) { + res, err := sizeParse(tt.inp) + if tt.err { + require.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tt.res, res) + }) + } + +}