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.

Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +13 -2
  3. data/CONTRIBUTING.md +1 -1
  4. data/build/agent.rb +1 -1
  5. data/build/cxx_tests.rb +6 -0
  6. data/build/support/cxx_dependency_map.rb +1286 -391
  7. data/build/support/general.rb +0 -26
  8. data/resources/templates/standalone/rails_asset_pipeline.erb +2 -2
  9. data/src/agent/Core/ApiServer.h +49 -44
  10. data/src/agent/Core/ApplicationPool/Pool.h +1 -1
  11. data/src/agent/Core/ApplicationPool/Process.h +1 -1
  12. data/src/agent/Core/ApplicationPool/Socket.h +1 -1
  13. data/src/agent/Core/Controller.h +16 -8
  14. data/src/agent/Core/Controller/CheckoutSession.cpp +1 -1
  15. data/src/agent/Core/Controller/Config.cpp +68 -0
  16. data/src/agent/Core/Controller/Config.h +70 -34
  17. data/src/agent/Core/Controller/ForwardResponse.cpp +5 -5
  18. data/src/agent/Core/Controller/Hooks.cpp +5 -14
  19. data/src/agent/Core/Controller/Implementation.cpp +1 -1
  20. data/src/agent/Core/Controller/InitRequest.cpp +31 -29
  21. data/src/agent/Core/Controller/InitializationAndShutdown.cpp +4 -4
  22. data/src/agent/Core/Controller/InternalUtils.cpp +3 -3
  23. data/src/agent/Core/Controller/Miscellaneous.cpp +1 -1
  24. data/src/agent/Core/Controller/Request.h +2 -2
  25. data/src/agent/Core/Controller/SendRequest.cpp +5 -5
  26. data/src/agent/Core/Controller/StateInspection.cpp +1 -1
  27. data/src/agent/Core/Controller/TurboCaching.h +2 -2
  28. data/src/agent/Core/CoreMain.cpp +2 -2
  29. data/src/agent/Core/ResponseCache.h +3 -3
  30. data/src/agent/Core/SpawningKit/BackgroundIOCapturer.h +3 -3
  31. data/src/agent/Core/SpawningKit/DirectSpawner.h +2 -2
  32. data/src/agent/Core/SpawningKit/PipeWatcher.h +3 -3
  33. data/src/agent/Core/SpawningKit/SmartSpawner.h +2 -2
  34. data/src/agent/Core/SpawningKit/Spawner.h +1 -1
  35. data/src/agent/Core/UnionStation/Connection.h +1 -1
  36. data/src/agent/Core/UnionStation/Context.h +1 -1
  37. data/src/agent/Core/UnionStation/Transaction.h +1 -1
  38. data/src/agent/Shared/ApiServerUtils.h +73 -27
  39. data/src/agent/Shared/Base.cpp +61 -73
  40. data/src/agent/UstRouter/ApiServer.h +34 -45
  41. data/src/agent/UstRouter/Controller.h +86 -60
  42. data/src/agent/UstRouter/RemoteSender.h +1 -1
  43. data/src/agent/UstRouter/RemoteSink.h +1 -1
  44. data/src/agent/Watchdog/ApiServer.h +42 -50
  45. data/src/agent/Watchdog/WatchdogMain.cpp +1 -1
  46. data/src/apache2_module/Configuration.hpp +1 -1
  47. data/src/apache2_module/Hooks.cpp +27 -13
  48. data/src/cxx_supportlib/AppTypes.h +1 -1
  49. data/src/cxx_supportlib/BackgroundEventLoop.cpp +1 -1
  50. data/src/cxx_supportlib/ConfigKit/AsyncUtils.h +86 -0
  51. data/src/cxx_supportlib/ConfigKit/Common.h +6 -3
  52. data/src/cxx_supportlib/ConfigKit/IN_PRACTICE.md +1039 -0
  53. data/src/cxx_supportlib/ConfigKit/README.md +112 -497
  54. data/src/cxx_supportlib/ConfigKit/Schema.h +78 -15
  55. data/src/cxx_supportlib/ConfigKit/Store.h +272 -53
  56. data/src/cxx_supportlib/ConfigKit/SubComponentUtils.h +59 -0
  57. data/src/cxx_supportlib/ConfigKit/Utils.h +26 -65
  58. data/src/cxx_supportlib/ConfigKit/ValidationUtils.h +69 -0
  59. data/src/cxx_supportlib/ConfigKit/VariantMapUtils.h +7 -4
  60. data/src/cxx_supportlib/Constants.h +4 -1
  61. data/src/cxx_supportlib/Crypto.cpp +1 -1
  62. data/src/cxx_supportlib/DataStructures/StringKeyTable.h +26 -7
  63. data/src/cxx_supportlib/FileDescriptor.h +1 -1
  64. data/src/cxx_supportlib/Hooks.h +1 -1
  65. data/src/cxx_supportlib/LoggingKit/Assert.h +130 -0
  66. data/src/cxx_supportlib/LoggingKit/Config.h +97 -0
  67. data/src/cxx_supportlib/LoggingKit/Context.h +94 -0
  68. data/src/cxx_supportlib/LoggingKit/Forward.h +95 -0
  69. data/src/cxx_supportlib/LoggingKit/Implementation.cpp +695 -0
  70. data/src/cxx_supportlib/LoggingKit/Logging.h +204 -0
  71. data/src/cxx_supportlib/LoggingKit/LoggingKit.h +33 -0
  72. data/src/cxx_supportlib/LveLoggingDecorator.h +1 -1
  73. data/src/cxx_supportlib/MemoryKit/mbuf.cpp +1 -1
  74. data/src/cxx_supportlib/RandomGenerator.h +1 -1
  75. data/src/cxx_supportlib/SafeLibev.h +1 -1
  76. data/src/cxx_supportlib/ServerKit/AcceptLoadBalancer.h +1 -1
  77. data/src/cxx_supportlib/ServerKit/Channel.h +1 -1
  78. data/src/cxx_supportlib/ServerKit/FileBufferedChannel.h +1 -1
  79. data/src/cxx_supportlib/ServerKit/FileBufferedFdSinkChannel.h +1 -1
  80. data/src/cxx_supportlib/ServerKit/HttpChunkedBodyParser.h +1 -1
  81. data/src/cxx_supportlib/ServerKit/HttpHeaderParser.h +1 -1
  82. data/src/cxx_supportlib/ServerKit/HttpServer.h +48 -15
  83. data/src/cxx_supportlib/ServerKit/Server.h +79 -52
  84. data/src/cxx_supportlib/StaticString.h +12 -0
  85. data/src/cxx_supportlib/Utils/Curl.h +16 -0
  86. data/src/cxx_supportlib/Utils/FastStringStream.h +6 -1
  87. data/src/cxx_supportlib/Utils/ScopeGuard.h +1 -1
  88. data/src/cxx_supportlib/Utils/StrIntUtils.cpp +2 -19
  89. data/src/cxx_supportlib/WatchdogLauncher.h +3 -2
  90. data/src/ruby_supportlib/phusion_passenger.rb +3 -3
  91. data/src/ruby_supportlib/phusion_passenger/common_library.rb +12 -12
  92. data/src/ruby_supportlib/phusion_passenger/constants.rb +6 -3
  93. data/src/ruby_supportlib/phusion_passenger/standalone/start_command.rb +1 -0
  94. data/src/ruby_supportlib/phusion_passenger/standalone/stop_command.rb +1 -0
  95. metadata +14 -4
  96. data/src/cxx_supportlib/Logging.cpp +0 -295
  97. 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
- - [Putting it all together: synchronous version](#putting-it-all-together-synchronous-version)
33
- - [SecurityChecker example: a configurable, low-level component](#securitychecker-example-a-configurable-low-level-component)
34
- - [DnsQuerier example: a low-level component with post-configuration application operations](#dnsquerier-example-a-low-level-component-with-post-configuration-application-operations)
35
- - [Downloader example: a high-level component that combines subcomponents](#downloader-example-a-high-level-component-that-combines-subcomponents)
36
- - [The special problem of conflicting overlapping configuration names and translation](#the-special-problem-of-conflicting-overlapping-configuration-names-and-translation)
37
- - [Code example](#code-example)
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. It would be great if we have a unified answer for all components in Passenger.
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
- ConfigKit backs these answers with code that helps you implement these principles, as well as with documented design patterns that provide guidance.
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` also allows data validation against the 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
- There is also `ConfigKit::Store`. This 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
+ `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 the section "The special problem of conflicting overlapping configuration names and translation".
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 the section "Putting it all together: synchronous version" why this is the recommended approach.
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": true
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 "boolean".
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`: whether a default value is defined.
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
- This will return a Json::Value in the following format:
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
- ## Putting it all together: synchronous version
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
- ### SecurityChecker example: a configurable, low-level component
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
- 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.
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
- A configurable component should have the following methods.
410
-
411
- - `previewConfigUpdate()`
412
- - `configure()`
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
- ### DnsQuerier example: a low-level component with post-configuration application operations
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
- 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.
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
- class DnsQuerier {
533
- private:
534
- // The configuration store, same as with SecurityChecker.
535
- ConfigKit::Store config;
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
- // Same as with SecurityChecker.
592
- Json::Value inspectConfig() const {
593
- return config.inspect();
594
- }
445
+ ConfigKit::Schema schema;
595
446
 
596
- string query() {
597
- // Fictional code here for performing the lookup.
598
- setSocketTimeout(config["timeout"].asInt());
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
- ### Downloader example: a high-level component that combines subcomponents
452
+ Note that inspect filter is called *after* [normalizers](#normalizing-data), so `value` refers to a normalized value.
610
453
 
611
- `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.
454
+ ## Normalizing data
612
455
 
613
- Downloader completely *encapsulates* its subcomponents. Users that use Downloader don't have to know that the subcomponents exist. Downloader also completely encapsulates subcomponents' configuration: subcomponents can only be configured (and their configuration only inspected) through Downloader.
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
- Because of this, downloader's configuration schema includes not only options directly pertaining Downloader itself, but also includes options pertaining the subcomponents it uses.
458
+ The examples below demonstrate two possible use cases and how to implement them.
616
459
 
617
- Similarly, the Downloader's internal configuration store stores not only the configuration values directly pertaining to Downloader itself, but also (a copy of) the configuration values pertaining to the subcomponents.
460
+ ### Normalization example 1
618
461
 
619
- Whenever Downloader is configured, it configures subcomponents too. But whenever Downloader's configuration is inspected, it only returns the data from its own configuration store. Because only the Downloader is allowed to configure its subcomponents, we know that the Downloader's internal configuration store contains the most up-to-date values.
462
+ Suppose that you have a "target" config option, which can be in one of these formats:
620
463
 
621
- #### The special problem of conflicting overlapping configuration names and translation
464
+ 1. `"/filename"`
465
+ 2. `{ "path": "/filename" }` (semantics equivalent to 1)
466
+ 3. `{ "stderr": true }`
622
467
 
623
- There is one special problem that deserves attention: a high-level component may not necessarily want to expose their subcomponents' configuration options using the same names. For example, both `SecurityChecker` and `DnsQuerier` expose a `timeout` option, but they are *different* timeouts, and are even distinct from the Downloader's own download timeout. To solve this special problem of **conflicting overlapping configuration names**, we utilize a **translation system**. We define how the Downloader's configuration keys are to be mapped a specific subcomponent's configuration keys. We obviously can't define the entire mapping, because that would return us to the original problem of having to manually write so much repeated code. There are several ways to deal with this, such as:
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
- - Assuming that most options don't have to be renamed, and only define exceptions to this rule. This is the approach that is demonstrated in this example. The `ConfigKit::TableTranslator` class implements this translation strategy.
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
- #### Code example
472
+ Normalizers are added to the corresponding schema.
629
473
 
630
474
  ~~~c++
631
- class Downloader {
632
- private:
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
- public:
643
- // Defines the Downloader's configuration schema. As explained, this schema
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
- void download() {
705
- // Fictional code here for performing the download
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
- // In addition to updating the internal configuration store, we also
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
- // As explained, simply returning our internal configuration store
771
- // is enough to expose all the subcomponents' configuration values too.
772
- Json::Value inspectConfig() const {
773
- return config.inspect();
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
- ### Main function example
494
+ ### Normalization example 2
779
495
 
780
- To close this section, here is an example of how the main function would look like to utilize the aforementioned components:
496
+ Suppose that you have a "security" config option that accepts this format:
781
497
 
782
- ~~~c++
783
- int
784
- main() {
785
- // Set configuration
786
- Json::Value config;
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
- ## Putting it all together: asynchronous version
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
- class SecurityChecker {
832
- private:
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
- public:
839
- // No change from synchronous version.
840
- struct Schema: public ConfigKit::Schema {
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
- // No change from synchronous version.
861
- Json::Value previewConfigUpdate(const Json::Value &updates,
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
- // No change from synchronous version.
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
- /****** Introduction of asynchronous methods below ******/
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
- // Performs the same thing as the synchronous version, but
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
- // Performs the same thing as the synchronous version, but
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
- // ...further fictional code here for performing the lookup...
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.