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

dunglas / mercure / 18881598057

28 Oct 2025 04:16PM UTC coverage: 84.838% (+0.07%) from 84.764%
18881598057

push

github

dunglas
feat: add support for wildcard in publish origins

40 of 40 new or added lines in 5 files covered. (100.0%)

20 existing lines in 4 files now uncovered.

1701 of 2005 relevant lines covered (84.84%)

68.0 hits per line

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

86.22
/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
        "net/http"
11
        "os"
12
        "os/signal"
13

14
        "github.com/gorilla/handlers"
15
        "github.com/gorilla/mux"
16
        "github.com/rs/cors"
17
        "github.com/unrolled/secure"
18
        "go.uber.org/zap"
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
9✔
26
        addr := h.config.GetString("addr")
9✔
27

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

9✔
36
        if _, ok := h.metrics.(*PrometheusMetrics); ok {
12✔
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
                if c := h.logger.Check(zap.InfoLevel, "Mercure metrics started"); c != nil {
3✔
48
                        c.Write(zap.String("addr", addr))
×
UNCOV
49
                }
×
50

51
                go h.metricsServer.ListenAndServe()
3✔
52
        }
53

54
        acme := len(h.allowedHosts) > 0
9✔
55

9✔
56
        certFile := h.config.GetString("cert_file")
9✔
57
        keyFile := h.config.GetString("key_file")
9✔
58

9✔
59
        done := h.listenShutdown()
9✔
60

9✔
61
        var err error
9✔
62

9✔
63
        if !acme && certFile == "" && keyFile == "" { //nolint:nestif
15✔
64
                if c := h.logger.Check(zap.InfoLevel, "Mercure started"); c != nil {
7✔
65
                        c.Write(zap.String("protocol", "http"), zap.String("addr", addr))
1✔
66
                }
1✔
67

68
                err = h.server.ListenAndServe()
6✔
69
        } else {
3✔
70
                // TLS
3✔
71
                if acme {
4✔
72
                        certManager := &autocert.Manager{
1✔
73
                                Prompt:     autocert.AcceptTOS,
1✔
74
                                HostPolicy: autocert.HostWhitelist(h.allowedHosts...),
1✔
75
                        }
1✔
76

1✔
77
                        acmeCertDir := h.config.GetString("acme_cert_dir")
1✔
78
                        if acmeCertDir != "" {
2✔
79
                                certManager.Cache = autocert.DirCache(acmeCertDir)
1✔
80
                        }
1✔
81

82
                        h.server.TLSConfig = certManager.TLSConfig()
1✔
83

1✔
84
                        // Mandatory for Let's Encrypt http-01 challenge
1✔
85
                        go http.ListenAndServe(h.config.GetString("acme_http01_addr"), certManager.HTTPHandler(nil)) //nolint:gosec
1✔
86
                }
87

88
                if c := h.logger.Check(zap.InfoLevel, "Mercure started"); c != nil {
3✔
89
                        c.Write(zap.String("protocol", "https"), zap.String("addr", addr))
×
UNCOV
90
                }
×
91

92
                err = h.server.ListenAndServeTLS(certFile, keyFile)
3✔
93
        }
94

95
        if !errors.Is(err, http.ErrServerClosed) {
9✔
96
                if c := h.logger.Check(zap.ErrorLevel, "Unexpected error"); c != nil {
×
97
                        c.Write(zap.Error(err))
×
UNCOV
98
                }
×
99
        }
100

101
        <-done
9✔
102
}
103

104
// Deprecated: use the Caddy server module or the standalone library instead.
105
func (h *Hub) listenShutdown() <-chan struct{} {
9✔
106
        idleConnsClosed := make(chan struct{})
9✔
107

9✔
108
        h.server.RegisterOnShutdown(func() {
18✔
109
                select {
9✔
UNCOV
110
                case <-idleConnsClosed:
×
111
                default:
9✔
112
                        close(idleConnsClosed)
9✔
113
                }
114
        })
115

116
        go func() {
18✔
117
                sigint := make(chan os.Signal, 1)
9✔
118
                signal.Notify(sigint, os.Interrupt)
9✔
119
                <-sigint
9✔
120

9✔
121
                if err := h.server.Shutdown(context.Background()); err != nil {
9✔
122
                        if c := h.logger.Check(zap.ErrorLevel, "Unexpected error during server shutdown"); c != nil {
×
123
                                c.Write(zap.Error(err))
×
UNCOV
124
                        }
×
125
                }
126

127
                if h.metricsServer != nil {
×
128
                        if err := h.metricsServer.Shutdown(context.Background()); err != nil {
×
129
                                if c := h.logger.Check(zap.ErrorLevel, "Unexpected error during metrics server shutdown"); c != nil {
×
130
                                        c.Write(zap.Error(err))
×
UNCOV
131
                                }
×
132
                        }
133
                }
134

135
                if c := h.logger.Check(zap.InfoLevel, "My Baby Shot Me Down"); c != nil {
×
136
                        c.Write()
×
UNCOV
137
                }
×
138

139
                select {
×
140
                case <-idleConnsClosed:
×
141
                default:
×
UNCOV
142
                        close(idleConnsClosed)
×
143
                }
144
        }()
145

146
        return idleConnsClosed
9✔
147
}
148

149
// chainHandlers configures and chains handlers.
150
// Deprecated: use the Caddy server module or the standalone library instead.
151
func (h *Hub) chainHandlers() http.Handler { //nolint:funlen
9✔
152
        r := mux.NewRouter()
9✔
153
        h.registerSubscriptionHandlers(r)
9✔
154

9✔
155
        r.HandleFunc(defaultHubURL, h.SubscribeHandler).Methods(http.MethodGet, http.MethodHead)
9✔
156
        r.HandleFunc(defaultHubURL, h.PublishHandler).Methods(http.MethodPost)
9✔
157

9✔
158
        csp := "default-src 'self'"
9✔
159

9✔
160
        if h.demo {
10✔
161
                r.PathPrefix("/demo").HandlerFunc(h.Demo).Methods(http.MethodGet, http.MethodHead)
1✔
162
        }
1✔
163

164
        if h.ui {
10✔
165
                public, err := fs.Sub(uiContent, "public")
1✔
166
                if err != nil {
1✔
UNCOV
167
                        panic(err)
×
168
                }
169

170
                r.PathPrefix("/").Handler(http.FileServer(http.FS(public)))
1✔
171

1✔
172
                csp += " mercure.rocks cdn.jsdelivr.net"
1✔
173
        } else {
8✔
174
                r.HandleFunc("/", welcomeHandler).Methods(http.MethodGet, http.MethodHead)
8✔
175
        }
8✔
176

177
        secureMiddleware := secure.New(secure.Options{
9✔
178
                IsDevelopment:         h.debug,
9✔
179
                AllowedHosts:          h.allowedHosts,
9✔
180
                FrameDeny:             true,
9✔
181
                ContentTypeNosniff:    true,
9✔
182
                BrowserXssFilter:      true,
9✔
183
                ContentSecurityPolicy: csp,
9✔
184
        })
9✔
185

9✔
186
        var corsHandler http.Handler
9✔
187

9✔
188
        if len(h.corsOrigins) > 0 {
11✔
189
                corsHandler = cors.New(cors.Options{
2✔
190
                        AllowedOrigins:   h.corsOrigins,
2✔
191
                        AllowCredentials: true,
2✔
192
                        AllowedHeaders:   []string{"authorization", "cache-control", "last-event-id"},
2✔
193
                }).Handler(r)
2✔
194
        } else {
9✔
195
                corsHandler = r
7✔
196
        }
7✔
197

198
        var compressHandler http.Handler
9✔
199
        if h.config.GetBool("compress") {
11✔
200
                compressHandler = handlers.CompressHandler(corsHandler)
2✔
201
        } else {
9✔
202
                compressHandler = corsHandler
7✔
203
        }
7✔
204

205
        var useForwardedHeadersHandlers http.Handler
9✔
206
        if h.config.GetBool("use_forwarded_headers") {
10✔
207
                useForwardedHeadersHandlers = handlers.ProxyHeaders(compressHandler)
1✔
208
        } else {
9✔
209
                useForwardedHeadersHandlers = compressHandler
8✔
210
        }
8✔
211

212
        secureHandler := secureMiddleware.Handler(useForwardedHeadersHandlers)
9✔
213

9✔
214
        var loggingHandler http.Handler
9✔
215
        if h.logger != nil && h.logger.Level().Enabled(zap.FatalLevel) {
10✔
216
                loggingHandler = handlers.CombinedLoggingHandler(os.Stderr, secureHandler)
1✔
217
        } else {
9✔
218
                loggingHandler = secureHandler
8✔
219
        }
8✔
220

221
        recoveryHandler := handlers.RecoveryHandler(
9✔
222
                handlers.RecoveryLogger(zapRecoveryHandlerLogger{h.logger}),
9✔
223
                handlers.PrintRecoveryStack(h.debug),
9✔
224
        )(loggingHandler)
9✔
225

9✔
226
        return recoveryHandler
9✔
227
}
228

229
// Deprecated: use the Caddy server module or the standalone library instead.
230
func (h *Hub) baseHandler() http.Handler {
9✔
231
        mainRouter := mux.NewRouter()
9✔
232
        mainRouter.UseEncodedPath()
9✔
233
        mainRouter.SkipClean(true)
9✔
234

9✔
235
        // Register /healthz (if enabled, in a way that doesn't pollute the HTTP logs).
9✔
236
        registerHealthz(mainRouter)
9✔
237

9✔
238
        handler := h.chainHandlers()
9✔
239
        mainRouter.PathPrefix("/").Handler(handler)
9✔
240

9✔
241
        return mainRouter
9✔
242
}
9✔
243

244
// Deprecated: use the Caddy server module or the standalone library instead.
245
func (h *Hub) metricsHandler() http.Handler {
3✔
246
        router := mux.NewRouter()
3✔
247

3✔
248
        registerHealthz(router)
3✔
249
        h.metrics.(*PrometheusMetrics).Register(router.PathPrefix("/").Subrouter())
3✔
250

3✔
251
        return router
3✔
252
}
3✔
253

254
// Deprecated: use the Caddy server module or the standalone library instead.
255
func registerHealthz(router *mux.Router) {
12✔
256
        router.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
14✔
257
                fmt.Fprint(w, "ok")
2✔
258
        }).Methods(http.MethodGet, http.MethodHead)
2✔
259
}
260

261
// Deprecated: use the Caddy server module or the standalone library instead.
262
func welcomeHandler(w http.ResponseWriter, _ *http.Request) {
5✔
263
        fmt.Fprint(w, `<!DOCTYPE html>
5✔
264
<title>Mercure Hub</title>
5✔
265
<h1>Welcome to <a href="https://mercure.rocks">Mercure</a>!</h1>`)
5✔
266
}
5✔
267

268
// Deprecated: use the Caddy server module or the standalone library instead.
269
type zapRecoveryHandlerLogger struct {
270
        logger Logger
271
}
272

273
func (z zapRecoveryHandlerLogger) Println(args ...any) {
×
UNCOV
274
        z.logger.Error(fmt.Sprint(args...))
×
UNCOV
275
}
×
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