@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 356 lines 10.8 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 caddyevents
16
17 import (
18 "context"
19 "encoding/json"
20 "errors"
21 "fmt"
22 "strings"
23
24 "go.uber.org/zap"
25 "go.uber.org/zap/zapcore"
26
27 "github.com/caddyserver/caddy/v2"
28 )
29
30 func init() {
31 caddy.RegisterModule(App{})
32 }
33
34 // App implements a global eventing system within Caddy.
35 // Modules can emit and subscribe to events, providing
36 // hooks into deep parts of the code base that aren't
37 // otherwise accessible. Events provide information about
38 // what and when things are happening, and this facility
39 // allows handlers to take action when events occur,
40 // add information to the event's metadata, and even
41 // control program flow in some cases.
42 //
43 // Events are propagated in a DOM-like fashion. An event
44 // emitted from module `a.b.c` (the "origin") will first
45 // invoke handlers listening to `a.b.c`, then `a.b`,
46 // then `a`, then those listening regardless of origin.
47 // If a handler returns the special error Aborted, then
48 // propagation immediately stops and the event is marked
49 // as aborted. Emitters may optionally choose to adjust
50 // program flow based on an abort.
51 //
52 // Modules can subscribe to events by origin and/or name.
53 // A handler is invoked only if it is subscribed to the
54 // event by name and origin. Subscriptions should be
55 // registered during the provisioning phase, before apps
56 // are started.
57 //
58 // Event handlers are fired synchronously as part of the
59 // regular flow of the program. This allows event handlers
60 // to control the flow of the program if the origin permits
61 // it and also allows handlers to convey new information
62 // back into the origin module before it continues.
63 // In essence, event handlers are similar to HTTP
64 // middleware handlers.
65 //
66 // Event bindings/subscribers are unordered; i.e.
67 // event handlers are invoked in an arbitrary order.
68 // Event handlers should not rely on the logic of other
69 // handlers to succeed.
70 //
71 // The entirety of this app module is EXPERIMENTAL and
72 // subject to change. Pay attention to release notes.
73 type App struct {
74 // Subscriptions bind handlers to one or more events
75 // either globally or scoped to specific modules or module
76 // namespaces.
77 Subscriptions []*Subscription `json:"subscriptions,omitempty"`
78
79 // Map of event name to map of module ID/namespace to handlers
80 subscriptions map[string]map[caddy.ModuleID][]Handler
81
82 logger *zap.Logger
83 started bool
84 }
85
86 // Subscription represents binding of one or more handlers to
87 // one or more events.
88 type Subscription struct {
89 // The name(s) of the event(s) to bind to. Default: all events.
90 Events []string `json:"events,omitempty"`
91
92 // The ID or namespace of the module(s) from which events
93 // originate to listen to for events. Default: all modules.
94 //
95 // Events propagate up, so events emitted by module "a.b.c"
96 // will also trigger the event for "a.b" and "a". Thus, to
97 // receive all events from "a.b.c" and "a.b.d", for example,
98 // one can subscribe to either "a.b" or all of "a" entirely.
99 Modules []caddy.ModuleID `json:"modules,omitempty"`
100
101 // The event handler modules. These implement the actual
102 // behavior to invoke when an event occurs. At least one
103 // handler is required.
104 HandlersRaw []json.RawMessage `json:"handlers,omitempty" caddy:"namespace=events.handlers inline_key=handler"`
105
106 // The decoded handlers; Go code that is subscribing to
107 // an event should set this field directly; HandlersRaw
108 // is meant for JSON configuration to fill out this field.
109 Handlers []Handler `json:"-"`
110 }
111
112 // CaddyModule returns the Caddy module information.
113 func (App) CaddyModule() caddy.ModuleInfo {
114 return caddy.ModuleInfo{
115 ID: "events",
116 New: func() caddy.Module { return new(App) },
117 }
118 }
119
120 // Provision sets up the app.
121 func (app *App) Provision(ctx caddy.Context) error {
122 app.logger = ctx.Logger()
123 app.subscriptions = make(map[string]map[caddy.ModuleID][]Handler)
124
125 for _, sub := range app.Subscriptions {
126 if sub.HandlersRaw == nil {
127 continue
128 }
129 handlersIface, err := ctx.LoadModule(sub, "HandlersRaw")
130 if err != nil {
131 return fmt.Errorf("loading event subscriber modules: %v", err)
132 }
133 for _, h := range handlersIface.([]any) {
134 sub.Handlers = append(sub.Handlers, h.(Handler))
135 }
136 if len(sub.Handlers) == 0 {
137 // pointless to bind without any handlers
138 return fmt.Errorf("no handlers defined")
139 }
140 }
141
142 return nil
143 }
144
145 // Start runs the app.
146 func (app *App) Start() error {
147 for _, sub := range app.Subscriptions {
148 if err := app.Subscribe(sub); err != nil {
149 return err
150 }
151 }
152
153 app.started = true
154
155 return nil
156 }
157
158 // Stop gracefully shuts down the app.
159 func (app *App) Stop() error {
160 return nil
161 }
162
163 // Subscribe binds one or more event handlers to one or more events
164 // according to the subscription s. For now, subscriptions can only
165 // be created during the provision phase; new bindings cannot be
166 // created after the events app has started.
167 func (app *App) Subscribe(s *Subscription) error {
168 if app.started {
169 return fmt.Errorf("events already started; new subscriptions closed")
170 }
171
172 // handle special case of catch-alls (omission of event name or module space implies all)
173 if len(s.Events) == 0 {
174 s.Events = []string{""}
175 }
176 if len(s.Modules) == 0 {
177 s.Modules = []caddy.ModuleID{""}
178 }
179
180 for _, eventName := range s.Events {
181 if app.subscriptions[eventName] == nil {
182 app.subscriptions[eventName] = make(map[caddy.ModuleID][]Handler)
183 }
184 for _, originModule := range s.Modules {
185 app.subscriptions[eventName][originModule] = append(app.subscriptions[eventName][originModule], s.Handlers...)
186 }
187 }
188
189 return nil
190 }
191
192 // On is syntactic sugar for Subscribe() that binds a single handler
193 // to a single event from any module. If the eventName is empty string,
194 // it counts for all events.
195 func (app *App) On(eventName string, handler Handler) error {
196 return app.Subscribe(&Subscription{
197 Events: []string{eventName},
198 Handlers: []Handler{handler},
199 })
200 }
201
202 // ShouldEmit reports whether emitting the named event could be observed by
203 // anything: a handler subscribed to it by name or to all events, or the debug
204 // log that Emit writes for every event. Emitters that assemble event data
205 // before calling Emit can use it to skip that work; it is the same question
206 // Emit answers internally, exported so callers do not have to guess.
207 func (app *App) ShouldEmit(eventName string) bool {
208 return app.subscriptions[eventName] != nil ||
209 app.subscriptions[""] != nil ||
210 app.logger.Core().Enabled(zapcore.DebugLevel)
211 }
212
213 // Emit creates and dispatches an event named eventName to all relevant handlers with
214 // the metadata data. Events are emitted and propagated synchronously. The returned Event
215 // value will have any additional information from the invoked handlers.
216 //
217 // Note that the data map is not copied, for efficiency. After Emit() is called, the
218 // data passed in should not be changed in other goroutines.
219 func (app *App) Emit(ctx caddy.Context, eventName string, data map[string]any) caddy.Event {
220 e, err := caddy.NewEvent(ctx, eventName, data)
221 if err != nil {
222 app.logger.Error("failed to create event",
223 zap.String("name", eventName), zap.Error(err))
224 }
225
226 // bail out before deriving loggers and registering replacer values if
227 // nothing can observe this event: some events, such as
228 // tls_get_certificate, are emitted on every TLS handshake, where that
229 // work is significant and always wasted
230 if !app.ShouldEmit(eventName) {
231 return e
232 }
233
234 logger := app.logger.With(zap.String("name", eventName))
235
236 var originModule caddy.ModuleInfo
237 var originModuleID caddy.ModuleID
238 var originModuleName string
239 if origin := e.Origin(); origin != nil {
240 originModule = origin.CaddyModule()
241 originModuleID = originModule.ID
242 originModuleName = originModule.String()
243 }
244
245 logger = logger.With(
246 zap.String("id", e.ID().String()),
247 zap.String("origin", originModuleName))
248
249 // add event info to replacer, make sure it's in the context
250 repl, ok := ctx.Context.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
251 if !ok {
252 repl = caddy.NewReplacer()
253 ctx.Context = context.WithValue(ctx.Context, caddy.ReplacerCtxKey, repl)
254 }
255 repl.Map(func(key string) (any, bool) {
256 switch key {
257 case "event":
258 return e, true
259 case "event.id":
260 return e.ID(), true
261 case "event.name":
262 return e.Name(), true
263 case "event.time":
264 return e.Timestamp(), true
265 case "event.time_unix":
266 return e.Timestamp().UnixMilli(), true
267 case "event.module":
268 return originModuleID, true
269 case "event.data":
270 return e.Data, true
271 }
272
273 if after, ok0 := strings.CutPrefix(key, "event.data."); ok0 {
274 key = after
275 if val, ok := e.Data[key]; ok {
276 return val, true
277 }
278 }
279
280 return nil, false
281 })
282
283 logger = logger.WithLazy(zap.Any("data", e.Data))
284
285 logger.Debug("event")
286
287 // invoke handlers bound to the event by name and also all events; this for loop
288 // iterates twice at most: once for the event name, once for "" (all events)
289 for {
290 moduleID := originModuleID
291
292 // implement propagation up the module tree (i.e. start with "a.b.c" then "a.b" then "a" then "")
293 for {
294 if app.subscriptions[eventName] == nil {
295 break // shortcut if event not bound at all
296 }
297
298 for _, handler := range app.subscriptions[eventName][moduleID] {
299 select {
300 case <-ctx.Done():
301 logger.Error("context canceled; event handling stopped")
302 return e
303 default:
304 }
305
306 // this log can be a useful sanity check to ensure your handlers are in fact being invoked
307 // (see https://github.com/mholt/caddy-events-exec/issues/6)
308 logger.Debug("invoking subscribed handler",
309 zap.String("subscribed_to", eventName),
310 zap.Any("handler", handler))
311
312 if err := handler.Handle(ctx, e); err != nil {
313 aborted := errors.Is(err, caddy.ErrEventAborted)
314
315 logger.Error("handler error",
316 zap.Error(err),
317 zap.Bool("aborted", aborted))
318
319 if aborted {
320 e.Aborted = err
321 return e
322 }
323 }
324 }
325
326 if moduleID == "" {
327 break
328 }
329 lastDot := strings.LastIndex(string(moduleID), ".")
330 if lastDot < 0 {
331 moduleID = "" // include handlers bound to events regardless of module
332 } else {
333 moduleID = moduleID[:lastDot]
334 }
335 }
336
337 // include handlers listening to all events
338 if eventName == "" {
339 break
340 }
341 eventName = ""
342 }
343
344 return e
345 }
346
347 // Handler is a type that can handle events.
348 type Handler interface {
349 Handle(context.Context, caddy.Event) error
350 }
351
352 // Interface guards
353 var (
354 _ caddy.App = (*App)(nil)
355 _ caddy.Provisioner = (*App)(nil)
356 )