@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 218 lines 5.88 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 integration
16
17 import (
18 "bufio"
19 "bytes"
20 "encoding/json"
21 "io"
22 "net/http"
23 "strings"
24 "sync"
25 "testing"
26 "time"
27
28 "github.com/caddyserver/caddy/v2"
29 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
30
31 // register the standard modules (http app, file log writer, etc.)
32 _ "github.com/caddyserver/caddy/v2/modules/standard"
33 )
34
35 const (
36 // panicTestHandlerID is the module ID of the test-only handler that panics.
37 panicTestHandlerID = "http.handlers.panic_test"
38 // panicLogPrefix is the prefix Go's net/http server writes to
39 // http.Server.ErrorLog when it recovers a handler panic. It mirrors the
40 // unexported production constant caddyhttp.stdlibLogPrefixPanic.
41 panicLogPrefix = "http: panic serving"
42 // panicErrorLevel is the zap level string the JSON encoder emits for
43 // entries logged at error level.
44 panicErrorLevel = "error"
45 // panicListenAddr is the HTTP listener for the panic route.
46 panicListenAddr = ":9080"
47 // panicRequestURL hits the panic route.
48 panicRequestURL = "http://localhost:9080/"
49 // panicLogPollInterval is how often the test re-reads the log file while
50 // waiting for the panic entry to be flushed to disk.
51 panicLogPollInterval = 50 * time.Millisecond
52 // panicLogPollTimeout bounds how long the test waits for the entry.
53 panicLogPollTimeout = 5 * time.Second
54 )
55
56 // panicHandler is a test-only HTTP handler module that panics in ServeHTTP
57 // with a non-ErrAbortHandler value, so Go's net/http server recovers it and
58 // writes an "http: panic serving" line to http.Server.ErrorLog.
59 type panicHandler struct{}
60
61 type panicLogWriter struct{}
62
63 type panicLogBuffer struct {
64 sync.Mutex
65 bytes.Buffer
66 }
67
68 var panicLogs panicLogBuffer
69
70 func (panicHandler) CaddyModule() caddy.ModuleInfo {
71 return caddy.ModuleInfo{
72 ID: panicTestHandlerID,
73 New: func() caddy.Module { return new(panicHandler) },
74 }
75 }
76
77 func (panicHandler) ServeHTTP(http.ResponseWriter, *http.Request, caddyhttp.Handler) error {
78 panic("boom from panic_test handler")
79 }
80
81 func (panicLogWriter) CaddyModule() caddy.ModuleInfo {
82 return caddy.ModuleInfo{
83 ID: "caddy.logging.writers.panic_test",
84 New: func() caddy.Module { return new(panicLogWriter) },
85 }
86 }
87
88 func (panicLogWriter) String() string {
89 return "panic test log"
90 }
91
92 func (panicLogWriter) WriterKey() string {
93 return "panic_test"
94 }
95
96 func (panicLogWriter) OpenWriter() (io.WriteCloser, error) {
97 return &panicLogs, nil
98 }
99
100 func (b *panicLogBuffer) Write(p []byte) (int, error) {
101 b.Lock()
102 defer b.Unlock()
103 return b.Buffer.Write(p)
104 }
105
106 func (*panicLogBuffer) Close() error {
107 return nil
108 }
109
110 func (b *panicLogBuffer) reset() {
111 b.Lock()
112 defer b.Unlock()
113 b.Buffer.Reset()
114 }
115
116 func (b *panicLogBuffer) snapshot() []byte {
117 b.Lock()
118 defer b.Unlock()
119 return bytes.Clone(b.Buffer.Bytes())
120 }
121
122 func init() {
123 caddy.RegisterModule(panicHandler{})
124 caddy.RegisterModule(panicLogWriter{})
125 }
126
127 // TestHandlerPanicLogsAtError boots a real Caddy HTTP server whose route panics
128 // while serving a request, and asserts that the recovered panic surfaces in
129 // Caddy's structured logs at ERROR level (so it stays visible at the default
130 // log level). Booting exercises the App.Start() server-boot wiring that routes
131 // http.Server.ErrorLog through Caddy's structured logger. Regression test for
132 // https://github.com/caddyserver/caddy/issues/7923.
133 func TestHandlerPanicLogsAtError(t *testing.T) {
134 panicLogs.reset()
135
136 // Boot a real Caddy instance. caddy.Run provisions and calls App.Start
137 // synchronously, so the panic-log wiring runs before we send the request.
138 config := `{
139 "admin": {"disabled": true},
140 "logging": {
141 "logs": {
142 "panic_test": {
143 "writer": {"output": "panic_test"},
144 "encoder": {"format": "json"},
145 "level": "DEBUG",
146 "include": ["http"]
147 }
148 }
149 },
150 "apps": {
151 "http": {
152 "grace_period": 1,
153 "servers": {
154 "srv0": {
155 "listen": ["` + panicListenAddr + `"],
156 "automatic_https": {"disable": true},
157 "routes": [
158 {"handle": [{"handler": "panic_test"}]}
159 ]
160 }
161 }
162 }
163 }
164 }`
165 if err := caddy.Load([]byte(config), true); err != nil {
166 t.Fatalf("failed to load caddy config: %v", err)
167 }
168 t.Cleanup(func() {
169 if err := caddy.Stop(); err != nil {
170 t.Errorf("failed to stop caddy: %v", err)
171 }
172 })
173
174 // The panic aborts the connection, so the client sees an error; that's
175 // expected. We only care that the server logged the panic.
176 resp, err := http.Get(panicRequestURL)
177 if err == nil {
178 resp.Body.Close()
179 }
180
181 if !waitForPanicLog(t) {
182 t.Fatalf("did not find an error-level %q entry", panicLogPrefix)
183 }
184 }
185
186 // waitForPanicLog polls the log output until it contains a JSON entry logged
187 // at error level whose message begins with the net/http panic prefix, or the
188 // timeout elapses.
189 func waitForPanicLog(t *testing.T) bool {
190 t.Helper()
191 deadline := time.Now().Add(panicLogPollTimeout)
192 for time.Now().Before(deadline) {
193 if scanForPanicEntry(t) {
194 return true
195 }
196 time.Sleep(panicLogPollInterval)
197 }
198 return false
199 }
200
201 func scanForPanicEntry(t *testing.T) bool {
202 t.Helper()
203
204 scanner := bufio.NewScanner(bytes.NewReader(panicLogs.snapshot()))
205 for scanner.Scan() {
206 var entry struct {
207 Level string `json:"level"`
208 Msg string `json:"msg"`
209 }
210 if err := json.Unmarshal(scanner.Bytes(), &entry); err != nil {
211 continue
212 }
213 if entry.Level == panicErrorLevel && strings.HasPrefix(entry.Msg, panicLogPrefix) {
214 return true
215 }
216 }
217 return false
218 }