caddy/server/server.go
2015-01-13 16:22:16 -07:00

179 lines
4.3 KiB
Go

package server
import (
"errors"
"log"
"net/http"
"os"
"github.com/mholt/caddy/config"
"github.com/mholt/caddy/middleware"
)
// servers maintains a registry of running servers.
var servers = make(map[string]*Server)
// Server represents an instance of a server, which serves
// static content at a particular address (host and port).
type Server struct {
config config.Config
reqlog *log.Logger
errlog *log.Logger
fileServer http.Handler
stack http.HandlerFunc
}
// New creates a new Server and registers it with the list
// of servers created. Each server must have a unique host:port
// combination. This function does not start serving.
func New(conf config.Config) (*Server, error) {
addr := conf.Address()
// Unique address check
if _, exists := servers[addr]; exists {
return nil, errors.New("Address " + addr + " is already in use")
}
// Initialize
s := new(Server)
s.config = conf
// Register the server
servers[addr] = s
return s, nil
}
// Serve starts the server. It blocks until the server quits.
func (s *Server) Serve() error {
err := s.configureStack()
if err != nil {
return err
}
if s.config.TLS.Enabled {
return http.ListenAndServeTLS(s.config.Address(), s.config.TLS.Certificate, s.config.TLS.Key, s)
} else {
return http.ListenAndServe(s.config.Address(), s)
}
}
// ServeHTTP is the entry point for each request to s.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.stack(w, r)
}
// Log writes a message to the server's configured error log,
// if there is one, or if there isn't, to the default stderr log.
func (s *Server) Log(v ...interface{}) {
if s.errlog != nil {
s.errlog.Println(v)
} else {
log.Println(v)
}
}
// configureStack builds the server's middleware stack based
// on its config. This method should be called last before
// ListenAndServe begins.
func (s *Server) configureStack() error {
var mid []middleware.Middleware
var err error
conf := s.config
// FileServer is the main application layer
s.fileServer = http.FileServer(http.Dir(conf.Root))
// push prepends each middleware to the stack so the
// compilation can iterate them in a natural, increasing order
push := func(m middleware.Middleware) {
mid = append(mid, nil)
copy(mid[1:], mid[0:])
mid[0] = m
}
// BEGIN ADDING MIDDLEWARE
// Middleware will be executed in the order they're added.
if conf.RequestLog.Enabled {
if conf.RequestLog.Enabled {
s.reqlog, err = enableLogging(conf.RequestLog)
if err != nil {
return err
}
}
push(middleware.RequestLog(s.reqlog, conf.RequestLog.Format))
}
if conf.ErrorLog.Enabled {
if conf.ErrorLog.Enabled {
s.errlog, err = enableLogging(conf.ErrorLog)
if err != nil {
return err
}
}
push(middleware.ErrorLog(s.errlog, conf.ErrorLog.Format))
}
if len(conf.Rewrites) > 0 {
push(middleware.Rewrite(conf.Rewrites))
}
if len(conf.Redirects) > 0 {
push(middleware.Redirect(conf.Redirects))
}
if len(conf.Extensions) > 0 {
push(middleware.Extensionless(conf.Root, conf.Extensions))
}
if len(conf.Headers) > 0 {
push(middleware.Headers(conf.Headers))
}
if conf.Gzip {
push(middleware.Gzip)
}
// END ADDING MIDDLEWARE
// Compiling the middleware unwraps each HandlerFunc,
// fully configured, ready to serve every request.
s.compile(mid)
return nil
}
// compile is an elegant alternative to nesting middleware generator
// function calls like handler1(handler2(handler3(finalHandler))).
func (s *Server) compile(layers []middleware.Middleware) {
s.stack = s.fileServer.ServeHTTP // core app layer
for _, layer := range layers {
s.stack = layer(s.stack)
}
}
// enableLogging opens a log file and keeps it open for the lifetime
// of the server. In fact, the log file is never closed as long as
// the program is running, since the server will be running for
// that long. If that ever changes, the log file should be closed.
func enableLogging(l config.Log) (*log.Logger, error) {
var file *os.File
var err error
if l.OutputFile == "stdout" {
file = os.Stdout
} else if l.OutputFile == "stderr" {
file = os.Stderr
} else {
file, err = os.OpenFile(l.OutputFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
return nil, err
}
}
return log.New(file, "", 0), nil
}
const DefaultConfigFile = "Caddyfile"