Refactor config package

- Parse configuration only once during startup time
- Store configuration values in a global variable
This commit is contained in:
Frédéric Guillot 2019-06-01 18:18:09 -07:00 committed by fguillot
parent 04d85b3c63
commit 228862fefa
28 changed files with 922 additions and 624 deletions

View file

@ -51,9 +51,11 @@ func Parse() {
flag.BoolVar(&flagDebugMode, "debug", false, flagDebugModeHelp)
flag.Parse()
cfg := config.NewConfig()
if err := config.ParseConfig(); err != nil {
logger.Fatal("%v", err)
}
if flagDebugMode || cfg.HasDebugMode() {
if flagDebugMode || config.Opts.HasDebugMode() {
logger.EnableDebug()
}
@ -67,7 +69,15 @@ func Parse() {
return
}
db, err := database.NewConnectionPool(cfg.DatabaseURL(), cfg.DatabaseMinConns(), cfg.DatabaseMaxConns())
if config.Opts.IsDefaultDatabaseURL() {
logger.Info("The default value for DATABASE_URL is used")
}
db, err := database.NewConnectionPool(
config.Opts.DatabaseURL(),
config.Opts.DatabaseMinConns(),
config.Opts.DatabaseMaxConns(),
)
if err != nil {
logger.Fatal("Unable to connect to the database: %v", err)
}
@ -101,14 +111,14 @@ func Parse() {
}
// Run migrations and start the deamon.
if cfg.RunMigrations() {
if config.Opts.RunMigrations() {
database.Migrate(db)
}
// Create admin user and start the deamon.
if cfg.CreateAdmin() {
if config.Opts.CreateAdmin() {
createAdmin(store)
}
startDaemon(cfg, store)
startDaemon(store)
}

View file

@ -16,13 +16,13 @@ import (
"miniflux.app/config"
"miniflux.app/logger"
"miniflux.app/reader/feed"
"miniflux.app/service/scheduler"
"miniflux.app/service/httpd"
"miniflux.app/service/scheduler"
"miniflux.app/storage"
"miniflux.app/worker"
)
func startDaemon(cfg *config.Config, store *storage.Storage) {
func startDaemon(store *storage.Storage) {
logger.Info("Starting Miniflux...")
stop := make(chan os.Signal, 1)
@ -30,17 +30,17 @@ func startDaemon(cfg *config.Config, store *storage.Storage) {
signal.Notify(stop, syscall.SIGTERM)
feedHandler := feed.NewFeedHandler(store)
pool := worker.NewPool(feedHandler, cfg.WorkerPoolSize())
pool := worker.NewPool(feedHandler, config.Opts.WorkerPoolSize())
go showProcessStatistics()
if cfg.HasSchedulerService() {
scheduler.Serve(cfg, store, pool)
if config.Opts.HasSchedulerService() {
scheduler.Serve(store, pool)
}
var httpServer *http.Server
if cfg.HasHTTPService() {
httpServer = httpd.Serve(cfg, store, pool, feedHandler)
if config.Opts.HasHTTPService() {
httpServer = httpd.Serve(store, pool, feedHandler)
}
<-stop

View file

@ -4,271 +4,11 @@
package config // import "miniflux.app/config"
import (
"net/url"
"os"
"strconv"
"strings"
// Opts contains configuration options after parsing.
var Opts *Options
"miniflux.app/logger"
)
const (
defaultBaseURL = "http://localhost"
defaultDatabaseURL = "user=postgres password=postgres dbname=miniflux2 sslmode=disable"
defaultWorkerPoolSize = 5
defaultPollingFrequency = 60
defaultBatchSize = 10
defaultDatabaseMaxConns = 20
defaultDatabaseMinConns = 1
defaultArchiveReadDays = 60
defaultListenAddr = "127.0.0.1:8080"
defaultCertFile = ""
defaultKeyFile = ""
defaultCertDomain = ""
defaultCertCache = "/tmp/cert_cache"
defaultCleanupFrequency = 24
defaultProxyImages = "http-only"
defaultOAuth2ClientID = ""
defaultOAuth2ClientSecret = ""
defaultOAuth2RedirectURL = ""
defaultOAuth2Provider = ""
)
// Config manages configuration parameters.
type Config struct {
IsHTTPS bool
baseURL string
rootURL string
basePath string
}
func (c *Config) parseBaseURL() {
baseURL := os.Getenv("BASE_URL")
if baseURL == "" {
return
}
if baseURL[len(baseURL)-1:] == "/" {
baseURL = baseURL[:len(baseURL)-1]
}
u, err := url.Parse(baseURL)
if err != nil {
logger.Error("Invalid BASE_URL: %v", err)
return
}
scheme := strings.ToLower(u.Scheme)
if scheme != "https" && scheme != "http" {
logger.Error("Invalid BASE_URL: scheme must be http or https")
return
}
c.baseURL = baseURL
c.basePath = u.Path
u.Path = ""
c.rootURL = u.String()
}
// HasDebugMode returns true if debug mode is enabled.
func (c *Config) HasDebugMode() bool {
return getBooleanValue("DEBUG")
}
// BaseURL returns the application base URL with path.
func (c *Config) BaseURL() string {
return c.baseURL
}
// RootURL returns the base URL without path.
func (c *Config) RootURL() string {
return c.rootURL
}
// BasePath returns the application base path according to the base URL.
func (c *Config) BasePath() string {
return c.basePath
}
// DatabaseURL returns the database URL.
func (c *Config) DatabaseURL() string {
value, exists := os.LookupEnv("DATABASE_URL")
if !exists {
logger.Info("The environment variable DATABASE_URL is not configured (the default value is used instead)")
}
if value == "" {
value = defaultDatabaseURL
}
return value
}
// DatabaseMaxConns returns the maximum number of database connections.
func (c *Config) DatabaseMaxConns() int {
return getIntValue("DATABASE_MAX_CONNS", defaultDatabaseMaxConns)
}
// DatabaseMinConns returns the minimum number of database connections.
func (c *Config) DatabaseMinConns() int {
return getIntValue("DATABASE_MIN_CONNS", defaultDatabaseMinConns)
}
// ListenAddr returns the listen address for the HTTP server.
func (c *Config) ListenAddr() string {
if port := os.Getenv("PORT"); port != "" {
return ":" + port
}
return getStringValue("LISTEN_ADDR", defaultListenAddr)
}
// CertFile returns the SSL certificate filename if any.
func (c *Config) CertFile() string {
return getStringValue("CERT_FILE", defaultCertFile)
}
// KeyFile returns the private key filename for custom SSL certificate.
func (c *Config) KeyFile() string {
return getStringValue("KEY_FILE", defaultKeyFile)
}
// CertDomain returns the domain to use for Let's Encrypt certificate.
func (c *Config) CertDomain() string {
return getStringValue("CERT_DOMAIN", defaultCertDomain)
}
// CertCache returns the directory to use for Let's Encrypt session cache.
func (c *Config) CertCache() string {
return getStringValue("CERT_CACHE", defaultCertCache)
}
// CleanupFrequency returns the interval for cleanup jobs.
func (c *Config) CleanupFrequency() int {
return getIntValue("CLEANUP_FREQUENCY", defaultCleanupFrequency)
}
// WorkerPoolSize returns the number of background worker.
func (c *Config) WorkerPoolSize() int {
return getIntValue("WORKER_POOL_SIZE", defaultWorkerPoolSize)
}
// PollingFrequency returns the interval to refresh feeds in the background.
func (c *Config) PollingFrequency() int {
return getIntValue("POLLING_FREQUENCY", defaultPollingFrequency)
}
// BatchSize returns the number of feeds to send for background processing.
func (c *Config) BatchSize() int {
return getIntValue("BATCH_SIZE", defaultBatchSize)
}
// IsOAuth2UserCreationAllowed returns true if user creation is allowed for OAuth2 users.
func (c *Config) IsOAuth2UserCreationAllowed() bool {
return getBooleanValue("OAUTH2_USER_CREATION")
}
// OAuth2ClientID returns the OAuth2 Client ID.
func (c *Config) OAuth2ClientID() string {
return getStringValue("OAUTH2_CLIENT_ID", defaultOAuth2ClientID)
}
// OAuth2ClientSecret returns the OAuth2 client secret.
func (c *Config) OAuth2ClientSecret() string {
return getStringValue("OAUTH2_CLIENT_SECRET", defaultOAuth2ClientSecret)
}
// OAuth2RedirectURL returns the OAuth2 redirect URL.
func (c *Config) OAuth2RedirectURL() string {
return getStringValue("OAUTH2_REDIRECT_URL", defaultOAuth2RedirectURL)
}
// OAuth2Provider returns the name of the OAuth2 provider configured.
func (c *Config) OAuth2Provider() string {
return getStringValue("OAUTH2_PROVIDER", defaultOAuth2Provider)
}
// HasHSTS returns true if HTTP Strict Transport Security is enabled.
func (c *Config) HasHSTS() bool {
return !getBooleanValue("DISABLE_HSTS")
}
// RunMigrations returns true if the environment variable RUN_MIGRATIONS is not empty.
func (c *Config) RunMigrations() bool {
return getBooleanValue("RUN_MIGRATIONS")
}
// CreateAdmin returns true if the environment variable CREATE_ADMIN is not empty.
func (c *Config) CreateAdmin() bool {
return getBooleanValue("CREATE_ADMIN")
}
// PocketConsumerKey returns the Pocket Consumer Key if defined as environment variable.
func (c *Config) PocketConsumerKey(defaultValue string) string {
return getStringValue("POCKET_CONSUMER_KEY", defaultValue)
}
// ProxyImages returns "none" to never proxy, "http-only" to proxy non-HTTPS, "all" to always proxy.
func (c *Config) ProxyImages() string {
return getStringValue("PROXY_IMAGES", defaultProxyImages)
}
// HasHTTPService returns true if the HTTP service is enabled.
func (c *Config) HasHTTPService() bool {
return !getBooleanValue("DISABLE_HTTP_SERVICE")
}
// HasSchedulerService returns true if the scheduler service is enabled.
func (c *Config) HasSchedulerService() bool {
return !getBooleanValue("DISABLE_SCHEDULER_SERVICE")
}
// ArchiveReadDays returns the number of days after which marking read items as removed.
func (c *Config) ArchiveReadDays() int {
return getIntValue("ARCHIVE_READ_DAYS", defaultArchiveReadDays)
}
// NewConfig returns a new Config.
func NewConfig() *Config {
cfg := &Config{
baseURL: defaultBaseURL,
rootURL: defaultBaseURL,
IsHTTPS: getBooleanValue("HTTPS"),
}
cfg.parseBaseURL()
return cfg
}
func getBooleanValue(key string) bool {
value := strings.ToLower(os.Getenv(key))
if value == "1" || value == "yes" || value == "true" || value == "on" {
return true
}
return false
}
func getStringValue(key, fallback string) string {
value := os.Getenv(key)
if value == "" {
return fallback
}
return value
}
func getIntValue(key string, fallback int) int {
value := os.Getenv(key)
if value == "" {
return fallback
}
v, err := strconv.Atoi(value)
if err != nil {
return fallback
}
return v
// ParseConfig parses configuration options.
func ParseConfig() (err error) {
Opts, err = parse()
return err
}

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,10 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Copyright 2019 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
/*
Package config handles configuration values for the application.
Package config handles configuration management for the application.
*/
package config // import "miniflux.app/config"

214
config/options.go Normal file
View file

@ -0,0 +1,214 @@
// Copyright 2019 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package config // import "miniflux.app/config"
const (
defaultBaseURL = "http://localhost"
defaultWorkerPoolSize = 5
defaultPollingFrequency = 60
defaultBatchSize = 10
defaultDatabaseURL = "user=postgres password=postgres dbname=miniflux2 sslmode=disable"
defaultDatabaseMaxConns = 20
defaultDatabaseMinConns = 1
defaultArchiveReadDays = 60
defaultListenAddr = "127.0.0.1:8080"
defaultCertFile = ""
defaultKeyFile = ""
defaultCertDomain = ""
defaultCertCache = "/tmp/cert_cache"
defaultCleanupFrequency = 24
defaultProxyImages = "http-only"
defaultOAuth2ClientID = ""
defaultOAuth2ClientSecret = ""
defaultOAuth2RedirectURL = ""
defaultOAuth2Provider = ""
)
// Options contains configuration options.
type Options struct {
HTTPS bool
hsts bool
httpService bool
schedulerService bool
debug bool
baseURL string
rootURL string
basePath string
databaseURL string
databaseMaxConns int
databaseMinConns int
runMigrations bool
listenAddr string
certFile string
certDomain string
certCache string
certKeyFile string
cleanupFrequency int
archiveReadDays int
pollingFrequency int
batchSize int
workerPoolSize int
createAdmin bool
proxyImages string
oauth2UserCreationAllowed bool
oauth2ClientID string
oauth2ClientSecret string
oauth2RedirectURL string
oauth2Provider string
pocketConsumerKey string
}
// HasDebugMode returns true if debug mode is enabled.
func (o *Options) HasDebugMode() bool {
return o.debug
}
// BaseURL returns the application base URL with path.
func (o *Options) BaseURL() string {
return o.baseURL
}
// RootURL returns the base URL without path.
func (o *Options) RootURL() string {
return o.rootURL
}
// BasePath returns the application base path according to the base URL.
func (o *Options) BasePath() string {
return o.basePath
}
// IsDefaultDatabaseURL returns true if the default database URL is used.
func (o *Options) IsDefaultDatabaseURL() bool {
return o.databaseURL == defaultDatabaseURL
}
// DatabaseURL returns the database URL.
func (o *Options) DatabaseURL() string {
return o.databaseURL
}
// DatabaseMaxConns returns the maximum number of database connections.
func (o *Options) DatabaseMaxConns() int {
return o.databaseMaxConns
}
// DatabaseMinConns returns the minimum number of database connections.
func (o *Options) DatabaseMinConns() int {
return o.databaseMinConns
}
// ListenAddr returns the listen address for the HTTP server.
func (o *Options) ListenAddr() string {
return o.listenAddr
}
// CertFile returns the SSL certificate filename if any.
func (o *Options) CertFile() string {
return o.certFile
}
// CertKeyFile returns the private key filename for custom SSL certificate.
func (o *Options) CertKeyFile() string {
return o.certKeyFile
}
// CertDomain returns the domain to use for Let's Encrypt certificate.
func (o *Options) CertDomain() string {
return o.certDomain
}
// CertCache returns the directory to use for Let's Encrypt session cache.
func (o *Options) CertCache() string {
return o.certCache
}
// CleanupFrequency returns the interval for cleanup jobs.
func (o *Options) CleanupFrequency() int {
return o.cleanupFrequency
}
// WorkerPoolSize returns the number of background worker.
func (o *Options) WorkerPoolSize() int {
return o.workerPoolSize
}
// PollingFrequency returns the interval to refresh feeds in the background.
func (o *Options) PollingFrequency() int {
return o.pollingFrequency
}
// BatchSize returns the number of feeds to send for background processing.
func (o *Options) BatchSize() int {
return o.batchSize
}
// IsOAuth2UserCreationAllowed returns true if user creation is allowed for OAuth2 users.
func (o *Options) IsOAuth2UserCreationAllowed() bool {
return o.oauth2UserCreationAllowed
}
// OAuth2ClientID returns the OAuth2 Client ID.
func (o *Options) OAuth2ClientID() string {
return o.oauth2ClientID
}
// OAuth2ClientSecret returns the OAuth2 client secret.
func (o *Options) OAuth2ClientSecret() string {
return o.oauth2ClientSecret
}
// OAuth2RedirectURL returns the OAuth2 redirect URL.
func (o *Options) OAuth2RedirectURL() string {
return o.oauth2RedirectURL
}
// OAuth2Provider returns the name of the OAuth2 provider configured.
func (o *Options) OAuth2Provider() string {
return o.oauth2Provider
}
// HasHSTS returns true if HTTP Strict Transport Security is enabled.
func (o *Options) HasHSTS() bool {
return o.hsts
}
// RunMigrations returns true if the environment variable RUN_MIGRATIONS is not empty.
func (o *Options) RunMigrations() bool {
return o.runMigrations
}
// CreateAdmin returns true if the environment variable CREATE_ADMIN is not empty.
func (o *Options) CreateAdmin() bool {
return o.createAdmin
}
// ProxyImages returns "none" to never proxy, "http-only" to proxy non-HTTPS, "all" to always proxy.
func (o *Options) ProxyImages() string {
return o.proxyImages
}
// HasHTTPService returns true if the HTTP service is enabled.
func (o *Options) HasHTTPService() bool {
return o.httpService
}
// HasSchedulerService returns true if the scheduler service is enabled.
func (o *Options) HasSchedulerService() bool {
return o.schedulerService
}
// ArchiveReadDays returns the number of days after which marking read items as removed.
func (o *Options) ArchiveReadDays() int {
return o.archiveReadDays
}
// PocketConsumerKey returns the Pocket Consumer Key if configured.
func (o *Options) PocketConsumerKey(defaultValue string) string {
if o.pocketConsumerKey != "" {
return o.pocketConsumerKey
}
return defaultValue
}

124
config/parser.go Normal file
View file

@ -0,0 +1,124 @@
// Copyright 2019 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package config // import "miniflux.app/config"
import (
"errors"
"fmt"
"net/url"
"os"
"strconv"
"strings"
)
func parse() (opts *Options, err error) {
opts = &Options{}
opts.baseURL, opts.rootURL, opts.basePath, err = parseBaseURL()
if err != nil {
return nil, err
}
opts.debug = getBooleanValue("DEBUG")
opts.listenAddr = parseListenAddr()
opts.databaseURL = getStringValue("DATABASE_URL", defaultDatabaseURL)
opts.databaseMaxConns = getIntValue("DATABASE_MAX_CONNS", defaultDatabaseMaxConns)
opts.databaseMinConns = getIntValue("DATABASE_MIN_CONNS", defaultDatabaseMinConns)
opts.runMigrations = getBooleanValue("RUN_MIGRATIONS")
opts.hsts = !getBooleanValue("DISABLE_HSTS")
opts.HTTPS = getBooleanValue("HTTPS")
opts.schedulerService = !getBooleanValue("DISABLE_SCHEDULER_SERVICE")
opts.httpService = !getBooleanValue("DISABLE_HTTP_SERVICE")
opts.certFile = getStringValue("CERT_FILE", defaultCertFile)
opts.certKeyFile = getStringValue("KEY_FILE", defaultKeyFile)
opts.certDomain = getStringValue("CERT_DOMAIN", defaultCertDomain)
opts.certCache = getStringValue("CERT_CACHE", defaultCertCache)
opts.cleanupFrequency = getIntValue("CLEANUP_FREQUENCY", defaultCleanupFrequency)
opts.workerPoolSize = getIntValue("WORKER_POOL_SIZE", defaultWorkerPoolSize)
opts.pollingFrequency = getIntValue("POLLING_FREQUENCY", defaultPollingFrequency)
opts.batchSize = getIntValue("BATCH_SIZE", defaultBatchSize)
opts.archiveReadDays = getIntValue("ARCHIVE_READ_DAYS", defaultArchiveReadDays)
opts.proxyImages = getStringValue("PROXY_IMAGES", defaultProxyImages)
opts.oauth2UserCreationAllowed = getBooleanValue("OAUTH2_USER_CREATION")
opts.oauth2ClientID = getStringValue("OAUTH2_CLIENT_ID", defaultOAuth2ClientID)
opts.oauth2ClientSecret = getStringValue("OAUTH2_CLIENT_SECRET", defaultOAuth2ClientSecret)
opts.oauth2RedirectURL = getStringValue("OAUTH2_REDIRECT_URL", defaultOAuth2RedirectURL)
opts.oauth2Provider = getStringValue("OAUTH2_PROVIDER", defaultOAuth2Provider)
opts.pocketConsumerKey = getStringValue("POCKET_CONSUMER_KEY", "")
opts.createAdmin = getBooleanValue("CREATE_ADMIN")
return opts, nil
}
func parseBaseURL() (string, string, string, error) {
baseURL := os.Getenv("BASE_URL")
if baseURL == "" {
return defaultBaseURL, defaultBaseURL, "", nil
}
if baseURL[len(baseURL)-1:] == "/" {
baseURL = baseURL[:len(baseURL)-1]
}
u, err := url.Parse(baseURL)
if err != nil {
return "", "", "", fmt.Errorf("Invalid BASE_URL: %v", err)
}
scheme := strings.ToLower(u.Scheme)
if scheme != "https" && scheme != "http" {
return "", "", "", errors.New("Invalid BASE_URL: scheme must be http or https")
}
basePath := u.Path
u.Path = ""
return baseURL, u.String(), basePath, nil
}
func parseListenAddr() string {
if port := os.Getenv("PORT"); port != "" {
return ":" + port
}
return getStringValue("LISTEN_ADDR", defaultListenAddr)
}
func getBooleanValue(key string) bool {
value := strings.ToLower(os.Getenv(key))
if value == "1" || value == "yes" || value == "true" || value == "on" {
return true
}
return false
}
func getStringValue(key, fallback string) string {
value := os.Getenv(key)
if value == "" {
return fallback
}
return value
}
func getIntValue(key string, fallback int) int {
value := os.Getenv(key)
if value == "" {
return fallback
}
v, err := strconv.Atoi(value)
if err != nil {
return fallback
}
return v
}

79
config/parser_test.go Normal file
View file

@ -0,0 +1,79 @@
// Copyright 2019 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package config // import "miniflux.app/config"
import (
"os"
"testing"
)
func TestGetBooleanValueWithUnsetVariable(t *testing.T) {
os.Clearenv()
if getBooleanValue("MY_TEST_VARIABLE") {
t.Errorf(`Unset variables should returns false`)
}
}
func TestGetBooleanValue(t *testing.T) {
scenarios := map[string]bool{
"": false,
"1": true,
"Yes": true,
"yes": true,
"True": true,
"true": true,
"on": true,
"false": false,
"off": false,
"invalid": false,
}
for input, expected := range scenarios {
os.Clearenv()
os.Setenv("MY_TEST_VARIABLE", input)
result := getBooleanValue("MY_TEST_VARIABLE")
if result != expected {
t.Errorf(`Unexpected result for %q, got %v instead of %v`, input, result, expected)
}
}
}
func TestGetStringValueWithUnsetVariable(t *testing.T) {
os.Clearenv()
if getStringValue("MY_TEST_VARIABLE", "defaultValue") != "defaultValue" {
t.Errorf(`Unset variables should returns the default value`)
}
}
func TestGetStringValue(t *testing.T) {
os.Clearenv()
os.Setenv("MY_TEST_VARIABLE", "test")
if getStringValue("MY_TEST_VARIABLE", "defaultValue") != "test" {
t.Errorf(`Defined variables should returns the specified value`)
}
}
func TestGetIntValueWithUnsetVariable(t *testing.T) {
os.Clearenv()
if getIntValue("MY_TEST_VARIABLE", 42) != 42 {
t.Errorf(`Unset variables should returns the default value`)
}
}
func TestGetIntValueWithInvalidInput(t *testing.T) {
os.Clearenv()
os.Setenv("MY_TEST_VARIABLE", "invalid integer")
if getIntValue("MY_TEST_VARIABLE", 42) != 42 {
t.Errorf(`Invalid integer should returns the default value`)
}
}
func TestGetIntValue(t *testing.T) {
os.Clearenv()
os.Setenv("MY_TEST_VARIABLE", "2018")
if getIntValue("MY_TEST_VARIABLE", 42) != 2018 {
t.Errorf(`Defined variables should returns the specified value`)
}
}

View file

@ -10,7 +10,6 @@ import (
"strings"
"time"
"miniflux.app/config"
"miniflux.app/http/request"
"miniflux.app/http/response/json"
"miniflux.app/integration"
@ -22,8 +21,8 @@ import (
)
// Serve handles Fever API calls.
func Serve(router *mux.Router, cfg *config.Config, store *storage.Storage) {
handler := &handler{cfg, store}
func Serve(router *mux.Router, store *storage.Storage) {
handler := &handler{store}
sr := router.PathPrefix("/fever").Subrouter()
sr.Use(newMiddleware(store).serve)
@ -31,7 +30,6 @@ func Serve(router *mux.Router, cfg *config.Config, store *storage.Storage) {
}
type handler struct {
cfg *config.Config
store *storage.Storage
}
@ -424,7 +422,7 @@ func (h *handler) handleWriteItems(w http.ResponseWriter, r *http.Request) {
}
go func() {
integration.SendEntry(h.cfg, entry, settings)
integration.SendEntry(entry, settings)
}()
}

View file

@ -16,7 +16,7 @@ import (
)
// SendEntry send the entry to the activated providers.
func SendEntry(cfg *config.Config, entry *model.Entry, integration *model.Integration) {
func SendEntry(entry *model.Entry, integration *model.Integration) {
if integration.PinboardEnabled {
client := pinboard.NewClient(integration.PinboardToken)
err := client.AddBookmark(
@ -64,7 +64,7 @@ func SendEntry(cfg *config.Config, entry *model.Entry, integration *model.Integr
}
if integration.PocketEnabled {
client := pocket.NewClient(cfg.PocketConsumerKey(integration.PocketConsumerKey), integration.PocketAccessToken)
client := pocket.NewClient(config.Opts.PocketConsumerKey(integration.PocketConsumerKey), integration.PocketAccessToken)
if err := client.AddURL(entry.URL, entry.Title); err != nil {
logger.Error("[Integration] UserID #%d: %v", integration.UserID, err)
}

View file

@ -27,17 +27,17 @@ import (
)
// Serve starts a new HTTP server.
func Serve(cfg *config.Config, store *storage.Storage, pool *worker.Pool, feedHandler *feed.Handler) *http.Server {
certFile := cfg.CertFile()
keyFile := cfg.KeyFile()
certDomain := cfg.CertDomain()
certCache := cfg.CertCache()
listenAddr := cfg.ListenAddr()
func Serve(store *storage.Storage, pool *worker.Pool, feedHandler *feed.Handler) *http.Server {
certFile := config.Opts.CertFile()
keyFile := config.Opts.CertKeyFile()
certDomain := config.Opts.CertDomain()
certCache := config.Opts.CertCache()
listenAddr := config.Opts.ListenAddr()
server := &http.Server{
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
Handler: setupHandler(cfg, store, feedHandler, pool),
Handler: setupHandler(store, feedHandler, pool),
}
switch {
@ -46,10 +46,10 @@ func Serve(cfg *config.Config, store *storage.Storage, pool *worker.Pool, feedHa
case strings.HasPrefix(listenAddr, "/"):
startUnixSocketServer(server, listenAddr)
case certDomain != "" && certCache != "":
cfg.IsHTTPS = true
config.Opts.HTTPS = true
startAutoCertTLSServer(server, certDomain, certCache)
case certFile != "" && keyFile != "":
cfg.IsHTTPS = true
config.Opts.HTTPS = true
server.Addr = listenAddr
startTLSServer(server, certFile, keyFile)
default:
@ -156,18 +156,18 @@ func startHTTPServer(server *http.Server) {
}()
}
func setupHandler(cfg *config.Config, store *storage.Storage, feedHandler *feed.Handler, pool *worker.Pool) *mux.Router {
func setupHandler(store *storage.Storage, feedHandler *feed.Handler, pool *worker.Pool) *mux.Router {
router := mux.NewRouter()
if cfg.BasePath() != "" {
router = router.PathPrefix(cfg.BasePath()).Subrouter()
if config.Opts.BasePath() != "" {
router = router.PathPrefix(config.Opts.BasePath()).Subrouter()
}
router.Use(newMiddleware(cfg).Serve)
router.Use(middleware)
fever.Serve(router, cfg, store)
fever.Serve(router, store)
api.Serve(router, store, feedHandler)
ui.Serve(router, cfg, store, pool, feedHandler)
ui.Serve(router, store, pool, feedHandler)
router.HandleFunc("/healthcheck", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))

View file

@ -13,32 +13,24 @@ import (
"miniflux.app/logger"
)
type middleware struct {
cfg *config.Config
}
func newMiddleware(cfg *config.Config) *middleware {
return &middleware{cfg}
}
func (m *middleware) Serve(next http.Handler) http.Handler {
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
clientIP := request.FindClientIP(r)
ctx := r.Context()
ctx = context.WithValue(ctx, request.ClientIPContextKey, clientIP)
if r.Header.Get("X-Forwarded-Proto") == "https" {
m.cfg.IsHTTPS = true
config.Opts.HTTPS = true
}
protocol := "HTTP"
if m.cfg.IsHTTPS {
if config.Opts.HTTPS {
protocol = "HTTPS"
}
logger.Debug("[%s] %s %s %s", protocol, clientIP, r.Method, r.RequestURI)
if m.cfg.IsHTTPS && m.cfg.HasHSTS() {
if config.Opts.HTTPS && config.Opts.HasHSTS() {
w.Header().Set("Strict-Transport-Security", "max-age=31536000")
}

View file

@ -14,10 +14,10 @@ import (
)
// Serve starts the internal scheduler.
func Serve(cfg *config.Config, store *storage.Storage, pool *worker.Pool) {
func Serve(store *storage.Storage, pool *worker.Pool) {
logger.Info(`Starting scheduler...`)
go feedScheduler(store, pool, cfg.PollingFrequency(), cfg.BatchSize())
go cleanupScheduler(store, cfg.CleanupFrequency(), cfg.ArchiveReadDays())
go feedScheduler(store, pool, config.Opts.PollingFrequency(), config.Opts.BatchSize())
go cleanupScheduler(store, config.Opts.CleanupFrequency(), config.Opts.ArchiveReadDays())
}
func feedScheduler(store *storage.Storage, pool *worker.Pool, frequency, batchSize int) {

View file

@ -9,7 +9,6 @@ import (
"html/template"
"time"
"miniflux.app/config"
"miniflux.app/errors"
"miniflux.app/locale"
"miniflux.app/logger"
@ -78,10 +77,10 @@ func (e *Engine) Render(name, language string, data interface{}) []byte {
}
// NewEngine returns a new template engine.
func NewEngine(cfg *config.Config, router *mux.Router) *Engine {
func NewEngine(router *mux.Router) *Engine {
tpl := &Engine{
templates: make(map[string]*template.Template),
funcMap: newFuncMap(cfg, router),
funcMap: &funcMap{router},
}
tpl.parseAll()

View file

@ -7,8 +7,8 @@ package template // import "miniflux.app/template"
import (
"encoding/base64"
"fmt"
"math"
"html/template"
"math"
"net/mail"
"strings"
"time"
@ -20,12 +20,11 @@ import (
"miniflux.app/timezone"
"miniflux.app/url"
"github.com/gorilla/mux"
"github.com/PuerkitoBio/goquery"
"github.com/gorilla/mux"
)
type funcMap struct {
cfg *config.Config
router *mux.Router
}
@ -37,13 +36,13 @@ func (f *funcMap) Map() template.FuncMap {
"truncate": truncate,
"isEmail": isEmail,
"baseURL": func() string {
return f.cfg.BaseURL()
return config.Opts.BaseURL()
},
"rootURL": func() string {
return f.cfg.RootURL()
return config.Opts.RootURL()
},
"hasOAuth2Provider": func(provider string) bool {
return f.cfg.OAuth2Provider() == provider
return config.Opts.OAuth2Provider() == provider
},
"route": func(name string, args ...interface{}) string {
return route.Path(f.router, name, args...)
@ -52,10 +51,10 @@ func (f *funcMap) Map() template.FuncMap {
return template.HTML(str)
},
"proxyFilter": func(data string) string {
return imageProxyFilter(f.router, f.cfg, data)
return imageProxyFilter(f.router, data)
},
"proxyURL": func(link string) string {
proxyImages := f.cfg.ProxyImages()
proxyImages := config.Opts.ProxyImages()
if proxyImages == "all" || (proxyImages != "none" && !url.IsHTTPS(link)) {
return proxify(f.router, link)
@ -92,10 +91,6 @@ func (f *funcMap) Map() template.FuncMap {
}
}
func newFuncMap(cfg *config.Config, router *mux.Router) *funcMap {
return &funcMap{cfg, router}
}
func dict(values ...interface{}) (map[string]interface{}, error) {
if len(values)%2 != 0 {
return nil, fmt.Errorf("dict expects an even number of arguments")
@ -178,8 +173,8 @@ func elapsedTime(printer *locale.Printer, tz string, t time.Time) string {
}
}
func imageProxyFilter(router *mux.Router, cfg *config.Config, data string) string {
proxyImages := cfg.ProxyImages()
func imageProxyFilter(router *mux.Router, data string) string {
proxyImages := config.Opts.ProxyImages()
if proxyImages == "none" {
return data
}

View file

@ -134,13 +134,13 @@ func TestElapsedTime(t *testing.T) {
func TestProxyFilterWithHttpDefault(t *testing.T) {
os.Clearenv()
os.Setenv("PROXY_IMAGES", "http-only")
c := config.NewConfig()
config.ParseConfig()
r := mux.NewRouter()
r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
input := `<p><img src="http://website/folder/image.png" alt="Test"/></p>`
output := imageProxyFilter(r, c, input)
output := imageProxyFilter(r, input)
expected := `<p><img src="/proxy/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlLnBuZw==" alt="Test"/></p>`
if expected != output {
@ -151,13 +151,13 @@ func TestProxyFilterWithHttpDefault(t *testing.T) {
func TestProxyFilterWithHttpsDefault(t *testing.T) {
os.Clearenv()
os.Setenv("PROXY_IMAGES", "http-only")
c := config.NewConfig()
config.ParseConfig()
r := mux.NewRouter()
r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
input := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
output := imageProxyFilter(r, c, input)
output := imageProxyFilter(r, input)
expected := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
if expected != output {
@ -168,13 +168,13 @@ func TestProxyFilterWithHttpsDefault(t *testing.T) {
func TestProxyFilterWithHttpNever(t *testing.T) {
os.Clearenv()
os.Setenv("PROXY_IMAGES", "none")
c := config.NewConfig()
config.ParseConfig()
r := mux.NewRouter()
r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
input := `<p><img src="http://website/folder/image.png" alt="Test"/></p>`
output := imageProxyFilter(r, c, input)
output := imageProxyFilter(r, input)
expected := input
if expected != output {
@ -185,13 +185,13 @@ func TestProxyFilterWithHttpNever(t *testing.T) {
func TestProxyFilterWithHttpsNever(t *testing.T) {
os.Clearenv()
os.Setenv("PROXY_IMAGES", "none")
c := config.NewConfig()
config.ParseConfig()
r := mux.NewRouter()
r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
input := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
output := imageProxyFilter(r, c, input)
output := imageProxyFilter(r, input)
expected := input
if expected != output {
@ -202,13 +202,13 @@ func TestProxyFilterWithHttpsNever(t *testing.T) {
func TestProxyFilterWithHttpAlways(t *testing.T) {
os.Clearenv()
os.Setenv("PROXY_IMAGES", "all")
c := config.NewConfig()
config.ParseConfig()
r := mux.NewRouter()
r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
input := `<p><img src="http://website/folder/image.png" alt="Test"/></p>`
output := imageProxyFilter(r, c, input)
output := imageProxyFilter(r, input)
expected := `<p><img src="/proxy/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlLnBuZw==" alt="Test"/></p>`
if expected != output {
@ -219,13 +219,13 @@ func TestProxyFilterWithHttpAlways(t *testing.T) {
func TestProxyFilterWithHttpsAlways(t *testing.T) {
os.Clearenv()
os.Setenv("PROXY_IMAGES", "all")
c := config.NewConfig()
config.ParseConfig()
r := mux.NewRouter()
r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
input := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
output := imageProxyFilter(r, c, input)
output := imageProxyFilter(r, input)
expected := `<p><img src="/proxy/aHR0cHM6Ly93ZWJzaXRlL2ZvbGRlci9pbWFnZS5wbmc=" alt="Test"/></p>`
if expected != output {
@ -236,13 +236,13 @@ func TestProxyFilterWithHttpsAlways(t *testing.T) {
func TestProxyFilterWithHttpInvalid(t *testing.T) {
os.Clearenv()
os.Setenv("PROXY_IMAGES", "invalid")
c := config.NewConfig()
config.ParseConfig()
r := mux.NewRouter()
r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
input := `<p><img src="http://website/folder/image.png" alt="Test"/></p>`
output := imageProxyFilter(r, c, input)
output := imageProxyFilter(r, input)
expected := `<p><img src="/proxy/aHR0cDovL3dlYnNpdGUvZm9sZGVyL2ltYWdlLnBuZw==" alt="Test"/></p>`
if expected != output {
@ -253,13 +253,13 @@ func TestProxyFilterWithHttpInvalid(t *testing.T) {
func TestProxyFilterWithHttpsInvalid(t *testing.T) {
os.Clearenv()
os.Setenv("PROXY_IMAGES", "invalid")
c := config.NewConfig()
config.ParseConfig()
r := mux.NewRouter()
r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
input := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
output := imageProxyFilter(r, c, input)
output := imageProxyFilter(r, input)
expected := `<p><img src="https://website/folder/image.png" alt="Test"/></p>`
if expected != output {

View file

@ -37,7 +37,7 @@ func (h *handler) saveEntry(w http.ResponseWriter, r *http.Request) {
}
go func() {
integration.SendEntry(h.cfg, entry, settings)
integration.SendEntry(entry, settings)
}()
json.Created(w, r, map[string]string{"message": "saved"})

View file

@ -5,7 +5,6 @@
package ui // import "miniflux.app/ui"
import (
"miniflux.app/config"
"miniflux.app/reader/feed"
"miniflux.app/storage"
"miniflux.app/template"
@ -16,7 +15,6 @@ import (
type handler struct {
router *mux.Router
cfg *config.Config
store *storage.Storage
tpl *template.Engine
pool *worker.Pool

View file

@ -7,8 +7,9 @@ package ui // import "miniflux.app/ui"
import (
"net/http"
"miniflux.app/http/response/html"
"miniflux.app/config"
"miniflux.app/http/request"
"miniflux.app/http/response/html"
"miniflux.app/http/route"
"miniflux.app/integration/pocket"
"miniflux.app/locale"
@ -31,8 +32,8 @@ func (h *handler) pocketAuthorize(w http.ResponseWriter, r *http.Request) {
}
sess := session.New(h.store, request.SessionID(r))
connector := pocket.NewConnector(h.cfg.PocketConsumerKey(integration.PocketConsumerKey))
redirectURL := h.cfg.BaseURL() + route.Path(h.router, "pocketCallback")
connector := pocket.NewConnector(config.Opts.PocketConsumerKey(integration.PocketConsumerKey))
redirectURL := config.Opts.BaseURL() + route.Path(h.router, "pocketCallback")
requestToken, err := connector.RequestToken(redirectURL)
if err != nil {
logger.Error("[Pocket:Authorize] %v", err)
@ -61,7 +62,7 @@ func (h *handler) pocketCallback(w http.ResponseWriter, r *http.Request) {
return
}
connector := pocket.NewConnector(h.cfg.PocketConsumerKey(integration.PocketConsumerKey))
connector := pocket.NewConnector(config.Opts.PocketConsumerKey(integration.PocketConsumerKey))
accessToken, err := connector.AccessToken(request.PocketRequestToken(r))
if err != nil {
logger.Error("[Pocket:Callback] %v", err)

View file

@ -7,6 +7,7 @@ package ui // import "miniflux.app/ui"
import (
"net/http"
"miniflux.app/config"
"miniflux.app/http/request"
"miniflux.app/http/response/html"
"miniflux.app/ui/form"
@ -59,7 +60,7 @@ func (h *handler) showIntegrationPage(w http.ResponseWriter, r *http.Request) {
view.Set("user", user)
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID))
view.Set("hasPocketConsumerKeyConfigured", h.cfg.PocketConsumerKey("") != "")
view.Set("hasPocketConsumerKeyConfigured", config.Opts.PocketConsumerKey("") != "")
html.OK(w, r, view.Render("integrations"))
}

View file

@ -3,6 +3,7 @@ package ui // import "miniflux.app/ui"
import (
"net/http"
"miniflux.app/config"
"miniflux.app/http/cookie"
"miniflux.app/http/request"
"miniflux.app/http/response/html"
@ -55,8 +56,8 @@ func (h *handler) checkLogin(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, cookie.New(
cookie.CookieUserSessionID,
sessionToken,
h.cfg.IsHTTPS,
h.cfg.BasePath(),
config.Opts.HTTPS,
config.Opts.BasePath(),
))
html.Redirect(w, r, route.Path(h.router, "unread"))

View file

@ -7,6 +7,7 @@ package ui // import "miniflux.app/ui"
import (
"net/http"
"miniflux.app/config"
"miniflux.app/http/cookie"
"miniflux.app/http/request"
"miniflux.app/http/response/html"
@ -32,8 +33,8 @@ func (h *handler) logout(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, cookie.Expired(
cookie.CookieUserSessionID,
h.cfg.IsHTTPS,
h.cfg.BasePath(),
config.Opts.HTTPS,
config.Opts.BasePath(),
))
html.Redirect(w, r, route.Path(h.router, "login"))

View file

@ -14,21 +14,20 @@ import (
"miniflux.app/http/request"
"miniflux.app/http/response/html"
"miniflux.app/http/route"
"miniflux.app/storage"
"miniflux.app/logger"
"miniflux.app/model"
"miniflux.app/storage"
"github.com/gorilla/mux"
)
type middleware struct {
router *mux.Router
cfg *config.Config
store *storage.Storage
}
func newMiddleware(router *mux.Router, cfg *config.Config, store *storage.Storage) *middleware {
return &middleware{router, cfg, store}
func newMiddleware(router *mux.Router, store *storage.Storage) *middleware {
return &middleware{router, store}
}
func (m *middleware) handleUserSession(next http.Handler) http.Handler {
@ -61,7 +60,7 @@ func (m *middleware) handleAppSession(next http.Handler) http.Handler {
session := m.getAppSessionValueFromCookie(r)
if session == nil {
if (request.IsAuthenticated(r)) {
if request.IsAuthenticated(r) {
userID := request.UserID(r)
logger.Debug("[UI:AppSession] Cookie expired but user #%d is logged: creating a new session", userID)
session, err = m.store.CreateAppSessionWithUserPrefs(userID)
@ -78,7 +77,7 @@ func (m *middleware) handleAppSession(next http.Handler) http.Handler {
}
}
http.SetCookie(w, cookie.New(cookie.CookieAppSessionID, session.ID, m.cfg.IsHTTPS, m.cfg.BasePath()))
http.SetCookie(w, cookie.New(cookie.CookieAppSessionID, session.ID, config.Opts.HTTPS, config.Opts.BasePath()))
} else {
logger.Debug("[UI:AppSession] %s", session)
}

View file

@ -9,10 +9,10 @@ import (
"miniflux.app/oauth2"
)
func getOAuth2Manager(cfg *config.Config) *oauth2.Manager {
func getOAuth2Manager() *oauth2.Manager {
return oauth2.NewManager(
cfg.OAuth2ClientID(),
cfg.OAuth2ClientSecret(),
cfg.OAuth2RedirectURL(),
config.Opts.OAuth2ClientID(),
config.Opts.OAuth2ClientSecret(),
config.Opts.OAuth2RedirectURL(),
)
}

View file

@ -7,6 +7,7 @@ package ui // import "miniflux.app/ui"
import (
"net/http"
"miniflux.app/config"
"miniflux.app/http/cookie"
"miniflux.app/http/request"
"miniflux.app/http/response/html"
@ -43,7 +44,7 @@ func (h *handler) oauth2Callback(w http.ResponseWriter, r *http.Request) {
return
}
authProvider, err := getOAuth2Manager(h.cfg).Provider(provider)
authProvider, err := getOAuth2Manager().Provider(provider)
if err != nil {
logger.Error("[OAuth2] %v", err)
html.Redirect(w, r, route.Path(h.router, "login"))
@ -90,7 +91,7 @@ func (h *handler) oauth2Callback(w http.ResponseWriter, r *http.Request) {
}
if user == nil {
if !h.cfg.IsOAuth2UserCreationAllowed() {
if !config.Opts.IsOAuth2UserCreationAllowed() {
html.Forbidden(w, r)
return
}
@ -121,8 +122,8 @@ func (h *handler) oauth2Callback(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, cookie.New(
cookie.CookieUserSessionID,
sessionToken,
h.cfg.IsHTTPS,
h.cfg.BasePath(),
config.Opts.HTTPS,
config.Opts.BasePath(),
))
html.Redirect(w, r, route.Path(h.router, "unread"))

View file

@ -24,7 +24,7 @@ func (h *handler) oauth2Redirect(w http.ResponseWriter, r *http.Request) {
return
}
authProvider, err := getOAuth2Manager(h.cfg).Provider(provider)
authProvider, err := getOAuth2Manager().Provider(provider)
if err != nil {
logger.Error("[OAuth2] %v", err)
html.Redirect(w, r, route.Path(h.router, "login"))

View file

@ -24,7 +24,7 @@ func (h *handler) oauth2Unlink(w http.ResponseWriter, r *http.Request) {
return
}
authProvider, err := getOAuth2Manager(h.cfg).Provider(provider)
authProvider, err := getOAuth2Manager().Provider(provider)
if err != nil {
logger.Error("[OAuth2] %v", err)
html.Redirect(w, r, route.Path(h.router, "settings"))

View file

@ -7,7 +7,6 @@ package ui // import "miniflux.app/ui"
import (
"net/http"
"miniflux.app/config"
"miniflux.app/reader/feed"
"miniflux.app/storage"
"miniflux.app/template"
@ -17,9 +16,9 @@ import (
)
// Serve declares all routes for the user interface.
func Serve(router *mux.Router, cfg *config.Config, store *storage.Storage, pool *worker.Pool, feedHandler *feed.Handler) {
middleware := newMiddleware(router, cfg, store)
handler := &handler{router, cfg, store, template.NewEngine(cfg, router), pool, feedHandler}
func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool, feedHandler *feed.Handler) {
middleware := newMiddleware(router, store)
handler := &handler{router, store, template.NewEngine(router), pool, feedHandler}
uiRouter := router.NewRoute().Subrouter()
uiRouter.Use(middleware.handleUserSession)