Envoy is an incredibly flexible proxy server. Almost every aspect of the processing chain from the listeners all the way to the management of the upstream connections is configurable. This is even more impressive given that the configuration can be changed on the fly (by reloading a file or via the xDS API) without restarting the server. Moreover, we are not restricted to Envoy's built-in features but can add our own components with their custom configuration.</p>
How does Envoy pull this off given that C++ is not known as a particular "dynamic" language? This post tries to shed some light on Envoy's factory registries that form the foundation of this mechanism.</p>
Let's start with the caller side. In various places, Envoy has to construct objects implementing some interface using a given configuration. Here is an example from the code handling the upstream connections.</p>
1</span>Network</span>::</span>UpstreamTransportSocketFactoryPtr</span> createTransportSocketFactory</span>(</span></span>
2</span> const</span> envoy</span>::</span>config</span>::</span>cluster</span>::</span>v3</span>::</span>Cluster</span>&</span> config</span>,</span></span>
3</span> Server</span>::</span>Configuration</span>::</span>TransportSocketFactoryContext</span>&</span> factory_context</span>)</span> {</span></span>
4</span> auto</span> transport_socket </span>=</span> config</span>.</span>transport_socket</span>(</span>)</span>;</span></span>
5</span> ...</span></span>
6</span> auto</span>&</span> config_factory </span>=</span> Config</span>::</span>Utility</span>::</span>getAndCheckFactory</span><</span></span>
7</span> Server</span>::</span>Configuration</span>::</span>UpstreamTransportSocketConfigFactory</span>></span>(</span>transport_socket</span>)</span>;</span></span>
8</span> ProtobufTypes</span>::</span>MessagePtr message </span>=</span> Config</span>::</span>Utility</span>::</span>translateToFactoryConfig</span>(</span></span>
9</span> transport_socket</span>,</span> factory_context</span>.</span>messageValidationVisitor</span>(</span>)</span>,</span> config_factory</span>)</span>;</span></span>
10</span> return</span> config_factory</span>.</span>createTransportSocketFactory</span>(</span>*</span>message</span>,</span> factory_context</span>)</span>;</span></span>
11</span>}</span></span></code></pre>
The createTransportSocketFactory</a> method creates
a socket factory based on the cluster configuration. The key is the
getAndCheckFactory</a> method that takes the
transport_socket</code> protobuf configuration and returns a factory object of type
UpstreamTransportSocketConfigFactory</a>.
TransportSocket</a> is one of Envoy's fully dynamic
configuration objects using a
TransportSocket.typed_config</a>, a protobuf
Any</a> message. The getAndCheckFactory</a> method calls
getFactoryByType</a> with the typed config. If this does not
result in a factory, the method tries a lookup by name instead.</p>
1</span>template</span> <</span>class</span> Factory</span>,</span> class</span> ProtoMessage</span>></span></span>
2</span>static</span> Factory</span>*</span> getAndCheckFactory</span>(</span>const</span> ProtoMessage</span>&</span> message</span>,</span> bool</span> is_optional</span>)</span> {</span></span>
3</span> Factory</span>*</span> factory </span>=</span> Utility</span>::</span>getFactoryByType</span><</span>Factory</span>></span>(</span>message</span>.</span>typed_config</span>(</span>)</span>)</span>;</span></span>
4</span> ...</span></span>
5</span> if</span> (</span>factory </span>!=</span> nullptr</span>)</span> {</span></span>
6</span> return</span> factory</span>;</span></span>
7</span> }</span></span>
8</span> return</span> Utility</span>::</span>getAndCheckFactoryByName</span><</span>Factory</span>></span>(</span>message</span>.</span>name</span>(</span>)</span>,</span> is_optional</span>)</span>;</span></span>
9</span>}</span></span></code></pre>
Now we have to understand the lookup of a factory by type (typed config) and by name.
Let's start with the lookup by name.</p>
1</span>template</span> <</span>class</span> Factory</span>></span></span>
2</span>static</span> Factory</span>*</span> getAndCheckFactoryByName</span>(</span>const</span> std</span>::</span>string</span>&</span> name</span>,</span> bool</span> is_optional</span>)</span> {</span></span>
3</span> ...</span></span>
4</span> Factory</span>*</span> factory </span>=</span> Registry</span>::</span>FactoryRegistry</span><</span>Factory</span>></span>::</span>