@marcel91200 / caddy Mirror · read-only
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 11, 01:00 UTC

master
go 186 lines 5.28 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package caddyhttp
16
17 import (
18 "context"
19 "fmt"
20 "io"
21 "net/http"
22 "net/http/httptest"
23 "sync"
24 "testing"
25 "time"
26
27 "github.com/caddyserver/caddy/v2"
28
29 "go.uber.org/zap"
30 "go.uber.org/zap/zapcore"
31 "go.uber.org/zap/zaptest/observer"
32 )
33
34 func TestStopWaitsForPreviousConfiguration(t *testing.T) {
35 for _, http2 := range []bool{false, true} {
36 for _, grace := range []time.Duration{0, 5 * time.Second} {
37 t.Run(fmt.Sprintf("http2=%t/grace=%s", http2, grace), func(t *testing.T) {
38 previous, response, release := appWithPendingResponse(t, http2)
39 previous.GracePeriod = caddy.Duration(grace)
40 if err := previous.stop(false); err != nil {
41 t.Fatal(err)
42 }
43
44 current := &App{logger: zap.NewNop()}
45 stopped := make(chan error, 1)
46 go func() { stopped <- current.stop(true) }()
47 select {
48 case err := <-stopped:
49 t.Fatalf("termination returned while the previous response was active: %v", err)
50 case <-time.After(50 * time.Millisecond):
51 }
52
53 release()
54 body, err := io.ReadAll(response.Body)
55 if err != nil {
56 t.Fatal(err)
57 }
58 if string(body) != "before\nafter\n" {
59 t.Fatalf("unexpected response body: %q", body)
60 }
61 select {
62 case err := <-stopped:
63 if err != nil {
64 t.Fatal(err)
65 }
66 case <-time.After(2 * time.Second):
67 t.Fatal("termination did not finish after the previous response completed")
68 }
69 })
70 }
71 }
72 }
73
74 func TestStopPreviousConfigurationGracePeriod(t *testing.T) {
75 previous, response, release := appWithPendingResponse(t, false)
76 if err := previous.stop(false); err != nil {
77 t.Fatal(err)
78 }
79
80 current := &App{GracePeriod: caddy.Duration(50 * time.Millisecond), logger: zap.NewNop()}
81 stopped := make(chan error, 1)
82 start := time.Now()
83 go func() { stopped <- current.stop(true) }()
84 select {
85 case err := <-stopped:
86 if err != nil {
87 t.Fatal(err)
88 }
89 case <-time.After(2 * time.Second):
90 t.Fatal("termination ignored its grace period while waiting for the previous configuration")
91 }
92 if elapsed := time.Since(start); elapsed < time.Duration(current.GracePeriod) {
93 t.Errorf("termination returned after %s, before its grace period expired", elapsed)
94 }
95
96 release()
97 if _, err := io.Copy(io.Discard, response.Body); err != nil {
98 t.Fatal(err)
99 }
100 }
101
102 func appWithPendingResponse(t *testing.T, http2 bool) (*App, *http.Response, func()) {
103 t.Helper()
104
105 released := make(chan struct{})
106 release := sync.OnceFunc(func() { close(released) })
107 server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
108 fmt.Fprintln(w, "before")
109 http.NewResponseController(w).Flush()
110 select {
111 case <-released:
112 fmt.Fprintln(w, "after")
113 case <-r.Context().Done():
114 }
115 }))
116 server.EnableHTTP2 = http2
117 server.StartTLS()
118 t.Cleanup(server.Close)
119 t.Cleanup(release)
120
121 request, err := http.NewRequestWithContext(t.Context(), http.MethodGet, server.URL, nil)
122 if err != nil {
123 t.Fatal(err)
124 }
125 response, err := server.Client().Do(request)
126 if err != nil {
127 t.Fatal(err)
128 }
129 t.Cleanup(func() { response.Body.Close() })
130 if http2 && response.ProtoMajor != 2 {
131 t.Fatalf("expected HTTP/2, got %s", response.Proto)
132 }
133 app := &App{
134 Servers: map[string]*Server{"test": {server: server.Config}},
135 logger: zap.NewNop(),
136 }
137 t.Cleanup(func() {
138 release()
139 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
140 defer cancel()
141 server.Config.Shutdown(ctx)
142 })
143 return app, response, release
144 }
145
146 // TestServerErrorLoggerLevels verifies that recovered net/http handler panics
147 // written to http.Server.ErrorLog surface at ERROR level (so they're visible
148 // at the default log level), while other standard library server messages stay
149 // at DEBUG. Regression test for #7923.
150 func TestServerErrorLoggerLevels(t *testing.T) {
151 for _, tc := range []struct {
152 name string
153 message string
154 wantLevel zapcore.Level
155 }{
156 {
157 name: "recovered handler panic logs at error",
158 message: "http: panic serving 127.0.0.1:12345: boom\ngoroutine 1 [running]:\nmain.handler()",
159 wantLevel: zapcore.ErrorLevel,
160 },
161 {
162 name: "other server message logs at debug",
163 message: "http: TLS handshake error from 127.0.0.1:12345: EOF",
164 wantLevel: zapcore.DebugLevel,
165 },
166 } {
167 t.Run(tc.name, func(t *testing.T) {
168 core, logs := observer.New(zapcore.DebugLevel)
169 serverLogger := serverErrorLogger(zap.New(core))
170
171 serverLogger.Print(tc.message)
172
173 entries := logs.All()
174 if len(entries) != 1 {
175 t.Fatalf("expected exactly 1 log entry, got %d", len(entries))
176 }
177 got := entries[0]
178 if got.Level != tc.wantLevel {
179 t.Errorf("expected level %s, got %s", tc.wantLevel, got.Level)
180 }
181 if got.Message != tc.message {
182 t.Errorf("expected message %q, got %q", tc.message, got.Message)
183 }
184 })
185 }
186 }