mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-05 18:44:58 +03:00
Merge branch 'master' of https://github.com/mholt/caddy
This commit is contained in:
commit
9e12c45d82
18 changed files with 269 additions and 91 deletions
|
@ -12,7 +12,7 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultHost = "localhost"
|
defaultHost = "localhost"
|
||||||
defaultPort = "8080"
|
defaultPort = "2015"
|
||||||
defaultRoot = "."
|
defaultRoot = "."
|
||||||
|
|
||||||
// The default configuration file to load if none is specified
|
// The default configuration file to load if none is specified
|
||||||
|
@ -47,9 +47,6 @@ type Config struct {
|
||||||
// these are executed in response to SIGINT and are blocking
|
// these are executed in response to SIGINT and are blocking
|
||||||
Shutdown []func() error
|
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
|
// The path to the configuration file from which this was loaded
|
||||||
ConfigFile string
|
ConfigFile string
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,6 @@ package config
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/mholt/caddy/middleware"
|
"github.com/mholt/caddy/middleware"
|
||||||
)
|
)
|
||||||
|
@ -74,46 +71,6 @@ func init() {
|
||||||
p.cfg.TLS = tls
|
p.cfg.TLS = tls
|
||||||
return nil
|
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 {
|
"startup": func(p *parser) error {
|
||||||
// TODO: This code is duplicated with the shutdown directive below
|
// TODO: This code is duplicated with the shutdown directive below
|
||||||
|
|
||||||
|
|
|
@ -149,7 +149,7 @@ func (d *dispenser) ArgErr() error {
|
||||||
if d.Val() == "{" {
|
if d.Val() == "{" {
|
||||||
return d.Err("Unexpected token '{', expecting argument")
|
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.
|
// Err generates a custom parse error with a message of msg.
|
||||||
|
|
|
@ -2,6 +2,7 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/mholt/caddy/middleware"
|
"github.com/mholt/caddy/middleware"
|
||||||
|
"github.com/mholt/caddy/middleware/basicauth"
|
||||||
"github.com/mholt/caddy/middleware/browse"
|
"github.com/mholt/caddy/middleware/browse"
|
||||||
"github.com/mholt/caddy/middleware/errors"
|
"github.com/mholt/caddy/middleware/errors"
|
||||||
"github.com/mholt/caddy/middleware/extensions"
|
"github.com/mholt/caddy/middleware/extensions"
|
||||||
|
@ -45,6 +46,7 @@ func init() {
|
||||||
register("rewrite", rewrite.New)
|
register("rewrite", rewrite.New)
|
||||||
register("redir", redirect.New)
|
register("redir", redirect.New)
|
||||||
register("ext", extensions.New)
|
register("ext", extensions.New)
|
||||||
|
register("basicauth", basicauth.New)
|
||||||
register("proxy", proxy.New)
|
register("proxy", proxy.New)
|
||||||
register("fastcgi", fastcgi.New)
|
register("fastcgi", fastcgi.New)
|
||||||
register("websocket", websockets.New)
|
register("websocket", websockets.New)
|
||||||
|
|
|
@ -19,6 +19,7 @@ type (
|
||||||
other []locationContext // tokens to be 'parsed' later by middleware generators
|
other []locationContext // tokens to be 'parsed' later by middleware generators
|
||||||
scope *locationContext // the current location context (path scope) being populated
|
scope *locationContext // the current location context (path scope) being populated
|
||||||
unused *token // sometimes a token will be read but not immediately consumed
|
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
|
// locationContext represents a location context
|
||||||
|
|
|
@ -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)
|
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) {
|
func TestParserImport(t *testing.T) {
|
||||||
|
|
|
@ -38,18 +38,25 @@ func (p *parser) addresses() error {
|
||||||
|
|
||||||
// address gets host and port in a format accepted by net.Dial
|
// address gets host and port in a format accepted by net.Dial
|
||||||
address := func(str string) (host, port string, err error) {
|
address := func(str string) (host, port string, err error) {
|
||||||
|
var schemePort string
|
||||||
|
|
||||||
if strings.HasPrefix(str, "https://") {
|
if strings.HasPrefix(str, "https://") {
|
||||||
port = "https"
|
schemePort = "https"
|
||||||
host = str[8:]
|
str = str[8:]
|
||||||
return
|
|
||||||
} else if strings.HasPrefix(str, "http://") {
|
} else if strings.HasPrefix(str, "http://") {
|
||||||
port = "http"
|
schemePort = "http"
|
||||||
host = str[7:]
|
str = str[7:]
|
||||||
return
|
|
||||||
} else if !strings.Contains(str, ":") {
|
} else if !strings.Contains(str, ":") {
|
||||||
str += ":" + defaultPort
|
str += ":" + defaultPort
|
||||||
}
|
}
|
||||||
|
|
||||||
host, port, err = net.SplitHostPort(str)
|
host, port, err = net.SplitHostPort(str)
|
||||||
|
if err != nil && schemePort != "" {
|
||||||
|
host = str
|
||||||
|
port = schemePort // assume port from scheme
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,6 +95,10 @@ func (p *parser) addresses() error {
|
||||||
if !expectingAnother && p.line() > startLine {
|
if !expectingAnother && p.line() > startLine {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if !hasNext {
|
||||||
|
p.eof = true
|
||||||
|
break // EOF
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -115,6 +126,12 @@ func (p *parser) addressBlock() error {
|
||||||
})
|
})
|
||||||
p.scope = &p.other[0]
|
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()
|
err := p.directives()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
60
main.go
60
main.go
|
@ -1,10 +1,14 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/mholt/caddy/config"
|
"github.com/mholt/caddy/config"
|
||||||
|
@ -13,18 +17,27 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
conf string
|
conf string
|
||||||
http2 bool
|
http2 bool // TODO: temporary flag until http2 is standard
|
||||||
|
quiet bool
|
||||||
|
cpu string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.StringVar(&conf, "conf", config.DefaultConfigFile, "the configuration file to use")
|
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.StringVar(&cpu, "cpu", "100%", "CPU cap")
|
||||||
|
flag.Parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
flag.Parse()
|
// Set CPU cap
|
||||||
|
err := setCPU(cpu)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Load config from file
|
// Load config from file
|
||||||
allConfigs, err := config.Load(conf)
|
allConfigs, err := config.Load(conf)
|
||||||
|
@ -60,6 +73,12 @@ func main() {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
}(s)
|
}(s)
|
||||||
|
|
||||||
|
if !quiet {
|
||||||
|
for _, config := range configs {
|
||||||
|
fmt.Println(config.Address())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
@ -102,3 +121,38 @@ func arrangeBindings(allConfigs []config.Config) (map[string][]config.Config, er
|
||||||
|
|
||||||
return addresses, nil
|
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
|
||||||
|
}
|
||||||
|
|
101
middleware/basicauth/basicauth.go
Normal file
101
middleware/basicauth/basicauth.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
package browse
|
package browse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -122,8 +123,6 @@ func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
||||||
|
|
||||||
files, err := file.Readdir(-1)
|
files, err := file.Readdir(-1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusForbidden, err
|
return http.StatusForbidden, err
|
||||||
|
@ -182,12 +181,15 @@ func (b Browse) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
Items: fileinfos,
|
Items: fileinfos,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Don't write to w until we know there wasn't an error
|
var buf bytes.Buffer
|
||||||
err = bc.Template.Execute(w, listing)
|
err = bc.Template.Execute(&buf, listing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
buf.WriteTo(w)
|
||||||
|
|
||||||
return http.StatusOK, nil
|
return http.StatusOK, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ const defaultTemplate = `<!DOCTYPE html>
|
||||||
|
|
||||||
body {
|
body {
|
||||||
padding: 1% 2%;
|
padding: 1% 2%;
|
||||||
font: 16px sans-serif;
|
font: 16px Arial;
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
|
@ -60,7 +60,7 @@ th {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 650px) {
|
@media (max-width: 700px) {
|
||||||
.hideable {
|
.hideable {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ th {
|
||||||
|
|
||||||
header,
|
header,
|
||||||
header h1 {
|
header h1 {
|
||||||
font-size: 14px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
|
@ -80,7 +80,7 @@ th {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: #333;
|
background: #333;
|
||||||
color: #FFF;
|
color: #FFF;
|
||||||
padding: 10px;
|
padding: 15px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,8 +95,8 @@ th {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 35px;
|
width: 40px;
|
||||||
height: 28px;
|
height: 48px;
|
||||||
font-size: 35px;
|
font-size: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ th {
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
margin-top: 50px;
|
margin-top: 70px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -88,6 +88,8 @@ func parse(c middleware.Controller) ([]LogRule, error) {
|
||||||
format = commonLogFormat
|
format = commonLogFormat
|
||||||
case "{combined}":
|
case "{combined}":
|
||||||
format = combinedLogFormat
|
format = combinedLogFormat
|
||||||
|
default:
|
||||||
|
format = args[2]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -135,7 +135,7 @@ func parse(c middleware.Controller) ([]MarkdownConfig, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the path scope
|
// Get the path scope
|
||||||
if !c.NextArg() {
|
if !c.NextArg() || c.Val() == "{" {
|
||||||
return mdconfigs, c.ArgErr()
|
return mdconfigs, c.ArgErr()
|
||||||
}
|
}
|
||||||
md.PathScope = c.Val()
|
md.PathScope = c.Val()
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import "net/http"
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
// responseRecorder is a type of ResponseWriter that captures
|
// responseRecorder is a type of ResponseWriter that captures
|
||||||
// the status code written to it and also the size of the body
|
// the status code written to it and also the size of the body
|
||||||
|
@ -12,6 +15,7 @@ type responseRecorder struct {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
status int
|
status int
|
||||||
size int
|
size int
|
||||||
|
start time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewResponseRecorder makes and returns a new responseRecorder,
|
// NewResponseRecorder makes and returns a new responseRecorder,
|
||||||
|
@ -24,6 +28,7 @@ func NewResponseRecorder(w http.ResponseWriter) *responseRecorder {
|
||||||
return &responseRecorder{
|
return &responseRecorder{
|
||||||
ResponseWriter: w,
|
ResponseWriter: w,
|
||||||
status: http.StatusOK,
|
status: http.StatusOK,
|
||||||
|
start: time.Now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,7 @@ func NewReplacer(r *http.Request, rr *responseRecorder) replacer {
|
||||||
}(),
|
}(),
|
||||||
"{status}": strconv.Itoa(rr.status),
|
"{status}": strconv.Itoa(rr.status),
|
||||||
"{size}": strconv.Itoa(rr.size),
|
"{size}": strconv.Itoa(rr.size),
|
||||||
|
"{latency}": time.Since(rr.start).String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header placeholders
|
// Header placeholders
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package templates
|
package templates
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
@ -47,10 +48,12 @@ func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute it
|
// Execute it
|
||||||
err = tpl.Execute(w, ctx)
|
var buf bytes.Buffer
|
||||||
|
err = tpl.Execute(&buf, ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
buf.WriteTo(w)
|
||||||
|
|
||||||
return http.StatusOK, nil
|
return http.StatusOK, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/bradfitz/http2"
|
"github.com/bradfitz/http2"
|
||||||
"github.com/mholt/caddy/config"
|
"github.com/mholt/caddy/config"
|
||||||
|
@ -21,9 +20,9 @@ import (
|
||||||
// static content at a particular address (host and port).
|
// static content at a particular address (host and port).
|
||||||
type Server struct {
|
type Server struct {
|
||||||
HTTP2 bool // temporary while http2 is not in std lib (TODO: remove flag when part of std lib)
|
HTTP2 bool // temporary while http2 is not in std lib (TODO: remove flag when part of std lib)
|
||||||
address string
|
address string // the actual address for net.Listen to listen on
|
||||||
tls bool
|
tls bool // whether this server is serving all HTTPS hosts or not
|
||||||
vhosts map[string]virtualHost
|
vhosts map[string]virtualHost // virtual hosts keyed by their address
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Server which will bind to addr and serve
|
// New creates a new Server which will bind to addr and serve
|
||||||
|
@ -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)
|
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}
|
vh := virtualHost{config: conf}
|
||||||
|
|
||||||
// Build middleware stack
|
// Build middleware stack
|
||||||
|
@ -73,7 +67,7 @@ func (s *Server) Serve() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, vh := range s.vhosts {
|
for _, vh := range s.vhosts {
|
||||||
// Execute startup functions
|
// Execute startup functions now
|
||||||
for _, start := range vh.config.Startup {
|
for _, start := range vh.config.Startup {
|
||||||
err := start()
|
err := start()
|
||||||
if err != nil {
|
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(vh.config.Shutdown) > 0 {
|
|
||||||
// Execute shutdown commands on exit
|
// Execute shutdown commands on exit
|
||||||
|
if len(vh.config.Shutdown) > 0 {
|
||||||
go func() {
|
go func() {
|
||||||
interrupt := make(chan os.Signal, 1)
|
interrupt := make(chan os.Signal, 1)
|
||||||
signal.Notify(interrupt, os.Interrupt, os.Kill) // TODO: syscall.SIGQUIT? (Ctrl+\, Unix-only)
|
signal.Notify(interrupt, os.Interrupt, os.Kill) // TODO: syscall.SIGQUIT? (Ctrl+\, Unix-only)
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
|
|
||||||
// virtualHost represents a virtual host/server. While a Server
|
// virtualHost represents a virtual host/server. While a Server
|
||||||
// is what actually binds to the address, a user may want to serve
|
// 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.
|
// virtualHost allows us to do.
|
||||||
type virtualHost struct {
|
type virtualHost struct {
|
||||||
config config.Config
|
config config.Config
|
||||||
|
|
Loading…
Reference in a new issue