FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Virtual hosts and SNI support · yourchanges/caddy@feec7c5 · GitHub

Commit feec7c5

Browse files
committed
Virtual hosts and SNI support
1 parent 07964a6 commit feec7c5

4 files changed

Lines changed: 222 additions & 96 deletions

File tree

‎config/config.go‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package config
44

55
import (
6+
"net"
67
"os"
78

89
"github.com/mholt/caddy/middleware"
@@ -12,13 +13,16 @@ const (
1213
defaultHost = "localhost"
1314
defaultPort = "8080"
1415
defaultRoot = "."
16+
17+
// The default configuration file to load if none is specified
18+
DefaultConfigFile = "Caddyfile"
1519
)
1620

1721
// config represents a server configuration. It
1822
// is populated by parsing a config file (via the
1923
// Load function).
2024
type Config struct {
21-
// The hostname or IP to which to bind the server
25+
// The hostname or IP on which to serve
2226
Host string
2327

2428
// The port to listen on
@@ -51,7 +55,7 @@ type Config struct {
5155

5256
// Address returns the host:port of c as a string.
5357
func (c Config) Address() string {
54-
return c.Host + ":" + c.Port
58+
return net.JoinHostPort(c.Host, c.Port)
5559
}
5660

5761
// TLSConfig describes how TLS should be configured and used,

‎main.go‎

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package main
22

33
import (
44
"flag"
5+
"fmt"
56
"log"
7+
"net"
68
"sync"
79

810
"github.com/mholt/caddy/config"
@@ -15,7 +17,7 @@ var (
1517
)
1618

1719
func init() {
18-
flag.StringVar(&conf, "conf", server.DefaultConfigFile, "the configuration file to use")
20+
flag.StringVar(&conf, "conf", config.DefaultConfigFile, "the configuration file to use")
1921
flag.BoolVar(&http2, "http2", true, "enable HTTP/2 support") // temporary flag until http2 merged into std lib
2022
}
2123

@@ -24,17 +26,25 @@ func main() {
2426

2527
flag.Parse()
2628

27-
vhosts, err := config.Load(conf)
29+
// Load config from file
30+
allConfigs, err := config.Load(conf)
2831
if err != nil {
2932
if config.IsNotFound(err) {
30-
vhosts = config.Default()
33+
allConfigs = config.Default()
3134
} else {
3235
log.Fatal(err)
3336
}
3437
}
3538

36-
for _, conf := range vhosts {
37-
s, err := server.New(conf)
39+
// Group by address (virtual hosting)
40+
addresses, err := arrangeBindings(allConfigs)
41+
if err != nil {
42+
log.Fatal(err)
43+
}
44+
45+
// Start each server with its one or more configurations
46+
for addr, configs := range addresses {
47+
s, err := server.New(addr, configs, configs[0].TLS.Enabled)
3848
if err != nil {
3949
log.Fatal(err)
4050
}
@@ -51,3 +61,41 @@ func main() {
5161

5262
wg.Wait()
5363
}
64+
65+
// arrangeBindings groups configurations by their bind address. For example,
66+
// a server that should listen on localhost and another on 127.0.0.1 will
67+
// be grouped into the same address: 127.0.0.1. It will return an error
68+
// if the address lookup fails or if a TLS listener is configured on the
69+
// same address as a plaintext HTTP listener.
70+
func arrangeBindings(allConfigs []config.Config) (map[string][]config.Config, error) {
71+
addresses := make(map[string][]config.Config)
72+
73+
// Group configs by bind address
74+
for _, conf := range allConfigs {
75+
addr, err := net.ResolveTCPAddr("tcp", conf.Address())
76+
if err != nil {
77+
return addresses, err
78+
}
79+
addresses[addr.String()] = append(addresses[addr.String()], conf)
80+
}
81+
82+
// Don't allow HTTP and HTTPS to be served on the same address
83+
for _, configs := range addresses {
84+
isTLS := configs[0].TLS.Enabled
85+
for _, config := range configs {
86+
if config.TLS.Enabled != isTLS {
87+
thisConfigProto, otherConfigProto := "HTTP", "HTTP"
88+
if config.TLS.Enabled {
89+
thisConfigProto = "HTTPS"
90+
}
91+
if configs[0].TLS.Enabled {
92+
otherConfigProto = "HTTPS"
93+
}
94+
return addresses, fmt.Errorf("Configuration error: Cannot multiplex %s (%s) and %s (%s) on same address",
95+
configs[0].Address(), otherConfigProto, config.Address(), thisConfigProto)
96+
}
97+
}
98+
}
99+
100+
return addresses, nil
101+
}

‎server/server.go‎

Lines changed: 121 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -4,83 +4,66 @@
44
package server
55

66
import (
7-
"errors"
7+
"crypto/tls"
88
"fmt"
99
"log"
10+
"net"
1011
"net/http"
1112
"os"
1213
"os/signal"
1314
"runtime"
1415

1516
"github.com/bradfitz/http2"
1617
"github.com/mholt/caddy/config"
17-
"github.com/mholt/caddy/middleware"
1818
)
1919

20-
// The default configuration file to load if none is specified
21-
const DefaultConfigFile = "Caddyfile"
22-
23-
// servers maintains a registry of running servers, keyed by address.
24-
var servers = make(map[string]*Server)
25-
2620
// Server represents an instance of a server, which serves
2721
// static content at a particular address (host and port).
2822
type Server struct {
29-
HTTP2 bool // temporary while http2 is not in std lib (TODO: remove flag when part of std lib)
30-
config config.Config
31-
fileServer middleware.Handler
32-
stack middleware.Handler
23+
HTTP2 bool // temporary while http2 is not in std lib (TODO: remove flag when part of std lib)
24+
address string
25+
tls bool
26+
vhosts map[string]virtualHost
3327
}
3428

35-
// New creates a new Server and registers it with the list
36-
// of servers created. Each server must have a unique host:port
37-
// combination. This function does not start serving.
38-
func New(conf config.Config) (*Server, error) {
39-
addr := conf.Address()
40-
41-
// Unique address check
42-
if _, exists := servers[addr]; exists {
43-
return nil, errors.New("Address " + addr + " is already in use")
29+
// New creates a new Server which will bind to addr and serve
30+
// the sites/hosts configured in configs. This function does
31+
// not start serving.
32+
func New(addr string, configs []config.Config, tls bool) (*Server, error) {
33+
s := &Server{
34+
address: addr,
35+
tls: tls,
36+
vhosts: make(map[string]virtualHost),
4437
}
4538

46-
// Use all CPUs (if needed) by default
47-
if conf.MaxCPU == 0 {
48-
conf.MaxCPU = runtime.NumCPU()
49-
}
39+
for _, conf := range configs {
40+
if _, exists := s.vhosts[conf.Host]; exists {
41+
return nil, fmt.Errorf("Cannot serve %s - host already defined for address %s", conf.Address(), s.address)
42+
}
5043

51-
// Initialize
52-
s := new(Server)
53-
s.config = conf
44+
// Use all CPUs (if needed) by default
45+
if conf.MaxCPU == 0 {
46+
conf.MaxCPU = runtime.NumCPU()
47+
}
5448

55-
// Register the server
56-
servers[addr] = s
49+
vh := virtualHost{config: conf}
5750

58-
return s, nil
59-
}
60-
61-
// Serve starts the server. It blocks until the server quits.
62-
func (s *Server) Serve() error {
63-
// Execute startup functions
64-
for _, start := range s.config.Startup {
65-
err := start()
51+
// Build middleware stack
52+
err := vh.buildStack()
6653
if err != nil {
67-
return err
54+
return nil, err
6855
}
69-
}
7056

71-
// Build middleware stack
72-
err := s.buildStack()
73-
if err != nil {
74-
return err
57+
s.vhosts[conf.Host] = vh
7558
}
7659

77-
// Use highest procs value across all configurations
78-
if s.config.MaxCPU > 0 && s.config.MaxCPU > runtime.GOMAXPROCS(0) {
79-
runtime.GOMAXPROCS(s.config.MaxCPU)
80-
}
60+
return s, nil
61+
}
8162

63+
// Serve starts the server. It blocks until the server quits.
64+
func (s *Server) Serve() error {
8265
server := &http.Server{
83-
Addr: s.config.Address(),
66+
Addr: s.address,
8467
Handler: s,
8568
}
8669

@@ -89,28 +72,91 @@ func (s *Server) Serve() error {
8972
http2.ConfigureServer(server, nil)
9073
}
9174

92-
// Execute shutdown commands on exit
93-
go func() {
94-
interrupt := make(chan os.Signal, 1)
95-
signal.Notify(interrupt, os.Interrupt, os.Kill) // TODO: syscall.SIGQUIT? (Ctrl+\, Unix-only)
96-
<-interrupt
97-
for _, shutdownFunc := range s.config.Shutdown {
98-
err := shutdownFunc()
75+
for _, vh := range s.vhosts {
76+
// Execute startup functions
77+
for _, start := range vh.config.Startup {
78+
err := start()
9979
if err != nil {
100-
log.Fatal(err)
80+
return err
10181
}
10282
}
103-
os.Exit(0)
104-
}()
10583

106-
if s.config.TLS.Enabled {
107-
return server.ListenAndServeTLS(s.config.TLS.Certificate, s.config.TLS.Key)
84+
// Use highest procs value across all configurations
85+
if vh.config.MaxCPU > 0 && vh.config.MaxCPU > runtime.GOMAXPROCS(0) {
86+
runtime.GOMAXPROCS(vh.config.MaxCPU)
87+
}
88+
89+
if len(vh.config.Shutdown) > 0 {
90+
// Execute shutdown commands on exit
91+
go func() {
92+
interrupt := make(chan os.Signal, 1)
93+
signal.Notify(interrupt, os.Interrupt, os.Kill) // TODO: syscall.SIGQUIT? (Ctrl+\, Unix-only)
94+
<-interrupt
95+
for _, shutdownFunc := range vh.config.Shutdown {
96+
err := shutdownFunc()
97+
if err != nil {
98+
log.Fatal(err)
99+
}
100+
}
101+
os.Exit(0)
102+
}()
103+
}
104+
}
105+
106+
if s.tls {
107+
var tlsConfigs []config.TLSConfig
108+
for _, vh := range s.vhosts {
109+
tlsConfigs = append(tlsConfigs, vh.config.TLS)
110+
}
111+
return ListenAndServeTLSWithSNI(server, tlsConfigs)
108112
} else {
109113
return server.ListenAndServe()
110114
}
111115
}
112116

113-
// ServeHTTP is the entry point for every request to s.
117+
// ListenAndServeTLSWithSNI serves TLS with Server Name Indication (SNI) support, which allows
118+
// multiple sites (different hostnames) to be served from the same address. This method is
119+
// adapted directly from the std lib's net/http ListenAndServeTLS function, which was
120+
// written by the Go Authors. It has been modified to support multiple certificate/key pairs.
121+
func ListenAndServeTLSWithSNI(srv *http.Server, tlsConfigs []config.TLSConfig) error {
122+
addr := srv.Addr
123+
if addr == "" {
124+
addr = ":https"
125+
}
126+
127+
config := new(tls.Config)
128+
if srv.TLSConfig != nil {
129+
*config = *srv.TLSConfig
130+
}
131+
if config.NextProtos == nil {
132+
config.NextProtos = []string{"http/1.1"}
133+
}
134+
135+
// Here we diverge from the stdlib a bit by loading multiple certs/key pairs
136+
// then we map the server names to their certs
137+
var err error
138+
config.Certificates = make([]tls.Certificate, len(tlsConfigs))
139+
for i, tlsConfig := range tlsConfigs {
140+
config.Certificates[i], err = tls.LoadX509KeyPair(tlsConfig.Certificate, tlsConfig.Key)
141+
if err != nil {
142+
return err
143+
}
144+
}
145+
config.BuildNameToCertificate()
146+
147+
conn, err := net.Listen("tcp", addr)
148+
if err != nil {
149+
return err
150+
}
151+
152+
tlsListener := tls.NewListener(conn, config)
153+
return srv.Serve(tlsListener)
154+
}
155+
156+
// ServeHTTP is the entry point for every request to the address that s
157+
// is bound to. It acts as a multiplexer for the requests hostname as
158+
// defined in the Host header so that the correct virtualhost
159+
// (configuration and middleware stack) will handle the request.
114160
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
115161
defer func() {
116162
// In case the user doesn't enable error middleware, we still
@@ -121,35 +167,21 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
121167
}
122168
}()
123169

124-
status, _ := s.stack.ServeHTTP(w, r)
125-
126-
// Fallback error response in case error handling wasn't chained in
127-
if status >= 400 {
128-
w.WriteHeader(status)
129-
fmt.Fprintf(w, "%d %s", status, http.StatusText(status))
170+
host, _, err := net.SplitHostPort(r.Host)
171+
if err != nil {
172+
host = r.Host // oh well
130173
}
131-
}
132-
133-
// buildStack builds the server's middleware stack based
134-
// on its config. This method should be called last before
135-
// ListenAndServe begins.
136-
func (s *Server) buildStack() error {
137-
s.fileServer = FileServer(http.Dir(s.config.Root), []string{s.config.ConfigFile})
138-
139-
// TODO: We only compile middleware for the "/" scope.
140-
// Partial support for multiple location contexts already
141-
// exists at the parser and config levels, but until full
142-
// support is implemented, this is all we do right here.
143-
s.compile(s.config.Middleware["/"])
144174

145-
return nil
146-
}
175+
if vh, ok := s.vhosts[host]; ok {
176+
status, _ := vh.stack.ServeHTTP(w, r)
147177

148-
// compile is an elegant alternative to nesting middleware function
149-
// calls like handler1(handler2(handler3(finalHandler))).
150-
func (s *Server) compile(layers []middleware.Middleware) {
151-
s.stack = s.fileServer // core app layer
152-
for i := len(layers) - 1; i >= 0; i-- {
153-
s.stack = layers[i](s.stack)
178+
// Fallback error response in case error handling wasn't chained in
179+
if status >= 400 {
180+
w.WriteHeader(status)
181+
fmt.Fprintf(w, "%d %s", status, http.StatusText(status))
182+
}
183+
} else {
184+
w.WriteHeader(http.StatusNotFound)
185+
fmt.Fprintf(w, "No such host at %s", s.address)
154186
}
155187
}

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL