1/*
2Copyright 2011 The Perkeep Authors
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8     http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17// Package webserver implements a superset wrapper of http.Server.
18//
19// Among other things, it can throttle its connections, inherit its
20// listening socket from a file descriptor in the environment, and
21// log all activity.
22package webserver // import "perkeep.org/pkg/webserver"
23
24import (
25	"crypto/rand"
26	"crypto/tls"
27	"fmt"
28	"log"
29	"net"
30	"net/http"
31	"os"
32	"strconv"
33	"strings"
34	"sync"
35	"time"
36
37	"perkeep.org/pkg/webserver/listen"
38
39	"go4.org/net/throttle"
40	"go4.org/wkfs"
41	"golang.org/x/net/http2"
42)
43
44const alpnProto = "acme-tls/1" // from golang.org/x/crypto/acme.ALPNProto
45
46type Server struct {
47	mux      *http.ServeMux
48	listener net.Listener
49	verbose  bool // log HTTP requests and response codes
50
51	Logger *log.Logger // or nil.
52
53	// H2Server is the HTTP/2 server config.
54	H2Server http2.Server
55
56	// enableTLS sets the Server up for listening to HTTPS connections.
57	enableTLS bool
58	// tlsCertFile (tlsKeyFile) is the path to the HTTPS certificate (key) file.
59	tlsCertFile, tlsKeyFile string
60	// certManager is set as GetCertificate in the tls.Config of the listener. But tlsCertFile takes precedence.
61	certManager func(*tls.ClientHelloInfo) (*tls.Certificate, error)
62
63	mu   sync.Mutex
64	reqs int64
65}
66
67func New() *Server {
68	verbose, _ := strconv.ParseBool(os.Getenv("CAMLI_HTTP_DEBUG"))
69	return &Server{
70		mux:     http.NewServeMux(),
71		verbose: verbose,
72	}
73}
74
75func (s *Server) printf(format string, v ...interface{}) {
76	if s.Logger != nil {
77		s.Logger.Printf(format, v...)
78		return
79	}
80	log.Printf(format, v...)
81}
82
83func (s *Server) fatalf(format string, v ...interface{}) {
84	if s.Logger != nil {
85		s.Logger.Fatalf(format, v...)
86		return
87	}
88	log.Fatalf(format, v...)
89}
90
91// TLSSetup specifies how the server gets its TLS certificate.
92type TLSSetup struct {
93	// Certfile is the path to the TLS certificate file. It takes precedence over CertManager.
94	CertFile string
95	// KeyFile is the path to the TLS key file.
96	KeyFile string
97	// CertManager is the tls.GetCertificate of the tls Config. But CertFile takes precedence.
98	CertManager func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error)
99}
100
101func (s *Server) SetTLS(setup TLSSetup) {
102	s.enableTLS = true
103	s.certManager = setup.CertManager
104	s.tlsCertFile = setup.CertFile
105	s.tlsKeyFile = setup.KeyFile
106}
107
108func (s *Server) ListenURL() string {
109	if s.listener == nil {
110		return ""
111	}
112	taddr, ok := s.listener.Addr().(*net.TCPAddr)
113	if !ok {
114		return ""
115	}
116	scheme := "http"
117	if s.enableTLS {
118		scheme = "https"
119	}
120	if taddr.IP.IsUnspecified() {
121		return fmt.Sprintf("%s://localhost:%d", scheme, taddr.Port)
122	}
123	return fmt.Sprintf("%s://%s", scheme, s.listener.Addr())
124}
125
126func (s *Server) HandleFunc(pattern string, fn func(http.ResponseWriter, *http.Request)) {
127	s.mux.HandleFunc(pattern, fn)
128}
129
130func (s *Server) Handle(pattern string, handler http.Handler) {
131	s.mux.Handle(pattern, handler)
132}
133
134func (s *Server) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
135	var n int64
136	if s.verbose {
137		s.mu.Lock()
138		s.reqs++
139		n = s.reqs
140		s.mu.Unlock()
141		s.printf("Request #%d: %s %s (from %s) ...", n, req.Method, req.RequestURI, req.RemoteAddr)
142		rw = &trackResponseWriter{ResponseWriter: rw}
143	}
144	s.mux.ServeHTTP(rw, req)
145	if s.verbose {
146		tw := rw.(*trackResponseWriter)
147		s.printf("Request #%d: %s %s = code %d, %d bytes", n, req.Method, req.RequestURI, tw.code, tw.resSize)
148	}
149}
150
151type trackResponseWriter struct {
152	http.ResponseWriter
153	code    int
154	resSize int64
155}
156
157func (tw *trackResponseWriter) WriteHeader(code int) {
158	tw.code = code
159	tw.ResponseWriter.WriteHeader(code)
160}
161
162func (tw *trackResponseWriter) Write(p []byte) (int, error) {
163	if tw.code == 0 {
164		tw.code = 200
165	}
166	tw.resSize += int64(len(p))
167	return tw.ResponseWriter.Write(p)
168}
169
170// Listen starts listening on the given host:port addr.
171func (s *Server) Listen(addr string) error {
172	if s.listener != nil {
173		return nil
174	}
175
176	if addr == "" {
177		return fmt.Errorf("<host>:<port> needs to be provided to start listening")
178	}
179
180	var err error
181	s.listener, err = listen.Listen(addr)
182	if err != nil {
183		return fmt.Errorf("Failed to listen on %s: %v", addr, err)
184	}
185	base := s.ListenURL()
186	s.printf("Starting to listen on %s\n", base)
187
188	doEnableTLS := func() error {
189		config := &tls.Config{
190			Rand:       rand.Reader,
191			Time:       time.Now,
192			NextProtos: []string{http2.NextProtoTLS, "http/1.1"},
193			MinVersion: tls.VersionTLS12,
194		}
195		if s.tlsCertFile == "" && s.certManager != nil {
196			config.GetCertificate = s.certManager
197			// TODO(mpl): see if we can instead use
198			// https://godoc.org/golang.org/x/crypto/acme/autocert#Manager.TLSConfig
199			config.NextProtos = append(config.NextProtos, alpnProto)
200			s.listener = tls.NewListener(s.listener, config)
201			return nil
202		}
203
204		config.Certificates = make([]tls.Certificate, 1)
205		config.Certificates[0], err = loadX509KeyPair(s.tlsCertFile, s.tlsKeyFile)
206		if err != nil {
207			return fmt.Errorf("Failed to load TLS cert: %v", err)
208		}
209		s.listener = tls.NewListener(s.listener, config)
210		return nil
211	}
212	if s.enableTLS {
213		if err := doEnableTLS(); err != nil {
214			return err
215		}
216	}
217
218	if strings.HasSuffix(base, ":0") {
219		s.printf("Now listening on %s\n", s.ListenURL())
220	}
221
222	return nil
223}
224
225func (s *Server) throttleListener() net.Listener {
226	kBps, _ := strconv.Atoi(os.Getenv("DEV_THROTTLE_KBPS"))
227	ms, _ := strconv.Atoi(os.Getenv("DEV_THROTTLE_LATENCY_MS"))
228	if kBps == 0 && ms == 0 {
229		return s.listener
230	}
231	rate := throttle.Rate{
232		KBps:    kBps,
233		Latency: time.Duration(ms) * time.Millisecond,
234	}
235	return &throttle.Listener{
236		Listener: s.listener,
237		Down:     rate,
238		Up:       rate, // TODO: separate rates?
239	}
240}
241
242func (s *Server) Serve() {
243	if err := s.Listen(""); err != nil {
244		s.fatalf("Listen error: %v", err)
245	}
246	go runTestHarnessIntegration(s.listener)
247
248	srv := &http.Server{
249		Handler: s,
250	}
251	// TODO: allow configuring src.ErrorLog (and plumb through to
252	// Google Cloud Logging when run on GCE, eventually)
253
254	// Setup the NPN NextProto map for HTTP/2 support:
255	http2.ConfigureServer(srv, &s.H2Server)
256
257	err := srv.Serve(s.throttleListener())
258	if err != nil {
259		s.printf("Error in http server: %v\n", err)
260		os.Exit(1)
261	}
262}
263
264// Signals the test harness that we've started listening.
265// TODO: write back the port number that we randomly selected?
266// For now just writes back a single byte.
267func runTestHarnessIntegration(listener net.Listener) {
268	addr := os.Getenv("CAMLI_SET_BASE_URL_AND_SEND_ADDR_TO")
269	c, err := net.Dial("tcp", addr)
270	if err == nil {
271		fmt.Fprintf(c, "%s\n", listener.Addr())
272		c.Close()
273	}
274}
275
276// loadX509KeyPair is a copy of tls.LoadX509KeyPair but using wkfs.
277func loadX509KeyPair(certFile, keyFile string) (cert tls.Certificate, err error) {
278	certPEMBlock, err := wkfs.ReadFile(certFile)
279	if err != nil {
280		return
281	}
282	keyPEMBlock, err := wkfs.ReadFile(keyFile)
283	if err != nil {
284		return
285	}
286	return tls.X509KeyPair(certPEMBlock, keyPEMBlock)
287}
288