webserv

package module
v0.9.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 23, 2025 License: MIT Imports: 15 Imported by: 4

README

build coverage goreport Docs

webserv

Thin web service library.

Given a listen address, certificate directory, user name and data directory:

  • If certificate directory is not blank, reads fullchain.pem and privkey.pem from it.
  • If the listen address does not specify a port, default port depends on initial user privileges and if we have a certificate.
  • Starts listening on the address and port.
  • If user name is given, switch to that user.
  • If data directory is given, create it if needed.
  • When serving, listen for SIGINT and SIGTERM and do a controlled shutdown.

Usage

go get github.com/linkdata/webserv

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())
}

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())
}

Index

Examples

Constants

View Source
const (
	FullchainPem = "fullchain.pem"
	PrivkeyPem   = "privkey.pem"
)

Variables

View Source
var ErrBecomeUser = errBecomeUser{}

Functions

func BecomeUser

func BecomeUser(userName string) error

BecomeUser switches to the given userName if not empty.

It sets the GID, UID and changes the USER and HOME environment variables accordingly. It unsets XDG_CONFIG_HOME.

Returns ErrBecomeUserNotImplemented if the current OS is not supported.

func DefaultDataDir

func DefaultDataDir(dataDir, defaultSuffix string) (string, error)

DefaultDataDir returns dataDir if not empty, otherwise if defaultSuffix is not empty it returns the joined path of os.UserConfigDir() and defaultSuffix.

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 or port 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.

Returns the net.Listener and listenURL if there was no error. If certificates were successfully loaded, absCertDir will be the absolute path to that directory.

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 from the files named fullchainPem and privkeyPem from the resulting directory.

If fullchainPem is empty, it defaults to "fullchain.pem". If privkeyPem is empty, it defaults to "privkey.pem".

Return a non-nil cert and absolute path to certDir if there are no errors.

func UseDataDir

func UseDataDir(dataDir string, mode fs.FileMode) (string, error)

UseDataDir expands environment variables in dataDir and transforms it into an absolute path. Then, if mode is not zero, it creates the path if it does not exist. Does nothing if dataDir is empty.

Returns the final path or an empty string if dataDir was empty.

Types

type Config

type Config struct {
	Address              string      // optional specific address (and/or port) to listen on
	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, create this directory, if unset will 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
	ListenURL            string      // if set, the external URL clients can reach us at, if unset will be filled in after Listen (e.g. "https://localhost:8443")
	Logger               Logger      // logger to use, if nil logs nothing
	// contains filtered or unexported fields
}

func (*Config) BreakChan added in v0.9.0

func (cfg *Config) BreakChan() (ch chan<- os.Signal)

BreakChan returns the break signalling channel, or nil if not yet started serving requests.

func (*Config) Listen

func (cfg *Config) Listen() (l net.Listener, err error)

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 and the users primary group. Note that this is not supported on Windows.

If cfg.DataDir or cfg.DefaultDataDirSuffix is set, calculates the absolute data directory path and sets cfg.DataDir. If cfg.DataDirMode is nonzero, the directory will be created if necessary.

On a non-error return, cfg.CertDir and cfg.DataDir will be absolute paths or be empty, and if cfg.ListenURL was empty it will be set to a best-guess printable and connectable URL like "http://localhost:80".

func (*Config) ListenAndServe added in v0.9.0

func (cfg *Config) ListenAndServe(ctx context.Context, handler http.Handler) (err error)

ListenAndServe calls Listen followed by Serve.

func (*Config) Serve added in v0.9.0

func (cfg *Config) Serve(ctx context.Context, l net.Listener, handler http.Handler) error

Serve creates a http.Server with reasonable defaults and calls ServeWith.

func (*Config) ServeWith added in v0.9.1

func (cfg *Config) ServeWith(ctx context.Context, srv *http.Server, l net.Listener) error

ServeWith sets up a signal handler to catch SIGINT and SIGTERM and then calls srv.Serve(l).

If the context is cancelled or a signal is received, calls srv.Shutdown(ctx). Returns nil if the server started successfully and then cleanly shut down.

type Logger added in v0.9.8

type Logger interface {
	Info(msg string, keyValuePairs ...any)
	Warn(msg string, keyValuePairs ...any)
	Error(msg string, keyValuePairs ...any)
}

Logger matches log/slog.Info(), Warn() and Error(), but allows one to use another logger using an adaptor.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL