rclone/lib/http/http.go
albertony 5d6b8141ec Replace deprecated ioutil
As of Go 1.16, the same functionality is now provided by package io or
package os, and those implementations should be preferred in new code.
2022-11-07 11:41:47 +00:00

442 lines
13 KiB
Go

// Package http provides a registration interface for http services
package http
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"log"
"net"
"net/http"
"os"
"strings"
"sync"
"time"
"github.com/go-chi/chi/v5"
"github.com/rclone/rclone/fs/config/flags"
"github.com/spf13/pflag"
)
// Help contains text describing the http server to add to the command
// help.
var Help = `
### Server options
Use ` + "`--addr`" + ` to specify which IP address and port the server should
listen on, eg ` + "`--addr 1.2.3.4:8000` or `--addr :8080`" + ` to listen to all
IPs. By default it only listens on localhost. You can use port
:0 to let the OS choose an available port.
If you set ` + "`--addr`" + ` to listen on a public or LAN accessible IP address
then using Authentication is advised - see the next section for info.
` + "`--server-read-timeout` and `--server-write-timeout`" + ` can be used to
control the timeouts on the server. Note that this is the total time
for a transfer.
` + "`--max-header-bytes`" + ` controls the maximum number of bytes the server will
accept in the HTTP header.
` + "`--baseurl`" + ` controls the URL prefix that rclone serves from. By default
rclone will serve from the root. If you used ` + "`--baseurl \"/rclone\"`" + ` then
rclone would serve from a URL starting with "/rclone/". This is
useful if you wish to proxy rclone serve. Rclone automatically
inserts leading and trailing "/" on ` + "`--baseurl`" + `, so ` + "`--baseurl \"rclone\"`" + `,
` + "`--baseurl \"/rclone\"` and `--baseurl \"/rclone/\"`" + ` are all treated
identically.
#### SSL/TLS
By default this will serve over http. If you want you can serve over
https. You will need to supply the ` + "`--cert` and `--key`" + ` flags.
If you wish to do client side certificate validation then you will need to
supply ` + "`--client-ca`" + ` also.
` + "`--cert`" + ` should be a either a PEM encoded certificate or a concatenation
of that with the CA certificate. ` + "`--key`" + ` should be the PEM encoded
private key and ` + "`--client-ca`" + ` should be the PEM encoded client
certificate authority certificate.
--min-tls-version is minimum TLS version that is acceptable. Valid
values are "tls1.0", "tls1.1", "tls1.2" and "tls1.3" (default
"tls1.0").
`
// Middleware function signature required by chi.Router.Use()
type Middleware func(http.Handler) http.Handler
// Options contains options for the http Server
type Options struct {
ListenAddr string // Port to listen on
BaseURL string // prefix to strip from URLs
ServerReadTimeout time.Duration // Timeout for server reading data
ServerWriteTimeout time.Duration // Timeout for server writing data
MaxHeaderBytes int // Maximum size of request header
SslCert string // Path to SSL PEM key (concatenation of certificate and CA certificate)
SslKey string // Path to SSL PEM Private key
SslCertBody []byte // SSL PEM key (concatenation of certificate and CA certificate) body, ignores SslCert
SslKeyBody []byte // SSL PEM Private key body, ignores SslKey
ClientCA string // Client certificate authority to verify clients with
MinTLSVersion string // MinTLSVersion contains the minimum TLS version that is acceptable.
}
// DefaultOpt is the default values used for Options
var DefaultOpt = Options{
ListenAddr: "127.0.0.1:8080",
ServerReadTimeout: 1 * time.Hour,
ServerWriteTimeout: 1 * time.Hour,
MaxHeaderBytes: 4096,
MinTLSVersion: "tls1.0",
}
// Server interface of http server
type Server interface {
Router() chi.Router
Route(pattern string, fn func(r chi.Router)) chi.Router
Mount(pattern string, h http.Handler)
Shutdown() error
}
type server struct {
addrs []net.Addr
tlsAddrs []net.Addr
listeners []net.Listener
tlsListeners []net.Listener
httpServer *http.Server
baseRouter chi.Router
closing *sync.WaitGroup
useSSL bool
}
var (
defaultServer *server
defaultServerOptions = DefaultOpt
defaultServerMutex sync.Mutex
)
func useSSL(opt Options) bool {
return opt.SslKey != "" || len(opt.SslKeyBody) > 0
}
// 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.SslKey is not provided
func NewServer(listeners, tlsListeners []net.Listener, opt Options) (Server, error) {
// Validate input
if len(listeners) == 0 && len(tlsListeners) == 0 {
return nil, errors.New("can't create server without listeners")
}
// Prepare TLS config
var tlsConfig *tls.Config
useSSL := useSSL(opt)
if (len(opt.SslCertBody) > 0) != (len(opt.SslKeyBody) > 0) {
err := errors.New("need both SslCertBody and SslKeyBody to use SSL")
log.Fatalf(err.Error())
return nil, err
}
if (opt.SslCert != "") != (opt.SslKey != "") {
err := errors.New("need both -cert and -key to use SSL")
log.Fatalf(err.Error())
return nil, err
}
if useSSL {
var cert tls.Certificate
var err error
if len(opt.SslCertBody) > 0 {
cert, err = tls.X509KeyPair(opt.SslCertBody, opt.SslKeyBody)
} else {
cert, err = tls.LoadX509KeyPair(opt.SslCert, opt.SslKey)
}
if err != nil {
log.Fatal(err)
}
var minTLSVersion uint16
switch opt.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:
err = errors.New("Invalid value for --min-tls-version")
log.Fatalf(err.Error())
return nil, err
}
tlsConfig = &tls.Config{
MinVersion: minTLSVersion,
Certificates: []tls.Certificate{cert},
}
} else if len(listeners) == 0 && len(tlsListeners) != 0 {
return nil, errors.New("no SslKey or non-tlsListeners")
}
if opt.ClientCA != "" {
if !useSSL {
err := errors.New("can't use --client-ca without --cert and --key")
log.Fatalf(err.Error())
return nil, err
}
certpool := x509.NewCertPool()
pem, err := os.ReadFile(opt.ClientCA)
if err != nil {
log.Fatalf("Failed to read client certificate authority: %v", err)
return nil, err
}
if !certpool.AppendCertsFromPEM(pem) {
err := errors.New("can't parse client certificate authority")
log.Fatalf(err.Error())
return nil, err
}
tlsConfig.ClientCAs = certpool
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
}
// Ignore passing "/" for BaseURL
opt.BaseURL = strings.Trim(opt.BaseURL, "/")
if opt.BaseURL != "" {
opt.BaseURL = "/" + opt.BaseURL
}
// Build base router
var router chi.Router = chi.NewRouter()
router.MethodNotAllowed(func(w http.ResponseWriter, _ *http.Request) {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
})
router.NotFound(func(w http.ResponseWriter, _ *http.Request) {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
})
handler := router.(http.Handler)
if opt.BaseURL != "" {
handler = http.StripPrefix(opt.BaseURL, handler)
}
// Serve on listeners
httpServer := &http.Server{
Handler: handler,
ReadTimeout: opt.ServerReadTimeout,
WriteTimeout: opt.ServerWriteTimeout,
MaxHeaderBytes: opt.MaxHeaderBytes,
ReadHeaderTimeout: 10 * time.Second, // time to send the headers
IdleTimeout: 60 * time.Second, // time to keep idle connections open
TLSConfig: tlsConfig,
}
addrs, tlsAddrs := make([]net.Addr, len(listeners)), make([]net.Addr, len(tlsListeners))
wg := &sync.WaitGroup{}
for i, l := range listeners {
addrs[i] = l.Addr()
}
if useSSL {
for i, l := range tlsListeners {
tlsAddrs[i] = l.Addr()
}
}
return &server{addrs, tlsAddrs, listeners, tlsListeners, httpServer, router, wg, useSSL}, nil
}
func (s *server) Serve() {
serve := func(l net.Listener, tls bool) {
defer s.closing.Done()
var err error
if tls {
err = s.httpServer.ServeTLS(l, "", "")
} else {
err = s.httpServer.Serve(l)
}
if err != http.ErrServerClosed && err != nil {
log.Fatalf(err.Error())
}
}
s.closing.Add(len(s.listeners))
for _, l := range s.listeners {
go serve(l, false)
}
if s.useSSL {
s.closing.Add(len(s.tlsListeners))
for _, l := range s.tlsListeners {
go serve(l, true)
}
}
}
// Wait blocks while the server is serving requests
func (s *server) Wait() {
s.closing.Wait()
}
// Router returns the server base router
func (s *server) Router() chi.Router {
return s.baseRouter
}
// Route mounts a sub-Router along a `pattern` string.
func (s *server) Route(pattern string, fn func(r chi.Router)) chi.Router {
return s.baseRouter.Route(pattern, fn)
}
// Mount attaches another http.Handler along ./pattern/*
func (s *server) Mount(pattern string, h http.Handler) {
s.baseRouter.Mount(pattern, h)
}
// Shutdown gracefully shuts down the server
func (s *server) Shutdown() error {
if err := s.httpServer.Shutdown(context.Background()); err != nil {
return err
}
s.closing.Wait()
return nil
}
//---- Default HTTP server convenience functions ----
// Router returns the server base router
func Router() (chi.Router, error) {
if err := start(); err != nil {
return nil, err
}
return defaultServer.baseRouter, nil
}
// Route mounts a sub-Router along a `pattern` string.
func Route(pattern string, fn func(r chi.Router)) (chi.Router, error) {
if err := start(); err != nil {
return nil, err
}
return defaultServer.Route(pattern, fn), nil
}
// Mount attaches another http.Handler along ./pattern/*
func Mount(pattern string, h http.Handler) error {
if err := start(); err != nil {
return err
}
defaultServer.Mount(pattern, h)
return nil
}
// Restart or start the default http server using the default options and no handlers
func Restart() error {
if e := Shutdown(); e != nil {
return e
}
return start()
}
// Wait blocks while the default http server is serving requests
func Wait() {
defaultServer.Wait()
}
// Start the default server
func start() error {
defaultServerMutex.Lock()
defer defaultServerMutex.Unlock()
if defaultServer != nil {
// Server already started, do nothing
return nil
}
var err error
var l net.Listener
l, err = net.Listen("tcp", defaultServerOptions.ListenAddr)
if err != nil {
return err
}
var s Server
if useSSL(defaultServerOptions) {
s, err = NewServer([]net.Listener{}, []net.Listener{l}, defaultServerOptions)
} else {
s, err = NewServer([]net.Listener{l}, []net.Listener{}, defaultServerOptions)
}
if err != nil {
return err
}
defaultServer = s.(*server)
defaultServer.Serve()
return nil
}
// Shutdown gracefully shuts down the default http server
func Shutdown() error {
defaultServerMutex.Lock()
defer defaultServerMutex.Unlock()
if defaultServer != nil {
s := defaultServer
defaultServer = nil
return s.Shutdown()
}
return nil
}
// GetOptions thread safe getter for the default server options
func GetOptions() Options {
defaultServerMutex.Lock()
defer defaultServerMutex.Unlock()
return defaultServerOptions
}
// SetOptions thread safe setter for the default server options
func SetOptions(opt Options) {
defaultServerMutex.Lock()
defer defaultServerMutex.Unlock()
defaultServerOptions = opt
}
//---- Utility functions ----
// URL of default http server
func URL() string {
if defaultServer == nil {
panic("Server not running")
}
for _, a := range defaultServer.addrs {
return fmt.Sprintf("http://%s%s/", a.String(), defaultServerOptions.BaseURL)
}
for _, a := range defaultServer.tlsAddrs {
return fmt.Sprintf("https://%s%s/", a.String(), defaultServerOptions.BaseURL)
}
panic("Server is running with no listener")
}
//---- Command line flags ----
// AddFlagsPrefix adds flags for the httplib
func AddFlagsPrefix(flagSet *pflag.FlagSet, prefix string, Opt *Options) {
flags.StringVarP(flagSet, &Opt.ListenAddr, prefix+"addr", "", Opt.ListenAddr, "IPaddress:Port or :Port to bind server to")
flags.DurationVarP(flagSet, &Opt.ServerReadTimeout, prefix+"server-read-timeout", "", Opt.ServerReadTimeout, "Timeout for server reading data")
flags.DurationVarP(flagSet, &Opt.ServerWriteTimeout, prefix+"server-write-timeout", "", Opt.ServerWriteTimeout, "Timeout for server writing data")
flags.IntVarP(flagSet, &Opt.MaxHeaderBytes, prefix+"max-header-bytes", "", Opt.MaxHeaderBytes, "Maximum size of request header")
flags.StringVarP(flagSet, &Opt.SslCert, prefix+"cert", "", Opt.SslCert, "SSL PEM key (concatenation of certificate and CA certificate)")
flags.StringVarP(flagSet, &Opt.SslKey, prefix+"key", "", Opt.SslKey, "SSL PEM Private key")
flags.StringVarP(flagSet, &Opt.ClientCA, prefix+"client-ca", "", Opt.ClientCA, "Client certificate authority to verify clients with")
flags.StringVarP(flagSet, &Opt.BaseURL, prefix+"baseurl", "", Opt.BaseURL, "Prefix for URLs - leave blank for root")
flags.StringVarP(flagSet, &Opt.MinTLSVersion, prefix+"min-tls-version", "", Opt.MinTLSVersion, "Minimum TLS version that is acceptable")
}
// AddFlags adds flags for the httplib
func AddFlags(flagSet *pflag.FlagSet) {
AddFlagsPrefix(flagSet, "", &defaultServerOptions)
}