2022-11-08 19:49:19 +08:00
// Package http provides a registration interface for http services
package http
import (
2023-01-11 13:05:44 +08:00
"bytes"
2022-11-08 19:49:19 +08:00
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"html/template"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/go-chi/chi/v5"
2024-07-08 23:22:48 +08:00
"github.com/rclone/rclone/fs"
2022-11-08 19:49:19 +08:00
"github.com/rclone/rclone/fs/config/flags"
2022-12-23 00:52:43 +08:00
"github.com/rclone/rclone/lib/atexit"
2024-05-14 03:13:10 +08:00
sdActivation "github.com/rclone/rclone/lib/sdactivation"
2022-11-08 19:49:19 +08:00
"github.com/spf13/pflag"
)
2023-01-11 13:05:44 +08:00
// Help returns text describing the http server to add to the command
2022-11-08 19:49:19 +08:00
// help.
2023-01-11 13:05:44 +08:00
func Help ( prefix string ) string {
2024-04-05 19:27:33 +08:00
help := ` # # # Server options
2022-11-08 19:49:19 +08:00
2023-01-11 13:05:44 +08:00
Use ` + " ` -- { { . Prefix } } addr ` " + ` to specify which IP address and port the server should
listen on , eg ` + " ` -- { { . Prefix } } addr 1.2 .3 .4 : 8000 ` or ` -- { { . Prefix } } addr : 8080 ` " + ` to listen to all
2022-11-08 19:49:19 +08:00
IPs . By default it only listens on localhost . You can use port
: 0 to let the OS choose an available port .
2023-01-11 13:05:44 +08:00
If you set ` + " ` -- { { . Prefix } } addr ` " + ` to listen on a public or LAN accessible IP address
2022-11-08 19:49:19 +08:00
then using Authentication is advised - see the next section for info .
You can use a unix socket by setting the url to ` + " ` unix : ///path/to/socket`" + `
2024-10-11 01:42:42 +08:00
or just by using an absolute path name .
2022-11-08 19:49:19 +08:00
2023-01-11 13:05:44 +08:00
` + " ` -- { { . Prefix } } addr ` " + ` may be repeated to listen on multiple IPs / ports / sockets .
2024-04-24 20:37:07 +08:00
Socket activation , described further below , can also be used to accomplish the same .
2022-11-08 19:49:19 +08:00
2023-01-11 13:05:44 +08:00
` + " ` -- { { . Prefix } } server - read - timeout ` and ` -- { { . Prefix } } server - write - timeout ` " + ` can be used to
2022-11-08 19:49:19 +08:00
control the timeouts on the server . Note that this is the total time
for a transfer .
2023-01-11 13:05:44 +08:00
` + " ` -- { { . Prefix } } max - header - bytes ` " + ` controls the maximum number of bytes the server will
2022-11-08 19:49:19 +08:00
accept in the HTTP header .
2023-01-11 13:05:44 +08:00
` + " ` -- { { . Prefix } } baseurl ` " + ` controls the URL prefix that rclone serves from . By default
rclone will serve from the root . If you used ` + " ` -- { { . Prefix } } baseurl \ "/rclone\"`" + ` then
2022-11-08 19:49:19 +08:00
rclone would serve from a URL starting with "/rclone/" . This is
useful if you wish to proxy rclone serve . Rclone automatically
2023-01-11 13:05:44 +08:00
inserts leading and trailing "/" on ` + " ` -- { { . Prefix } } baseurl ` " + ` , so ` + " ` -- { { . Prefix } } baseurl \ "rclone\"`" + ` ,
` + " ` -- { { . Prefix } } baseurl \ "/rclone\"` and `--{{ .Prefix }}baseurl \"/rclone/\"`" + ` are all treated
2022-11-08 19:49:19 +08:00
identically .
# # # # TLS ( SSL )
By default this will serve over http . If you want you can serve over
2023-01-11 13:05:44 +08:00
https . You will need to supply the ` + " ` -- { { . Prefix } } cert ` and ` -- { { . Prefix } } key ` " + ` flags .
2022-11-08 19:49:19 +08:00
If you wish to do client side certificate validation then you will need to
2023-01-11 13:05:44 +08:00
supply ` + " ` -- { { . Prefix } } client - ca ` " + ` also .
2022-11-08 19:49:19 +08:00
2023-01-11 13:05:44 +08:00
` + " ` -- { { . Prefix } } cert ` " + ` should be a either a PEM encoded certificate or a concatenation
of that with the CA certificate . ` + " ` -- k { { . Prefix } } ey ` " + ` should be the PEM encoded
private key and ` + " ` -- { { . Prefix } } client - ca ` " + ` should be the PEM encoded client
2022-11-08 19:49:19 +08:00
certificate authority certificate .
2024-05-13 18:34:56 +08:00
` + " ` -- { { . Prefix } } min - tls - version ` " + ` is minimum TLS version that is acceptable . Valid
2022-11-08 19:49:19 +08:00
values are "tls1.0" , "tls1.1" , "tls1.2" and "tls1.3" ( default
"tls1.0" ) .
2024-04-05 19:27:33 +08:00
2024-04-24 20:37:07 +08:00
# # # Socket activation
Instead of the listening addresses specified above , rclone will listen to all
FDs passed by the service manager , if any ( and ignore any arguments passed by ` +
"--{{ .Prefix }}addr`" + ` ) .
This allows rclone to be a socket - activated service .
It can be configured with . socket and . service unit files as described in
https : //www.freedesktop.org/software/systemd/man/latest/systemd.socket.html
Socket activation can be tested ad - hoc with the ` + " ` systemd - socket - activate ` " + ` command
systemd - socket - activate - l 8000 -- rclone serve
This will socket - activate rclone on the first connection to port 8000 over TCP .
2022-11-08 19:49:19 +08:00
`
2023-01-11 13:05:44 +08:00
tmpl , err := template . New ( "server help" ) . Parse ( help )
if err != nil {
2024-08-18 22:58:35 +08:00
fs . Fatal ( nil , fmt . Sprint ( "Fatal error parsing template" , err ) )
2023-01-11 13:05:44 +08:00
}
data := struct {
Prefix string
} {
Prefix : prefix ,
}
buf := & bytes . Buffer { }
err = tmpl . Execute ( buf , data )
if err != nil {
2024-08-18 22:58:35 +08:00
fs . Fatal ( nil , fmt . Sprint ( "Fatal error executing template" , err ) )
2023-01-11 13:05:44 +08:00
}
return buf . String ( )
}
2022-11-08 19:49:19 +08:00
// Middleware function signature required by chi.Router.Use()
type Middleware func ( http . Handler ) http . Handler
2024-07-08 23:22:48 +08:00
// ConfigInfo descripts the Options in use
var ConfigInfo = fs . Options { {
Name : "addr" ,
Default : [ ] string { "127.0.0.1:8080" } ,
Help : "IPaddress:Port or :Port to bind server to" ,
} , {
Name : "server_read_timeout" ,
Default : 1 * time . Hour ,
Help : "Timeout for server reading data" ,
} , {
Name : "server_write_timeout" ,
Default : 1 * time . Hour ,
Help : "Timeout for server writing data" ,
} , {
Name : "max_header_bytes" ,
Default : 4096 ,
Help : "Maximum size of request header" ,
} , {
Name : "cert" ,
Default : "" ,
Help : "TLS PEM key (concatenation of certificate and CA certificate)" ,
} , {
Name : "key" ,
Default : "" ,
Help : "TLS PEM Private key" ,
} , {
Name : "client_ca" ,
Default : "" ,
Help : "Client certificate authority to verify clients with" ,
} , {
Name : "baseurl" ,
Default : "" ,
Help : "Prefix for URLs - leave blank for root" ,
} , {
Name : "min_tls_version" ,
Default : "tls1.0" ,
Help : "Minimum TLS version that is acceptable" ,
} , {
Name : "allow_origin" ,
Default : "" ,
Help : "Origin which cross-domain request (CORS) can be executed from" ,
} }
2022-11-08 20:03:26 +08:00
// Config contains options for the http Server
type Config struct {
2024-07-08 23:22:48 +08:00
ListenAddr [ ] string ` config:"addr" ` // Port to listen on
BaseURL string ` config:"baseurl" ` // prefix to strip from URLs
ServerReadTimeout time . Duration ` config:"server_read_timeout" ` // Timeout for server reading data
ServerWriteTimeout time . Duration ` config:"server_write_timeout" ` // Timeout for server writing data
MaxHeaderBytes int ` config:"max_header_bytes" ` // Maximum size of request header
TLSCert string ` config:"cert" ` // Path to TLS PEM key (concatenation of certificate and CA certificate)
TLSKey string ` config:"key" ` // Path to TLS PEM Private key
TLSCertBody [ ] byte ` config:"-" ` // TLS PEM key (concatenation of certificate and CA certificate) body, ignores TLSCert
TLSKeyBody [ ] byte ` config:"-" ` // TLS PEM Private key body, ignores TLSKey
ClientCA string ` config:"client_ca" ` // Client certificate authority to verify clients with
MinTLSVersion string ` config:"min_tls_version" ` // MinTLSVersion contains the minimum TLS version that is acceptable.
AllowOrigin string ` config:"allow_origin" ` // AllowOrigin sets the Access-Control-Allow-Origin header
2022-11-08 19:49:19 +08:00
}
// AddFlagsPrefix adds flags for the httplib
2022-11-08 20:03:26 +08:00
func ( cfg * Config ) AddFlagsPrefix ( flagSet * pflag . FlagSet , prefix string ) {
2024-04-24 20:34:27 +08:00
flags . StringArrayVarP ( flagSet , & cfg . ListenAddr , prefix + "addr" , "" , cfg . ListenAddr , "IPaddress:Port, :Port or [unix://]/path/to/socket to bind server to" , prefix )
2023-07-11 01:34:10 +08:00
flags . DurationVarP ( flagSet , & cfg . ServerReadTimeout , prefix + "server-read-timeout" , "" , cfg . ServerReadTimeout , "Timeout for server reading data" , prefix )
flags . DurationVarP ( flagSet , & cfg . ServerWriteTimeout , prefix + "server-write-timeout" , "" , cfg . ServerWriteTimeout , "Timeout for server writing data" , prefix )
flags . IntVarP ( flagSet , & cfg . MaxHeaderBytes , prefix + "max-header-bytes" , "" , cfg . MaxHeaderBytes , "Maximum size of request header" , prefix )
flags . StringVarP ( flagSet , & cfg . TLSCert , prefix + "cert" , "" , cfg . TLSCert , "TLS PEM key (concatenation of certificate and CA certificate)" , prefix )
flags . StringVarP ( flagSet , & cfg . TLSKey , prefix + "key" , "" , cfg . TLSKey , "TLS PEM Private key" , prefix )
flags . StringVarP ( flagSet , & cfg . ClientCA , prefix + "client-ca" , "" , cfg . ClientCA , "Client certificate authority to verify clients with" , prefix )
flags . StringVarP ( flagSet , & cfg . BaseURL , prefix + "baseurl" , "" , cfg . BaseURL , "Prefix for URLs - leave blank for root" , prefix )
flags . StringVarP ( flagSet , & cfg . MinTLSVersion , prefix + "min-tls-version" , "" , cfg . MinTLSVersion , "Minimum TLS version that is acceptable" , prefix )
flags . StringVarP ( flagSet , & cfg . AllowOrigin , prefix + "allow-origin" , "" , cfg . AllowOrigin , "Origin which cross-domain request (CORS) can be executed from" , prefix )
2022-11-08 19:49:19 +08:00
}
// AddHTTPFlagsPrefix adds flags for the httplib
2022-11-08 20:03:26 +08:00
func AddHTTPFlagsPrefix ( flagSet * pflag . FlagSet , prefix string , cfg * Config ) {
2022-11-08 19:49:19 +08:00
cfg . AddFlagsPrefix ( flagSet , prefix )
}
2022-11-08 20:03:26 +08:00
// DefaultCfg is the default values used for Config
2024-07-08 23:22:48 +08:00
//
// Note that this needs to be kept in sync with ConfigInfo above and
// can be removed when all callers have been converted.
2022-11-08 20:03:26 +08:00
func DefaultCfg ( ) Config {
return Config {
2022-11-08 19:49:19 +08:00
ListenAddr : [ ] string { "127.0.0.1:8080" } ,
ServerReadTimeout : 1 * time . Hour ,
ServerWriteTimeout : 1 * time . Hour ,
MaxHeaderBytes : 4096 ,
MinTLSVersion : "tls1.0" ,
}
}
type instance struct {
url string
listener net . Listener
httpServer * http . Server
}
func ( s instance ) serve ( wg * sync . WaitGroup ) {
defer wg . Done ( )
err := s . httpServer . Serve ( s . listener )
if err != http . ErrServerClosed && err != nil {
2024-08-18 22:58:35 +08:00
fs . Logf ( nil , "%s: unexpected error: %s" , s . listener . Addr ( ) , err . Error ( ) )
2022-11-08 19:49:19 +08:00
}
}
2022-11-23 17:44:53 +08:00
// Server contains info about the running http server
type Server struct {
2022-11-08 19:49:19 +08:00
wg sync . WaitGroup
mux chi . Router
tlsConfig * tls . Config
instances [ ] instance
auth AuthConfig
2022-11-08 20:03:26 +08:00
cfg Config
2022-11-08 19:49:19 +08:00
template * TemplateConfig
htmlTemplate * template . Template
2022-12-10 18:51:57 +08:00
usingAuth bool // set if we are using auth middleware
2022-12-23 00:52:43 +08:00
atexitHandle atexit . FnHandle
2022-11-08 19:49:19 +08:00
}
// Option allows customizing the server
2022-11-23 17:44:53 +08:00
type Option func ( * Server )
2022-11-08 19:49:19 +08:00
// WithAuth option initializes the appropriate auth middleware
func WithAuth ( cfg AuthConfig ) Option {
2022-11-23 17:44:53 +08:00
return func ( s * Server ) {
2022-11-08 19:49:19 +08:00
s . auth = cfg
}
}
2022-11-08 20:03:26 +08:00
// WithConfig option applies the Config to the server, overriding defaults
func WithConfig ( cfg Config ) Option {
2022-11-23 17:44:53 +08:00
return func ( s * Server ) {
2022-11-08 19:49:19 +08:00
s . cfg = cfg
}
}
// WithTemplate option allows the parsing of a template
func WithTemplate ( cfg TemplateConfig ) Option {
2022-11-23 17:44:53 +08:00
return func ( s * Server ) {
2022-11-08 19:49:19 +08:00
s . template = & cfg
}
}
2024-04-24 20:37:07 +08:00
// For a given listener, and optional tlsConfig, construct a instance.
// The url string ends up in the `url` field of the `instance`.
// This unconditionally wraps the listener with the provided TLS config if one
// is specified, so all decision logic on whether to use TLS needs to live at
// the callsite.
func newInstance ( ctx context . Context , s * Server , listener net . Listener , tlsCfg * tls . Config , url string ) * instance {
if tlsCfg != nil {
listener = tls . NewListener ( listener , tlsCfg )
}
return & instance {
url : url ,
listener : listener ,
httpServer : & http . Server {
Handler : s . mux ,
ReadTimeout : s . cfg . ServerReadTimeout ,
WriteTimeout : s . cfg . ServerWriteTimeout ,
MaxHeaderBytes : s . cfg . MaxHeaderBytes ,
ReadHeaderTimeout : 10 * time . Second , // time to send the headers
IdleTimeout : 60 * time . Second , // time to keep idle connections open
TLSConfig : tlsCfg ,
BaseContext : NewBaseContext ( ctx , url ) ,
} ,
}
}
2022-11-08 19:49:19 +08:00
// NewServer instantiates a new http server using provided listeners and options
// This function is provided if the default http server does not meet a services requirements and should not generally be used
// A http server can listen using multiple listeners. For example, a listener for port 80, and a listener for port 443.
// tlsListeners are ignored if opt.TLSKey is not provided
2022-11-23 17:44:53 +08:00
func NewServer ( ctx context . Context , options ... Option ) ( * Server , error ) {
s := & Server {
2022-11-08 19:49:19 +08:00
mux : chi . NewRouter ( ) ,
2022-11-08 20:03:26 +08:00
cfg : DefaultCfg ( ) ,
2022-11-08 19:49:19 +08:00
}
2022-11-23 17:44:53 +08:00
// Make sure default logger is logging where everything else is
// middleware.DefaultLogger = middleware.RequestLogger(&middleware.DefaultLogFormatter{Logger: log.Default(), NoColor: true})
// Log requests
// s.mux.Use(middleware.Logger)
2022-11-08 19:49:19 +08:00
for _ , opt := range options {
opt ( s )
}
// Build base router
s . mux . MethodNotAllowed ( func ( w http . ResponseWriter , _ * http . Request ) {
http . Error ( w , http . StatusText ( http . StatusMethodNotAllowed ) , http . StatusMethodNotAllowed )
} )
s . mux . NotFound ( func ( w http . ResponseWriter , _ * http . Request ) {
http . Error ( w , http . StatusText ( http . StatusNotFound ) , http . StatusNotFound )
} )
// Ignore passing "/" for BaseURL
s . cfg . BaseURL = strings . Trim ( s . cfg . BaseURL , "/" )
if s . cfg . BaseURL != "" {
s . cfg . BaseURL = "/" + s . cfg . BaseURL
s . mux . Use ( MiddlewareStripPrefix ( s . cfg . BaseURL ) )
}
err := s . initTemplate ( )
if err != nil {
return nil , err
}
err = s . initTLS ( )
if err != nil {
return nil , err
}
2023-07-26 17:15:54 +08:00
s . mux . Use ( MiddlewareCORS ( s . cfg . AllowOrigin ) )
2023-05-26 13:26:13 +08:00
s . initAuth ( )
2024-04-24 20:37:07 +08:00
// (Only) listen on FDs provided by the service manager, if any.
sdListeners , err := sdActivation . ListenersWithNames ( )
if err != nil {
return nil , fmt . Errorf ( "unable to acquire listeners: %w" , err )
}
if len ( sdListeners ) != 0 {
for listenerName , listeners := range sdListeners {
for i , listener := range listeners {
url := fmt . Sprintf ( "sd-listen:%s-%d/%s" , listenerName , i , s . cfg . BaseURL )
if s . tlsConfig != nil {
url = fmt . Sprintf ( "sd-listen+tls:%s-%d/%s" , listenerName , i , s . cfg . BaseURL )
}
instance := newInstance ( ctx , s , listener , s . tlsConfig , url )
s . instances = append ( s . instances , * instance )
}
}
return s , nil
}
// Process all listeners specified in the CLI Args.
2022-11-08 19:49:19 +08:00
for _ , addr := range s . cfg . ListenAddr {
2024-04-24 20:37:07 +08:00
var instance * instance
2022-11-08 19:49:19 +08:00
if strings . HasPrefix ( addr , "unix://" ) || filepath . IsAbs ( addr ) {
addr = strings . TrimPrefix ( addr , "unix://" )
2024-04-24 20:37:07 +08:00
listener , err := net . Listen ( "unix" , addr )
if err != nil {
return nil , err
}
instance = newInstance ( ctx , s , listener , s . tlsConfig , addr )
2022-11-08 19:49:19 +08:00
} else if strings . HasPrefix ( addr , "tls://" ) || ( len ( s . cfg . ListenAddr ) == 1 && s . tlsConfig != nil ) {
addr = strings . TrimPrefix ( addr , "tls://" )
2024-04-24 20:37:07 +08:00
listener , err := net . Listen ( "tcp" , addr )
if err != nil {
return nil , err
}
instance = newInstance ( ctx , s , listener , s . tlsConfig , fmt . Sprintf ( "https://%s%s/" , listener . Addr ( ) . String ( ) , s . cfg . BaseURL ) )
2022-11-08 19:49:19 +08:00
} else {
2024-04-24 20:37:07 +08:00
// HTTP case
addr = strings . TrimPrefix ( addr , "http://" )
listener , err := net . Listen ( "tcp" , addr )
if err != nil {
return nil , err
2022-11-08 19:49:19 +08:00
}
2024-04-24 20:37:07 +08:00
instance = newInstance ( ctx , s , listener , nil , fmt . Sprintf ( "http://%s%s/" , listener . Addr ( ) . String ( ) , s . cfg . BaseURL ) )
2022-11-08 19:49:19 +08:00
}
2024-04-24 20:37:07 +08:00
s . instances = append ( s . instances , * instance )
2022-11-08 19:49:19 +08:00
}
return s , nil
}
2022-11-23 17:44:53 +08:00
func ( s * Server ) initAuth ( ) {
2023-05-26 13:26:13 +08:00
s . usingAuth = false
authCertificateUserEnabled := s . tlsConfig != nil && s . tlsConfig . ClientAuth != tls . NoClientCert && s . auth . HtPasswd == "" && s . auth . BasicUser == ""
if authCertificateUserEnabled {
s . usingAuth = true
s . mux . Use ( MiddlewareAuthCertificateUser ( ) )
}
2022-11-08 19:49:19 +08:00
if s . auth . CustomAuthFn != nil {
2022-12-10 18:51:57 +08:00
s . usingAuth = true
2023-05-26 13:26:13 +08:00
s . mux . Use ( MiddlewareAuthCustom ( s . auth . CustomAuthFn , s . auth . Realm , authCertificateUserEnabled ) )
2022-11-08 19:49:19 +08:00
return
}
if s . auth . HtPasswd != "" {
2022-12-10 18:51:57 +08:00
s . usingAuth = true
2022-11-08 19:49:19 +08:00
s . mux . Use ( MiddlewareAuthHtpasswd ( s . auth . HtPasswd , s . auth . Realm ) )
return
}
if s . auth . BasicUser != "" {
2022-12-10 18:51:57 +08:00
s . usingAuth = true
2022-11-08 19:49:19 +08:00
s . mux . Use ( MiddlewareAuthBasic ( s . auth . BasicUser , s . auth . BasicPass , s . auth . Realm , s . auth . Salt ) )
return
}
}
2022-11-23 17:44:53 +08:00
func ( s * Server ) initTemplate ( ) error {
2022-11-08 19:49:19 +08:00
if s . template == nil {
return nil
}
var err error
s . htmlTemplate , err = GetTemplate ( s . template . Path )
if err != nil {
err = fmt . Errorf ( "failed to get template: %w" , err )
}
return err
}
var (
2022-11-08 20:03:26 +08:00
// ErrInvalidMinTLSVersion - hard coded errors, allowing for easier testing
2022-11-08 19:49:19 +08:00
ErrInvalidMinTLSVersion = errors . New ( "invalid value for --min-tls-version" )
2022-11-08 20:03:26 +08:00
// ErrTLSBodyMismatch - hard coded errors, allowing for easier testing
ErrTLSBodyMismatch = errors . New ( "need both TLSCertBody and TLSKeyBody to use TLS" )
// ErrTLSFileMismatch - hard coded errors, allowing for easier testing
ErrTLSFileMismatch = errors . New ( "need both --cert and --key to use TLS" )
// ErrTLSParseCA - hard coded errors, allowing for easier testing
ErrTLSParseCA = errors . New ( "unable to parse client certificate authority" )
2022-11-08 19:49:19 +08:00
)
2022-11-23 17:44:53 +08:00
func ( s * Server ) initTLS ( ) error {
2022-11-08 19:49:19 +08:00
if s . cfg . TLSCert == "" && s . cfg . TLSKey == "" && len ( s . cfg . TLSCertBody ) == 0 && len ( s . cfg . TLSKeyBody ) == 0 {
return nil
}
if ( len ( s . cfg . TLSCertBody ) > 0 ) != ( len ( s . cfg . TLSKeyBody ) > 0 ) {
return ErrTLSBodyMismatch
}
if ( s . cfg . TLSCert != "" ) != ( s . cfg . TLSKey != "" ) {
return ErrTLSFileMismatch
}
var cert tls . Certificate
var err error
if len ( s . cfg . TLSCertBody ) > 0 {
cert , err = tls . X509KeyPair ( s . cfg . TLSCertBody , s . cfg . TLSKeyBody )
} else {
cert , err = tls . LoadX509KeyPair ( s . cfg . TLSCert , s . cfg . TLSKey )
}
if err != nil {
return err
}
var minTLSVersion uint16
switch s . cfg . MinTLSVersion {
case "tls1.0" :
minTLSVersion = tls . VersionTLS10
case "tls1.1" :
minTLSVersion = tls . VersionTLS11
case "tls1.2" :
minTLSVersion = tls . VersionTLS12
case "tls1.3" :
minTLSVersion = tls . VersionTLS13
default :
return fmt . Errorf ( "%w: %s" , ErrInvalidMinTLSVersion , s . cfg . MinTLSVersion )
}
s . tlsConfig = & tls . Config {
MinVersion : minTLSVersion ,
Certificates : [ ] tls . Certificate { cert } ,
}
if s . cfg . ClientCA != "" {
// if !useTLS {
// err := errors.New("can't use --client-ca without --cert and --key")
// log.Fatalf(err.Error())
// }
certpool := x509 . NewCertPool ( )
pem , err := os . ReadFile ( s . cfg . ClientCA )
if err != nil {
return err
}
if ! certpool . AppendCertsFromPEM ( pem ) {
return ErrTLSParseCA
}
s . tlsConfig . ClientCAs = certpool
s . tlsConfig . ClientAuth = tls . RequireAndVerifyClientCert
}
return nil
}
// Serve starts the HTTP server on each listener
2022-11-23 17:44:53 +08:00
func ( s * Server ) Serve ( ) {
2022-11-08 19:49:19 +08:00
s . wg . Add ( len ( s . instances ) )
for _ , ii := range s . instances {
// TODO: decide how/when to log listening url
// log.Printf("listening on %s", ii.url)
go ii . serve ( & s . wg )
}
2022-12-23 00:52:43 +08:00
// Install an atexit handler to shutdown gracefully
s . atexitHandle = atexit . Register ( func ( ) { _ = s . Shutdown ( ) } )
2022-11-08 19:49:19 +08:00
}
// Wait blocks while the server is serving requests
2022-11-23 17:44:53 +08:00
func ( s * Server ) Wait ( ) {
2022-11-08 19:49:19 +08:00
s . wg . Wait ( )
}
// Router returns the server base router
2022-11-23 17:44:53 +08:00
func ( s * Server ) Router ( ) chi . Router {
2022-11-08 19:49:19 +08:00
return s . mux
}
2022-12-23 00:52:43 +08:00
// Time to wait to Shutdown an HTTP server
const gracefulShutdownTime = 10 * time . Second
2022-11-08 19:49:19 +08:00
// Shutdown gracefully shuts down the server
2022-11-23 17:44:53 +08:00
func ( s * Server ) Shutdown ( ) error {
2022-12-23 00:52:43 +08:00
// Stop the atexit handler
if s . atexitHandle != nil {
atexit . Unregister ( s . atexitHandle )
s . atexitHandle = nil
}
2022-11-08 19:49:19 +08:00
for _ , ii := range s . instances {
2022-12-23 00:52:43 +08:00
expiry := time . Now ( ) . Add ( gracefulShutdownTime )
ctx , cancel := context . WithDeadline ( context . Background ( ) , expiry )
2022-11-08 19:49:19 +08:00
if err := ii . httpServer . Shutdown ( ctx ) ; err != nil {
2024-08-18 22:58:35 +08:00
fs . Logf ( nil , "error shutting down server: %s" , err )
2022-11-08 19:49:19 +08:00
}
2022-12-23 00:52:43 +08:00
cancel ( )
2022-11-08 19:49:19 +08:00
}
s . wg . Wait ( )
return nil
}
// HTMLTemplate returns the parsed template, if WithTemplate option was passed.
2022-11-23 17:44:53 +08:00
func ( s * Server ) HTMLTemplate ( ) * template . Template {
2022-11-08 19:49:19 +08:00
return s . htmlTemplate
}
// URLs returns all configured URLS
2022-11-23 17:44:53 +08:00
func ( s * Server ) URLs ( ) [ ] string {
2022-11-08 19:49:19 +08:00
var out [ ] string
for _ , ii := range s . instances {
if ii . listener . Addr ( ) . Network ( ) == "unix" {
continue
}
out = append ( out , ii . url )
}
return out
}
2022-12-10 18:51:57 +08:00
// UsingAuth returns true if authentication is required
func ( s * Server ) UsingAuth ( ) bool {
return s . usingAuth
}