mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-01 00:23:48 +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 (
|
||||
defaultHost = "localhost"
|
||||
defaultPort = "8080"
|
||||
defaultPort = "2015"
|
||||
defaultRoot = "."
|
||||
|
||||
// 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
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
60
main.go
60
main.go
|
@ -1,10 +1,14 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mholt/caddy/config"
|
||||
|
@ -13,18 +17,27 @@ import (
|
|||
|
||||
var (
|
||||
conf string
|
||||
http2 bool
|
||||
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") // 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() {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
flag.Parse()
|
||||
// Set CPU cap
|
||||
err := setCPU(cpu)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Load config from file
|
||||
allConfigs, err := config.Load(conf)
|
||||
|
@ -60,6 +73,12 @@ func main() {
|
|||
log.Println(err)
|
||||
}
|
||||
}(s)
|
||||
|
||||
if !quiet {
|
||||
for _, config := range configs {
|
||||
fmt.Println(config.Address())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
@ -102,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
|
||||
}
|
||||
|
|
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
|
||||
|
||||
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,12 +181,15 @@ 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)
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ const defaultTemplate = `<!DOCTYPE html>
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -88,6 +88,8 @@ func parse(c middleware.Controller) ([]LogRule, error) {
|
|||
format = commonLogFormat
|
||||
case "{combined}":
|
||||
format = combinedLogFormat
|
||||
default:
|
||||
format = args[2]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"path"
|
||||
"text/template"
|
||||
|
@ -47,10 +48,12 @@ func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error
|
|||
}
|
||||
|
||||
// Execute it
|
||||
err = tpl.Execute(w, ctx)
|
||||
var buf bytes.Buffer
|
||||
err = tpl.Execute(&buf, ctx)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
buf.WriteTo(w)
|
||||
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
|
||||
"github.com/bradfitz/http2"
|
||||
"github.com/mholt/caddy/config"
|
||||
|
@ -20,10 +19,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
|
||||
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue