github.com/blend/go-sdk@v1.20220411.3/web/config.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package web
     9  
    10  import (
    11  	"context"
    12  	"fmt"
    13  	"net/http"
    14  	"net/url"
    15  	"time"
    16  
    17  	"github.com/blend/go-sdk/configutil"
    18  	"github.com/blend/go-sdk/ref"
    19  
    20  	"github.com/blend/go-sdk/webutil"
    21  )
    22  
    23  // Config is an object used to set up a web app.
    24  type Config struct {
    25  	Port                      int32         `json:"port,omitempty" yaml:"port,omitempty" env:"PORT"`
    26  	BindAddr                  string        `json:"bindAddr,omitempty" yaml:"bindAddr,omitempty" env:"BIND_ADDR"`
    27  	BaseURL                   string        `json:"baseURL,omitempty" yaml:"baseURL,omitempty" env:"BASE_URL"`
    28  	SkipRedirectTrailingSlash bool          `json:"skipRedirectTrailingSlash,omitempty" yaml:"skipRedirectTrailingSlash,omitempty"`
    29  	HandleOptions             bool          `json:"handleOptions,omitempty" yaml:"handleOptions,omitempty"`
    30  	HandleMethodNotAllowed    bool          `json:"handleMethodNotAllowed,omitempty" yaml:"handleMethodNotAllowed,omitempty"`
    31  	DisablePanicRecovery      bool          `json:"disablePanicRecovery,omitempty" yaml:"disablePanicRecovery,omitempty"`
    32  	SessionTimeout            time.Duration `json:"sessionTimeout,omitempty" yaml:"sessionTimeout,omitempty" env:"SESSION_TIMEOUT"`
    33  	SessionTimeoutIsRelative  bool          `json:"sessionTimeoutIsRelative,omitempty" yaml:"sessionTimeoutIsRelative,omitempty"`
    34  
    35  	CookieSecure   *bool  `json:"cookieSecure,omitempty" yaml:"cookieSecure,omitempty" env:"COOKIE_SECURE"`
    36  	CookieHTTPOnly *bool  `json:"cookieHTTPOnly,omitempty" yaml:"cookieHTTPOnly,omitempty" env:"COOKIE_HTTP_ONLY"`
    37  	CookieSameSite string `json:"cookieSameSite,omitempty" yaml:"cookieSameSite,omitempty" env:"COOKIE_SAME_SITE"`
    38  	CookieName     string `json:"cookieName,omitempty" yaml:"cookieName,omitempty" env:"COOKIE_NAME"`
    39  	CookiePath     string `json:"cookiePath,omitempty" yaml:"cookiePath,omitempty" env:"COOKIE_PATH"`
    40  	CookieDomain   string `json:"cookieDomain,omitempty" yaml:"cookieDomain,omitempty" env:"COOKIE_DOMAIN"`
    41  
    42  	DefaultHeaders      map[string]string `json:"defaultHeaders,omitempty" yaml:"defaultHeaders,omitempty"`
    43  	MaxHeaderBytes      int               `json:"maxHeaderBytes,omitempty" yaml:"maxHeaderBytes,omitempty" env:"MAX_HEADER_BYTES"`
    44  	ReadTimeout         time.Duration     `json:"readTimeout,omitempty" yaml:"readTimeout,omitempty" env:"READ_TIMEOUT"`
    45  	ReadHeaderTimeout   time.Duration     `json:"readHeaderTimeout,omitempty" yaml:"readHeaderTimeout,omitempty" env:"READ_HEADER_TIMEOUT"`
    46  	WriteTimeout        time.Duration     `json:"writeTimeout,omitempty" yaml:"writeTimeout,omitempty" env:"WRITE_TIMEOUT"`
    47  	IdleTimeout         time.Duration     `json:"idleTimeout,omitempty" yaml:"idleTimeout,omitempty" env:"IDLE_TIMEOUT"`
    48  	ShutdownGracePeriod time.Duration     `json:"shutdownGracePeriod,omitempty" yaml:"shutdownGracePeriod,omitempty" env:"SHUTDOWN_GRACE_PERIOD"`
    49  
    50  	KeepAlive        *bool         `json:"keepAlive,omitempty" yaml:"keepAlive,omitempty" env:"KEEP_ALIVE"`
    51  	KeepAlivePeriod  time.Duration `json:"keepAlivePeriod,omitempty" yaml:"keepAlivePeriod,omitempty" env:"KEEP_ALIVE_PERIOD"`
    52  	UseProxyProtocol bool          `json:"useProxyProtocol,omitempty" yaml:"useProxyProtocol,omitempty"`
    53  
    54  	Views ViewCacheConfig `json:"views,omitempty" yaml:"views,omitempty"`
    55  }
    56  
    57  // IsZero returns if the config is unset or not.
    58  func (c Config) IsZero() bool {
    59  	return c.BindAddr == "" && c.Port == 0
    60  }
    61  
    62  // Resolve resolves the config from other sources.
    63  func (c *Config) Resolve(ctx context.Context) error {
    64  	return configutil.Resolve(ctx,
    65  		(&c.Views).Resolve,
    66  		configutil.SetInt32(&c.Port, configutil.Env("PORT"), configutil.Int32(c.Port)),
    67  		configutil.SetString(&c.BindAddr, configutil.Env("BIND_ADDR"), configutil.String(c.BindAddr)),
    68  		configutil.SetString(&c.BaseURL, configutil.Env("BASE_URL"), configutil.String(c.BaseURL)),
    69  		configutil.SetDuration(&c.SessionTimeout, configutil.Env("SESSION_TIMEOUT"), configutil.Duration(c.SessionTimeout)),
    70  		configutil.SetBoolPtr(&c.CookieSecure, configutil.Env("COOKIE_SECURE"), configutil.Bool(c.CookieSecure), configutil.BoolFunc(c.ResolveCookieSecure)),
    71  		configutil.SetBoolPtr(&c.CookieHTTPOnly, configutil.Env("COOKIE_HTTP_ONLY"), configutil.Bool(c.CookieHTTPOnly)),
    72  		configutil.SetString(&c.CookieSameSite, configutil.Env("COOKIE_SAME_SITE"), configutil.String(c.CookieSameSite)),
    73  		configutil.SetString(&c.CookieName, configutil.Env("COOKIE_NAME"), configutil.String(c.CookieName)),
    74  		configutil.SetString(&c.CookiePath, configutil.Env("COOKIE_PATH"), configutil.String(c.CookiePath)),
    75  		configutil.SetString(&c.CookieDomain, configutil.Env("COOKIE_DOMAIN"), configutil.String(c.CookieDomain), configutil.StringFunc(c.ResolveCookieDomain)),
    76  		configutil.SetInt(&c.MaxHeaderBytes, configutil.Env("MAX_HEADER_BYTES"), configutil.Int(c.MaxHeaderBytes)),
    77  		configutil.SetDuration(&c.ReadTimeout, configutil.Env("READ_TIMEOUT"), configutil.Duration(c.ReadTimeout)),
    78  		configutil.SetDuration(&c.ReadHeaderTimeout, configutil.Env("READ_HEADER_TIMEOUT"), configutil.Duration(c.ReadHeaderTimeout)),
    79  		configutil.SetDuration(&c.WriteTimeout, configutil.Env("WRITE_TIMEOUT"), configutil.Duration(c.WriteTimeout)),
    80  		configutil.SetDuration(&c.IdleTimeout, configutil.Env("IDLE_TIMEOUT"), configutil.Duration(c.IdleTimeout)),
    81  		configutil.SetDuration(&c.ShutdownGracePeriod, configutil.Env("SHUTDOWN_GRACE_PERIOD"), configutil.Duration(c.ShutdownGracePeriod)),
    82  		configutil.SetBoolPtr(&c.KeepAlive, configutil.Env("KEEP_ALIVE"), configutil.Bool(c.KeepAlive)),
    83  		configutil.SetDuration(&c.KeepAlivePeriod, configutil.Env("KEEP_ALIVE_PERIOD"), configutil.Duration(c.KeepAlivePeriod)),
    84  	)
    85  }
    86  
    87  // BindAddrOrDefault returns the bind address or a default.
    88  func (c Config) BindAddrOrDefault(defaults ...string) string {
    89  	if len(c.BindAddr) > 0 {
    90  		return c.BindAddr
    91  	}
    92  	if c.Port > 0 {
    93  		return fmt.Sprintf(":%d", c.Port)
    94  	}
    95  	if len(defaults) > 0 {
    96  		return defaults[0]
    97  	}
    98  	return DefaultBindAddr
    99  }
   100  
   101  // PortOrDefault returns the int32 port for a given config.
   102  // This is useful in things like kubernetes pod templates.
   103  // If the config .Port is unset, it will parse the .BindAddr,
   104  // or the DefaultBindAddr for the port number.
   105  func (c Config) PortOrDefault() int32 {
   106  	if c.Port > 0 {
   107  		return c.Port
   108  	}
   109  	if len(c.BindAddr) > 0 {
   110  		return webutil.PortFromBindAddr(c.BindAddr)
   111  	}
   112  	return webutil.PortFromBindAddr(DefaultBindAddr)
   113  }
   114  
   115  // BaseURLOrDefault gets the base url for the app or a default.
   116  func (c Config) BaseURLOrDefault() string {
   117  	return c.BaseURL
   118  }
   119  
   120  // SessionTimeoutOrDefault returns a property or a default.
   121  func (c Config) SessionTimeoutOrDefault() time.Duration {
   122  	if c.SessionTimeout > 0 {
   123  		return c.SessionTimeout
   124  	}
   125  	return DefaultSessionTimeout
   126  }
   127  
   128  // CookieNameOrDefault returns a property or a default.
   129  func (c Config) CookieNameOrDefault() string {
   130  	if c.CookieName != "" {
   131  		return c.CookieName
   132  	}
   133  	return DefaultCookieName
   134  }
   135  
   136  // CookiePathOrDefault returns a property or a default.
   137  func (c Config) CookiePathOrDefault() string {
   138  	if c.CookiePath != "" {
   139  		return c.CookiePath
   140  	}
   141  	return DefaultCookiePath
   142  }
   143  
   144  // CookieDomainOrDefault returns a property or a default.
   145  func (c Config) CookieDomainOrDefault() string {
   146  	if c.CookieDomain != "" {
   147  		return c.CookieDomain
   148  	}
   149  	return ""
   150  }
   151  
   152  // CookieSecureOrDefault returns a property or a default.
   153  func (c Config) CookieSecureOrDefault() bool {
   154  	if c.CookieSecure != nil {
   155  		return *c.CookieSecure
   156  	}
   157  	if baseURL := c.BaseURLOrDefault(); baseURL != "" {
   158  		return webutil.SchemeIsSecure(webutil.MustParseURL(baseURL).Scheme)
   159  	}
   160  	return DefaultCookieSecure
   161  }
   162  
   163  // CookieHTTPOnlyOrDefault returns a property or a default.
   164  func (c Config) CookieHTTPOnlyOrDefault() bool {
   165  	if c.CookieHTTPOnly != nil {
   166  		return *c.CookieHTTPOnly
   167  	}
   168  	return DefaultCookieHTTPOnly
   169  }
   170  
   171  // CookieSameSiteOrDefault returns a property or a default.
   172  func (c Config) CookieSameSiteOrDefault() http.SameSite {
   173  	if c.CookieSameSite != "" {
   174  		return webutil.MustParseSameSite(c.CookieSameSite)
   175  	}
   176  	return DefaultCookieSameSiteMode
   177  }
   178  
   179  // MaxHeaderBytesOrDefault returns the maximum header size in bytes or a default.
   180  func (c Config) MaxHeaderBytesOrDefault() int {
   181  	if c.MaxHeaderBytes > 0 {
   182  		return c.MaxHeaderBytes
   183  	}
   184  	return DefaultMaxHeaderBytes
   185  }
   186  
   187  // ReadTimeoutOrDefault gets a property.
   188  func (c Config) ReadTimeoutOrDefault() time.Duration {
   189  	if c.ReadTimeout > 0 {
   190  		return c.ReadTimeout
   191  	}
   192  	return DefaultReadTimeout
   193  }
   194  
   195  // ReadHeaderTimeoutOrDefault gets a property.
   196  func (c Config) ReadHeaderTimeoutOrDefault() time.Duration {
   197  	if c.ReadHeaderTimeout > 0 {
   198  		return c.ReadHeaderTimeout
   199  	}
   200  	return DefaultReadHeaderTimeout
   201  }
   202  
   203  // WriteTimeoutOrDefault gets a property.
   204  func (c Config) WriteTimeoutOrDefault() time.Duration {
   205  	if c.WriteTimeout > 0 {
   206  		return c.WriteTimeout
   207  	}
   208  	return DefaultWriteTimeout
   209  }
   210  
   211  // IdleTimeoutOrDefault gets a property.
   212  func (c Config) IdleTimeoutOrDefault() time.Duration {
   213  	if c.IdleTimeout > 0 {
   214  		return c.IdleTimeout
   215  	}
   216  	return DefaultIdleTimeout
   217  }
   218  
   219  // ShutdownGracePeriodOrDefault gets the shutdown grace period.
   220  func (c Config) ShutdownGracePeriodOrDefault() time.Duration {
   221  	if c.ShutdownGracePeriod > 0 {
   222  		return c.ShutdownGracePeriod
   223  	}
   224  	return DefaultShutdownGracePeriod
   225  }
   226  
   227  // KeepAliveOrDefault returns if we should keep TCP connections open.
   228  func (c Config) KeepAliveOrDefault() bool {
   229  	if c.KeepAlive != nil {
   230  		return *c.KeepAlive
   231  	}
   232  	return DefaultKeepAlive
   233  }
   234  
   235  // KeepAlivePeriodOrDefault returns the TCP keep alive period or a default.
   236  func (c Config) KeepAlivePeriodOrDefault() time.Duration {
   237  	if c.KeepAlivePeriod > 0 {
   238  		return c.KeepAlivePeriod
   239  	}
   240  	return DefaultKeepAlivePeriod
   241  }
   242  
   243  // ResolveCookieSecure is a resolver for the `CookieSecure` field based on the BaseURL if one is provided.
   244  func (c Config) ResolveCookieSecure(_ context.Context) (*bool, error) {
   245  	if c.BaseURL != "" {
   246  		baseURL, err := url.Parse(c.BaseURL)
   247  		if err != nil {
   248  			return nil, err
   249  		}
   250  		return ref.Bool(webutil.SchemeIsSecure(baseURL.Scheme)), nil
   251  	}
   252  	return nil, nil
   253  }
   254  
   255  // ResolveCookieDomain is a resolver for the `CookieDomain` field based on the BaseURL if one is provided.
   256  func (c Config) ResolveCookieDomain(_ context.Context) (*string, error) {
   257  	if c.BaseURL != "" {
   258  		baseURL, err := url.Parse(c.BaseURL)
   259  		if err != nil {
   260  			return nil, err
   261  		}
   262  		return ref.String(baseURL.Hostname()), nil
   263  	}
   264  	return nil, nil
   265  }