Make HTTP Client timeout and max body size configurable
This commit is contained in:
parent
228862fefa
commit
bb720c87c1
5 changed files with 117 additions and 33 deletions
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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},
|
||||||
|
|
10
miniflux.1
10
miniflux.1
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue