Documentation
¶
Overview ¶
Example ¶
package main
import (
"context"
"flag"
"log/slog"
"net/http"
"os"
"github.com/linkdata/webserv"
)
var (
flagAddress = flag.String("address", os.Getenv("WEBSERV_ADDRESS"), "serve HTTP requests on given [address][:port]")
flagCertDir = flag.String("certdir", os.Getenv("WEBSERV_CERTDIR"), "where to find fullchain.pem and privkey.pem")
flagUser = flag.String("user", envOrDefault("WEBSERV_USER", "www-data"), "switch to this user after startup (*nix only)")
flagDataDir = flag.String("datadir", envOrDefault("WEBSERV_DATADIR", "$HOME"), "where to store data files after startup")
flagListenURL = flag.String("listenurl", os.Getenv("WEBSERV_LISTENURL"), "specify the external URL clients can reach us at")
)
func envOrDefault(envvar, defval string) (s string) {
if s = os.Getenv(envvar); s == "" {
s = defval
}
return
}
func main() {
flag.Parse()
cfg := webserv.Config{
Address: *flagAddress,
CertDir: *flagCertDir,
User: *flagUser,
DataDir: *flagDataDir,
ListenURL: *flagListenURL,
Logger: slog.Default(),
}
http.DefaultServeMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("<html><body>Hello world!</body></html>"))
})
l, err := cfg.Listen()
if err == nil {
if err = cfg.Serve(context.Background(), l, nil); err == nil {
return
}
}
slog.Error(err.Error())
}
Output:
Index ¶
- Constants
- Variables
- func BecomeUser(userName string) error
- func DefaultDataDir(dataDir, defaultSuffix string) (result string, err error)
- func Listener(listenAddr, certDir, fullchainPem, privkeyPem, overrideUrl string) (l net.Listener, listenUrl, absCertDir string, err error)
- func LoadCert(certDir, fullchainPem, privkeyPem string) (cert *tls.Certificate, absCertDir string, err error)
- func UseDataDir(dataDir string, mode fs.FileMode) (string, error)
- type Config
- func (cfg *Config) Listen() (l net.Listener, err error)
- func (cfg *Config) ListenAndServe(ctx context.Context, handler http.Handler) (err error)
- func (cfg *Config) Serve(ctx context.Context, l net.Listener, handler http.Handler) error
- func (cfg *Config) ServeWith(ctx context.Context, srv *http.Server, l net.Listener) (err error)
- type Logger
Examples ¶
Constants ¶
const ( // FullchainPem is the default certificate chain filename used by LoadCert. FullchainPem = "fullchain.pem" // PrivkeyPem is the default private key filename used by LoadCert. PrivkeyPem = "privkey.pem" )
Variables ¶
var ErrBecomeUser = errBecomeUser{}
ErrBecomeUser matches errors returned by BecomeUser on failure.
var ErrServePanic = errServePanic{}
ErrServePanic matches errors returned by Config.ServeWith when srv.Serve panics.
Functions ¶
func BecomeUser ¶
BecomeUser switches to the given userName if not empty.
When running as root (euid 0) it resets the supplementary groups to those of the target user and then sets the GID and UID, in that order (setgroups, setgid, setuid). When not root the supplementary-groups reset is skipped and setgid/setuid will fail unless the target ids already match the process.
It then sets the HOME and USER environment variables to match the target user and unsets XDG_CONFIG_HOME so config-directory lookups follow the new HOME.
Returns an error matching ErrBecomeUser on failure.
func DefaultDataDir ¶
DefaultDataDir returns the absolute path to dataDir if not empty, otherwise if defaultSuffix is not empty it returns the absolute joined path of os.UserConfigDir and defaultSuffix.
It will expand environment variables in the path before evaluating the absolute path. If expansion collapses the path to empty (for example a lone "$VAR" whose variable is unset), the result is empty rather than the current working directory.
dataDir and defaultSuffix may contain paths, ".." segments and symlinks. They are not confined to the user config directory, so they may resolve outside of it. Caller is responsible for validating or sandboxing untrusted path input.
func Listener ¶
func Listener(listenAddr, certDir, fullchainPem, privkeyPem, overrideUrl string) (l net.Listener, listenUrl, absCertDir string, err error)
Listener creates a net.Listener given an optional preferred address and an optional directory containing certificate files.
If certDir is not empty, it calls LoadCert to load fullchain.pem and privkey.pem.
The listener will default to all addresses and standard port depending on privileges and if a certificate was loaded or not.
These defaults can be overridden with the listenAddr argument. To specify only a port, use an address like ":8080".
Returns the net.Listener and listenURL if there was no error. absCertDir is the resolved absolute path to certDir whenever certDir was non-empty and could be resolved, even if loading the certificate then failed.
func LoadCert ¶
func LoadCert(certDir, fullchainPem, privkeyPem string) (cert *tls.Certificate, absCertDir string, err error)
LoadCert does nothing if certDir is empty, otherwise it expands environment variables and transforms it into an absolute path. It then tries to load a X509 key pair with crypto/tls.LoadX509KeyPair from the files named fullchainPem and privkeyPem in the resulting directory.
If expansion collapses certDir to empty (for example a lone "$VAR" whose variable is unset), LoadCert does nothing rather than resolving to the current working directory.
The filenames may contain paths, ".." segments and symlinks. They are not confined to certDir, so they may resolve outside of it. Caller is responsible for validating or sandboxing untrusted path input.
If fullchainPem is empty, it defaults to FullchainPem. If privkeyPem is empty, it defaults to PrivkeyPem.
cert is non-nil only when the key pair loaded successfully. absCertDir is the resolved absolute directory whenever certDir was non-empty after expansion and path/filepath.Abs succeeded, regardless of whether the key pair then loaded.
func UseDataDir ¶
UseDataDir transforms dataDir into an absolute path. Then, if mode is not zero, it creates the path if it does not exist using os.MkdirAll. As with os.MkdirAll, mode is subject to the process umask, so the created directory's permissions may be more restrictive than mode. Does nothing if dataDir is empty. Does not expand environment variables in the path.
Returns the final path or an empty string if dataDir was empty.
Types ¶
type Config ¶
type Config struct {
Address string // optional specific address to listen on; use ":port" for port-only
CertDir string // if set, directory to look for fullchain.pem and privkey.pem
FullchainPem string // set to override filename for "fullchain.pem"
PrivkeyPem string // set to override filename for "privkey.pem"
User string // if set, user to switch to after opening listening port
DataDir string // if set, the data directory to use (created only when DataDirMode is nonzero); if unset, may be filled in after Listen
DefaultDataDirSuffix string // if set and DataDir is not set, set DataDir to the user's default data dir plus this suffix
DataDirMode fs.FileMode // if nonzero, create DataDir if it does not exist using this mode (subject to the process umask, like os.MkdirAll)
ListenURL string // if set, the external URL clients can reach us at. If unset, Listen may fill this in (e.g. "https://localhost:8443"), even when Listen later returns an error after binding.
ShutdownTimeLimit time.Duration // maximum time ServeWith waits for graceful shutdown; zero uses a 1 second default
LogTLSErrors bool // if set, http.Server TLS handshake error messages are not filtered
Logger Logger // logger to use, if nil logs nothing
}
Config contains the startup and serving settings for a simple web service.
The zero value is usable: Config.Listen serves HTTP on the default address and port, Config.Serve uses a default net/http.Server, no user switch or data directory setup is performed, and no logs are emitted.
func (*Config) Listen ¶
Listen performs initial setup for a simple web server and returns a net.Listener if successful.
First it loads certificates if cfg.CertDir is set, and then starts a net.Listener (TLS or normal). The listener will default to all addresses and standard port depending on privileges and if a certificate was loaded or not.
If cfg.Address was set, any address or port given there overrides these defaults.
If cfg.User is set it then switches to that user with BecomeUser, dropping supplementary groups, GID and UID (when running as root). Note that this is not supported on Windows.
If cfg.DataDir or cfg.DefaultDataDirSuffix is set, calculates the absolute data directory path with DefaultDataDir and sets cfg.DataDir. If cfg.DataDirMode is nonzero, UseDataDir creates the directory if necessary, using cfg.DataDirMode subject to the process umask.
On return, cfg.CertDir and cfg.DataDir will be absolute paths or be empty. If Listen returns an error, cfg.DataDir is reset to empty regardless of the value the caller supplied, while cfg.CertDir keeps any absolute path resolved before the failure. If cfg.ListenURL was empty it may be set to a best-guess printable and connectable URL like "http://localhost:80" as soon as the socket is opened. Therefore cfg.ListenURL can be non-empty even if Listen returns an error from a later step, such as user switching or data directory setup.
func (*Config) ListenAndServe ¶ added in v0.9.0
ListenAndServe calls Config.Listen followed by Config.Serve.
It returns ctx.Err() without opening a listener if ctx is already canceled. Otherwise, it performs the setup documented by Config.Listen and then serves requests with the default server settings documented by Config.Serve.
The returned error is from Listen, Serve, ctx cancellation, or shutdown. A nil return means the server started successfully and then shut down cleanly.
Panics if ctx is nil.
func (*Config) Serve ¶ added in v0.9.0
Serve creates an net/http.Server with reasonable defaults and calls Config.ServeWith.
The server uses handler as its Handler; if handler is nil, net/http.DefaultServeMux is used by net/http. ReadHeaderTimeout is set to 5 seconds and IdleTimeout is set to 1 minute.
Serve takes ownership of l for serving and returns the error from Config.ServeWith; see that method for the full return contract (nil on a clean shutdown, ctx.Err() on ctx cancellation, otherwise the shutdown or serve error).
Panics if ctx or l is nil.
func (*Config) ServeWith ¶ added in v0.9.1
ServeWith catches SIGINT and SIGTERM and calls srv.Serve(l). A controlled shutdown is triggered by either of those signals or by ctx being canceled, using net/http.Server.Shutdown bounded by [Config.ShutdownTimeLimit] (or 1 second when [Config.ShutdownTimeLimit] is zero).
The returned error depends on what ended serving:
- a clean shutdown returns nil (net/http.ErrServerClosed is mapped to nil);
- if ctx was canceled, it returns an error matching ctx.Err(); if srv.Serve also exits with a non-clean error, the errors are joined with errors.Join; a shutdown that then exceeds [Config.ShutdownTimeLimit] does not replace that ctx.Err();
- if a signal triggered the shutdown, it returns the shutdown error (such as context.DeadlineExceeded when draining exceeds [Config.ShutdownTimeLimit]), otherwise the error from srv.Serve.
Unless [Config.LogTLSErrors] is set, srv.ErrorLog is replaced for the lifetime of the call with a filter that drops TLS handshake error lines and forwards the rest; the original logger is not restored.
Panics if ctx, srv or l is nil. Panics from srv.Serve are recovered and returned as an error matching ErrServePanic.