@marcel91200 / caddy Mirror · read-only / commits / a8dc73b4
Fork 0

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 },