Make sure that you can compile and run core tests and read a basic example Writing your first HTTP server.
An ability to change service behavior at runtime without restarting the service is priceless! We have that ability, it is called dynamic configs and it allows you:
In previous example we made a simple HTTP server with some dynamic configs set in stone. To make the dynamic configs dynamic for real the following steps should be done:
dynamic-config-client: # A client that knows how to request configs via HTTP
config-url: http://localhost:8083/ # URL of dynamic config service
http-retries: 5
http-timeout: 20s
service-name: configs-service
fallback-to-no-proxy: false # On error do not attempt to retrieve configs
# by bypassing proxy from USERVER_HTTP_PROXY dynamic config
dynamic-config-client-updater: # A component that periodically uses `dynamic-config-client` to retrieve new values
update-interval: 5s # Request for new configs every 5 seconds
full-update-interval: 1m
config-settings: false
first-update-fail-ok: true
Now let's create a configuration service.
Dynamic configs are requested via JSON requests, so we need to create a simple JSON handler that is responding with config values.
There are two ways to write a JSON handler:
We are going to take the second approach:
Handle*
functions are invoked concurrently on the same instance of the handler class. Use synchronization primitives or do not modify shared data in Handle*
.Note the rcu::Variable. There may be (and will be!) multiple concurrent requests and the HandleRequestJsonThrow
would be invoked concurrently on the same instance of ConfigDistributor
. The rcu::Variable allows us to atomically update the config value, concurrently with the HandleRequestJsonThrow
invocations.
Function ConfigDistributor::SetNewValues
is meant for setting config values to send. For example, you can write another component that accepts JSON requests with new config values and puts those values into ConfigDistributor
via SetNewValues(new_values)
.
All the interesting things happen in the HandleRequestJsonThrow
function, where we grab a rcu::Variable snapshot, fill the update time and the configuration from it:
The "configs" field is formed in the MakeConfigs
function depending on the request parameters:
Note that the service name is sent in the "service" field of the JSON request body. Using it you can make service specific dynamic configs.
Now we have to configure our new HTTP handle. The configuration is quite straightforward:
handler-config:
path: /configs/values
method: POST # Only for HTTP POST requests. Other handlers may reuse the same URL but use different method.
task_processor: main-task-processor
Finally, we add required components to the components::MinimalServerComponentList()
, and start the server with static config kStaticConfig
.
To build the sample, execute the following build steps at the userver root directory:
mkdir build_release
cd build_release
cmake -DCMAKE_BUILD_TYPE=Release ..
make userver-samples-config_service
The sample could be started by running make start-userver-samples-config_service
. The command would invoke testsuite start target that sets proper paths in the configuration files and starts the service.
To start the service manually run ./samples/config_service/userver-samples-config_service -c </path/to/static_config.yaml>
.
Now you can send a request to your server from another terminal:
$ curl -X POST -d '{}' 127.0.0.1:8083/configs/values | jq
{
"configs": {
"USERVER_DUMPS": {},
"USERVER_LRU_CACHES": {},
"USERVER_CACHES": {},
"USERVER_LOG_REQUEST": true,
"USERVER_TASK_PROCESSOR_QOS": {
"default-service": {
"default-task-processor": {
"wait_queue_overload": {
"action": "ignore",
"length_limit": 5000,
"time_limit_us": 3000
}
}
}
},
"USERVER_TASK_PROCESSOR_PROFILER_DEBUG": {},
"USERVER_LOG_REQUEST_HEADERS": true,
"USERVER_CANCEL_HANDLE_REQUEST_BY_DEADLINE": false,
"USERVER_HTTP_PROXY": ""
},
"updated_at": "2021-06-29T14:15:31.173239295+0000"
}
$ curl -X POST -d '{"ids":["USERVER_TASK_PROCESSOR_QOS"]}' 127.0.0.1:8083/configs/values | jq
{
"configs": {
"USERVER_TASK_PROCESSOR_QOS": {
"default-service": {
"default-task-processor": {
"wait_queue_overload": {
"action": "ignore",
"length_limit": 5000,
"time_limit_us": 3000
}
}
}
}
},
"updated_at": "2021-06-29T14:15:31.173239295+0000"
}
Functional tests for the service could be implemented using the service_client fixture in the following way:
Do not forget to add the plugin in conftest.py:
Note that there is a ready to use opensource uservice-dynconf dynamic configs service. Use it for your projects or just disable dynamic config updates and keep developing without a supplementary service.
See the full example: