• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dunglas / mercure / 19233086250

10 Nov 2025 01:22PM UTC coverage: 86.414% (+1.6%) from 84.853%
19233086250

push

github

web-flow
feat!: switch from Zap to log/slog (#1115)

* feat!: switch from Zap to log/slog

* add context to the API

* clean using a custom handler

* fix

* lint/fix

124 of 168 new or added lines in 15 files covered. (73.81%)

9 existing lines in 6 files now uncovered.

1730 of 2002 relevant lines covered (86.41%)

70.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.01
/handler_deprecated.go
1
//go:build deprecated_server
2

3
package mercure
4

5
import (
6
        "context"
7
        "errors"
8
        "fmt"
9
        "io/fs"
10
        "log/slog"
11
        "net/http"
12
        "os"
13
        "os/signal"
14

15
        "github.com/gorilla/handlers"
16
        "github.com/gorilla/mux"
17
        "github.com/rs/cors"
18
        "github.com/unrolled/secure"
19
        "golang.org/x/crypto/acme/autocert"
20
)
21

22
// Serve starts the HTTP server.
23
//
24
// Deprecated: use the Caddy server module or the standalone library instead.
25
func (h *Hub) Serve() { //nolint:funlen
8✔
26
        addr := h.config.GetString("addr")
8✔
27

8✔
28
        h.server = &http.Server{
8✔
29
                Addr:              addr,
8✔
30
                Handler:           h.baseHandler(),
8✔
31
                ReadTimeout:       h.config.GetDuration("read_timeout"),
8✔
32
                ReadHeaderTimeout: h.config.GetDuration("read_header_timeout"),
8✔
33
                WriteTimeout:      h.config.GetDuration("write_timeout"),
8✔
34
        }
8✔
35

8✔
36
        if _, ok := h.metrics.(*PrometheusMetrics); ok {
11✔
37
                addr := h.config.GetString("metrics_addr")
3✔
38

3✔
39
                h.metricsServer = &http.Server{
3✔
40
                        Addr:              addr,
3✔
41
                        Handler:           h.metricsHandler(),
3✔
42
                        ReadTimeout:       h.config.GetDuration("read_timeout"),
3✔
43
                        ReadHeaderTimeout: h.config.GetDuration("read_header_timeout"),
3✔
44
                        WriteTimeout:      h.config.GetDuration("write_timeout"),
3✔
45
                }
3✔
46

3✔
47
                h.logger.Info("Mercure metrics started", slog.String("addr", addr))
3✔
48

3✔
49
                go h.metricsServer.ListenAndServe()
3✔
50
        }
3✔
51

52
        acme := len(h.allowedHosts) > 0
8✔
53

8✔
54
        certFile := h.config.GetString("cert_file")
8✔
55
        keyFile := h.config.GetString("key_file")
8✔
56

8✔
57
        done := h.listenShutdown()
8✔
58

8✔
59
        var err error
8✔
60

8✔
61
        if !acme && certFile == "" && keyFile == "" { //nolint:nestif
13✔
62
                h.logger.Info("Mercure started", slog.String("protocol", "http"), slog.String("addr", addr))
5✔
63

5✔
64
                err = h.server.ListenAndServe()
5✔
65
        } else {
8✔
66
                // TLS
3✔
67
                if acme {
4✔
68
                        certManager := &autocert.Manager{
1✔
69
                                Prompt:     autocert.AcceptTOS,
1✔
70
                                HostPolicy: autocert.HostWhitelist(h.allowedHosts...),
1✔
71
                        }
1✔
72

1✔
73
                        acmeCertDir := h.config.GetString("acme_cert_dir")
1✔
74
                        if acmeCertDir != "" {
2✔
75
                                certManager.Cache = autocert.DirCache(acmeCertDir)
1✔
76
                        }
1✔
77

78
                        h.server.TLSConfig = certManager.TLSConfig()
1✔
79

1✔
80
                        // Mandatory for Let's Encrypt http-01 challenge
1✔
81
                        go http.ListenAndServe(h.config.GetString("acme_http01_addr"), certManager.HTTPHandler(nil)) //nolint:gosec
1✔
82
                }
83

84
                h.logger.Info("Mercure started", slog.String("protocol", "https"), slog.String("addr", addr))
3✔
85

3✔
86
                err = h.server.ListenAndServeTLS(certFile, keyFile)
3✔
87
        }
88

89
        if !errors.Is(err, http.ErrServerClosed) {
8✔
NEW
90
                h.logger.Error("Unexpected error", slog.Any("error", err))
×
91
        }
×
92

93
        <-done
8✔
94
}
95

96
// Deprecated: use the Caddy server module or the standalone library instead.
97
func (h *Hub) listenShutdown() <-chan struct{} {
8✔
98
        idleConnsClosed := make(chan struct{})
8✔
99

8✔
100
        h.server.RegisterOnShutdown(func() {
16✔
101
                select {
8✔
102
                case <-idleConnsClosed:
×
103
                default:
8✔
104
                        close(idleConnsClosed)
8✔
105
                }
106
        })
107

108
        go func() {
16✔
109
                sigint := make(chan os.Signal, 1)
8✔
110
                signal.Notify(sigint, os.Interrupt)
8✔
111
                <-sigint
8✔
112

8✔
113
                if err := h.server.Shutdown(context.Background()); err != nil {
8✔
NEW
114
                        h.logger.Error("Unexpected error during server shutdown", slog.Any("error", err))
×
115
                }
×
116

117
                if h.metricsServer != nil {
×
118
                        if err := h.metricsServer.Shutdown(context.Background()); err != nil {
×
NEW
119
                                h.logger.Error("Unexpected error during metrics server shutdown", slog.Any("error", err))
×
120
                        }
×
121
                }
122

NEW
123
                h.logger.Info("My Baby Shot Me Down")
×
124

×
125
                select {
×
126
                case <-idleConnsClosed:
×
127
                default:
×
128
                        close(idleConnsClosed)
×
129
                }
130
        }()
131

132
        return idleConnsClosed
8✔
133
}
134

135
// chainHandlers configures and chains handlers.
136
//
137
// Deprecated: use the Caddy server module or the standalone library instead.
138
func (h *Hub) chainHandlers() http.Handler { //nolint:funlen
8✔
139
        r := mux.NewRouter()
8✔
140
        h.registerSubscriptionHandlers(context.Background(), r)
8✔
141

8✔
142
        r.HandleFunc(defaultHubURL, h.SubscribeHandler).Methods(http.MethodGet, http.MethodHead)
8✔
143
        r.HandleFunc(defaultHubURL, h.PublishHandler).Methods(http.MethodPost)
8✔
144

8✔
145
        csp := "default-src 'self'"
8✔
146

8✔
147
        if h.demo {
9✔
148
                r.PathPrefix("/demo").HandlerFunc(h.Demo).Methods(http.MethodGet, http.MethodHead)
1✔
149
        }
1✔
150

151
        if h.ui {
9✔
152
                public, err := fs.Sub(uiContent, "public")
1✔
153
                if err != nil {
1✔
154
                        panic(err)
×
155
                }
156

157
                r.PathPrefix("/").Handler(http.FileServer(http.FS(public)))
1✔
158

1✔
159
                csp += " mercure.rocks cdn.jsdelivr.net"
1✔
160
        } else {
7✔
161
                r.HandleFunc("/", welcomeHandler).Methods(http.MethodGet, http.MethodHead)
7✔
162
        }
7✔
163

164
        secureMiddleware := secure.New(secure.Options{
8✔
165
                IsDevelopment:         h.debug,
8✔
166
                AllowedHosts:          h.allowedHosts,
8✔
167
                FrameDeny:             true,
8✔
168
                ContentTypeNosniff:    true,
8✔
169
                BrowserXssFilter:      true,
8✔
170
                ContentSecurityPolicy: csp,
8✔
171
        })
8✔
172

8✔
173
        var corsHandler http.Handler
8✔
174

8✔
175
        if len(h.corsOrigins) > 0 {
10✔
176
                corsHandler = cors.New(cors.Options{
2✔
177
                        AllowedOrigins:   h.corsOrigins,
2✔
178
                        AllowCredentials: true,
2✔
179
                        AllowedHeaders:   []string{"authorization", "cache-control", "last-event-id"},
2✔
180
                }).Handler(r)
2✔
181
        } else {
8✔
182
                corsHandler = r
6✔
183
        }
6✔
184

185
        var compressHandler http.Handler
8✔
186
        if h.config.GetBool("compress") {
10✔
187
                compressHandler = handlers.CompressHandler(corsHandler)
2✔
188
        } else {
8✔
189
                compressHandler = corsHandler
6✔
190
        }
6✔
191

192
        var useForwardedHeadersHandlers http.Handler
8✔
193
        if h.config.GetBool("use_forwarded_headers") {
8✔
UNCOV
194
                useForwardedHeadersHandlers = handlers.ProxyHeaders(compressHandler)
×
195
        } else {
8✔
196
                useForwardedHeadersHandlers = compressHandler
8✔
197
        }
8✔
198

199
        secureHandler := secureMiddleware.Handler(useForwardedHeadersHandlers)
8✔
200

8✔
201
        var loggingHandler http.Handler
8✔
202

8✔
203
        if h.logger.Enabled(context.Background(), slog.LevelError) {
8✔
UNCOV
204
                loggingHandler = handlers.CombinedLoggingHandler(os.Stderr, secureHandler)
×
205
        } else {
8✔
206
                loggingHandler = secureHandler
8✔
207
        }
8✔
208

209
        recoveryHandler := handlers.RecoveryHandler(
8✔
210
                handlers.RecoveryLogger(slogRecoveryHandlerLogger{h.logger}),
8✔
211
                handlers.PrintRecoveryStack(h.debug),
8✔
212
        )(loggingHandler)
8✔
213

8✔
214
        return recoveryHandler
8✔
215
}
216

217
// Deprecated: use the Caddy server module or the standalone library instead.
218
func (h *Hub) baseHandler() http.Handler {
8✔
219
        mainRouter := mux.NewRouter()
8✔
220
        mainRouter.UseEncodedPath()
8✔
221
        mainRouter.SkipClean(true)
8✔
222

8✔
223
        // Register /healthz (if enabled, in a way that doesn't pollute the HTTP logs).
8✔
224
        registerHealthz(mainRouter)
8✔
225

8✔
226
        handler := h.chainHandlers()
8✔
227
        mainRouter.PathPrefix("/").Handler(handler)
8✔
228

8✔
229
        return mainRouter
8✔
230
}
8✔
231

232
// Deprecated: use the Caddy server module or the standalone library instead.
233
func (h *Hub) metricsHandler() http.Handler {
3✔
234
        router := mux.NewRouter()
3✔
235

3✔
236
        registerHealthz(router)
3✔
237
        h.metrics.(*PrometheusMetrics).Register(router.PathPrefix("/").Subrouter())
3✔
238

3✔
239
        return router
3✔
240
}
3✔
241

242
// Deprecated: use the Caddy server module or the standalone library instead.
243
func registerHealthz(router *mux.Router) {
11✔
244
        router.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
13✔
245
                fmt.Fprint(w, "ok")
2✔
246
        }).Methods(http.MethodGet, http.MethodHead)
2✔
247
}
248

249
// Deprecated: use the Caddy server module or the standalone library instead.
250
func welcomeHandler(w http.ResponseWriter, _ *http.Request) {
5✔
251
        fmt.Fprint(w, `<!DOCTYPE html>
5✔
252
<title>Mercure Hub</title>
5✔
253
<h1>Welcome to <a href="https://mercure.rocks">Mercure</a>!</h1>`)
5✔
254
}
5✔
255

256
// Deprecated: use the Caddy server module or the standalone library instead.
257
type slogRecoveryHandlerLogger struct {
258
        logger *slog.Logger
259
}
260

NEW
261
func (l slogRecoveryHandlerLogger) Println(args ...any) {
×
NEW
262
        l.logger.Error(fmt.Sprint(args...))
×
UNCOV
263
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc