caddy/server/server.go
2015-06-15 10:17:09 -06:00

236 lines
6.4 KiB
Go

// Package server implements a configurable, general-purpose web server.
// It relies on configurations obtained from the adjacent config package
// and can execute middleware as defined by the adjacent middleware package.
package server
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"os/signal"
"github.com/bradfitz/http2"
)
// Server represents an instance of a server, which serves
// static content at a particular address (host and port).
type Server struct {
HTTP2 bool // temporary while http2 is not in std lib (TODO: remove flag when part of std lib)
address string // the actual address for net.Listen to listen on
tls bool // whether this server is serving all HTTPS hosts or not
vhosts map[string]virtualHost // virtual hosts keyed by their address
}
// New creates a new Server which will bind to addr and serve
// the sites/hosts configured in configs. This function does
// not start serving.
func New(addr string, configs []Config, tls bool) (*Server, error) {
s := &Server{
address: addr,
tls: tls,
vhosts: make(map[string]virtualHost),
}
for _, conf := range configs {
if _, exists := s.vhosts[conf.Host]; exists {
return nil, fmt.Errorf("cannot serve %s - host already defined for address %s", conf.Address(), s.address)
}
vh := virtualHost{config: conf}
// Build middleware stack
err := vh.buildStack()
if err != nil {
return nil, err
}
s.vhosts[conf.Host] = vh
}
return s, nil
}
// Serve starts the server. It blocks until the server quits.
func (s *Server) Serve() error {
server := &http.Server{
Addr: s.address,
Handler: s,
}
if s.HTTP2 {
// TODO: This call may not be necessary after HTTP/2 is merged into std lib
http2.ConfigureServer(server, nil)
}
for _, vh := range s.vhosts {
// Execute startup functions now
for _, start := range vh.config.Startup {
err := start()
if err != nil {
return err
}
}
// Execute shutdown commands on exit
if len(vh.config.Shutdown) > 0 {
go func() {
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt, os.Kill) // TODO: syscall.SIGQUIT? (Ctrl+\, Unix-only)
<-interrupt
for _, shutdownFunc := range vh.config.Shutdown {
err := shutdownFunc()
if err != nil {
log.Fatal(err)
}
}
os.Exit(0)
}()
}
}
if s.tls {
var tlsConfigs []TLSConfig
for _, vh := range s.vhosts {
tlsConfigs = append(tlsConfigs, vh.config.TLS)
}
return ListenAndServeTLSWithSNI(server, tlsConfigs)
}
return server.ListenAndServe()
}
// ListenAndServeTLSWithSNI serves TLS with Server Name Indication (SNI) support, which allows
// multiple sites (different hostnames) to be served from the same address. This method is
// adapted directly from the std lib's net/http ListenAndServeTLS function, which was
// written by the Go Authors. It has been modified to support multiple certificate/key pairs.
func ListenAndServeTLSWithSNI(srv *http.Server, tlsConfigs []TLSConfig) error {
addr := srv.Addr
if addr == "" {
addr = ":https"
}
config := new(tls.Config)
if srv.TLSConfig != nil {
*config = *srv.TLSConfig
}
if config.NextProtos == nil {
config.NextProtos = []string{"http/1.1"}
}
// Here we diverge from the stdlib a bit by loading multiple certs/key pairs
// then we map the server names to their certs
var err error
config.Certificates = make([]tls.Certificate, len(tlsConfigs))
for i, tlsConfig := range tlsConfigs {
config.Certificates[i], err = tls.LoadX509KeyPair(tlsConfig.Certificate, tlsConfig.Key)
if err != nil {
return err
}
}
config.BuildNameToCertificate()
// Customize our TLS configuration
config.MinVersion = tlsConfigs[0].ProtocolMinVersion
config.MaxVersion = tlsConfigs[0].ProtocolMaxVersion
config.CipherSuites = tlsConfigs[0].Ciphers
config.PreferServerCipherSuites = tlsConfigs[0].PreferServerCipherSuites
// TLS client authentication, if user enabled it
err = setupClientAuth(tlsConfigs, config)
if err != nil {
return err
}
// Create listener and we're on our way
conn, err := net.Listen("tcp", addr)
if err != nil {
return err
}
tlsListener := tls.NewListener(conn, config)
return srv.Serve(tlsListener)
}
// setupClientAuth sets up TLS client authentication only if
// any of the TLS configs specified at least one cert file.
func setupClientAuth(tlsConfigs []TLSConfig, config *tls.Config) error {
var clientAuth bool
for _, cfg := range tlsConfigs {
if len(cfg.ClientCerts) > 0 {
clientAuth = true
break
}
}
if clientAuth {
pool := x509.NewCertPool()
for _, cfg := range tlsConfigs {
for _, caFile := range cfg.ClientCerts {
caCrt, err := ioutil.ReadFile(caFile) // Anyone that gets a cert from Matt Holt can connect
if err != nil {
return err
}
if !pool.AppendCertsFromPEM(caCrt) {
return fmt.Errorf("error loading client certificate '%s': no certificates were successfully parsed", caFile)
}
}
}
config.ClientCAs = pool
config.ClientAuth = tls.RequireAndVerifyClientCert
}
return nil
}
// ServeHTTP is the entry point for every request to the address that s
// is bound to. It acts as a multiplexer for the requests hostname as
// defined in the Host header so that the correct virtualhost
// (configuration and middleware stack) will handle the request.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer func() {
// In case the user doesn't enable error middleware, we still
// need to make sure that we stay alive up here
if rec := recover(); rec != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError),
http.StatusInternalServerError)
}
}()
host, _, err := net.SplitHostPort(r.Host)
if err != nil {
host = r.Host // oh well
}
// Try the host as given, or try falling back to 0.0.0.0 (wildcard)
if _, ok := s.vhosts[host]; !ok {
if _, ok2 := s.vhosts["0.0.0.0"]; ok2 {
host = "0.0.0.0"
} else if _, ok2 := s.vhosts[""]; ok2 {
host = ""
}
}
if vh, ok := s.vhosts[host]; ok {
w.Header().Set("Server", "Caddy")
status, _ := vh.stack.ServeHTTP(w, r)
// Fallback error response in case error handling wasn't chained in
if status >= 400 {
DefaultErrorFunc(w, r, status)
}
} else {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "No such host at %s", s.address)
}
}
func DefaultErrorFunc(w http.ResponseWriter, r *http.Request, status int) {
w.WriteHeader(status)
fmt.Fprintf(w, "%d %s", status, http.StatusText(status))
}