80
ctx.cleanupFuncs = append(ctx.cleanupFuncs, f)
81
}
82
83
-// LoadModule decodes rawMsg into a new instance of mod and
84
-// returns the value. If mod.New() does not return a pointer
85
-// value, it is converted to one so that it is unmarshaled
86
-// into the underlying concrete type. If mod.New is nil, an
87
-// error is returned. If the module implements Validator or
88
-// Provisioner interfaces, those methods are invoked to
89
-// ensure the module is fully configured and valid before
90
-// being used.
91
-func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{}, error) {
92
- modulesMu.Lock()
93
- mod, ok := modules[name]
94
- modulesMu.Unlock()
83
+// LoadModule loads the Caddy module(s) from the specified field of the parent struct
84
+// pointer and returns the loaded module(s). The struct pointer and its field name as
85
+// a string are necessary so that reflection can be used to read the struct tag on the
86
+// field to get the module namespace and inline module name key (if specified).
87
+//
88
+// The field can be any one of the supported raw module types: json.RawMessage,
89
+// []json.RawMessage, map[string]json.RawMessage, or []map[string]json.RawMessage.
90
+// ModuleMap may be used in place of map[string]json.RawMessage. The return value's
91
+// underlying type mirrors the input field's type:
92
+//
93
+// json.RawMessage => interface{}
94
+// []json.RawMessage => []interface{}
95
+// map[string]json.RawMessage => map[string]interface{}
96
+// []map[string]json.RawMessage => []map[string]interface{}
97
+//
98
+// The field must have a "caddy" struct tag in this format:
99
+//
100
+// caddy:"key1=val1 key2=val2"
101
+//
102
+// To load modules, a "namespace" key is required. For example, to load modules
103
+// in the "http.handlers" namespace, you'd put: `namespace=http.handlers` in the
104
+// Caddy struct tag.
105
+//
106
+// The module name must also be available. If the field type is a map or slice of maps,
107
+// then key is assumed to be the module name if an "inline_key" is NOT specified in the
108
+// caddy struct tag. In this case, the module name does NOT need to be specified in-line
109
+// with the module itself.
110
+//
111
+// If not a map, or if inline_key is non-empty, then the module name must be embedded
112
+// into the values, which must be objects; then there must be a key in those objects
113
+// where its associated value is the module name. This is called the "inline key",
114
+// meaning the key containing the module's name that is defined inline with the module
115
+// itself. You must specify the inline key in a struct tag, along with the namespace:
116
+//
117
+// caddy:"namespace=http.handlers inline_key=handler"
118
+//
119
+// This will look for a key/value pair like `"handler": "..."` in the json.RawMessage
120
+// in order to know the module name.
121
+//
122
+// To make use of the loaded module(s) (the return value), you will probably want
123
+// to type-assert each interface{} value(s) to the types that are useful to you
124
+// and store them on the same struct. Storing them on the same struct makes for
125
+// easy garbage collection when your host module is no longer needed.
126
+//
127
+// Loaded modules have already been provisioned and validated.
128
+func (ctx Context) LoadModule(structPointer interface{}, fieldName string) (interface{}, error) {
129
+ val := reflect.ValueOf(structPointer).Elem().FieldByName(fieldName)
130
+ typ := val.Type()
131
+
132
+ field, ok := reflect.TypeOf(structPointer).Elem().FieldByName(fieldName)
133
+ if !ok {
134
+ panic(fmt.Sprintf("field %s does not exist in %#v", fieldName, structPointer))
135
+ }
136
+
137
+ opts, err := ParseStructTag(field.Tag.Get("caddy"))
138
+ if err != nil {
139
+ panic(fmt.Sprintf("malformed tag on field %s: %v", fieldName, err))
140
+ }
141
+
142
+ moduleNamespace, ok := opts["namespace"]
143
+ if !ok {
144
+ panic(fmt.Sprintf("missing 'namespace' key in struct tag on field %s", fieldName))
145
+ }
146
+ inlineModuleKey := opts["inline_key"]
147
+
148
+ var result interface{}
149
+
150
+ switch val.Kind() {
151
+ case reflect.Slice:
152
+ if isJSONRawMessage(typ) {
153
+ // val is `json.RawMessage` ([]uint8 under the hood)
154
+
155
+ if inlineModuleKey == "" {
156
+ panic("unable to determine module name without inline_key when type is not a ModuleMap")
157
+ }
158
+ val, err := ctx.loadModuleInline(inlineModuleKey, moduleNamespace, val.Interface().(json.RawMessage))
159
+ if err != nil {
160
+ return nil, err
161
+ }
162
+ result = val
163
+
164
+ } else if isJSONRawMessage(typ.Elem()) {
165
+ // val is `[]json.RawMessage`
166
+
167
+ if inlineModuleKey == "" {
168
+ panic("unable to determine module name without inline_key because type is not a ModuleMap")
169
+ }
170
+ var all []interface{}
171
+ for i := 0; i < val.Len(); i++ {
172
+ val, err := ctx.loadModuleInline(inlineModuleKey, moduleNamespace, val.Index(i).Interface().(json.RawMessage))
173
+ if err != nil {
174
+ return nil, fmt.Errorf("position %d: %v", i, err)
175
+ }
176
+ all = append(all, val)
177
+ }
178
+ result = all
179
+
180
+ } else if isModuleMapType(typ.Elem()) {
181
+ // val is `[]map[string]json.RawMessage`
182
+
183
+ var all []map[string]interface{}
184
+ for i := 0; i < val.Len(); i++ {
185
+ thisSet, err := ctx.loadModulesFromSomeMap(moduleNamespace, inlineModuleKey, val.Index(i))
186
+ if err != nil {
187
+ return nil, err
188
+ }
189
+ all = append(all, thisSet)
190
+ }
191
+ result = all
192
+ }
193
+
194
+ case reflect.Map:
195
+ // val is a ModuleMap or some other kind of map
196
+ result, err = ctx.loadModulesFromSomeMap(moduleNamespace, inlineModuleKey, val)
197
+ if err != nil {
198
+ return nil, err
199
+ }
200
+
201
+ default:
202
+ return nil, fmt.Errorf("unrecognized type for module: %s", typ)
203
+ }
204
+
205
+ // we're done with the raw bytes; allow GC to deallocate
206
+ val.Set(reflect.Zero(typ))
207
+
208
+ return result, nil
209
+}
210
+
211
+// loadModulesFromSomeMap loads modules from val, which must be a type of map[string]interface{}.
212
+// Depending on inlineModuleKey, it will be interpeted as either a ModuleMap (key is the module
213
+// name) or as a regular map (key is not the module name, and module name is defined inline).
214
+func (ctx Context) loadModulesFromSomeMap(namespace, inlineModuleKey string, val reflect.Value) (map[string]interface{}, error) {
215
+ // if no inline_key is specified, then val must be a ModuleMap,
216
+ // where the key is the module name
217
+ if inlineModuleKey == "" {
218
+ if !isModuleMapType(val.Type()) {
219
+ panic(fmt.Sprintf("expected ModuleMap because inline_key is empty; but we do not recognize this type: %s", val.Type()))
220
+ }
221
+ return ctx.loadModuleMap(namespace, val)
222
+ }
223
+
224
+ // otherwise, val is a map with modules, but the module name is
225
+ // inline with each value (the key means something else)
226
+ return ctx.loadModulesFromRegularMap(namespace, inlineModuleKey, val)
227
+}
228
+
229
+// loadModulesFromRegularMap loads modules from val, where val is a map[string]json.RawMessage.
230
+// Map keys are NOT interpreted as module names, so module names are still expected to appear
231
+// inline with the objects.
232
+func (ctx Context) loadModulesFromRegularMap(namespace, inlineModuleKey string, val reflect.Value) (map[string]interface{}, error) {
233
+ mods := make(map[string]interface{})
234
+ iter := val.MapRange()
235
+ for iter.Next() {
236
+ k := iter.Key()
237
+ v := iter.Value()
238
+ mod, err := ctx.loadModuleInline(inlineModuleKey, namespace, v.Interface().(json.RawMessage))
239
+ if err != nil {
240
+ return nil, fmt.Errorf("key %s: %v", k, err)
241
+ }
242
+ mods[k.String()] = mod
243
+ }
244
+ return mods, nil
245
+}
246
+
247
+// loadModuleMap loads modules from a ModuleMap, i.e. map[string]interface{}, where the key is the
248
+// module name. With a module map, module names do not need to be defined inline with their values.
249
+func (ctx Context) loadModuleMap(namespace string, val reflect.Value) (map[string]interface{}, error) {
250
+ all := make(map[string]interface{})
251
+ iter := val.MapRange()
252
+ for iter.Next() {
253
+ k := iter.Key().Interface().(string)
254
+ v := iter.Value().Interface().(json.RawMessage)
255
+ moduleName := namespace + "." + k
256
+ if namespace == "" {
257
+ moduleName = k
258
+ }
259
+ val, err := ctx.LoadModuleByID(moduleName, v)
260
+ if err != nil {
261
+ return nil, fmt.Errorf("module name '%s': %v", k, err)
262
+ }
263
+ all[k] = val
264
+ }
265
+ return all, nil
266
+}
267
+
268
+// LoadModuleByID decodes rawMsg into a new instance of mod and
269
+// returns the value. If mod.New is nil, an error is returned.
270
+// If the module implements Validator or Provisioner interfaces,
271
+// those methods are invoked to ensure the module is fully
272
+// configured and valid before being used.
273
+//
274
+// This is a lower-level method and will usually not be called
275
+// directly by most modules. However, this method is useful when
276
+// dynamically loading/unloading modules in their own context,
277
+// like from embedded scripts, etc.
278
+func (ctx Context) LoadModuleByID(id string, rawMsg json.RawMessage) (interface{}, error) {
279
+ modulesMu.RLock()
280
+ mod, ok := modules[id]
281
+ modulesMu.RUnlock()
282
if !ok {
96
- return nil, fmt.Errorf("unknown module: %s", name)
283
+ return nil, fmt.Errorf("unknown module: %s", id)
284
}
285
286
if mod.New == nil {
100
- return nil, fmt.Errorf("module '%s' has no constructor", mod.Name)
287
+ return nil, fmt.Errorf("module '%s' has no constructor", mod.ID)
288
}
289
290
val := mod.New().(interface{})
295
if rv := reflect.ValueOf(val); rv.Kind() != reflect.Ptr {
296
log.Printf("[WARNING] ModuleInfo.New() for module '%s' did not return a pointer,"+
297
" so we are using reflection to make a pointer instead; please fix this by"+
111
- " using new(Type) or &Type notation in your module's New() function.", name)
298
+ " using new(Type) or &Type notation in your module's New() function.", id)
299
val = reflect.New(rv.Type()).Elem().Addr().Interface().(Module)
300
}
301
303
if len(rawMsg) > 0 {
304
err := strictUnmarshalJSON(rawMsg, &val)
305
if err != nil {
119
- return nil, fmt.Errorf("decoding module config: %s: %v", mod.Name, err)
306
+ return nil, fmt.Errorf("decoding module config: %s: %v", mod, err)
307
}
308
}
309
311
// returned module values are almost always type-asserted
312
// before being used, so a nil value would panic; and there
313
// is no good reason to explicitly declare null modules in
127
- // a config; it might be because the user is trying to
128
- // achieve a result they aren't expecting, which is a smell
314
+ // a config; it might be because the user is trying to achieve
315
+ // a result the developer isn't expecting, which is a smell
316
return nil, fmt.Errorf("module value cannot be null")
317
}
318
327
err = fmt.Errorf("%v; additionally, cleanup: %v", err, err2)
328
}
329
}
143
- return nil, fmt.Errorf("provision %s: %v", mod.Name, err)
330
+ return nil, fmt.Errorf("provision %s: %v", mod, err)
331
}
332
}
333
341
err = fmt.Errorf("%v; additionally, cleanup: %v", err, err2)
342
}
343
}
157
- return nil, fmt.Errorf("%s: invalid configuration: %v", mod.Name, err)
344
+ return nil, fmt.Errorf("%s: invalid configuration: %v", mod, err)
345
}
346
}
347
161
- ctx.moduleInstances[name] = append(ctx.moduleInstances[name], val)
348
+ ctx.moduleInstances[id] = append(ctx.moduleInstances[id], val)
349
350
return val, nil
351
}
352
166
-// LoadModuleInline loads a module from a JSON raw message which decodes
167
-// to a map[string]interface{}, where one of the keys is moduleNameKey
168
-// and the corresponding value is the module name as a string, which
169
-// can be found in the given scope.
170
-//
171
-// This allows modules to be decoded into their concrete types and
172
-// used when their names cannot be the unique key in a map, such as
173
-// when there are multiple instances in the map or it appears in an
174
-// array (where there are no custom keys). In other words, the key
175
-// containing the module name is treated special/separate from all
176
-// the other keys.
177
-func (ctx Context) LoadModuleInline(moduleNameKey, moduleScope string, raw json.RawMessage) (interface{}, error) {
353
+// loadModuleInline loads a module from a JSON raw message which decodes to
354
+// a map[string]interface{}, where one of the object keys is moduleNameKey
355
+// and the corresponding value is the module name (as a string) which can
356
+// be found in the given scope. In other words, the module name is declared
357
+// in-line with the module itself.
358
+//
359
+// This allows modules to be decoded into their concrete types and used when
360
+// their names cannot be the unique key in a map, such as when there are
361
+// multiple instances in the map or it appears in an array (where there are
362
+// no custom keys). In other words, the key containing the module name is
363
+// treated special/separate from all the other keys in the object.
364
+func (ctx Context) loadModuleInline(moduleNameKey, moduleScope string, raw json.RawMessage) (interface{}, error) {
365
moduleName, raw, err := getModuleNameInline(moduleNameKey, raw)
366
if err != nil {
367
return nil, err
368
}
369
183
- val, err := ctx.LoadModule(moduleScope+"."+moduleName, raw)
370
+ val, err := ctx.LoadModuleByID(moduleScope+"."+moduleName, raw)
371
if err != nil {
372
return nil, fmt.Errorf("loading module '%s': %v", moduleName, err)
373
}
382
if app, ok := ctx.cfg.apps[name]; ok {
383
return app, nil
384
}
198
- modVal, err := ctx.LoadModule(name, nil)
385
+ modVal, err := ctx.LoadModuleByID(name, nil)
386
if err != nil {
387
return nil, fmt.Errorf("instantiating new module %s: %v", name, err)
388
}