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