passenger 5.1.7 → 5.1.8
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of passenger might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG +13 -2
- data/CONTRIBUTING.md +1 -1
- data/build/agent.rb +1 -1
- data/build/cxx_tests.rb +6 -0
- data/build/support/cxx_dependency_map.rb +1286 -391
- data/build/support/general.rb +0 -26
- data/resources/templates/standalone/rails_asset_pipeline.erb +2 -2
- data/src/agent/Core/ApiServer.h +49 -44
- data/src/agent/Core/ApplicationPool/Pool.h +1 -1
- data/src/agent/Core/ApplicationPool/Process.h +1 -1
- data/src/agent/Core/ApplicationPool/Socket.h +1 -1
- data/src/agent/Core/Controller.h +16 -8
- data/src/agent/Core/Controller/CheckoutSession.cpp +1 -1
- data/src/agent/Core/Controller/Config.cpp +68 -0
- data/src/agent/Core/Controller/Config.h +70 -34
- data/src/agent/Core/Controller/ForwardResponse.cpp +5 -5
- data/src/agent/Core/Controller/Hooks.cpp +5 -14
- data/src/agent/Core/Controller/Implementation.cpp +1 -1
- data/src/agent/Core/Controller/InitRequest.cpp +31 -29
- data/src/agent/Core/Controller/InitializationAndShutdown.cpp +4 -4
- data/src/agent/Core/Controller/InternalUtils.cpp +3 -3
- data/src/agent/Core/Controller/Miscellaneous.cpp +1 -1
- data/src/agent/Core/Controller/Request.h +2 -2
- data/src/agent/Core/Controller/SendRequest.cpp +5 -5
- data/src/agent/Core/Controller/StateInspection.cpp +1 -1
- data/src/agent/Core/Controller/TurboCaching.h +2 -2
- data/src/agent/Core/CoreMain.cpp +2 -2
- data/src/agent/Core/ResponseCache.h +3 -3
- data/src/agent/Core/SpawningKit/BackgroundIOCapturer.h +3 -3
- data/src/agent/Core/SpawningKit/DirectSpawner.h +2 -2
- data/src/agent/Core/SpawningKit/PipeWatcher.h +3 -3
- data/src/agent/Core/SpawningKit/SmartSpawner.h +2 -2
- data/src/agent/Core/SpawningKit/Spawner.h +1 -1
- data/src/agent/Core/UnionStation/Connection.h +1 -1
- data/src/agent/Core/UnionStation/Context.h +1 -1
- data/src/agent/Core/UnionStation/Transaction.h +1 -1
- data/src/agent/Shared/ApiServerUtils.h +73 -27
- data/src/agent/Shared/Base.cpp +61 -73
- data/src/agent/UstRouter/ApiServer.h +34 -45
- data/src/agent/UstRouter/Controller.h +86 -60
- data/src/agent/UstRouter/RemoteSender.h +1 -1
- data/src/agent/UstRouter/RemoteSink.h +1 -1
- data/src/agent/Watchdog/ApiServer.h +42 -50
- data/src/agent/Watchdog/WatchdogMain.cpp +1 -1
- data/src/apache2_module/Configuration.hpp +1 -1
- data/src/apache2_module/Hooks.cpp +27 -13
- data/src/cxx_supportlib/AppTypes.h +1 -1
- data/src/cxx_supportlib/BackgroundEventLoop.cpp +1 -1
- data/src/cxx_supportlib/ConfigKit/AsyncUtils.h +86 -0
- data/src/cxx_supportlib/ConfigKit/Common.h +6 -3
- data/src/cxx_supportlib/ConfigKit/IN_PRACTICE.md +1039 -0
- data/src/cxx_supportlib/ConfigKit/README.md +112 -497
- data/src/cxx_supportlib/ConfigKit/Schema.h +78 -15
- data/src/cxx_supportlib/ConfigKit/Store.h +272 -53
- data/src/cxx_supportlib/ConfigKit/SubComponentUtils.h +59 -0
- data/src/cxx_supportlib/ConfigKit/Utils.h +26 -65
- data/src/cxx_supportlib/ConfigKit/ValidationUtils.h +69 -0
- data/src/cxx_supportlib/ConfigKit/VariantMapUtils.h +7 -4
- data/src/cxx_supportlib/Constants.h +4 -1
- data/src/cxx_supportlib/Crypto.cpp +1 -1
- data/src/cxx_supportlib/DataStructures/StringKeyTable.h +26 -7
- data/src/cxx_supportlib/FileDescriptor.h +1 -1
- data/src/cxx_supportlib/Hooks.h +1 -1
- data/src/cxx_supportlib/LoggingKit/Assert.h +130 -0
- data/src/cxx_supportlib/LoggingKit/Config.h +97 -0
- data/src/cxx_supportlib/LoggingKit/Context.h +94 -0
- data/src/cxx_supportlib/LoggingKit/Forward.h +95 -0
- data/src/cxx_supportlib/LoggingKit/Implementation.cpp +695 -0
- data/src/cxx_supportlib/LoggingKit/Logging.h +204 -0
- data/src/cxx_supportlib/LoggingKit/LoggingKit.h +33 -0
- data/src/cxx_supportlib/LveLoggingDecorator.h +1 -1
- data/src/cxx_supportlib/MemoryKit/mbuf.cpp +1 -1
- data/src/cxx_supportlib/RandomGenerator.h +1 -1
- data/src/cxx_supportlib/SafeLibev.h +1 -1
- data/src/cxx_supportlib/ServerKit/AcceptLoadBalancer.h +1 -1
- data/src/cxx_supportlib/ServerKit/Channel.h +1 -1
- data/src/cxx_supportlib/ServerKit/FileBufferedChannel.h +1 -1
- data/src/cxx_supportlib/ServerKit/FileBufferedFdSinkChannel.h +1 -1
- data/src/cxx_supportlib/ServerKit/HttpChunkedBodyParser.h +1 -1
- data/src/cxx_supportlib/ServerKit/HttpHeaderParser.h +1 -1
- data/src/cxx_supportlib/ServerKit/HttpServer.h +48 -15
- data/src/cxx_supportlib/ServerKit/Server.h +79 -52
- data/src/cxx_supportlib/StaticString.h +12 -0
- data/src/cxx_supportlib/Utils/Curl.h +16 -0
- data/src/cxx_supportlib/Utils/FastStringStream.h +6 -1
- data/src/cxx_supportlib/Utils/ScopeGuard.h +1 -1
- data/src/cxx_supportlib/Utils/StrIntUtils.cpp +2 -19
- data/src/cxx_supportlib/WatchdogLauncher.h +3 -2
- data/src/ruby_supportlib/phusion_passenger.rb +3 -3
- data/src/ruby_supportlib/phusion_passenger/common_library.rb +12 -12
- data/src/ruby_supportlib/phusion_passenger/constants.rb +6 -3
- data/src/ruby_supportlib/phusion_passenger/standalone/start_command.rb +1 -0
- data/src/ruby_supportlib/phusion_passenger/standalone/stop_command.rb +1 -0
- metadata +14 -4
- data/src/cxx_supportlib/Logging.cpp +0 -295
- data/src/cxx_supportlib/Logging.h +0 -385
@@ -29,14 +29,12 @@ ConfigKit is a configuration management system that lets you define configuratio
|
|
29
29
|
- [Fetching data](#fetching-data)
|
30
30
|
- [Default values](#default-values)
|
31
31
|
- [Inspecting all data](#inspecting-all-data)
|
32
|
-
- [
|
33
|
-
|
34
|
-
- [
|
35
|
-
- [
|
36
|
-
|
37
|
-
|
38
|
-
- [Main function example](#main-function-example)
|
39
|
-
- [Putting it all together: asynchronous version](#putting-it-all-together-asynchronous-version)
|
32
|
+
- [Inspection filters](#inspection-filters)
|
33
|
+
- [Normalizing data](#normalizing-data)
|
34
|
+
- [Normalization example 1](#normalization-example-1)
|
35
|
+
- [Normalization example 2](#normalization-example-2)
|
36
|
+
- [Normalizers and validation](#normalizers-and-validation)
|
37
|
+
- [ConfigKit in practice & design patterns](#configkit-in-practice--design-patterns)
|
40
38
|
|
41
39
|
<!-- /MarkdownTOC -->
|
42
40
|
|
@@ -80,7 +78,7 @@ ConfigKit implements these aspects, and also provides various other useful featu
|
|
80
78
|
|
81
79
|
### Unifying configuration management
|
82
80
|
|
83
|
-
Another challenge pertains having different ways to configure a component. Should components be configured using getters and setters for each option? Or should they have a single method that accepts a struct (or some kind of key-value map) that specifies multiple options? Should components be configurable at all after construction
|
81
|
+
Another challenge pertains having different ways to configure a component. Should components be configured using getters and setters for each option? Or should they have a single method that accepts a struct (or some kind of key-value map) that specifies multiple options? Should components be configurable at all after construction? It would be great if we have a unified answer for all components in Passenger.
|
84
82
|
|
85
83
|
ConfigKit provides this unified answer:
|
86
84
|
|
@@ -96,7 +94,10 @@ ConfigKit provides this unified answer:
|
|
96
94
|
|
97
95
|
So we want some kind of key-value data structure. JSON is a pretty popular format nowadays, and is likely to be the format used in the I/O channel, so may as well optimize for JSON.
|
98
96
|
|
99
|
-
|
97
|
+
- JSON's various data types allow us to easily describe complex configuration settings.
|
98
|
+
- JSON is a widely-accepted format.
|
99
|
+
|
100
|
+
ConfigKit backs these answers with code that helps you implement these principles, as well as with [documented design patterns](IN_PRACTICE.md) that provide guidance.
|
100
101
|
|
101
102
|
## Status inside the Passenger codebase
|
102
103
|
|
@@ -106,17 +107,17 @@ At the time of writing (25 Feb 2017), ConfigKit was just introduced, so these pr
|
|
106
107
|
|
107
108
|
### ConfigKit::Schema
|
108
109
|
|
109
|
-
Everything starts with `ConfigKit::Schema`. This is a class that lets you define a schema of supported configuration keys, their types and other properties like default values. Default values may either be static or dynamically calculated. `ConfigKit::Schema`
|
110
|
+
Everything starts with `ConfigKit::Schema`. This is a class that lets you define a schema of supported configuration keys, their types and other properties like default values. Default values may either be static or dynamically calculated. The type information defined in a `ConfigKit::Schema` allows data validation against the schema.
|
110
111
|
|
111
112
|
### ConfigKit::Store
|
112
113
|
|
113
|
-
|
114
|
+
`ConfigKit::Store` is a class that stores configuration values in such a way that it respects a schema. The values supplied to and stored in `ConfigKit::Store` are JSON values (i.e. of the `Json::Value` type), although Store uses the schema to validate that you are actually putting the right JSON types in the Store.
|
114
115
|
|
115
116
|
`ConfigKit::Store` also keeps track of which values are explicitly supplied and which ones are not.
|
116
117
|
|
117
118
|
### Translators
|
118
119
|
|
119
|
-
And finally there are "translator" classes: `ConfigKit::TableTranslator` and `ConfigKit::PrefixTranslator`. The role of translators are described in
|
120
|
+
And finally there are "translator" classes: `ConfigKit::TableTranslator` and `ConfigKit::PrefixTranslator`. The role of translators are described in `IN_PRACTICE.md`, section "The special problem of conflicting overlapping configuration names and translation".
|
120
121
|
|
121
122
|
## Using the schema
|
122
123
|
|
@@ -136,11 +137,14 @@ schema.add("bar", ConfigKit::FLOAT_TYPE, ConfigKit::OPTIONAL);
|
|
136
137
|
// An optional integer key, with default value 123.
|
137
138
|
schema.add("baz", ConfigKit::INTEGER_TYPE, ConfigKit::OPTIONAL, 123);
|
138
139
|
|
140
|
+
// An optional string key, without default value, marked as containing a secret.
|
141
|
+
schema.add("password", ConfigKit::STRING_TYPE, ConfigKit::OPTIONAL | ConfigKit::SECRET);
|
142
|
+
|
139
143
|
// Call this when done, otherwise the object cannot be used yet.
|
140
144
|
schema.finalize();
|
141
145
|
~~~
|
142
146
|
|
143
|
-
The second one, and the one we recommend, is to subclass ConfigKit::Schema and to add the definitions inside the subclass's constructor. It will become apparent in
|
147
|
+
The second one, and the one we recommend, is to subclass ConfigKit::Schema and to add the definitions inside the subclass's constructor. It will become apparent in `IN_PRACTICE.md`, section "Synchronous examples" why this is the recommended approach.
|
144
148
|
|
145
149
|
~~~c++
|
146
150
|
struct YourSchema: public ConfigKit::Schema {
|
@@ -149,6 +153,7 @@ struct YourSchema: public ConfigKit::Schema {
|
|
149
153
|
add("foo", STRING_TYPE, REQUIRED);
|
150
154
|
add("bar", FLOAT_TYPE, OPTIONAL);
|
151
155
|
add("baz", INT_TYPE, OPTIONAL, 123);
|
156
|
+
add("password", STRING_TYPE, OPTIONAL | SECRET);
|
152
157
|
finalize();
|
153
158
|
}
|
154
159
|
};
|
@@ -162,13 +167,14 @@ YourSchema schema;
|
|
162
167
|
The following types are available:
|
163
168
|
|
164
169
|
* `STRING_TYPE` -- a string.
|
165
|
-
* `PASSWORD_TYPE` -- a password string. Unlike `STRING_TYPE`, the value of password fields will be filtered out in the output generated by `ConfigKit::Store::inspect()`. Learn more about `inspect()` in [Using the store -- Inspecting all data](#inspecting-all-data).
|
166
170
|
* `INT_TYPE` -- a signed integer.
|
167
171
|
* `UINT_TYPE` -- an unsigned integer.
|
168
172
|
* `FLOAT_TYPE` -- a floating point number.
|
169
173
|
* `BOOL_TYPE` -- a boolean.
|
170
174
|
* `ARRAY_TYPE` -- an generic array. May contain any values.
|
171
175
|
* `STRING_ARRAY_TYPE` -- an array of strings.
|
176
|
+
* `OBJECT_TYPE` -- a generic JSON object. May contain any values.
|
177
|
+
* `ANY_TYPE` -- any JSON value.
|
172
178
|
|
173
179
|
#### Flags
|
174
180
|
|
@@ -176,6 +182,7 @@ The following types are available:
|
|
176
182
|
* `OPTIONAL` -- this field is optional. Mutually exclusive with `REQUIRED`.
|
177
183
|
* `CACHE_DEFAULT_VALUE` -- use in combination with [dynamic default values](#defining-default-values). When this flag is set, the value returned by the dynamic value function is cached so that the function won't be called over and over again.
|
178
184
|
* `READ_ONLY` -- this field can only be set once. Only the first `ConfigKit::Store::update()` call actually updates the value; subsequent calls won't. Learn more about `update()` in [Using the store -- Putting data in the store](#putting-data-in-the-store).
|
185
|
+
* `SECRET` -- this field contains a secret. `ConfigKit::Store::previewUpdate()` and `ConfigKit::Store::inspect()` will filter out the values of such fields. Learn more this in [Using the store -- Inspecting all data](#inspecting-all-data).
|
179
186
|
|
180
187
|
### Defining default values
|
181
188
|
|
@@ -215,6 +222,11 @@ schema.addValidator(myValidator);
|
|
215
222
|
schema.finalize();
|
216
223
|
~~~
|
217
224
|
|
225
|
+
Miscellaneous notes about custom validators:
|
226
|
+
|
227
|
+
- They are always run, even if the normal type validation fails. For example if the caller tries to set the "foo" key to an array value (which is incompatible with the string type), then `myValidator` will still be called.
|
228
|
+
- All registered validators are called. A validator cannot prevent other validators from running.
|
229
|
+
|
218
230
|
### Inspecting the schema
|
219
231
|
|
220
232
|
You can inspect the schema using the `inspect()` method. It returns a Json::Value in the following format:
|
@@ -224,22 +236,28 @@ You can inspect the schema using the `inspect()` method. It returns a Json::Valu
|
|
224
236
|
"foo": {
|
225
237
|
"type": "string",
|
226
238
|
"required": true,
|
227
|
-
"has_default_value":
|
239
|
+
"has_default_value": "static",
|
240
|
+
"default_value": "hello"
|
228
241
|
},
|
229
242
|
"bar": {
|
230
243
|
"type": "float"
|
231
244
|
},
|
232
245
|
"baz": {
|
233
246
|
"type": "integer"
|
247
|
+
},
|
248
|
+
"password": {
|
249
|
+
"type": "string",
|
250
|
+
"secret": true
|
234
251
|
}
|
235
252
|
}
|
236
253
|
~~~
|
237
254
|
|
238
255
|
Description of the members:
|
239
256
|
|
240
|
-
- `type`: the schema definition's type. Could be one of "string", "integer", "unsigned integer", "float" or "
|
257
|
+
- `type`: the schema definition's type. Could be one of "string", "integer", "unsigned integer", "float", "boolean", "array", "array of strings", "object" or "any".
|
241
258
|
- `required`: whether this key is required.
|
242
|
-
- `has_default_value`:
|
259
|
+
- `has_default_value`: "static" if a static a default value is defined, "dynamic" if a dynamic default value is defined.
|
260
|
+
- `default_value`: the static default value. This field is absent when there is no default value, or if the default value is dynamic.
|
243
261
|
|
244
262
|
## Using the store
|
245
263
|
|
@@ -346,8 +364,9 @@ store.get("unknown").isNull(); // => true
|
|
346
364
|
|
347
365
|
### Inspecting all data
|
348
366
|
|
349
|
-
You can fetch an overview of all data in the store using `inspect()`.
|
350
|
-
|
367
|
+
You can fetch an overview of all data in the store using `inspect()`. This function is normally used to allow users of a component to inspect the configuration options set for that component, without allowing them direct access to the embedded store.
|
368
|
+
|
369
|
+
This function will return a Json::Value in the following format:
|
351
370
|
|
352
371
|
~~~javascript
|
353
372
|
// Assuming we are using the store that went through
|
@@ -369,6 +388,11 @@ This will return a Json::Value in the following format:
|
|
369
388
|
"default_value": 123,
|
370
389
|
"effective_value": 123,
|
371
390
|
// ...members from ConfigKit::Schema::inspect() go here...
|
391
|
+
},
|
392
|
+
"password": {
|
393
|
+
"user_value": "[FILTERED]",
|
394
|
+
"effective_value": "[FILTERED]",
|
395
|
+
// ...members from ConfigKit::Schema::inspect() go here...
|
372
396
|
}
|
373
397
|
}
|
374
398
|
~~~
|
@@ -379,6 +403,8 @@ Description of the members:
|
|
379
403
|
- `default_value`: the default value as defined in the schema. May be absent.
|
380
404
|
- `effective_value`: the effective value, i.e. the value that `get()` will return.
|
381
405
|
|
406
|
+
Note that `inspect()` filters out the values of fields with the `SECRET` flag (by setting the returned value to `"[FILTERED]"`), except for null values.
|
407
|
+
|
382
408
|
If you want to fetch the effective values only, then use `inspectEffectiveValues()`:
|
383
409
|
|
384
410
|
~~~javascript
|
@@ -392,529 +418,118 @@ If you want to fetch the effective values only, then use `inspectEffectiveValues
|
|
392
418
|
}
|
393
419
|
~~~
|
394
420
|
|
395
|
-
|
396
|
-
|
397
|
-
Now that you've learned how to use ConfigKit by itself, how does fit in the bigger picture? This section describes good practices and design patterns can that can be used throughout the overall Passenger C++ codebase. At the time of writing (25 Feb 2017), ConfigKit was just introduced, so these practices and patterns aren't yet used everywhere, but the long-term plan is to adopt these practices/patterns throughout the entire codebase.
|
398
|
-
|
399
|
-
Let's demonstrate the good practices and design patterns through a number of annotated example classes:
|
400
|
-
|
401
|
-
- `SecurityChecker` checks whether the given URL is secure to connect to, by checking a database of vulnerable sites. This example demonstrates basic usage of ConfigKit in a low-level component.
|
402
|
-
- `DnsQuerier` looks up DNS information for a given URL, from multiple DNS servers. Because the algorithm that DnsQuerier uses is a performance-critical hot path (or so we claim for the sake of the example), it should cache certain configuration values in a variables instead of looking up the config store over and over, because the latter involve unnecessary hash table lookups. This example demonstrates caching of configuration values. More generally, it demonstrates how to perform arbitrary operations necessary for applying a configuration change.
|
403
|
-
- `Downloader` is a high-level class for downloading a specific URL. Under the hood it utilizes `SecurityChecker` and `DnsQuerier`. This example demonstrates how to combine its own configuration with the configuration of multiple lower-level classes.
|
421
|
+
### Inspection filters
|
404
422
|
|
405
|
-
|
423
|
+
Since `inspect()` is usually used to allow users of a component to inspect that component's configuration, you may run into situations where you want the inspected return value to be different from its actual value. Inspection filters allow you to transform `inspect()` results.
|
406
424
|
|
407
|
-
|
425
|
+
A typical use case for inspection filters can be found in LoggingKit. One can configure LoggingKit to log to a specific file descriptor of an open log file. The format is like this:
|
408
426
|
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
- `inspectConfig()`
|
414
|
-
|
415
|
-
These methods are further explained in the code example
|
416
|
-
|
417
|
-
~~~c++
|
418
|
-
class SecurityChecker {
|
419
|
-
private:
|
420
|
-
// This is the internal configuration store. This is also the
|
421
|
-
// recommended naming scheme: `config`.
|
422
|
-
ConfigKit::Store config;
|
423
|
-
|
424
|
-
public:
|
425
|
-
// This defines the SecurityChecker's configuration schema.
|
426
|
-
// This is also the recommended naming scheme:
|
427
|
-
// <YourComponentName>::Schema.
|
428
|
-
struct Schema: public ConfigKit::Schema {
|
429
|
-
Schema() {
|
430
|
-
using namespace ConfigKit;
|
431
|
-
add("db_path", STRING_TYPE, REQUIRED);
|
432
|
-
add("url", STRING_TYPE, REQUIRED);
|
433
|
-
add("timeout", INT_TYPE, OPTIONAL, 60);
|
434
|
-
finalize();
|
435
|
-
}
|
436
|
-
};
|
437
|
-
|
438
|
-
// The constructor takes a Schema so that we can initialize the
|
439
|
-
// configuration store. It should also immediately accept some
|
440
|
-
// initial configuration.
|
441
|
-
//
|
442
|
-
// You might wonder, how about instead of taking a Schema as a parameter,
|
443
|
-
// we define the Schema as a global variable? After all, a
|
444
|
-
// ConfigKit::Schema is immutable after finalization, and different
|
445
|
-
// instances of <YourComponentName>::Schema contain the same content.
|
446
|
-
//
|
447
|
-
// Defining as a global variable is *also* a valid approach, but requires
|
448
|
-
// you to declare it inside a .cpp file and adding that file to the linker
|
449
|
-
// invocation. It's up to you really. I've found taking a schema as a
|
450
|
-
// parameter to be the easiest.
|
451
|
-
SecurityChecker(const Schema &schema, const Json::Value &initialConfig)
|
452
|
-
: config(schema)
|
453
|
-
{
|
454
|
-
vector<ConfigKit::Error> errors;
|
455
|
-
|
456
|
-
if (!config.update(initialConfig, errors)) {
|
457
|
-
throw ArgumentException("Invalid initial configuration: "
|
458
|
-
+ toString(errors));
|
459
|
-
}
|
460
|
-
}
|
461
|
-
|
462
|
-
// This method allows checking whether the given configuration updates
|
463
|
-
// would result in any validation errors, without actually changing the
|
464
|
-
// configuration.
|
465
|
-
//
|
466
|
-
// Every configurable component should define such a method, because
|
467
|
-
// higher-level components need this method in order to make configuration
|
468
|
-
// updates across multiple low-level components transactional. You
|
469
|
-
// will learn more about this in the Downloader example.
|
470
|
-
Json::Value previewConfigUpdate(const Json::Value &updates,
|
471
|
-
vector<ConfigKit::Error> &errors)
|
472
|
-
{
|
473
|
-
// In low-level components, it's as simple as forwarding
|
474
|
-
// the call to the configuration store.
|
475
|
-
return config.previewUpdate(updates, errors);
|
476
|
-
}
|
477
|
-
|
478
|
-
// This method actually configures the component.
|
479
|
-
bool configure(const Json::Value &updates, vector<ConfigKit::Error> &errors) {
|
480
|
-
// In low-level components, it's as simple as forwarding
|
481
|
-
// the call to the configuration store.
|
482
|
-
return config.update(updates, errors);
|
483
|
-
}
|
484
|
-
|
485
|
-
// This method inspects the component's configuration.
|
486
|
-
Json::Value inspectConfig() const {
|
487
|
-
// In low-level components, it's as simple as forwarding
|
488
|
-
// the call to the configuration store.
|
489
|
-
return config.inspect();
|
490
|
-
}
|
491
|
-
|
492
|
-
bool check() const {
|
493
|
-
// Fictional code here for performing the lookup.
|
494
|
-
openDatabase(config["db_path"].asString());
|
495
|
-
Entry entry = lookupEntry(config["url"].asString(),
|
496
|
-
config["timeout"].asInt());
|
497
|
-
closeDatabase();
|
498
|
-
return !entry.isNull();
|
499
|
-
}
|
500
|
-
};
|
501
|
-
~~~
|
502
|
-
|
503
|
-
Use SecurityChecker like this:
|
504
|
-
|
505
|
-
~~~c++
|
506
|
-
SecurityChecker::Schema schema;
|
507
|
-
Json::Value config;
|
508
|
-
config["db_path"] = "/db";
|
509
|
-
config["url"] = "http://www.google.com";
|
510
|
-
|
511
|
-
// Initiate SecurityChecker with initial configuration
|
512
|
-
SecurityChecker securityChecker(schema, config);
|
513
|
-
securityChecker.check();
|
514
|
-
|
515
|
-
// Change configuration
|
516
|
-
vector<ConfigKit::Error> errors;
|
517
|
-
config["url"] = "http://www.example.com";
|
518
|
-
if (!securityChecker.configure(config, errors)) {
|
519
|
-
cout << "Configuration change failed: " << toString(errors) << endl;
|
427
|
+
~~~json
|
428
|
+
{
|
429
|
+
"path": "/foo.log",
|
430
|
+
"fd": 12
|
520
431
|
}
|
521
|
-
|
522
|
-
// Inspect configuration
|
523
|
-
cout << "Final configuration:" << endl;
|
524
|
-
cout << securityChecker.inspectConfig().toStyledString() << endl;
|
525
432
|
~~~
|
526
433
|
|
527
|
-
|
434
|
+
LoggingKit will internally take over ownership of the file descriptor and will perform a bunch of actions on the fd, such as redirecting stderr to that fd and closing the original fd. Because of this, the fd value is only valid at the time of configuration; it makes no sense to output the fd value in `inspect()`. Inspect filters allows LoggingKit to filter out the "fd" field when `store.inspect()` is called, but the "fd" field can still be accessed internally by LoggingKit by calling `store["target"]["fd"]`.
|
528
435
|
|
529
|
-
|
436
|
+
An inspect filter is a function takes a value and returns a transformed value. It is installed by calling `setInspectFilter()` on the object returned by `schema.add()`, like this:
|
530
437
|
|
531
438
|
~~~c++
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
// Caches the "url" configuration value.
|
538
|
-
string url;
|
539
|
-
|
540
|
-
// Populates the cache using values from the configuration store.
|
541
|
-
void updateConfigCache() {
|
542
|
-
url = config["url"].asString();
|
543
|
-
}
|
544
|
-
|
545
|
-
public:
|
546
|
-
// The schema, same as with SecurityChecker.
|
547
|
-
struct Schema: public ConfigKit::Schema {
|
548
|
-
Schema() {
|
549
|
-
using namespace ConfigKit;
|
550
|
-
add("url", STRING_TYPE, REQUIRED);
|
551
|
-
add("timeout", INT_TYPE, OPTIONAL, 60);
|
552
|
-
finalize();
|
553
|
-
}
|
554
|
-
};
|
555
|
-
|
556
|
-
// The constructor, same as with SecurityChecker with only one change:
|
557
|
-
// it populates the configuration cache before returning.
|
558
|
-
DnsQuerier(const Schema &schema, const Json::Value &initialConfig)
|
559
|
-
: config(schema)
|
560
|
-
{
|
561
|
-
vector<ConfigKit::Error> errors;
|
562
|
-
|
563
|
-
if (!config.update(initialConfig, errors)) {
|
564
|
-
throw ArgumentException("Invalid initial configuration: "
|
565
|
-
+ toString(errors));
|
566
|
-
}
|
567
|
-
updateConfigCache();
|
568
|
-
}
|
569
|
-
|
570
|
-
// Same as with SecurityChecker.
|
571
|
-
Json::Value previewConfigUpdate(const Json::Value &updates,
|
572
|
-
vector<ConfigKit::Error> &errors)
|
573
|
-
{
|
574
|
-
return config.previewUpdate(updates, errors);
|
575
|
-
}
|
576
|
-
|
577
|
-
// Configures the DnsQuerier. Same as with SecurityChecker with only one
|
578
|
-
// modification: it populates the configuration cache if updating succeeds.
|
579
|
-
bool configure(const Json::Value &updates, vector<ConfigKit::Error> &errors) {
|
580
|
-
if (config.update(updates, errors)) {
|
581
|
-
updateConfigCache();
|
582
|
-
// In addition to updating the config cache, this is
|
583
|
-
// the right place for performing any other operations
|
584
|
-
// necessary for applying a configuration change.
|
585
|
-
return true;
|
586
|
-
} else {
|
587
|
-
return false;
|
588
|
-
}
|
589
|
-
}
|
439
|
+
static Json::Value filterTargetFd(const Json::Value &value) {
|
440
|
+
Json::Value result = value;
|
441
|
+
result.removeMember("fd");
|
442
|
+
return result;
|
443
|
+
}
|
590
444
|
|
591
|
-
|
592
|
-
Json::Value inspectConfig() const {
|
593
|
-
return config.inspect();
|
594
|
-
}
|
445
|
+
ConfigKit::Schema schema;
|
595
446
|
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
for (unsigned i = 0; i < 1000; i++) {
|
600
|
-
// Fictional hot code path that requires the url config option.
|
601
|
-
queryNextDnsServer(url);
|
602
|
-
receiveResponse();
|
603
|
-
}
|
604
|
-
return result;
|
605
|
-
}
|
606
|
-
};
|
447
|
+
schema.add("target", ANY_TYPE, OPTIONAL).
|
448
|
+
setInspectFilter(filterTargetFd);
|
449
|
+
schema.finalize();
|
607
450
|
~~~
|
608
451
|
|
609
|
-
|
452
|
+
Note that inspect filter is called *after* [normalizers](#normalizing-data), so `value` refers to a normalized value.
|
610
453
|
|
611
|
-
|
454
|
+
## Normalizing data
|
612
455
|
|
613
|
-
|
456
|
+
You sometimes may want to allow users to supply data in multiple formats. Normalizers allow you to transform user-supplied data in a canonical format, so that your configuration value handling code only has to deal with data in the canonical format instead of all possibly allowed formats.
|
614
457
|
|
615
|
-
|
458
|
+
The examples below demonstrate two possible use cases and how to implement them.
|
616
459
|
|
617
|
-
|
460
|
+
### Normalization example 1
|
618
461
|
|
619
|
-
|
462
|
+
Suppose that you have a "target" config option, which can be in one of these formats:
|
620
463
|
|
621
|
-
|
464
|
+
1. `"/filename"`
|
465
|
+
2. `{ "path": "/filename" }` (semantics equivalent to 1)
|
466
|
+
3. `{ "stderr": true }`
|
622
467
|
|
623
|
-
|
468
|
+
You can write a normalizer that transforms format 1 into format 2. That way, no matter whether the user has actually supplied format 1, 2 or 3, your config handling code only has to deal with format 2 and 3.
|
624
469
|
|
625
|
-
|
626
|
-
- Prefixing the subcomponents' options. The `ConfigKit::PrefixTranslator` class implements this translation strategy.
|
470
|
+
A normalizer is a function that accepts a JSON document of effective values (in the format outputted by `ConfigKit::Store::inspectEffectiveValues()`). It is expected to return either Json::nullValue (indicating that no normalization work needs to be done), or a JSON object containing proposed normalization changes.
|
627
471
|
|
628
|
-
|
472
|
+
Normalizers are added to the corresponding schema.
|
629
473
|
|
630
474
|
~~~c++
|
631
|
-
|
632
|
-
|
633
|
-
// The subcomponents used by Downloader.
|
634
|
-
SecurityChecker securityChecker;
|
635
|
-
DnsQuerier dnsQuerier;
|
636
|
-
|
637
|
-
// The internal configuration store. As explained, this also contains (a
|
638
|
-
// copy of) the configuration options pertaining SecurityChecker and the
|
639
|
-
// DnsQuerier.
|
640
|
-
ConfigKit::Store config;
|
475
|
+
static Json::Value myNormalizer(const Json::Value &effectiveValues) {
|
476
|
+
Json::Value updates(Json::objectValue);
|
641
477
|
|
642
|
-
|
643
|
-
|
644
|
-
// includes not only options directly pertaining Downloader itself, but also
|
645
|
-
// options the subcomponents.
|
646
|
-
struct Schema: public ConfigKit::Schema {
|
647
|
-
// For each subcomponent that Downloader uses, we define a struct member
|
648
|
-
// that contains that subcomponent's schema, as well as a translation table
|
649
|
-
// for mapping Downloader's config keys to the subcomponent's config keys.
|
650
|
-
struct {
|
651
|
-
SecurityChecker::Schema schema;
|
652
|
-
ConfigKit::TableTranslator translator;
|
653
|
-
} securityChecker;
|
654
|
-
struct {
|
655
|
-
DnsQuerier::Schema schema;
|
656
|
-
ConfigKit::TableTranslator translator;
|
657
|
-
} dnsQuerier;
|
658
|
-
|
659
|
-
Schema() {
|
660
|
-
using namespace ConfigKit;
|
661
|
-
|
662
|
-
// Here we define how to map Downloader configuration keys to
|
663
|
-
// SecurityChecker configuration keys. We add the SecurityChecker's
|
664
|
-
// configuration schema to our own, taking into account the
|
665
|
-
// translations.
|
666
|
-
//
|
667
|
-
// Note that everything not defined in these translation mapping
|
668
|
-
// definitions are simply not translated (i.e. left as-is). So
|
669
|
-
// the Downloader's "url" option maps directly to SecurityChecker's
|
670
|
-
// "url" option.
|
671
|
-
securityChecker.translator.add("security_checker_db_path", "db_path");
|
672
|
-
securityChecker.translator.add("security_checker_timeout", "timeout");
|
673
|
-
securityChecker.translator.finalize();
|
674
|
-
addSubSchema(securityChecker.schema, securityChecker.translator);
|
675
|
-
|
676
|
-
// Ditto for DnsQuerier.
|
677
|
-
dnsQuerier.translator.add("dns_timeout", "timeout");
|
678
|
-
dnsQuerier.translator.finalize();
|
679
|
-
addSubSchema(dnsQuerier.schema, dnsQuerier.translator);
|
680
|
-
|
681
|
-
// Here we define Downloader's own configuration keys.
|
682
|
-
add("download_timeout", INT_TYPE, OPTIONAL, 60);
|
683
|
-
finalize();
|
684
|
-
}
|
685
|
-
};
|
686
|
-
|
687
|
-
// The constructor creates subcomponents, passing to them translated
|
688
|
-
// versions of the initial configuration passed.
|
689
|
-
Downloader(const Schema &schema, const Json::Value &initialConfig)
|
690
|
-
: securityChecker(schema.securityChecker.schema,
|
691
|
-
schema.securityChecker.translator.translate(initialConfig)),
|
692
|
-
dnsQuerier(schema.dnsQuerier.schema,
|
693
|
-
schema.dnsQuerier.translator.translate(initialConfig)),
|
694
|
-
config(schema)
|
695
|
-
{
|
696
|
-
vector<ConfigKit::Error> errors;
|
697
|
-
|
698
|
-
if (!config.update(initialConfig, errors)) {
|
699
|
-
throw ArgumentException("Invalid initial configuration: "
|
700
|
-
+ toString(errors));
|
701
|
-
}
|
478
|
+
if (effectiveValues["target"].isString()) {
|
479
|
+
updates["target"]["path"] = effectiveValues["target"];
|
702
480
|
}
|
703
481
|
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
cout << "Downloading " << config["url"].asString() << endl;
|
708
|
-
securityChecker.check();
|
709
|
-
dnsQuerier.query();
|
710
|
-
|
711
|
-
openSocket(config["url"].asString());
|
712
|
-
sendRequest();
|
713
|
-
receiveData(config["download_timeout"].asInt());
|
714
|
-
closeSocket();
|
715
|
-
}
|
716
|
-
|
717
|
-
// In addition to calling previewUpdate() on the internal configuration
|
718
|
-
// store, we also perform similar operations on our subcomponents.
|
719
|
-
// We use the `ConfigKit::previewConfigUpdateSubComponent()` utility
|
720
|
-
// function to achieve this, passing to it a corresponding translator.
|
721
|
-
// This function assumes that the subcomponent implements the
|
722
|
-
// `previewConfigUpdate()` method.
|
723
|
-
Json::Value previewConfigUpdate(const Json::Value &updates,
|
724
|
-
vector<ConfigKit::Error> &errors)
|
725
|
-
{
|
726
|
-
using namespace ConfigKit;
|
727
|
-
const Schema &schema = static_cast<const Schema &>(config.getSchema());
|
482
|
+
return updates;
|
483
|
+
}
|
728
484
|
|
729
|
-
previewConfigUpdateSubComponent(securityChecker, updates,
|
730
|
-
schema.securityChecker.translator, errors);
|
731
|
-
previewConfigUpdateSubComponent(dnsQuerier, updates,
|
732
|
-
schema.dnsQuerier.translator, errors);
|
733
|
-
return config.previewUpdate(updates, errors);
|
734
|
-
}
|
735
485
|
|
736
|
-
|
737
|
-
// configure the subcomponents. But we only do this after having verified
|
738
|
-
// that *all* subcomponents (as well as Downloader itself) successfully
|
739
|
-
// validates of the new configuration data. This is how we make
|
740
|
-
// `configure()` *transactional*: if we don't do this then we can end up in
|
741
|
-
// a situation where one subcomponent is configured, but another is not.
|
742
|
-
//
|
743
|
-
// We use the `ConfigKit::configureSubComponent()` utility function to
|
744
|
-
// achieve this, passing it a corresponding translator. This function
|
745
|
-
// assumes that the subcomponent implements the `configure()` method.
|
746
|
-
bool configure(const Json::Value &updates, vector<ConfigKit::Error> &errors) {
|
747
|
-
using namespace ConfigKit;
|
748
|
-
const Schema &schema = static_cast<const Schema &>(config.getSchema());
|
749
|
-
|
750
|
-
previewConfigUpdate(updates, errors);
|
751
|
-
|
752
|
-
if (errors.empty()) {
|
753
|
-
configureSubComponent(securityChecker, updates,
|
754
|
-
schema.securityChecker.translator, errors);
|
755
|
-
configureSubComponent(dnsQuerier, updates,
|
756
|
-
schema.dnsQuerier.translator, errors);
|
757
|
-
config.update(updates, errors);
|
758
|
-
}
|
759
|
-
|
760
|
-
if (errors.empty()) {
|
761
|
-
// In addition to updating the subcomponents and the
|
762
|
-
// internal configuration store, this is the right
|
763
|
-
// place for performing any other operations
|
764
|
-
// necessary for applying a configuration change.
|
765
|
-
}
|
766
|
-
|
767
|
-
return errors.empty();
|
768
|
-
}
|
486
|
+
ConfigKit::Schema schema;
|
769
487
|
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
}
|
775
|
-
};
|
488
|
+
schema.add("target", ANY_TYPE, OPTIONAL);
|
489
|
+
schema.addValidator(validateThatTargetIsStringOrObject);
|
490
|
+
schema.addNormalizer(myNormalizer);
|
491
|
+
schema.finalize();
|
776
492
|
~~~
|
777
493
|
|
778
|
-
###
|
494
|
+
### Normalization example 2
|
779
495
|
|
780
|
-
|
496
|
+
Suppose that you have a "security" config option that accepts this format:
|
781
497
|
|
782
|
-
~~~
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
config["url"] = "http://www.google.com";
|
788
|
-
config["security_checker_db_path"] = "/db";
|
789
|
-
config["dns_timeout"] = 30;
|
790
|
-
config["download_timeout"] = 20;
|
791
|
-
|
792
|
-
// Instantiate and print schema
|
793
|
-
Downloader::Schema schema;
|
794
|
-
cout << "Configuration schema:" << endl;
|
795
|
-
cout << schema.inspect().toStyledString() << endl;
|
796
|
-
|
797
|
-
// Instantiate a Downloader and perform a download
|
798
|
-
Downloader downloader(schema, config);
|
799
|
-
downloader.download();
|
800
|
-
|
801
|
-
// Change configuration, perform another download
|
802
|
-
cout << "Changing configuration" << endl;
|
803
|
-
vector<ConfigKit::Error> errors;
|
804
|
-
config["url"] = "http://www.slashdot.org";
|
805
|
-
if (!downloader.configure(config, errors)) {
|
806
|
-
cout << "Configure failed!" << endl;
|
807
|
-
}
|
808
|
-
downloader.download();
|
809
|
-
|
810
|
-
// Print final configuration
|
811
|
-
cout << "Final configuration:" << endl;
|
812
|
-
cout << downloader.inspectConfig().toStyledString() << endl;
|
813
|
-
|
814
|
-
return 0;
|
498
|
+
~~~json
|
499
|
+
{
|
500
|
+
"username": "a string", // required
|
501
|
+
"password": "a string", // required
|
502
|
+
"level": "full" | "readonly" // optional; default value: "full"
|
815
503
|
}
|
816
504
|
~~~
|
817
505
|
|
818
|
-
|
819
|
-
|
820
|
-
The above examples demonstrate how things would work with synchronous components. What about components that deal with asynchronous I/O? They usually run inside an event loop, and all access to their internal data should be performed from the event loop.
|
821
|
-
|
822
|
-
Our recommendation is that you implement synchronous as well as asynchronous versions of the `previewConfigUpdate()`, `configure()`, and `inspectConfig()` methods. The synchronous versions must be called from the event loop. The asynchronous versions...
|
823
|
-
|
824
|
-
- accept a callback which is to be called with the result.
|
825
|
-
- schedule an operation via the event loop the perform the equivalent synchronous operation and call the callback.
|
826
|
-
- are to be thread-safe.
|
827
|
-
|
828
|
-
The following example demonstrates how SecurityChecker would look like if introduces asynchronous methods as described above. Other classes, like Downloader, should also be modified in a similar manner.
|
506
|
+
If the user did not specify "level", then will want to automatically insert "level: full".
|
829
507
|
|
830
508
|
~~~c++
|
831
|
-
|
832
|
-
|
833
|
-
EventLoop &eventLoop;
|
834
|
-
|
835
|
-
// No change from synchronous version.
|
836
|
-
ConfigKit::Store config;
|
509
|
+
static Json::Value myNormalizer(const Json::Value &effectiveValues) {
|
510
|
+
Json::Value updates(Json::objectValue);
|
837
511
|
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
Schema() {
|
842
|
-
using namespace ConfigKit;
|
843
|
-
add("db_path", STRING_TYPE, REQUIRED);
|
844
|
-
add("url", STRING_TYPE, REQUIRED);
|
845
|
-
add("timeout", INTEGER_TYPE, OPTIONAL, 60);
|
846
|
-
finalize();
|
847
|
-
}
|
848
|
-
};
|
849
|
-
|
850
|
-
// Only change from synchronous version is that we now accept
|
851
|
-
// an event loop object.
|
852
|
-
SecurityChecker(const Schema &schema, const EventLoopType &_eventLoop,
|
853
|
-
const Json::Value &initialConfig)
|
854
|
-
: eventLoop(eventLoop),
|
855
|
-
config(schema)
|
856
|
-
{
|
857
|
-
// ...code omitted for sake of brevity...
|
512
|
+
if (effectiveValues["security"]["level"].isNull()) {
|
513
|
+
updates["security"] = effectiveValues["security"];
|
514
|
+
updates["security"]["level"] = "full";
|
858
515
|
}
|
859
516
|
|
860
|
-
|
861
|
-
|
862
|
-
vector<ConfigKit::Error> &errors)
|
863
|
-
{
|
864
|
-
// ...code omitted for sake of brevity...
|
865
|
-
}
|
517
|
+
return updates;
|
518
|
+
}
|
866
519
|
|
867
|
-
// No change from synchronous version.
|
868
|
-
bool configure(const Json::Value &updates, vector<ConfigKit::Error> &errors) {
|
869
|
-
// ...code omitted for sake of brevity...
|
870
|
-
}
|
871
520
|
|
872
|
-
|
873
|
-
Json::Value inspectConfig() const {
|
874
|
-
// ...code omitted for sake of brevity...
|
875
|
-
}
|
521
|
+
ConfigKit::Schema schema;
|
876
522
|
|
523
|
+
schema.add("security", OBJECT_TYPE, OPTIONAL);
|
524
|
+
schema.addValidator(validateSecurity);
|
525
|
+
schema.addNormalizer(myNormalizer);
|
526
|
+
schema.finalize();
|
527
|
+
~~~
|
877
528
|
|
878
|
-
|
879
|
-
|
880
|
-
// Performs the same thing as the synchronous version, but
|
881
|
-
// over the event loop.
|
882
|
-
void asyncPreviewConfigUpdate(const Json::Value &updates,
|
883
|
-
const ConfigKit::ConfigCallback &callback)
|
884
|
-
{
|
885
|
-
// The exact API depends on which event loop implementation
|
886
|
-
// you use. When using libev, use SafeLibev::runLater().
|
887
|
-
// When using Boost Asio, use io_service.post().
|
888
|
-
//
|
889
|
-
// We use the utility function ConfigKit::callPreviewConfigUpdateAndCallback.
|
890
|
-
// This function assumes that SecurityChecker supports previewConfigUpdate().
|
891
|
-
eventLoop.threadSafeRunInNextTick(boost::bind(
|
892
|
-
ConfigKit::callPreviewConfigUpdateAndCallback<SecurityChecker>,
|
893
|
-
this, updates, callback));
|
894
|
-
}
|
529
|
+
### Normalizers and validation
|
895
530
|
|
896
|
-
|
897
|
-
// over the event loop.
|
898
|
-
void asyncConfigure(const Json::Value &updates,
|
899
|
-
const ConfigKit::ConfigCallback &callback)
|
900
|
-
{
|
901
|
-
// We use the utility function ConfigKit::callPreviewConfigUpdateAndCallback.
|
902
|
-
// This function assumes that SecurityChecker supports configure().
|
903
|
-
eventLoop.threadSafeRunInNextTick(boost::bind(
|
904
|
-
ConfigKit::callConfigureAndCallback<SecurityChecker>,
|
905
|
-
this, updates, callback));
|
906
|
-
}
|
531
|
+
Normalizers are only run when validation passes! That way normalizers don't have to worry about validation problems.
|
907
532
|
|
908
|
-
|
909
|
-
// over the event loop.
|
910
|
-
void asyncInspectConfig(const ConfigKit::InspectCallback &callback) const {
|
911
|
-
// We use the utility function ConfigKit::callPreviewConfigUpdateAndCallback.
|
912
|
-
// This function assumes that SecurityChecker supports inspectConfig().
|
913
|
-
eventLoop.threadSafeRunInNextTick(boost::bind(
|
914
|
-
ConfigKit::callInspectConfigAndCallback<SecurityChecker>,
|
915
|
-
this, callback));
|
916
|
-
}
|
533
|
+
## ConfigKit in practice & design patterns
|
917
534
|
|
918
|
-
|
919
|
-
};
|
920
|
-
~~~
|
535
|
+
This README has taught you how to use ConfigKit by itself. But how does ConfigKit fit in the bigger picture? [IN_PRACTICE.md](IN_PRACTICE.md) describes *good practices* and *design patterns* can that can be used throughout the overall Passenger C++ codebase. It provides practical guidance on how to use ConfigKit in the Passenger codebase.
|