Make HTTP Client timeout and max body size configurable

This commit is contained in:
Frédéric Guillot 2019-06-02 07:13:35 -07:00 committed by fguillot
parent 228862fefa
commit bb720c87c1
5 changed files with 117 additions and 33 deletions

View File

@ -984,3 +984,69 @@ func TestHTTPSOn(t *testing.T) {
t.Fatalf(`Unexpected HTTPS value, got "%v"`, opts.HTTPS) t.Fatalf(`Unexpected HTTPS value, got "%v"`, opts.HTTPS)
} }
} }
func TestHTTPClientTimeout(t *testing.T) {
os.Clearenv()
os.Setenv("HTTP_CLIENT_TIMEOUT", "42")
opts, err := parse()
if err != nil {
t.Fatalf(`Parsing failure: %q`, err)
}
expected := 42
result := opts.HTTPClientTimeout()
if result != expected {
t.Fatalf(`Unexpected HTTP_CLIENT_TIMEOUT value, got %d instead of %d`, result, expected)
}
}
func TestDefaultHTTPClientTimeoutValue(t *testing.T) {
os.Clearenv()
opts, err := parse()
if err != nil {
t.Fatalf(`Parsing failure: %q`, err)
}
expected := defaultHTTPClientTimeout
result := opts.HTTPClientTimeout()
if result != expected {
t.Fatalf(`Unexpected HTTP_CLIENT_TIMEOUT value, got %d instead of %d`, result, expected)
}
}
func TestHTTPClientMaxBodySize(t *testing.T) {
os.Clearenv()
os.Setenv("HTTP_CLIENT_MAX_BODY_SIZE", "42")
opts, err := parse()
if err != nil {
t.Fatalf(`Parsing failure: %q`, err)
}
expected := int64(42 * 1024 * 1024)
result := opts.HTTPClientMaxBodySize()
if result != expected {
t.Fatalf(`Unexpected HTTP_CLIENT_MAX_BODY_SIZE value, got %d instead of %d`, result, expected)
}
}
func TestDefaultHTTPClientMaxBodySizeValue(t *testing.T) {
os.Clearenv()
opts, err := parse()
if err != nil {
t.Fatalf(`Parsing failure: %q`, err)
}
expected := int64(defaultHTTPClientMaxBodySize * 1024 * 1024)
result := opts.HTTPClientMaxBodySize()
if result != expected {
t.Fatalf(`Unexpected HTTP_CLIENT_MAX_BODY_SIZE value, got %d instead of %d`, result, expected)
}
}

View File

@ -5,25 +5,27 @@
package config // import "miniflux.app/config" package config // import "miniflux.app/config"
const ( const (
defaultBaseURL = "http://localhost" defaultBaseURL = "http://localhost"
defaultWorkerPoolSize = 5 defaultWorkerPoolSize = 5
defaultPollingFrequency = 60 defaultPollingFrequency = 60
defaultBatchSize = 10 defaultBatchSize = 10
defaultDatabaseURL = "user=postgres password=postgres dbname=miniflux2 sslmode=disable" defaultDatabaseURL = "user=postgres password=postgres dbname=miniflux2 sslmode=disable"
defaultDatabaseMaxConns = 20 defaultDatabaseMaxConns = 20
defaultDatabaseMinConns = 1 defaultDatabaseMinConns = 1
defaultArchiveReadDays = 60 defaultArchiveReadDays = 60
defaultListenAddr = "127.0.0.1:8080" defaultListenAddr = "127.0.0.1:8080"
defaultCertFile = "" defaultCertFile = ""
defaultKeyFile = "" defaultKeyFile = ""
defaultCertDomain = "" defaultCertDomain = ""
defaultCertCache = "/tmp/cert_cache" defaultCertCache = "/tmp/cert_cache"
defaultCleanupFrequency = 24 defaultCleanupFrequency = 24
defaultProxyImages = "http-only" defaultProxyImages = "http-only"
defaultOAuth2ClientID = "" defaultOAuth2ClientID = ""
defaultOAuth2ClientSecret = "" defaultOAuth2ClientSecret = ""
defaultOAuth2RedirectURL = "" defaultOAuth2RedirectURL = ""
defaultOAuth2Provider = "" defaultOAuth2Provider = ""
defaultHTTPClientTimeout = 20
defaultHTTPClientMaxBodySize = 15
) )
// Options contains configuration options. // Options contains configuration options.
@ -58,6 +60,8 @@ type Options struct {
oauth2RedirectURL string oauth2RedirectURL string
oauth2Provider string oauth2Provider string
pocketConsumerKey string pocketConsumerKey string
httpClientTimeout int
httpClientMaxBodySize int64
} }
// HasDebugMode returns true if debug mode is enabled. // HasDebugMode returns true if debug mode is enabled.
@ -212,3 +216,13 @@ func (o *Options) PocketConsumerKey(defaultValue string) string {
} }
return defaultValue return defaultValue
} }
// HTTPClientTimeout returns the time limit in seconds before the HTTP client cancel the request.
func (o *Options) HTTPClientTimeout() int {
return o.httpClientTimeout
}
// HTTPClientMaxBodySize returns the number of bytes allowed for the HTTP client to transfer.
func (o *Options) HTTPClientMaxBodySize() int64 {
return o.httpClientMaxBodySize
}

View File

@ -45,6 +45,8 @@ func parse() (opts *Options, err error) {
opts.batchSize = getIntValue("BATCH_SIZE", defaultBatchSize) opts.batchSize = getIntValue("BATCH_SIZE", defaultBatchSize)
opts.archiveReadDays = getIntValue("ARCHIVE_READ_DAYS", defaultArchiveReadDays) opts.archiveReadDays = getIntValue("ARCHIVE_READ_DAYS", defaultArchiveReadDays)
opts.proxyImages = getStringValue("PROXY_IMAGES", defaultProxyImages) opts.proxyImages = getStringValue("PROXY_IMAGES", defaultProxyImages)
opts.createAdmin = getBooleanValue("CREATE_ADMIN")
opts.pocketConsumerKey = getStringValue("POCKET_CONSUMER_KEY", "")
opts.oauth2UserCreationAllowed = getBooleanValue("OAUTH2_USER_CREATION") opts.oauth2UserCreationAllowed = getBooleanValue("OAUTH2_USER_CREATION")
opts.oauth2ClientID = getStringValue("OAUTH2_CLIENT_ID", defaultOAuth2ClientID) opts.oauth2ClientID = getStringValue("OAUTH2_CLIENT_ID", defaultOAuth2ClientID)
@ -52,9 +54,8 @@ func parse() (opts *Options, err error) {
opts.oauth2RedirectURL = getStringValue("OAUTH2_REDIRECT_URL", defaultOAuth2RedirectURL) opts.oauth2RedirectURL = getStringValue("OAUTH2_REDIRECT_URL", defaultOAuth2RedirectURL)
opts.oauth2Provider = getStringValue("OAUTH2_PROVIDER", defaultOAuth2Provider) opts.oauth2Provider = getStringValue("OAUTH2_PROVIDER", defaultOAuth2Provider)
opts.pocketConsumerKey = getStringValue("POCKET_CONSUMER_KEY", "") opts.httpClientTimeout = getIntValue("HTTP_CLIENT_TIMEOUT", defaultHTTPClientTimeout)
opts.httpClientMaxBodySize = int64(getIntValue("HTTP_CLIENT_MAX_BODY_SIZE", defaultHTTPClientMaxBodySize) * 1024 * 1024)
opts.createAdmin = getBooleanValue("CREATE_ADMIN")
return opts, nil return opts, nil
} }

View File

@ -18,20 +18,13 @@ import (
"strings" "strings"
"time" "time"
"miniflux.app/config"
"miniflux.app/errors" "miniflux.app/errors"
"miniflux.app/logger" "miniflux.app/logger"
"miniflux.app/timer" "miniflux.app/timer"
"miniflux.app/version" "miniflux.app/version"
) )
const (
// 20 seconds max.
requestTimeout = 20
// 15MB max.
maxBodySize = 1024 * 1024 * 15
)
var ( var (
// DefaultUserAgent sets the User-Agent header used for any requests by miniflux. // DefaultUserAgent sets the User-Agent header used for any requests by miniflux.
DefaultUserAgent = "Mozilla/5.0 (compatible; Miniflux/" + version.Version + "; +https://miniflux.app)" DefaultUserAgent = "Mozilla/5.0 (compatible; Miniflux/" + version.Version + "; +https://miniflux.app)"
@ -144,7 +137,7 @@ func (c *Client) executeRequest(request *http.Request) (*Response, error) {
case net.Error: case net.Error:
nerr := uerr.Err.(net.Error) nerr := uerr.Err.(net.Error)
if nerr.Timeout() { if nerr.Timeout() {
err = errors.NewLocalizedError(errRequestTimeout, requestTimeout) err = errors.NewLocalizedError(errRequestTimeout, config.Opts.HTTPClientTimeout())
} else if nerr.Temporary() { } else if nerr.Temporary() {
err = errors.NewLocalizedError(errTemporaryNetworkOperation, nerr) err = errors.NewLocalizedError(errTemporaryNetworkOperation, nerr)
} }
@ -154,7 +147,7 @@ func (c *Client) executeRequest(request *http.Request) (*Response, error) {
return nil, err return nil, err
} }
if resp.ContentLength > maxBodySize { if resp.ContentLength > config.Opts.HTTPClientMaxBodySize() {
return nil, fmt.Errorf("client: response too large (%d bytes)", resp.ContentLength) return nil, fmt.Errorf("client: response too large (%d bytes)", resp.ContentLength)
} }
@ -212,7 +205,7 @@ func (c *Client) buildRequest(method string, body io.Reader) (*http.Request, err
} }
func (c *Client) buildClient() http.Client { func (c *Client) buildClient() http.Client {
client := http.Client{Timeout: time.Duration(requestTimeout * time.Second)} client := http.Client{Timeout: time.Duration(config.Opts.HTTPClientTimeout()) * time.Second}
if c.Insecure { if c.Insecure {
client.Transport = &http.Transport{ client.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, TLSClientConfig: &tls.Config{InsecureSkipVerify: true},

View File

@ -169,6 +169,16 @@ Pocket consumer API key for all users\&.
Avoids mixed content warnings for external images: http-only, all, or none\&. Avoids mixed content warnings for external images: http-only, all, or none\&.
.br .br
Default is http-only\&. Default is http-only\&.
.TP
.B HTTP_CLIENT_TIMEOUT
Time limit in seconds before the HTTP client cancel the request\&.
.br
Default is 20 seconds\&.
.TP
.B HTTP_CLIENT_MAX_BODY_SIZE
Maximum body size for HTTP requests in Mebibyte (MiB)\&.
.br
Default is 20 MiB\&.
.SH AUTHORS .SH AUTHORS
.sp .sp