Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 14, 05:15 UTC
Performance testing Load function
Matthew Holt committed
Mar 26, 2019 at 19:42 UTC
a8dc73b4d9db5edf85e78314c9759b9d12a79b71
7 files changed
+132
-12
admin.go
+35
-2
@@ -1,6 +1,7 @@
1
package caddy2
2
3
import (
4
+ "bytes"
5
"context"
6
"encoding/json"
7
"fmt"
@@ -8,8 +9,10 @@ import (
9
"log"
10
"net"
11
"net/http"
12
+ "net/http/pprof"
13
"strings"
14
"sync"
15
+ "time"
16
)
17
18
var (
@@ -30,6 +33,14 @@ func StartAdmin(addr string) error {
33
mux := http.NewServeMux()
34
mux.HandleFunc("/load", handleLoadConfig)
35
36
+ ///// BEGIN PPROF STUFF //////
37
+ mux.HandleFunc("/debug/pprof/", pprof.Index)
38
+ mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
39
+ mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
40
+ mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
41
+ mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
42
+ ///// END PPROF STUFF //////
43
+
44
for _, m := range GetModules("admin") {
45
moduleValue, err := m.New()
46
if err != nil {
@@ -40,7 +51,11 @@ func StartAdmin(addr string) error {
51
}
52
53
cfgEndptSrv = &http.Server{
43
- Handler: mux,
54
+ Handler: mux,
55
+ ReadTimeout: 5 * time.Second,
56
+ ReadHeaderTimeout: 5 * time.Second,
57
+ IdleTimeout: 5 * time.Second,
58
+ MaxHeaderBytes: 1024 * 256,
59
}
60
61
go cfgEndptSrv.Serve(ln)
@@ -74,6 +89,7 @@ type AdminRoute struct {
89
}
90
91
func handleLoadConfig(w http.ResponseWriter, r *http.Request) {
92
+ r.Close = true
93
if r.Method != "POST" {
94
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
95
return
@@ -94,14 +110,31 @@ func handleLoadConfig(w http.ResponseWriter, r *http.Request) {
110
111
// Load loads and starts a configuration.
112
func Load(r io.Reader) error {
113
+ buf := bufPool.Get().(*bytes.Buffer)
114
+ buf.Reset()
115
+ defer bufPool.Put(buf)
116
+
117
+ _, err := io.Copy(buf, io.LimitReader(r, 1024*1024))
118
+ if err != nil {
119
+ return err
120
+ }
121
+
122
var cfg Config
98
- err := json.NewDecoder(r).Decode(&cfg)
123
+ err = json.Unmarshal(buf.Bytes(), &cfg)
124
if err != nil {
125
return fmt.Errorf("decoding config: %v", err)
126
}
127
+
128
err = Start(cfg)
129
if err != nil {
130
return fmt.Errorf("starting: %v", err)
131
}
132
+
133
return nil
134
}
135
+
136
+var bufPool = sync.Pool{
137
+ New: func() interface{} {
138
+ return new(bytes.Buffer)
139
+ },
140
+}
admin_test.go
new
+30
@@ -0,0 +1,30 @@
1
+package caddy2
2
+
3
+import (
4
+ "strings"
5
+ "testing"
6
+)
7
+
8
+func BenchmarkLoad(b *testing.B) {
9
+ for i := 0; i < b.N; i++ {
10
+ r := strings.NewReader(`{
11
+ "testval": "Yippee!",
12
+ "modules": {
13
+ "http": {
14
+ "servers": {
15
+ "myserver": {
16
+ "listen": ["tcp/localhost:8080-8084"],
17
+ "read_timeout": "30s"
18
+ },
19
+ "yourserver": {
20
+ "listen": ["127.0.0.1:5000"],
21
+ "read_header_timeout": "15s"
22
+ }
23
+ }
24
+ }
25
+ }
26
+ }
27
+ `)
28
+ Load(r)
29
+ }
30
+}
caddy.go
+23
@@ -3,10 +3,16 @@ package caddy2
3
import (
4
"encoding/json"
5
"fmt"
6
+ "log"
7
+ "runtime/debug"
8
"strings"
9
+ "sync"
10
"time"
11
)
12
13
+var currentCfg *Config
14
+var currentCfgMu sync.Mutex
15
+
16
// Start runs Caddy with the given config.
17
func Start(cfg Config) error {
18
cfg.runners = make(map[string]Runner)
@@ -26,16 +32,33 @@ func Start(cfg Config) error {
32
for name, r := range cfg.runners {
33
err := r.Run()
34
if err != nil {
35
+ // TODO: If any one has an error, stop the others
36
return fmt.Errorf("%s module: %v", name, err)
37
}
38
}
39
40
+ currentCfgMu.Lock()
41
+ if currentCfg != nil {
42
+ for _, r := range cfg.runners {
43
+ err := r.Cancel()
44
+ if err != nil {
45
+ log.Println(err)
46
+ }
47
+ }
48
+ }
49
+ currentCfg = &cfg
50
+ currentCfgMu.Unlock()
51
+
52
+ // TODO: debugging memory leak...
53
+ debug.FreeOSMemory()
54
+
55
return nil
56
}
57
58
// Runner is a thing that Caddy runs.
59
type Runner interface {
60
Run() error
61
+ Cancel() error
62
}
63
64
// Config represents a Caddy configuration.
cmd/caddy2/main.go
+4
-2
@@ -5,17 +5,19 @@ import (
5
6
"bitbucket.org/lightcodelabs/caddy2"
7
8
+ _ "net/http/pprof"
9
+
10
// this is where modules get plugged in
11
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp"
12
_ "bitbucket.org/lightcodelabs/dynamicconfig"
13
)
14
15
func main() {
14
- err := caddy2.Start("127.0.0.1:1234")
16
+ err := caddy2.StartAdmin("127.0.0.1:1234")
17
if err != nil {
18
log.Fatal(err)
19
}
18
- defer caddy2.Stop()
20
+ defer caddy2.StopAdmin()
21
22
select {}
23
}
listeners.go
+19
@@ -3,15 +3,29 @@ package caddy2
3
import (
4
"fmt"
5
"net"
6
+ "sync"
7
"sync/atomic"
8
)
9
10
// Listen returns a listener suitable for use in a Caddy module.
11
func Listen(proto, addr string) (net.Listener, error) {
12
+ lnKey := proto + "/" + addr
13
+
14
+ listenersMu.Lock()
15
+ defer listenersMu.Unlock()
16
+
17
+ // if listener already exists, return it
18
+ if ln, ok := listeners[lnKey]; ok {
19
+ return &fakeCloseListener{Listener: ln}, nil
20
+ }
21
+
22
+ // or, create new one and save it
23
ln, err := net.Listen(proto, addr)
24
if err != nil {
25
return nil, err
26
}
27
+ listeners[lnKey] = ln
28
+
29
return &fakeCloseListener{Listener: ln}, nil
30
}
31
@@ -49,3 +63,8 @@ func (fcl *fakeCloseListener) CloseUnderlying() error {
63
// Close() is called, indicating that it is pretending to
64
// be closed so that the server using it can terminate.
65
var ErrSwappingServers = fmt.Errorf("listener 'closed' 😉")
66
+
67
+var (
68
+ listeners = make(map[string]net.Listener)
69
+ listenersMu sync.Mutex
70
+)
modules/caddyhttp/caddyhttp.go
+17
-4
@@ -1,6 +1,7 @@
1
package caddyhttp
2
3
import (
4
+ "context"
5
"fmt"
6
"log"
7
"net"
@@ -24,13 +25,15 @@ func init() {
25
26
type httpModuleConfig struct {
27
Servers map[string]httpServerConfig `json:"servers"`
28
+
29
+ servers []*http.Server
30
}
31
32
func (hc *httpModuleConfig) Run() error {
30
- fmt.Printf("RUNNING: %#v\n", hc)
33
+ // fmt.Printf("RUNNING: %#v\n", hc)
34
35
for _, srv := range hc.Servers {
33
- s := http.Server{
36
+ s := &http.Server{
37
ReadTimeout: time.Duration(srv.ReadTimeout),
38
ReadHeaderTimeout: time.Duration(srv.ReadHeaderTimeout),
39
}
@@ -53,11 +56,21 @@ func (hc *httpModuleConfig) Run() error {
56
return nil
57
}
58
59
+func (hc *httpModuleConfig) Cancel() error {
60
+ for _, s := range hc.servers {
61
+ err := s.Shutdown(context.Background()) // TODO
62
+ if err != nil {
63
+ return err
64
+ }
65
+ }
66
+ return nil
67
+}
68
+
69
func parseListenAddr(a string) (proto string, addrs []string, err error) {
70
proto = "tcp"
58
- if idx := strings.Index(a, ":::"); idx >= 0 {
71
+ if idx := strings.Index(a, "/"); idx >= 0 {
72
proto = strings.ToLower(strings.TrimSpace(a[:idx]))
60
- a = a[idx+3:]
73
+ a = a[idx+1:]
74
}
75
var host, port string
76
host, port, err = net.SplitHostPort(a)
modules/caddyhttp/caddyhttp_test.go
+4
-4
@@ -28,22 +28,22 @@ func TestParseListenerAddr(t *testing.T) {
28
expectAddrs: []string{":1234"},
29
},
30
{
31
- input: "tcp::::1234",
31
+ input: "tcp/:1234",
32
expectProto: "tcp",
33
expectAddrs: []string{":1234"},
34
},
35
{
36
- input: "tcp6::::1234",
36
+ input: "tcp6/:1234",
37
expectProto: "tcp6",
38
expectAddrs: []string{":1234"},
39
},
40
{
41
- input: "tcp4:::localhost:1234",
41
+ input: "tcp4/localhost:1234",
42
expectProto: "tcp4",
43
expectAddrs: []string{"localhost:1234"},
44
},
45
{
46
- input: "unix:::localhost:1234-1236",
46
+ input: "unix/localhost:1234-1236",
47
expectProto: "unix",
48
expectAddrs: []string{"localhost:1234", "localhost:1235", "localhost:1236"},
49
},