From 99c0cbdf29d54b352a0a6cb3bac24c2fb48ca747 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Tue, 21 Apr 2015 12:12:58 -0600 Subject: [PATCH 01/17] Fixed a typo --- server/virtualhost.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/virtualhost.go b/server/virtualhost.go index 57c5651c..eab59423 100644 --- a/server/virtualhost.go +++ b/server/virtualhost.go @@ -9,7 +9,7 @@ import ( // virtualHost represents a virtual host/server. While a Server // is what actually binds to the address, a user may want to serve -// multiple sites on a single address, and what is what a +// multiple sites on a single address, and this is what a // virtualHost allows us to do. type virtualHost struct { config config.Config From 0cbaed24435f5a37d7d22f48abcfb2aef52cd330 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Tue, 21 Apr 2015 16:00:16 -0600 Subject: [PATCH 02/17] A few helpful comments --- server/server.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/server.go b/server/server.go index cde3d182..76880eca 100644 --- a/server/server.go +++ b/server/server.go @@ -20,10 +20,10 @@ import ( // 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 - tls bool - vhosts map[string]virtualHost + 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 From bdd145b0defbf96e1be18545d0c8944e9edfa20d Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Tue, 21 Apr 2015 21:36:30 -0600 Subject: [PATCH 03/17] Better error handling when executing templates --- middleware/browse/browse.go | 10 ++++++---- middleware/templates/templates.go | 6 +++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/middleware/browse/browse.go b/middleware/browse/browse.go index eb941332..321033d5 100644 --- a/middleware/browse/browse.go +++ b/middleware/browse/browse.go @@ -3,6 +3,7 @@ package browse import ( + "bytes" "fmt" "html/template" "io/ioutil" @@ -122,8 +123,6 @@ func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { } defer file.Close() - w.Header().Set("Content-Type", "text/html; charset=utf-8") - files, err := file.Readdir(-1) if err != nil { return http.StatusForbidden, err @@ -182,11 +181,14 @@ func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { Items: fileinfos, } - // TODO: Don't write to w until we know there wasn't an error - err = bc.Template.Execute(w, listing) + // TODO: Use pooled buffers to reduce allocations + var buf bytes.Buffer + err = bc.Template.Execute(&buf, listing) if err != nil { return http.StatusInternalServerError, err } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + buf.WriteTo(w) return http.StatusOK, nil } diff --git a/middleware/templates/templates.go b/middleware/templates/templates.go index e641fd7d..16678833 100644 --- a/middleware/templates/templates.go +++ b/middleware/templates/templates.go @@ -1,6 +1,7 @@ package templates import ( + "bytes" "net/http" "path" "text/template" @@ -47,10 +48,13 @@ func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error } // Execute it - err = tpl.Execute(w, ctx) + // TODO: Use pooled buffers to reduce allocations + var buf bytes.Buffer + err = bc.Template.Execute(&buf, ctx) if err != nil { return http.StatusInternalServerError, err } + buf.WriteTo(w) return http.StatusOK, nil } From 23f7f5ebbab7cf0f934352b8bd961c388fcc69d3 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Tue, 21 Apr 2015 21:36:43 -0600 Subject: [PATCH 04/17] Minor UI tweaks to directory listings --- middleware/browse/template.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/middleware/browse/template.go b/middleware/browse/template.go index 7bc6cc4d..8e97af4d 100644 --- a/middleware/browse/template.go +++ b/middleware/browse/template.go @@ -11,7 +11,7 @@ const defaultTemplate = ` body { padding: 1% 2%; - font: 16px sans-serif; + font: 16px Arial; } header { @@ -60,7 +60,7 @@ th { text-align: left; } -@media (max-width: 650px) { +@media (max-width: 700px) { .hideable { display: none; } @@ -71,7 +71,7 @@ th { header, header h1 { - font-size: 14px; + font-size: 16px; } header { @@ -80,7 +80,7 @@ th { width: 100%; background: #333; color: #FFF; - padding: 10px; + padding: 15px; text-align: center; } @@ -95,8 +95,8 @@ th { position: absolute; left: 0; top: 0; - width: 35px; - height: 28px; + width: 40px; + height: 48px; font-size: 35px; } @@ -105,7 +105,7 @@ th { } main { - margin-top: 50px; + margin-top: 70px; } } From 1a8f753303dd3f91797bb38b2a16892a1bea3bea Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Tue, 21 Apr 2015 21:41:43 -0600 Subject: [PATCH 05/17] Meh. --- middleware/browse/browse.go | 2 +- middleware/templates/templates.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/middleware/browse/browse.go b/middleware/browse/browse.go index 321033d5..a3708b11 100644 --- a/middleware/browse/browse.go +++ b/middleware/browse/browse.go @@ -181,12 +181,12 @@ func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { Items: fileinfos, } - // TODO: Use pooled buffers to reduce allocations var buf bytes.Buffer err = bc.Template.Execute(&buf, listing) if err != nil { return http.StatusInternalServerError, err } + w.Header().Set("Content-Type", "text/html; charset=utf-8") buf.WriteTo(w) diff --git a/middleware/templates/templates.go b/middleware/templates/templates.go index 16678833..8361e465 100644 --- a/middleware/templates/templates.go +++ b/middleware/templates/templates.go @@ -48,7 +48,6 @@ func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error } // Execute it - // TODO: Use pooled buffers to reduce allocations var buf bytes.Buffer err = bc.Template.Execute(&buf, ctx) if err != nil { From c10d2e0d450f5c442af6ec7f1a25d403258a844b Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Tue, 21 Apr 2015 22:30:47 -0600 Subject: [PATCH 06/17] Make the thing compile --- middleware/templates/templates.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/middleware/templates/templates.go b/middleware/templates/templates.go index 8361e465..14743ff3 100644 --- a/middleware/templates/templates.go +++ b/middleware/templates/templates.go @@ -49,7 +49,7 @@ func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error // Execute it var buf bytes.Buffer - err = bc.Template.Execute(&buf, ctx) + err = tpl.Execute(&buf, ctx) if err != nil { return http.StatusInternalServerError, err } From 5f187738e67bf3725e04d922827900e9b731853d Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Wed, 22 Apr 2015 13:21:51 -0600 Subject: [PATCH 07/17] Better parse support for files with only an address line --- config/parser.go | 1 + config/parser_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++ config/parsing.go | 29 ++++++++++++++++++++------ 3 files changed, 71 insertions(+), 6 deletions(-) diff --git a/config/parser.go b/config/parser.go index 61859442..1c8cc2c8 100644 --- a/config/parser.go +++ b/config/parser.go @@ -19,6 +19,7 @@ type ( other []locationContext // tokens to be 'parsed' later by middleware generators scope *locationContext // the current location context (path scope) being populated unused *token // sometimes a token will be read but not immediately consumed + eof bool // if we encounter a valid EOF in a hard place } // locationContext represents a location context diff --git a/config/parser_test.go b/config/parser_test.go index 43a482aa..79fe0d3e 100644 --- a/config/parser_test.go +++ b/config/parser_test.go @@ -211,6 +211,53 @@ func TestParserBasicWithAlternateAddressStyles(t *testing.T) { t.Fatalf("Expected root for conf of %s to be '/test/www', but got: %s", conf.Address(), conf.Root) } } + + p = &parser{filename: "test"} + input = `host:port, http://host:port, http://host, https://host:port, host` + p.lexer.load(strings.NewReader(input)) + + confs, err = p.parse() + if err != nil { + t.Fatalf("Expected no errors, but got '%s'", err) + } + if len(confs) != 5 { + t.Fatalf("Expected 5 configurations, but got %d: %#v", len(confs), confs) + } + + if confs[0].Host != "host" { + t.Errorf("Expected conf[0] Host='host', got '%#v'", confs[0]) + } + if confs[0].Port != "port" { + t.Errorf("Expected conf[0] Port='port', got '%#v'", confs[0]) + } + + if confs[1].Host != "host" { + t.Errorf("Expected conf[1] Host='host', got '%#v'", confs[1]) + } + if confs[1].Port != "port" { + t.Errorf("Expected conf[1] Port='port', got '%#v'", confs[1]) + } + + if confs[2].Host != "host" { + t.Errorf("Expected conf[2] Host='host', got '%#v'", confs[2]) + } + if confs[2].Port != "http" { + t.Errorf("Expected conf[2] Port='http', got '%#v'", confs[2]) + } + + if confs[3].Host != "host" { + t.Errorf("Expected conf[3] Host='host', got '%#v'", confs[3]) + } + if confs[3].Port != "port" { + t.Errorf("Expected conf[3] Port='port', got '%#v'", confs[3]) + } + + if confs[4].Host != "host" { + t.Errorf("Expected conf[4] Host='host', got '%#v'", confs[4]) + } + if confs[4].Port != defaultPort { + t.Errorf("Expected conf[4] Port='%s', got '%#v'", defaultPort, confs[4].Port) + } } func TestParserImport(t *testing.T) { diff --git a/config/parsing.go b/config/parsing.go index 376ccc7d..aba880dc 100644 --- a/config/parsing.go +++ b/config/parsing.go @@ -38,18 +38,25 @@ func (p *parser) addresses() error { // address gets host and port in a format accepted by net.Dial address := func(str string) (host, port string, err error) { + var schemePort string + if strings.HasPrefix(str, "https://") { - port = "https" - host = str[8:] - return + schemePort = "https" + str = str[8:] } else if strings.HasPrefix(str, "http://") { - port = "http" - host = str[7:] - return + schemePort = "http" + str = str[7:] } else if !strings.Contains(str, ":") { str += ":" + defaultPort } + host, port, err = net.SplitHostPort(str) + if err != nil && schemePort != "" { + host = str + port = schemePort // assume port from scheme + err = nil + } + return } @@ -88,6 +95,10 @@ func (p *parser) addresses() error { if !expectingAnother && p.line() > startLine { break } + if !hasNext { + p.eof = true + break // EOF + } } return nil @@ -115,6 +126,12 @@ func (p *parser) addressBlock() error { }) p.scope = &p.other[0] + if p.eof { + // this happens if the Caddyfile consists of only + // a line of addresses and nothing else + return nil + } + err := p.directives() if err != nil { return err From d0881945855b2327fb2c775e9cf31444c19c404b Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Wed, 22 Apr 2015 13:22:03 -0600 Subject: [PATCH 08/17] Default port is now 80 --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index 151f9b82..548ca266 100644 --- a/config/config.go +++ b/config/config.go @@ -12,7 +12,7 @@ import ( const ( defaultHost = "localhost" - defaultPort = "8080" + defaultPort = "80" defaultRoot = "." // The default configuration file to load if none is specified From dd3ff0fcb526b3a0e3924114ec89ff53a520e787 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Thu, 23 Apr 2015 13:28:05 -0600 Subject: [PATCH 09/17] Shows site addresses at start; new -quiet flag --- main.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 9c018e8a..d3faa9d9 100644 --- a/main.go +++ b/main.go @@ -13,19 +13,20 @@ import ( var ( conf string - http2 bool + http2 bool // TODO: temporary flag until http2 is standard + quiet bool ) func init() { flag.StringVar(&conf, "conf", config.DefaultConfigFile, "the configuration file to use") - flag.BoolVar(&http2, "http2", true, "enable HTTP/2 support") // temporary flag until http2 merged into std lib + flag.BoolVar(&http2, "http2", true, "enable HTTP/2 support") // TODO: temporary flag until http2 merged into std lib + flag.BoolVar(&quiet, "quiet", false, "quiet mode (no initialization output)") + flag.Parse() } func main() { var wg sync.WaitGroup - flag.Parse() - // Load config from file allConfigs, err := config.Load(conf) if err != nil { @@ -60,6 +61,12 @@ func main() { log.Println(err) } }(s) + + if !quiet { + for _, config := range configs { + fmt.Printf("%s -> OK\n", config.Address()) + } + } } wg.Wait() From 51139a5f568e3fac9b6a6c935669f39ec6ff5eb5 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Thu, 23 Apr 2015 13:35:21 -0600 Subject: [PATCH 10/17] log: Fix so user can specify custom log format --- middleware/log/log.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/middleware/log/log.go b/middleware/log/log.go index a5602c02..ffbfa69e 100644 --- a/middleware/log/log.go +++ b/middleware/log/log.go @@ -88,6 +88,8 @@ func parse(c middleware.Controller) ([]LogRule, error) { format = commonLogFormat case "{combined}": format = combinedLogFormat + default: + format = args[2] } } From 95dce5cdfce74a16efcad9b17ff28417580443e9 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Thu, 23 Apr 2015 13:35:56 -0600 Subject: [PATCH 11/17] Latency now available with recorder and replacer --- middleware/recorder.go | 7 ++++++- middleware/replacer.go | 5 +++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/middleware/recorder.go b/middleware/recorder.go index ef5b69bf..4884026c 100644 --- a/middleware/recorder.go +++ b/middleware/recorder.go @@ -1,6 +1,9 @@ package middleware -import "net/http" +import ( + "net/http" + "time" +) // responseRecorder is a type of ResponseWriter that captures // the status code written to it and also the size of the body @@ -12,6 +15,7 @@ type responseRecorder struct { http.ResponseWriter status int size int + start time.Time } // NewResponseRecorder makes and returns a new responseRecorder, @@ -24,6 +28,7 @@ func NewResponseRecorder(w http.ResponseWriter) *responseRecorder { return &responseRecorder{ ResponseWriter: w, status: http.StatusOK, + start: time.Now(), } } diff --git a/middleware/replacer.go b/middleware/replacer.go index b6194ad7..6e47d5f7 100644 --- a/middleware/replacer.go +++ b/middleware/replacer.go @@ -50,8 +50,9 @@ func NewReplacer(r *http.Request, rr *responseRecorder) replacer { "{when}": func() string { return time.Now().Format(timeFormat) }(), - "{status}": strconv.Itoa(rr.status), - "{size}": strconv.Itoa(rr.size), + "{status}": strconv.Itoa(rr.status), + "{size}": strconv.Itoa(rr.size), + "{latency}": time.Since(rr.start).String(), } // Header placeholders From e6c5482b7c2399d33383481d2636d14bc8a93839 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Thu, 23 Apr 2015 14:39:21 -0600 Subject: [PATCH 12/17] Slightly more helpful parse error message --- config/dispenser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/dispenser.go b/config/dispenser.go index fad07c8e..13cd8542 100644 --- a/config/dispenser.go +++ b/config/dispenser.go @@ -149,7 +149,7 @@ func (d *dispenser) ArgErr() error { if d.Val() == "{" { return d.Err("Unexpected token '{', expecting argument") } - return d.Err("Unexpected line ending after '" + d.Val() + "' (missing arguments?)") + return d.Err("Wrong argument count or unexpected line ending after '" + d.Val() + "'") } // Err generates a custom parse error with a message of msg. From 27fc1672d45fe2089fdcbc14459e46c30f825104 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Thu, 23 Apr 2015 14:57:07 -0600 Subject: [PATCH 13/17] Basic auth middleware --- config/middleware.go | 2 + middleware/basicauth/basicauth.go | 101 ++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 middleware/basicauth/basicauth.go diff --git a/config/middleware.go b/config/middleware.go index 4f2fe547..d144cc6a 100644 --- a/config/middleware.go +++ b/config/middleware.go @@ -2,6 +2,7 @@ package config import ( "github.com/mholt/caddy/middleware" + "github.com/mholt/caddy/middleware/basicauth" "github.com/mholt/caddy/middleware/browse" "github.com/mholt/caddy/middleware/errors" "github.com/mholt/caddy/middleware/extensions" @@ -45,6 +46,7 @@ func init() { register("rewrite", rewrite.New) register("redir", redirect.New) register("ext", extensions.New) + register("basicauth", basicauth.New) register("proxy", proxy.New) register("fastcgi", fastcgi.New) register("websocket", websockets.New) diff --git a/middleware/basicauth/basicauth.go b/middleware/basicauth/basicauth.go new file mode 100644 index 00000000..d81d3a2e --- /dev/null +++ b/middleware/basicauth/basicauth.go @@ -0,0 +1,101 @@ +package basicauth + +import ( + "net/http" + + "github.com/mholt/caddy/middleware" +) + +// New constructs a new BasicAuth middleware instance. +func New(c middleware.Controller) (middleware.Middleware, error) { + rules, err := parse(c) + if err != nil { + return nil, err + } + + basic := BasicAuth{ + Rules: rules, + } + + return func(next middleware.Handler) middleware.Handler { + basic.Next = next + return basic + }, nil +} + +// ServeHTTP implements the middleware.Handler interface. +func (a BasicAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { + for _, rule := range a.Rules { + for _, res := range rule.Resources { + if !middleware.Path(r.URL.Path).Matches(res) { + continue + } + + // Path matches; parse auth header + username, password, ok := r.BasicAuth() + + // Check credentials + if !ok || username != rule.Username || password != rule.Password { + w.Header().Set("WWW-Authenticate", "Basic") + return http.StatusUnauthorized, nil + } + + // "It's an older code, sir, but it checks out. I was about to clear them." + return a.Next.ServeHTTP(w, r) + } + } + + // Pass-thru when no paths match + return a.Next.ServeHTTP(w, r) +} + +func parse(c middleware.Controller) ([]Rule, error) { + var rules []Rule + + for c.Next() { + var rule Rule + + args := c.RemainingArgs() + + switch len(args) { + case 2: + rule.Username = args[0] + rule.Password = args[1] + for c.NextBlock() { + rule.Resources = append(rule.Resources, c.Val()) + if c.NextArg() { + return rules, c.Err("Expecting only one resource per line (extra '" + c.Val() + "')") + } + } + case 3: + rule.Resources = append(rule.Resources, args[0]) + rule.Username = args[1] + rule.Password = args[2] + default: + return rules, c.ArgErr() + } + + rules = append(rules, rule) + } + + return rules, nil +} + +// BasicAuth is middleware to protect resources with a username and password. +// Note that HTTP Basic Authentication is not secure by itself and should +// not be used to protect important assets without HTTPS. Even then, the +// security of HTTP Basic Auth is disputed. Use discretion when deciding +// what to protect with BasicAuth. +type BasicAuth struct { + Next middleware.Handler + Rules []Rule +} + +// Rule represents a BasicAuth rule. A username and password +// combination protect the associated resources, which are +// file or directory paths. +type Rule struct { + Username string + Password string + Resources []string +} From aa89b95075171f796a347971fb65ca0e8a1ed912 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Fri, 24 Apr 2015 20:08:14 -0600 Subject: [PATCH 14/17] Replaced cpu directive with command line flag --- config/config.go | 3 --- config/directives.go | 43 ---------------------------------------- main.go | 47 ++++++++++++++++++++++++++++++++++++++++++++ server/server.go | 15 ++------------ 4 files changed, 49 insertions(+), 59 deletions(-) diff --git a/config/config.go b/config/config.go index 548ca266..8a9a4f18 100644 --- a/config/config.go +++ b/config/config.go @@ -47,9 +47,6 @@ type Config struct { // these are executed in response to SIGINT and are blocking Shutdown []func() error - // MaxCPU is the maximum number of cores for the whole process to use - MaxCPU int - // The path to the configuration file from which this was loaded ConfigFile string } diff --git a/config/directives.go b/config/directives.go index 162d56bf..68773832 100644 --- a/config/directives.go +++ b/config/directives.go @@ -3,9 +3,6 @@ package config import ( "os" "os/exec" - "runtime" - "strconv" - "strings" "github.com/mholt/caddy/middleware" ) @@ -74,46 +71,6 @@ func init() { p.cfg.TLS = tls return nil }, - "cpu": func(p *parser) error { - sysCores := runtime.NumCPU() - - if !p.nextArg() { - return p.argErr() - } - strNum := p.tkn() - - setCPU := func(val int) { - if val < 1 { - val = 1 - } - if val > sysCores { - val = sysCores - } - if val > p.cfg.MaxCPU { - p.cfg.MaxCPU = val - } - } - - if strings.HasSuffix(strNum, "%") { - // Percent - var percent float32 - pctStr := strNum[:len(strNum)-1] - pctInt, err := strconv.Atoi(pctStr) - if err != nil || pctInt < 1 || pctInt > 100 { - return p.err("Parse", "Invalid number '"+strNum+"' (must be a positive percentage between 1 and 100)") - } - percent = float32(pctInt) / 100 - setCPU(int(float32(sysCores) * percent)) - } else { - // Number - num, err := strconv.Atoi(strNum) - if err != nil || num < 0 { - return p.err("Parse", "Invalid number '"+strNum+"' (requires positive integer or percent)") - } - setCPU(num) - } - return nil - }, "startup": func(p *parser) error { // TODO: This code is duplicated with the shutdown directive below diff --git a/main.go b/main.go index d3faa9d9..60fe0096 100644 --- a/main.go +++ b/main.go @@ -1,10 +1,14 @@ package main import ( + "errors" "flag" "fmt" "log" "net" + "runtime" + "strconv" + "strings" "sync" "github.com/mholt/caddy/config" @@ -15,18 +19,26 @@ var ( conf string http2 bool // TODO: temporary flag until http2 is standard quiet bool + cpu string ) func init() { flag.StringVar(&conf, "conf", config.DefaultConfigFile, "the configuration file to use") flag.BoolVar(&http2, "http2", true, "enable HTTP/2 support") // TODO: temporary flag until http2 merged into std lib flag.BoolVar(&quiet, "quiet", false, "quiet mode (no initialization output)") + flag.StringVar(&cpu, "cpu", "100%", "CPU cap") flag.Parse() } func main() { var wg sync.WaitGroup + // Set CPU cap + err := setCPU(cpu) + if err != nil { + log.Fatal(err) + } + // Load config from file allConfigs, err := config.Load(conf) if err != nil { @@ -109,3 +121,38 @@ func arrangeBindings(allConfigs []config.Config) (map[string][]config.Config, er return addresses, nil } + +// setCPU parses string cpu and sets GOMAXPROCS +// according to its value. It accepts either +// a number (e.g. 3) or a percent (e.g. 50%). +func setCPU(cpu string) error { + var numCPU int + + availCPU := runtime.NumCPU() + + if strings.HasSuffix(cpu, "%") { + // Percent + var percent float32 + pctStr := cpu[:len(cpu)-1] + pctInt, err := strconv.Atoi(pctStr) + if err != nil || pctInt < 1 || pctInt > 100 { + return errors.New("Invalid CPU value: percentage must be between 1-100") + } + percent = float32(pctInt) / 100 + numCPU = int(float32(availCPU) * percent) + } else { + // Number + num, err := strconv.Atoi(cpu) + if err != nil || num < 1 { + return errors.New("Invalid CPU value: provide a number or percent greater than 0") + } + numCPU = num + } + + if numCPU > availCPU { + numCPU = availCPU + } + + runtime.GOMAXPROCS(numCPU) + return nil +} diff --git a/server/server.go b/server/server.go index 76880eca..1f9d40e3 100644 --- a/server/server.go +++ b/server/server.go @@ -11,7 +11,6 @@ import ( "net/http" "os" "os/signal" - "runtime" "github.com/bradfitz/http2" "github.com/mholt/caddy/config" @@ -41,11 +40,6 @@ func New(addr string, configs []config.Config, tls bool) (*Server, error) { return nil, fmt.Errorf("Cannot serve %s - host already defined for address %s", conf.Address(), s.address) } - // Use all CPUs (if needed) by default - if conf.MaxCPU == 0 { - conf.MaxCPU = runtime.NumCPU() - } - vh := virtualHost{config: conf} // Build middleware stack @@ -73,7 +67,7 @@ func (s *Server) Serve() error { } for _, vh := range s.vhosts { - // Execute startup functions + // Execute startup functions now for _, start := range vh.config.Startup { err := start() if err != nil { @@ -81,13 +75,8 @@ func (s *Server) Serve() error { } } - // Use highest procs value across all configurations - if vh.config.MaxCPU > 0 && vh.config.MaxCPU > runtime.GOMAXPROCS(0) { - runtime.GOMAXPROCS(vh.config.MaxCPU) - } - + // Execute shutdown commands on exit if len(vh.config.Shutdown) > 0 { - // Execute shutdown commands on exit go func() { interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt, os.Kill) // TODO: syscall.SIGQUIT? (Ctrl+\, Unix-only) From 46f5325c15191993a04d59aa7165c5232de07f86 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Fri, 24 Apr 2015 20:09:31 -0600 Subject: [PATCH 15/17] More accurate initialization output --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 60fe0096..c2e6d44e 100644 --- a/main.go +++ b/main.go @@ -76,7 +76,7 @@ func main() { if !quiet { for _, config := range configs { - fmt.Printf("%s -> OK\n", config.Address()) + fmt.Println(config.Address()) } } } From ce7433334801cc1895c0d9aa6cf1c36fd573a749 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Sat, 25 Apr 2015 12:26:04 -0600 Subject: [PATCH 16/17] Markdown requires a base path (for now) --- middleware/markdown/markdown.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/middleware/markdown/markdown.go b/middleware/markdown/markdown.go index 4d0fc043..162528e4 100644 --- a/middleware/markdown/markdown.go +++ b/middleware/markdown/markdown.go @@ -135,7 +135,7 @@ func parse(c middleware.Controller) ([]MarkdownConfig, error) { } // Get the path scope - if !c.NextArg() { + if !c.NextArg() || c.Val() == "{" { return mdconfigs, c.ArgErr() } md.PathScope = c.Val() From 24d9d23743de47e3d6f01a52635ff6bbe69e1a9e Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Sat, 25 Apr 2015 14:28:56 -0600 Subject: [PATCH 17/17] Default port is 2015 --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index 8a9a4f18..4f35fb7a 100644 --- a/config/config.go +++ b/config/config.go @@ -12,7 +12,7 @@ import ( const ( defaultHost = "localhost" - defaultPort = "80" + defaultPort = "2015" defaultRoot = "." // The default configuration file to load if none is specified