mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-04 03:27:23 -05:00 
			
		
		
		
	Early prototype; initial commit
This commit is contained in:
		
						commit
						4497a16fb0
					
				
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					.DS_Store
 | 
				
			||||||
 | 
					Thumbs.db
 | 
				
			||||||
 | 
					_gitignore/
 | 
				
			||||||
 | 
					error.log
 | 
				
			||||||
 | 
					access.log
 | 
				
			||||||
 | 
					/*.conf
 | 
				
			||||||
 | 
					Caddyfile
 | 
				
			||||||
							
								
								
									
										190
									
								
								config/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								config/config.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,190 @@
 | 
				
			|||||||
 | 
					// Package config contains utilities and types necessary for
 | 
				
			||||||
 | 
					// launching specially-configured server instances.
 | 
				
			||||||
 | 
					package config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Load loads a configuration file, parses it,
 | 
				
			||||||
 | 
					// and returns a slice of Config structs which
 | 
				
			||||||
 | 
					// can be used to create and configure server
 | 
				
			||||||
 | 
					// instances.
 | 
				
			||||||
 | 
					func Load(filename string) ([]Config, error) {
 | 
				
			||||||
 | 
						p := parser{}
 | 
				
			||||||
 | 
						err := p.lexer.Load(filename)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer p.lexer.Close()
 | 
				
			||||||
 | 
						return p.Parse()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsNotFound returns whether or not the error is
 | 
				
			||||||
 | 
					// one which indicates that the configuration file
 | 
				
			||||||
 | 
					// was not found. (Useful for checking the error
 | 
				
			||||||
 | 
					// returned from Load).
 | 
				
			||||||
 | 
					func IsNotFound(err error) bool {
 | 
				
			||||||
 | 
						return os.IsNotExist(err)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Default makes a default configuration
 | 
				
			||||||
 | 
					// that's empty except for root, host, and port,
 | 
				
			||||||
 | 
					// which are essential for serving the cwd.
 | 
				
			||||||
 | 
					func Default() []Config {
 | 
				
			||||||
 | 
						cfg := []Config{
 | 
				
			||||||
 | 
							Config{
 | 
				
			||||||
 | 
								Root: defaultRoot,
 | 
				
			||||||
 | 
								Host: defaultHost,
 | 
				
			||||||
 | 
								Port: defaultPort,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return cfg
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// config represents a server configuration. It
 | 
				
			||||||
 | 
					// is populated by parsing a config file. (Use
 | 
				
			||||||
 | 
					// the Load function.)
 | 
				
			||||||
 | 
					type Config struct {
 | 
				
			||||||
 | 
						Host       string
 | 
				
			||||||
 | 
						Port       string
 | 
				
			||||||
 | 
						Root       string
 | 
				
			||||||
 | 
						Gzip       bool
 | 
				
			||||||
 | 
						RequestLog Log
 | 
				
			||||||
 | 
						ErrorLog   Log
 | 
				
			||||||
 | 
						Rewrites   []Rewrite
 | 
				
			||||||
 | 
						Redirects  []Redirect
 | 
				
			||||||
 | 
						Extensions []string
 | 
				
			||||||
 | 
						ErrorPages map[int]string // Map of HTTP status code to filename
 | 
				
			||||||
 | 
						Headers    []Headers
 | 
				
			||||||
 | 
						TLS        TLSConfig
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Address returns the host:port of c as a string.
 | 
				
			||||||
 | 
					func (c Config) Address() string {
 | 
				
			||||||
 | 
						return c.Host + ":" + c.Port
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Rewrite describes an internal location rewrite.
 | 
				
			||||||
 | 
					type Rewrite struct {
 | 
				
			||||||
 | 
						From string
 | 
				
			||||||
 | 
						To   string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Redirect describes an HTTP redirect.
 | 
				
			||||||
 | 
					type Redirect struct {
 | 
				
			||||||
 | 
						From string
 | 
				
			||||||
 | 
						To   string
 | 
				
			||||||
 | 
						Code int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Log represents the settings for a log.
 | 
				
			||||||
 | 
					type Log struct {
 | 
				
			||||||
 | 
						Enabled    bool
 | 
				
			||||||
 | 
						OutputFile string
 | 
				
			||||||
 | 
						Format     string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Headers groups a slice of HTTP headers by a URL pattern.
 | 
				
			||||||
 | 
					type Headers struct {
 | 
				
			||||||
 | 
						Url     string
 | 
				
			||||||
 | 
						Headers []Header
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Header represents a single HTTP header, simply a name and value.
 | 
				
			||||||
 | 
					type Header struct {
 | 
				
			||||||
 | 
						Name  string
 | 
				
			||||||
 | 
						Value string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TLSConfig describes how TLS should be configured and used,
 | 
				
			||||||
 | 
					// if at all. At least a certificate and key are required.
 | 
				
			||||||
 | 
					type TLSConfig struct {
 | 
				
			||||||
 | 
						Enabled     bool
 | 
				
			||||||
 | 
						Certificate string
 | 
				
			||||||
 | 
						Key         string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// httpRedirs is a list of supported HTTP redirect codes.
 | 
				
			||||||
 | 
					var httpRedirs = map[string]int{
 | 
				
			||||||
 | 
						"300": 300,
 | 
				
			||||||
 | 
						"301": 301,
 | 
				
			||||||
 | 
						"302": 302,
 | 
				
			||||||
 | 
						"303": 303,
 | 
				
			||||||
 | 
						"304": 304,
 | 
				
			||||||
 | 
						"305": 305,
 | 
				
			||||||
 | 
						"306": 306,
 | 
				
			||||||
 | 
						"307": 307,
 | 
				
			||||||
 | 
						"308": 308,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// httpErrors is a list of supported HTTP error codes.
 | 
				
			||||||
 | 
					var httpErrors = map[string]int{
 | 
				
			||||||
 | 
						"400": 400,
 | 
				
			||||||
 | 
						"401": 401,
 | 
				
			||||||
 | 
						"402": 402,
 | 
				
			||||||
 | 
						"403": 403,
 | 
				
			||||||
 | 
						"404": 404,
 | 
				
			||||||
 | 
						"405": 405,
 | 
				
			||||||
 | 
						"406": 406,
 | 
				
			||||||
 | 
						"407": 407,
 | 
				
			||||||
 | 
						"408": 408,
 | 
				
			||||||
 | 
						"409": 409,
 | 
				
			||||||
 | 
						"410": 410,
 | 
				
			||||||
 | 
						"411": 411,
 | 
				
			||||||
 | 
						"412": 412,
 | 
				
			||||||
 | 
						"413": 413,
 | 
				
			||||||
 | 
						"414": 414,
 | 
				
			||||||
 | 
						"415": 415,
 | 
				
			||||||
 | 
						"416": 416,
 | 
				
			||||||
 | 
						"417": 417,
 | 
				
			||||||
 | 
						"418": 418,
 | 
				
			||||||
 | 
						"419": 419,
 | 
				
			||||||
 | 
						"420": 420,
 | 
				
			||||||
 | 
						"422": 422,
 | 
				
			||||||
 | 
						"423": 423,
 | 
				
			||||||
 | 
						"424": 424,
 | 
				
			||||||
 | 
						"426": 426,
 | 
				
			||||||
 | 
						"428": 428,
 | 
				
			||||||
 | 
						"429": 429,
 | 
				
			||||||
 | 
						"431": 431,
 | 
				
			||||||
 | 
						"440": 440,
 | 
				
			||||||
 | 
						"444": 444,
 | 
				
			||||||
 | 
						"449": 449,
 | 
				
			||||||
 | 
						"450": 450,
 | 
				
			||||||
 | 
						"451": 451,
 | 
				
			||||||
 | 
						"494": 494,
 | 
				
			||||||
 | 
						"495": 495,
 | 
				
			||||||
 | 
						"496": 496,
 | 
				
			||||||
 | 
						"497": 497,
 | 
				
			||||||
 | 
						"498": 498,
 | 
				
			||||||
 | 
						"499": 499,
 | 
				
			||||||
 | 
						"500": 500,
 | 
				
			||||||
 | 
						"501": 501,
 | 
				
			||||||
 | 
						"502": 502,
 | 
				
			||||||
 | 
						"503": 503,
 | 
				
			||||||
 | 
						"504": 504,
 | 
				
			||||||
 | 
						"505": 505,
 | 
				
			||||||
 | 
						"506": 506,
 | 
				
			||||||
 | 
						"507": 507,
 | 
				
			||||||
 | 
						"508": 508,
 | 
				
			||||||
 | 
						"509": 509,
 | 
				
			||||||
 | 
						"510": 510,
 | 
				
			||||||
 | 
						"511": 511,
 | 
				
			||||||
 | 
						"520": 520,
 | 
				
			||||||
 | 
						"521": 521,
 | 
				
			||||||
 | 
						"522": 522,
 | 
				
			||||||
 | 
						"523": 523,
 | 
				
			||||||
 | 
						"524": 524,
 | 
				
			||||||
 | 
						"598": 598,
 | 
				
			||||||
 | 
						"599": 599,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						defaultHost = "localhost"
 | 
				
			||||||
 | 
						defaultPort = "8080"
 | 
				
			||||||
 | 
						defaultRoot = "."
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						DefaultRequestsLog = "requests.log"
 | 
				
			||||||
 | 
						DefaultErrorsLog   = "errors.log"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										257
									
								
								config/directives.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										257
									
								
								config/directives.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,257 @@
 | 
				
			|||||||
 | 
					package config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// dirFunc is a type of parsing function which processes
 | 
				
			||||||
 | 
					// a particular directive and populates the config.
 | 
				
			||||||
 | 
					type dirFunc func(*parser) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// validDirectives is a map of valid directive names to
 | 
				
			||||||
 | 
					// their parsing function.
 | 
				
			||||||
 | 
					var validDirectives map[string]dirFunc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						// This has to be in the init function
 | 
				
			||||||
 | 
						// to avoid an initialization loop error because
 | 
				
			||||||
 | 
						// the 'import' directive (key) in this map
 | 
				
			||||||
 | 
						// invokes a method that uses this map.
 | 
				
			||||||
 | 
						validDirectives = map[string]dirFunc{
 | 
				
			||||||
 | 
							"root": func(p *parser) error {
 | 
				
			||||||
 | 
								if !p.lexer.NextArg() {
 | 
				
			||||||
 | 
									return p.argErr()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								p.cfg.Root = p.tkn()
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"import": func(p *parser) error {
 | 
				
			||||||
 | 
								if !p.lexer.NextArg() {
 | 
				
			||||||
 | 
									return p.argErr()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								p2 := parser{}
 | 
				
			||||||
 | 
								err := p2.lexer.Load(p.tkn())
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return p.err("Parse", err.Error())
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								defer p2.lexer.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								p2.cfg = p.cfg
 | 
				
			||||||
 | 
								err = p2.directives()
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								p.cfg = p2.cfg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"gzip": func(p *parser) error {
 | 
				
			||||||
 | 
								p.cfg.Gzip = true
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"log": func(p *parser) error {
 | 
				
			||||||
 | 
								log := Log{Enabled: true}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Get the type of log (requests, errors, etc.)
 | 
				
			||||||
 | 
								if !p.lexer.NextArg() {
 | 
				
			||||||
 | 
									return p.argErr()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								logWhat := p.tkn()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Set the log output file
 | 
				
			||||||
 | 
								if p.lexer.NextArg() {
 | 
				
			||||||
 | 
									log.OutputFile = p.tkn()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Set the log output format
 | 
				
			||||||
 | 
								if p.lexer.NextArg() {
 | 
				
			||||||
 | 
									log.Format = p.tkn()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								switch logWhat {
 | 
				
			||||||
 | 
								case "requests":
 | 
				
			||||||
 | 
									if log.OutputFile == "" || log.OutputFile == "_" {
 | 
				
			||||||
 | 
										log.OutputFile = DefaultRequestsLog
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									p.cfg.RequestLog = log
 | 
				
			||||||
 | 
								case "errors":
 | 
				
			||||||
 | 
									if log.OutputFile == "" || log.OutputFile == "_" {
 | 
				
			||||||
 | 
										log.OutputFile = DefaultErrorsLog
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									p.cfg.ErrorLog = log
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
									return p.err("Parse", "Unknown log '"+logWhat+"'")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"rewrite": func(p *parser) error {
 | 
				
			||||||
 | 
								var rw Rewrite
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if !p.lexer.NextArg() {
 | 
				
			||||||
 | 
									return p.argErr()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								rw.From = p.tkn()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if !p.lexer.NextArg() {
 | 
				
			||||||
 | 
									return p.argErr()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								rw.To = p.tkn()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								p.cfg.Rewrites = append(p.cfg.Rewrites, rw)
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"redir": func(p *parser) error {
 | 
				
			||||||
 | 
								var redir Redirect
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// From
 | 
				
			||||||
 | 
								if !p.lexer.NextArg() {
 | 
				
			||||||
 | 
									return p.argErr()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								redir.From = p.tkn()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// To
 | 
				
			||||||
 | 
								if !p.lexer.NextArg() {
 | 
				
			||||||
 | 
									return p.argErr()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								redir.To = p.tkn()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Status Code
 | 
				
			||||||
 | 
								if !p.lexer.NextArg() {
 | 
				
			||||||
 | 
									return p.argErr()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if code, ok := httpRedirs[p.tkn()]; !ok {
 | 
				
			||||||
 | 
									return p.err("Parse", "Invalid redirect code '"+p.tkn()+"'")
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									redir.Code = code
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								p.cfg.Redirects = append(p.cfg.Redirects, redir)
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"ext": func(p *parser) error {
 | 
				
			||||||
 | 
								if !p.lexer.NextArg() {
 | 
				
			||||||
 | 
									return p.argErr()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								p.cfg.Extensions = append(p.cfg.Extensions, p.tkn())
 | 
				
			||||||
 | 
								for p.lexer.NextArg() {
 | 
				
			||||||
 | 
									p.cfg.Extensions = append(p.cfg.Extensions, p.tkn())
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"error": func(p *parser) error {
 | 
				
			||||||
 | 
								if !p.lexer.NextArg() {
 | 
				
			||||||
 | 
									return p.argErr()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if code, ok := httpErrors[p.tkn()]; !ok {
 | 
				
			||||||
 | 
									return p.err("Syntax", "Invalid error code '"+p.tkn()+"'")
 | 
				
			||||||
 | 
								} else if val, exists := p.cfg.ErrorPages[code]; exists {
 | 
				
			||||||
 | 
									return p.err("Config", p.tkn()+" error page already configured to be '"+val+"'")
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									if !p.lexer.NextArg() {
 | 
				
			||||||
 | 
										return p.argErr()
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									p.cfg.ErrorPages[code] = p.tkn()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"header": func(p *parser) error {
 | 
				
			||||||
 | 
								var head Headers
 | 
				
			||||||
 | 
								var isNewPattern bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if !p.lexer.NextArg() {
 | 
				
			||||||
 | 
									return p.argErr()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								pattern := p.tkn()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// See if we already have a definition for this URL pattern...
 | 
				
			||||||
 | 
								for _, h := range p.cfg.Headers {
 | 
				
			||||||
 | 
									if h.Url == pattern {
 | 
				
			||||||
 | 
										head = h
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// ...otherwise, this is a new pattern
 | 
				
			||||||
 | 
								if head.Url == "" {
 | 
				
			||||||
 | 
									head.Url = pattern
 | 
				
			||||||
 | 
									isNewPattern = true
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								processHeaderBlock := func() error {
 | 
				
			||||||
 | 
									err := p.openCurlyBrace()
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									for p.lexer.Next() {
 | 
				
			||||||
 | 
										if p.tkn() == "}" {
 | 
				
			||||||
 | 
											break
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										h := Header{Name: p.tkn()}
 | 
				
			||||||
 | 
										if p.lexer.NextArg() {
 | 
				
			||||||
 | 
											h.Value = p.tkn()
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										head.Headers = append(head.Headers, h)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									err = p.closeCurlyBrace()
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// A single header could be declared on the same line, or
 | 
				
			||||||
 | 
								// multiple headers can be grouped by URL pattern, so we have
 | 
				
			||||||
 | 
								// to look for both here.
 | 
				
			||||||
 | 
								if p.lexer.NextArg() {
 | 
				
			||||||
 | 
									if p.tkn() == "{" {
 | 
				
			||||||
 | 
										err := processHeaderBlock()
 | 
				
			||||||
 | 
										if err != nil {
 | 
				
			||||||
 | 
											return err
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										h := Header{Name: p.tkn()}
 | 
				
			||||||
 | 
										if p.lexer.NextArg() {
 | 
				
			||||||
 | 
											h.Value = p.tkn()
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										head.Headers = append(head.Headers, h)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									// Okay, it might be an opening curly brace on the next line
 | 
				
			||||||
 | 
									if !p.lexer.Next() {
 | 
				
			||||||
 | 
										return p.eofErr()
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									err := processHeaderBlock()
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if isNewPattern {
 | 
				
			||||||
 | 
									p.cfg.Headers = append(p.cfg.Headers, head)
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									for i := 0; i < len(p.cfg.Headers); i++ {
 | 
				
			||||||
 | 
										if p.cfg.Headers[i].Url == pattern {
 | 
				
			||||||
 | 
											p.cfg.Headers[i] = head
 | 
				
			||||||
 | 
											break
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"tls": func(p *parser) error {
 | 
				
			||||||
 | 
								tls := TLSConfig{Enabled: true}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if !p.lexer.NextArg() {
 | 
				
			||||||
 | 
									return p.argErr()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								tls.Certificate = p.tkn()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if !p.lexer.NextArg() {
 | 
				
			||||||
 | 
									return p.argErr()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								tls.Key = p.tkn()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								p.cfg.TLS = tls
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										146
									
								
								config/lexer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								config/lexer.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,146 @@
 | 
				
			|||||||
 | 
					package config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bufio"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"unicode"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Lexer is a utility which can get values, token by
 | 
				
			||||||
 | 
					// token, from a config file. A token is a word, and tokens
 | 
				
			||||||
 | 
					// are separated by whitespace. A word can be enclosed in
 | 
				
			||||||
 | 
					// quotes if it contains whitespace.
 | 
				
			||||||
 | 
					type lexer struct {
 | 
				
			||||||
 | 
						file   *os.File
 | 
				
			||||||
 | 
						reader *bufio.Reader
 | 
				
			||||||
 | 
						token  token
 | 
				
			||||||
 | 
						line   int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Load opens a file and prepares to scan the file.
 | 
				
			||||||
 | 
					func (l *lexer) Load(filename string) error {
 | 
				
			||||||
 | 
						f, err := os.Open(filename)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						l.reader = bufio.NewReader(f)
 | 
				
			||||||
 | 
						l.file = f
 | 
				
			||||||
 | 
						l.line = 1
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Close closes the file.
 | 
				
			||||||
 | 
					func (l *lexer) Close() {
 | 
				
			||||||
 | 
						l.file.Close()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Next gets the next token from the input. The resulting token
 | 
				
			||||||
 | 
					// is in l.token if next returns true. If Next returns false,
 | 
				
			||||||
 | 
					// there are no more tokens.
 | 
				
			||||||
 | 
					func (l *lexer) Next() bool {
 | 
				
			||||||
 | 
						return l.next(true)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NextArg works just like Next, but returns false if the next
 | 
				
			||||||
 | 
					// token is not on the same line as the one before. This method
 | 
				
			||||||
 | 
					// makes it easier to throw syntax errors when more values are
 | 
				
			||||||
 | 
					// expected on the same line.
 | 
				
			||||||
 | 
					func (l *lexer) NextArg() bool {
 | 
				
			||||||
 | 
						return l.next(false)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// next gets the next token according to newlineOK, which
 | 
				
			||||||
 | 
					// specifies whether it's OK if the next token is on another
 | 
				
			||||||
 | 
					// line. Returns true if there was a new token loaded, false
 | 
				
			||||||
 | 
					// otherwise.
 | 
				
			||||||
 | 
					func (l *lexer) next(newlineOK bool) bool {
 | 
				
			||||||
 | 
						var val []rune
 | 
				
			||||||
 | 
						var comment, quoted, escaped bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						makeToken := func() bool {
 | 
				
			||||||
 | 
							l.token.text = string(val)
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							ch, _, err := l.reader.ReadRune()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								if len(val) > 0 {
 | 
				
			||||||
 | 
									return makeToken()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if err == io.EOF {
 | 
				
			||||||
 | 
									return false
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									panic(err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if quoted {
 | 
				
			||||||
 | 
								if !escaped {
 | 
				
			||||||
 | 
									if ch == '\\' {
 | 
				
			||||||
 | 
										escaped = true
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									} else if ch == '"' {
 | 
				
			||||||
 | 
										quoted = false
 | 
				
			||||||
 | 
										return makeToken()
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if ch == '\\' && !escaped {
 | 
				
			||||||
 | 
									escaped = true
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if ch == '\n' {
 | 
				
			||||||
 | 
									l.line++
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								val = append(val, ch)
 | 
				
			||||||
 | 
								escaped = false
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if unicode.IsSpace(ch) {
 | 
				
			||||||
 | 
								if ch == '\n' {
 | 
				
			||||||
 | 
									l.line++
 | 
				
			||||||
 | 
									comment = false
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if len(val) > 0 {
 | 
				
			||||||
 | 
									return makeToken()
 | 
				
			||||||
 | 
								} else if !newlineOK {
 | 
				
			||||||
 | 
									err := l.reader.UnreadRune()
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										panic(err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if ch == '\n' {
 | 
				
			||||||
 | 
										l.line--
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return false
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if ch == '#' {
 | 
				
			||||||
 | 
								comment = true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if comment {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(val) == 0 {
 | 
				
			||||||
 | 
								l.token = token{line: l.line}
 | 
				
			||||||
 | 
								if ch == '"' {
 | 
				
			||||||
 | 
									quoted = true
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							val = append(val, ch)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// A token represents a single valuable/processable unit
 | 
				
			||||||
 | 
					// in a config file.
 | 
				
			||||||
 | 
					type token struct {
 | 
				
			||||||
 | 
						line int
 | 
				
			||||||
 | 
						text string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										84
									
								
								config/parser.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								config/parser.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,84 @@
 | 
				
			|||||||
 | 
					package config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// parser is a type which can parse config files.
 | 
				
			||||||
 | 
					type parser struct {
 | 
				
			||||||
 | 
						lexer lexer
 | 
				
			||||||
 | 
						cfg   Config
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Parse parses the configuration file. It produces a slice of Config
 | 
				
			||||||
 | 
					// structs which can be used to create and configure server instances.
 | 
				
			||||||
 | 
					func (p *parser) Parse() ([]Config, error) {
 | 
				
			||||||
 | 
						var configs []Config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for p.lexer.Next() {
 | 
				
			||||||
 | 
							p.cfg = Config{ErrorPages: make(map[int]string)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							err := p.parse()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return configs, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							configs = append(configs, p.cfg)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return configs, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// tkn is shorthand to get the text/value of the current token.
 | 
				
			||||||
 | 
					func (p *parser) tkn() string {
 | 
				
			||||||
 | 
						return p.lexer.token.text
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// line is shorthand to get the line number of the current token.
 | 
				
			||||||
 | 
					func (p *parser) line() int {
 | 
				
			||||||
 | 
						return p.lexer.token.line
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// syntaxErr creates a syntax error which explains what was
 | 
				
			||||||
 | 
					// found and expected.
 | 
				
			||||||
 | 
					func (p *parser) syntaxErr(expected string) error {
 | 
				
			||||||
 | 
						return p.err("Syntax", fmt.Sprintf("Unexpected token '%s', expecting '%s'", p.tkn(), expected))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// syntaxErr creates a syntax error that explains that there
 | 
				
			||||||
 | 
					// weren't enough arguments on the line.
 | 
				
			||||||
 | 
					func (p *parser) argErr() error {
 | 
				
			||||||
 | 
						return p.err("Syntax", "Unexpected line break after '"+p.tkn()+"' (missing arguments?)")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eofErr creates a syntax error describing an unexpected EOF.
 | 
				
			||||||
 | 
					func (p *parser) eofErr() error {
 | 
				
			||||||
 | 
						return p.err("Syntax", "Unexpected EOF")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// err creates a "{{kind}} error: ..." with a custom message msg. The
 | 
				
			||||||
 | 
					// file name and line number are included in the error message.
 | 
				
			||||||
 | 
					func (p *parser) err(kind, msg string) error {
 | 
				
			||||||
 | 
						msg = fmt.Sprintf("%s error: %s:%d - %s", kind, p.lexer.file.Name(), p.line(), msg)
 | 
				
			||||||
 | 
						return errors.New(msg)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// parseAddress takes a host:port string (val), and returns the host
 | 
				
			||||||
 | 
					// and port as separate values. If either value that is missing, the
 | 
				
			||||||
 | 
					// default will be used.4
 | 
				
			||||||
 | 
					func (p *parser) parseAddress(val string) (string, string) {
 | 
				
			||||||
 | 
						if val == "" {
 | 
				
			||||||
 | 
							return defaultHost, defaultPort
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						parts := strings.SplitN(val, ":", 3)
 | 
				
			||||||
 | 
						if parts[0] == "" {
 | 
				
			||||||
 | 
							parts[0] = defaultHost
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(parts) == 1 || parts[1] == "" {
 | 
				
			||||||
 | 
							return parts[0], defaultPort
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return parts[0], parts[1]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										90
									
								
								config/parsing.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								config/parsing.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					package config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// This file contains the recursive-descent parsing
 | 
				
			||||||
 | 
					// functions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// parse is the top of the recursive-descent parsing.
 | 
				
			||||||
 | 
					// It parses at most 1 server configuration (an address
 | 
				
			||||||
 | 
					// and its directives).
 | 
				
			||||||
 | 
					func (p *parser) parse() error {
 | 
				
			||||||
 | 
						err := p.address()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = p.addressBlock()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// address expects that the current token is a host:port
 | 
				
			||||||
 | 
					// combination.
 | 
				
			||||||
 | 
					func (p *parser) address() error {
 | 
				
			||||||
 | 
						p.cfg.Host, p.cfg.Port = p.parseAddress(p.tkn())
 | 
				
			||||||
 | 
						p.lexer.Next()
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// addressBlock leads into parsing directives. It
 | 
				
			||||||
 | 
					// handles directives enclosed by curly braces and
 | 
				
			||||||
 | 
					// directives not enclosed by curly braces.
 | 
				
			||||||
 | 
					func (p *parser) addressBlock() error {
 | 
				
			||||||
 | 
						err := p.openCurlyBrace()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							// meh, single-server configs don't need curly braces
 | 
				
			||||||
 | 
							return p.directives()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = p.directives()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = p.closeCurlyBrace()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// openCurlyBrace expects the current token to be an
 | 
				
			||||||
 | 
					// opening curly brace.
 | 
				
			||||||
 | 
					func (p *parser) openCurlyBrace() error {
 | 
				
			||||||
 | 
						if p.tkn() != "{" {
 | 
				
			||||||
 | 
							return p.syntaxErr("{")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// closeCurlyBrace expects the current token to be
 | 
				
			||||||
 | 
					// a closing curly brace.
 | 
				
			||||||
 | 
					func (p *parser) closeCurlyBrace() error {
 | 
				
			||||||
 | 
						if p.tkn() != "}" {
 | 
				
			||||||
 | 
							return p.syntaxErr("}")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// directives parses through all the directives
 | 
				
			||||||
 | 
					// and it expects the current token to be the first
 | 
				
			||||||
 | 
					// directive. It goes until EOF or closing curly
 | 
				
			||||||
 | 
					// brace.
 | 
				
			||||||
 | 
					func (p *parser) directives() error {
 | 
				
			||||||
 | 
						for p.lexer.Next() {
 | 
				
			||||||
 | 
							if p.tkn() == "}" {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if fn, ok := validDirectives[p.tkn()]; !ok {
 | 
				
			||||||
 | 
								return p.syntaxErr("[directive]")
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								err := fn(p)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										39
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/mholt/caddy/config"
 | 
				
			||||||
 | 
						"github.com/mholt/caddy/server"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						var wg sync.WaitGroup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						vhosts, err := config.Load("Caddyfile")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if config.IsNotFound(err) {
 | 
				
			||||||
 | 
								vhosts = config.Default()
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								log.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, conf := range vhosts {
 | 
				
			||||||
 | 
							s, err := server.New(conf)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Fatal(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							wg.Add(1)
 | 
				
			||||||
 | 
							go func(s *server.Server) {
 | 
				
			||||||
 | 
								defer wg.Done()
 | 
				
			||||||
 | 
								err := s.Serve()
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									s.Log(err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}(s)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						wg.Wait()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										44
									
								
								middleware/extensionless.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								middleware/extensionless.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					package middleware
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Extensionless is middleware for clean URLs. A root path is
 | 
				
			||||||
 | 
					// passed in as well as possible extensions to add, internally,
 | 
				
			||||||
 | 
					// to paths requested. The first path+ext that matches a resource
 | 
				
			||||||
 | 
					// that exists will be used.
 | 
				
			||||||
 | 
					func Extensionless(root string, extensions []string) Middleware {
 | 
				
			||||||
 | 
						resourceExists := func(path string) bool {
 | 
				
			||||||
 | 
							_, err := os.Stat(root + path)
 | 
				
			||||||
 | 
							// technically we should use os.IsNotExist(err)
 | 
				
			||||||
 | 
							// but we don't handle any other error types anyway
 | 
				
			||||||
 | 
							return err == nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hasExt := func(r *http.Request) bool {
 | 
				
			||||||
 | 
							if r.URL.Path[len(r.URL.Path)-1] == '/' {
 | 
				
			||||||
 | 
								// directory
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							lastSep := strings.LastIndex(r.URL.Path, "/")
 | 
				
			||||||
 | 
							lastDot := strings.LastIndex(r.URL.Path, ".")
 | 
				
			||||||
 | 
							return lastDot > lastSep
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return func(next http.HandlerFunc) http.HandlerFunc {
 | 
				
			||||||
 | 
							return func(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
								if !hasExt(r) {
 | 
				
			||||||
 | 
									for _, ext := range extensions {
 | 
				
			||||||
 | 
										if resourceExists(r.URL.Path + ext) {
 | 
				
			||||||
 | 
											r.URL.Path = r.URL.Path + ext
 | 
				
			||||||
 | 
											break
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								next(w, r)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										40
									
								
								middleware/gzip.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								middleware/gzip.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					package middleware
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"compress/gzip"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Adapted from https://gist.github.com/the42/1956518
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Gzip is middleware that gzip-compresses the response.
 | 
				
			||||||
 | 
					func Gzip(next http.HandlerFunc) http.HandlerFunc {
 | 
				
			||||||
 | 
						return func(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
							if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
 | 
				
			||||||
 | 
								next(w, r)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							w.Header().Set("Content-Encoding", "gzip")
 | 
				
			||||||
 | 
							gzipWriter := gzip.NewWriter(w)
 | 
				
			||||||
 | 
							defer gzipWriter.Close()
 | 
				
			||||||
 | 
							gz := gzipResponseWriter{Writer: gzipWriter, ResponseWriter: w}
 | 
				
			||||||
 | 
							next(gz, r)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// gzipResponeWriter wraps the underlying Write method
 | 
				
			||||||
 | 
					// with a gzip.Writer to compress the output.
 | 
				
			||||||
 | 
					type gzipResponseWriter struct {
 | 
				
			||||||
 | 
						io.Writer
 | 
				
			||||||
 | 
						http.ResponseWriter
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Write wraps the underlying Write method to do compression.
 | 
				
			||||||
 | 
					func (w gzipResponseWriter) Write(b []byte) (int, error) {
 | 
				
			||||||
 | 
						if w.Header().Get("Content-Type") == "" {
 | 
				
			||||||
 | 
							w.Header().Set("Content-Type", http.DetectContentType(b))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return w.Writer.Write(b)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										35
									
								
								middleware/headers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								middleware/headers.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					package middleware
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/mholt/caddy/config"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Headers is middleware that adds headers to the responses
 | 
				
			||||||
 | 
					// for requests matching a certain path.
 | 
				
			||||||
 | 
					func Headers(headers []config.Headers) Middleware {
 | 
				
			||||||
 | 
						return func(next http.HandlerFunc) http.HandlerFunc {
 | 
				
			||||||
 | 
							return func(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
								for _, rule := range headers {
 | 
				
			||||||
 | 
									if pathsMatch(r.URL.Path, rule.Url) {
 | 
				
			||||||
 | 
										for _, header := range rule.Headers {
 | 
				
			||||||
 | 
											w.Header().Set(header.Name, header.Value)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								next(w, r)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Returns whether or not p1 and p2 are matching
 | 
				
			||||||
 | 
					// paths. This can be defined a number of ways
 | 
				
			||||||
 | 
					// and it is not for sure yet how to match URL/path
 | 
				
			||||||
 | 
					// strings. It may be a prefix match or a full
 | 
				
			||||||
 | 
					// string match, it may strip trailing slashes.
 | 
				
			||||||
 | 
					// Until the software hits 1.0, this will be in flux.
 | 
				
			||||||
 | 
					func pathsMatch(p1, p2 string) bool {
 | 
				
			||||||
 | 
						return strings.HasPrefix(p1, p2)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										42
									
								
								middleware/log.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								middleware/log.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					package middleware
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func RequestLog(logger *log.Logger, format string) Middleware {
 | 
				
			||||||
 | 
						if format == "" {
 | 
				
			||||||
 | 
							format = defaultReqLogFormat
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return func(next http.HandlerFunc) http.HandlerFunc {
 | 
				
			||||||
 | 
							return func(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
								sw := newResponseRecorder(w)
 | 
				
			||||||
 | 
								next(sw, r)
 | 
				
			||||||
 | 
								rep := newReplacer(r, sw)
 | 
				
			||||||
 | 
								logger.Println(rep.replace(format))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO.
 | 
				
			||||||
 | 
					func ErrorLog(logger *log.Logger, format string) Middleware {
 | 
				
			||||||
 | 
						if format == "" {
 | 
				
			||||||
 | 
							format = defaultErrLogFormat
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return func(next http.HandlerFunc) http.HandlerFunc {
 | 
				
			||||||
 | 
							return func(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
								sw := newResponseRecorder(w)
 | 
				
			||||||
 | 
								next(sw, r)
 | 
				
			||||||
 | 
								// This is still TODO -- we need to define what constitutes an error to be logged
 | 
				
			||||||
 | 
								//logger.Println("TODO")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						commonLogFormat     = `{remote} ` + emptyStringReplacer + ` [{time}] "{method} {uri} {proto}" {status} {size}`
 | 
				
			||||||
 | 
						combinedLogFormat   = commonLogFormat + ` "{>Referer}" "{>User-Agent}"`
 | 
				
			||||||
 | 
						defaultReqLogFormat = commonLogFormat
 | 
				
			||||||
 | 
						defaultErrLogFormat = "[TODO]"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										11
									
								
								middleware/middleware.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								middleware/middleware.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					// Package middleware includes a variety of middleware for
 | 
				
			||||||
 | 
					// the servers to use, according to their configuration.
 | 
				
			||||||
 | 
					package middleware
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Middleware is a type of function that generates a new
 | 
				
			||||||
 | 
					// layer of middleware. It is imperative that the HandlerFunc
 | 
				
			||||||
 | 
					// being passed in is executed by the middleware, otherwise
 | 
				
			||||||
 | 
					// part of the stack will not be called.
 | 
				
			||||||
 | 
					type Middleware func(http.HandlerFunc) http.HandlerFunc
 | 
				
			||||||
							
								
								
									
										23
									
								
								middleware/redirect.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								middleware/redirect.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					package middleware
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/mholt/caddy/config"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Redirect is middleware for redirecting certain requests
 | 
				
			||||||
 | 
					// to other locations.
 | 
				
			||||||
 | 
					func Redirect(redirs []config.Redirect) Middleware {
 | 
				
			||||||
 | 
						return func(next http.HandlerFunc) http.HandlerFunc {
 | 
				
			||||||
 | 
							return func(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
								for _, rule := range redirs {
 | 
				
			||||||
 | 
									if r.URL.Path == rule.From {
 | 
				
			||||||
 | 
										http.Redirect(w, r, rule.To, rule.Code)
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								next(w, r)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								middleware/rewrite.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								middleware/rewrite.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					package middleware
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/mholt/caddy/config"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Rewrite is middleware for rewriting requests internally to
 | 
				
			||||||
 | 
					// a different path.
 | 
				
			||||||
 | 
					func Rewrite(rewrites []config.Rewrite) Middleware {
 | 
				
			||||||
 | 
						return func(next http.HandlerFunc) http.HandlerFunc {
 | 
				
			||||||
 | 
							return func(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
								for _, rule := range rewrites {
 | 
				
			||||||
 | 
									if r.URL.Path == rule.From {
 | 
				
			||||||
 | 
										r.URL.Path = rule.To
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								next(w, r)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										45
									
								
								middleware/util_recorder.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								middleware/util_recorder.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					package middleware
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// responseRecorder is a type of ResponseWriter that captures
 | 
				
			||||||
 | 
					// the status code written to it and also the size of the body
 | 
				
			||||||
 | 
					// written in the response. A status code does not have
 | 
				
			||||||
 | 
					// to be written, however, in which case 200 must be assumed.
 | 
				
			||||||
 | 
					// It is best to have the constructor initialize this type
 | 
				
			||||||
 | 
					// with that default status code.
 | 
				
			||||||
 | 
					type responseRecorder struct {
 | 
				
			||||||
 | 
						http.ResponseWriter
 | 
				
			||||||
 | 
						status int
 | 
				
			||||||
 | 
						size   int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// newResponseRecorder makes and returns a new responseRecorder,
 | 
				
			||||||
 | 
					// which captures the HTTP Status code from the ResponseWriter
 | 
				
			||||||
 | 
					// and also the length of the response body written through it.
 | 
				
			||||||
 | 
					// Because a status is not set unless WriteHeader is called
 | 
				
			||||||
 | 
					// explicitly, this constructor initializes with a status code
 | 
				
			||||||
 | 
					// of 200 to cover the default case.
 | 
				
			||||||
 | 
					func newResponseRecorder(w http.ResponseWriter) *responseRecorder {
 | 
				
			||||||
 | 
						return &responseRecorder{
 | 
				
			||||||
 | 
							ResponseWriter: w,
 | 
				
			||||||
 | 
							status:         http.StatusOK,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WriteHeader records the status code and calls the
 | 
				
			||||||
 | 
					// underlying ResponseWriter's WriteHeader method.
 | 
				
			||||||
 | 
					func (r *responseRecorder) WriteHeader(status int) {
 | 
				
			||||||
 | 
						r.status = status
 | 
				
			||||||
 | 
						r.ResponseWriter.WriteHeader(status)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Write is a wrapper that records the size of the body
 | 
				
			||||||
 | 
					// that gets written.
 | 
				
			||||||
 | 
					func (r *responseRecorder) Write(buf []byte) (int, error) {
 | 
				
			||||||
 | 
						n, err := r.ResponseWriter.Write(buf)
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							r.size += n
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return n, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										91
									
								
								middleware/util_replacer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								middleware/util_replacer.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,91 @@
 | 
				
			|||||||
 | 
					package middleware
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// replacer is a type which can replace placeholder
 | 
				
			||||||
 | 
					// substrings in a string with actual values from a
 | 
				
			||||||
 | 
					// http.Request and responseRecorder. Always use
 | 
				
			||||||
 | 
					// newReplacer to get one of these.
 | 
				
			||||||
 | 
					type replacer map[string]string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// newReplacer makes a new replacer based on r and rw.
 | 
				
			||||||
 | 
					// Do not create a new replacer until r and rw have all
 | 
				
			||||||
 | 
					// the needed values, because this function copies those
 | 
				
			||||||
 | 
					// values into the replacer.
 | 
				
			||||||
 | 
					func newReplacer(r *http.Request, rw *responseRecorder) replacer {
 | 
				
			||||||
 | 
						rep := replacer{
 | 
				
			||||||
 | 
							"{method}": r.Method,
 | 
				
			||||||
 | 
							"{scheme}": func() string {
 | 
				
			||||||
 | 
								if r.TLS != nil {
 | 
				
			||||||
 | 
									return "https"
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return "http"
 | 
				
			||||||
 | 
							}(),
 | 
				
			||||||
 | 
							"{host}":     r.Host,
 | 
				
			||||||
 | 
							"{path}":     r.URL.Path,
 | 
				
			||||||
 | 
							"{query}":    r.URL.RawQuery,
 | 
				
			||||||
 | 
							"{fragment}": r.URL.Fragment,
 | 
				
			||||||
 | 
							"{proto}":    r.Proto,
 | 
				
			||||||
 | 
							"{remote}": func() string {
 | 
				
			||||||
 | 
								if idx := strings.Index(r.RemoteAddr, ":"); idx > -1 {
 | 
				
			||||||
 | 
									return r.RemoteAddr[:idx] // IP address only
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									return r.RemoteAddr
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}(),
 | 
				
			||||||
 | 
							"{port}": func() string {
 | 
				
			||||||
 | 
								if idx := strings.Index(r.Host, ":"); idx > -1 {
 | 
				
			||||||
 | 
									return r.Host[idx+1:] // port only
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return ""
 | 
				
			||||||
 | 
							}(),
 | 
				
			||||||
 | 
							"{uri}": r.RequestURI,
 | 
				
			||||||
 | 
							"{time}": func() string {
 | 
				
			||||||
 | 
								return time.Now().Format(timeFormat)
 | 
				
			||||||
 | 
							}(),
 | 
				
			||||||
 | 
							"{status}": strconv.Itoa(rw.status),
 | 
				
			||||||
 | 
							"{size}":   strconv.Itoa(rw.size),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Header placeholders
 | 
				
			||||||
 | 
						for header, val := range r.Header {
 | 
				
			||||||
 | 
							rep[headerReplacer+header+"}"] = strings.Join(val, ",")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return rep
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// replace performs a replacement of values on s and returns
 | 
				
			||||||
 | 
					// the string with the replaced values.
 | 
				
			||||||
 | 
					func (r replacer) replace(s string) string {
 | 
				
			||||||
 | 
						for placeholder, replacement := range r {
 | 
				
			||||||
 | 
							if replacement == "" {
 | 
				
			||||||
 | 
								replacement = emptyStringReplacer
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							s = strings.Replace(s, placeholder, replacement, -1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Replace any header placeholders that weren't found
 | 
				
			||||||
 | 
						for strings.Contains(s, headerReplacer) {
 | 
				
			||||||
 | 
							idxStart := strings.Index(s, headerReplacer)
 | 
				
			||||||
 | 
							endOffset := idxStart + len(headerReplacer)
 | 
				
			||||||
 | 
							idxEnd := strings.Index(s[endOffset:], "}")
 | 
				
			||||||
 | 
							if idxEnd > -1 {
 | 
				
			||||||
 | 
								s = s[:idxStart] + emptyStringReplacer + s[endOffset+idxEnd+1:]
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return s
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						timeFormat          = "02/Jan/2006:15:04:05 -0700"
 | 
				
			||||||
 | 
						headerReplacer      = "{>"
 | 
				
			||||||
 | 
						emptyStringReplacer = "-"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										176
									
								
								server/server.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								server/server.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,176 @@
 | 
				
			|||||||
 | 
					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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user