File: http.go

package info (click to toggle)
rclone 1.60.1%2Bdfsg-4
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 34,832 kB
  • sloc: sh: 957; xml: 857; python: 655; javascript: 612; makefile: 269; ansic: 101; php: 74
file content (441 lines) | stat: -rw-r--r-- 13,467 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
// Package http provides a registration interface for http services
package http

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"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 := ioutil.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)
}