Skip to content

Commit 158c388

Browse files
committed
Support HttpClient options for external routing group selector
1 parent a54cb14 commit 158c388

File tree

7 files changed

+80
-29
lines changed

7 files changed

+80
-29
lines changed

docs/routing-rules.md

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ To enable the routing rules engine, find the following lines in
2323

2424
* Set `rulesEngineEnabled` to `true`, then `rulesType` as `FILE` or `EXTERNAL`.
2525
* If you set `rulesType: FILE`, then set `rulesConfigPath` to the path to your
26-
rules config file.
26+
rules config file.
2727
* If you set `rulesType: EXTERNAL`, set `rulesExternalConfiguration` to the URL
2828
of an external service for routing rules processing.
2929
* `rulesType` is by default `FILE` unless specified.
@@ -50,15 +50,30 @@ If there is error parsing the routing rules configuration file, an error is
5050
logged, and requests are routed using the routing group header
5151
`X-Trino-Routing-Group` as default.
5252

53+
### Configuring API Requests with HttpClient Config
54+
55+
Trino Gateway uses the Airlift HttpClient library when making HTTP requests.
56+
You can configure the HttpClient by adding the following configuration to
57+
the `serverConfig:` section with the `router` prefix.
58+
59+
```yaml
60+
serverConfig:
61+
router.http-client.request-timeout: 1s
62+
```
63+
64+
Please refer to the [Trino HTTP Client properties](
65+
https://trino.io/docs/current/admin/properties-http-client.html)
66+
documentation for more.
67+
5368
### Use an external service for routing rules
5469

5570
You can use an external service for processing your routing by setting the
5671
`rulesType` to `EXTERNAL` and configuring the `rulesExternalConfiguration`.
5772

58-
Trino Gateway then sends all headers, other than those specified in
73+
Trino Gateway then sends all headers, other than those specified in
5974
`excludeHeaders`, as a map in the body of a POST request to the external
6075
service. If `requestAnalyzerConfig.analyzeRequest` is set to `true`,
61-
`TrinoRequestUser` and `TrinoQueryProperties` are also included.
76+
`TrinoRequestUser` and `TrinoQueryProperties` are also included.
6277

6378
Additionally, the following HTTP information is included:
6479

@@ -77,7 +92,7 @@ return a result with the following criteria:
7792
* Response status code of OK (200)
7893
* Message in JSON format
7994
* Only one group can be returned
80-
* If errors is not null, then query would route to default routing group adhoc
95+
* If errors is not null, then query would route to default routing group adhoc
8196

8297
```json
8398
{
@@ -120,8 +135,8 @@ object called `request`. Rules may also utilize
120135
[trinoQueryProperties](#trinoqueryproperties)
121136
objects, which provide information about the user and query respectively.
122137
You must include an action of the form `result.put(\"routingGroup\", \"foo\")`
123-
to trigger routing of a request that satisfies the condition to the specific
124-
routing group. Without this action, the default adhoc group is used and the
138+
to trigger routing of a request that satisfies the condition to the specific
139+
routing group. Without this action, the default adhoc group is used and the
125140
whole routing rule is redundant.
126141

127142
The condition and actions are written in [MVEL](http://mvel.documentnode.com/),
@@ -146,7 +161,7 @@ group.
146161
### TrinoStatus
147162

148163
The `TrinoStatus` class attempts to track the current state of the configured
149-
Trino clusters. The three possible states of these cluster are updated with
164+
Trino clusters. The three possible states of these cluster are updated with
150165
every healthcheck:
151166

152167
- `PENDING`: A Trino cluster shows this state when it is still starting up. It
@@ -169,7 +184,7 @@ request, in the following order:
169184
3. `Authorization: Bearer` header. Requires configuring an OAuth2 User Info URL.
170185
4. `Trino-UI-Token` or `__Secure-Trino-ID-Token` cookie.
171186

172-
Kerberos and Certificate authentication are not currently supported. If the
187+
Kerberos and Certificate authentication are not currently supported. If the
173188
request contains the `Authorization: Bearer` header, an attempt is made to treat
174189
the token as a JWT and deserialize it. If this is successful, the value of the
175190
claim named in `requestAnalyzerConfig.tokenUserField` is used as the username.
@@ -201,7 +216,7 @@ syntactic analysis is performed.
201216

202217
If a query references a view, then that view is not expanded, and tables
203218
referenced by the view are not recognized. Views and materialized views are
204-
treated as tables and added to the list of tables in all contexts, including
219+
treated as tables and added to the list of tables in all contexts, including
205220
statements such as `CREATE VIEW`.
206221

207222
A routing rule can call the following methods on the `trinoQueryProperties`
@@ -210,7 +225,7 @@ object:
210225
* `String errorMessage()`: the error message, only if there was any error while
211226
creating the `trinoQueryProperties` object.
212227
* `boolean isNewQuerySubmission()`: boolean flag to indicate if the
213-
request is a POST to the `v1/statement` query endpoint.
228+
request is a POST to the `v1/statement` query endpoint.
214229
* `String getQueryType()`: the class name of the `Statement`, e.g. `ShowCreate`.
215230
Note that these are not mapped to the `ResourceGroup` query types. For a full
216231
list of potential query types, see the classes in
@@ -220,7 +235,7 @@ object:
220235
the Trino documentation](https://trino.io/docs/current/admin/resource-groups.html#selector-rules)
221236
* `String getDefaultCatalog()`: the default catalog, if set. It may or may not
222237
be referenced in the actual SQL
223-
* `String getDefaultSchema()`: the default schema, if set. It may or may not
238+
* `String getDefaultSchema()`: the default schema, if set. It may or may not
224239
be referenced in the actual SQL
225240
* `Set<String> getCatalogs()`: the set of catalogs used in the query. Includes
226241
the default catalog if used by a non-fully qualified table reference
@@ -248,7 +263,7 @@ Set to `True` to make `trinoQueryProperties` and `trinoRequestUser` available.
248263
`maxBodySize`:
249264

250265
By default, the max body size is 1,000,000 characters. This can be modified by
251-
configuring `maxBodySize`. If the request body is greater or equal to this
266+
configuring `maxBodySize`. If the request body is greater or equal to this
252267
limit, Trino Gateway does not process the query. A buffer of length
253268
`maxBodySize` is allocated per query. Reduce this value if you observe
254269
excessive garbage collection at runtime. `maxBodySize` cannot be set to values
@@ -265,8 +280,8 @@ configuration.
265280
`tokenUserField`:
266281

267282
When extracting the user from a JWT token, this field is used as the username.
268-
By default, the `email` claim is used.
269-
283+
By default, the `email` claim is used.
284+
270285
`oauthTokenInfoUrl`:
271286

272287
If configured, Trino Gateway attempts to retrieve the user info by exchanging
@@ -275,7 +290,7 @@ minutes to avoid triggering rate limits.
275290

276291
### Execution of rules
277292

278-
All rules whose conditions are satisfied fire. For example, in the "airflow"
293+
All rules whose conditions are satisfied fire. For example, in the "airflow"
279294
and "airflow special" example rules from the following rule priority section, a
280295
query with source `airflow` and label `special` satisfies both rules. The
281296
`routingGroup` is set to `etl` and then to `etl-special` because of the order in

gateway-ha/src/main/java/io/trino/gateway/baseapp/BaseApp.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import io.trino.gateway.ha.resource.LoginResource;
3232
import io.trino.gateway.ha.resource.PublicResource;
3333
import io.trino.gateway.ha.resource.TrinoResource;
34+
import io.trino.gateway.ha.router.ForRouter;
3435
import io.trino.gateway.ha.security.AuthorizedExceptionMapper;
3536
import io.trino.gateway.proxyserver.ForProxy;
3637
import io.trino.gateway.proxyserver.ProxyRequestHandler;
@@ -170,5 +171,6 @@ private static void registerProxyResources(Binder binder)
170171
jaxrsBinder(binder).bind(ProxyRequestHandler.class);
171172
httpClientBinder(binder).bindHttpClient("proxy", ForProxy.class);
172173
httpClientBinder(binder).bindHttpClient("monitor", ForMonitor.class);
174+
httpClientBinder(binder).bindHttpClient("router", ForRouter.class);
173175
}
174176
}

gateway-ha/src/main/java/io/trino/gateway/ha/module/HaGatewayProviderModule.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.google.inject.AbstractModule;
1818
import com.google.inject.Provides;
1919
import com.google.inject.Singleton;
20+
import io.airlift.http.client.HttpClient;
2021
import io.trino.gateway.ha.config.AuthenticationConfiguration;
2122
import io.trino.gateway.ha.config.AuthorizationConfiguration;
2223
import io.trino.gateway.ha.config.GatewayCookieConfigurationPropertiesProvider;
@@ -26,6 +27,7 @@
2627
import io.trino.gateway.ha.config.RulesExternalConfiguration;
2728
import io.trino.gateway.ha.config.UserConfiguration;
2829
import io.trino.gateway.ha.router.BackendStateManager;
30+
import io.trino.gateway.ha.router.ForRouter;
2931
import io.trino.gateway.ha.router.RoutingGroupSelector;
3032
import io.trino.gateway.ha.security.ApiAuthenticator;
3133
import io.trino.gateway.ha.security.AuthorizationManager;
@@ -176,7 +178,7 @@ public BackendStateManager getBackendStateConnectionManager()
176178

177179
@Provides
178180
@Singleton
179-
public RoutingGroupSelector getRoutingGroupSelector()
181+
public RoutingGroupSelector getRoutingGroupSelector(@ForRouter HttpClient httpClient)
180182
{
181183
RoutingRulesConfiguration routingRulesConfig = configuration.getRoutingRules();
182184
if (routingRulesConfig.isRulesEngineEnabled()) {
@@ -188,7 +190,7 @@ public RoutingGroupSelector getRoutingGroupSelector()
188190
}
189191
case EXTERNAL -> {
190192
RulesExternalConfiguration rulesExternalConfiguration = routingRulesConfig.getRulesExternalConfiguration();
191-
yield RoutingGroupSelector.byRoutingExternal(rulesExternalConfiguration, configuration.getRequestAnalyzerConfig());
193+
yield RoutingGroupSelector.byRoutingExternal(rulesExternalConfiguration, configuration.getRequestAnalyzerConfig(), httpClient);
192194
}
193195
};
194196
}

gateway-ha/src/main/java/io/trino/gateway/ha/router/ExternalRoutingGroupSelector.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,9 @@
1818
import com.google.common.collect.ImmutableSet;
1919
import com.google.common.collect.Multimap;
2020
import io.airlift.http.client.HttpClient;
21-
import io.airlift.http.client.HttpClientConfig;
2221
import io.airlift.http.client.JsonBodyGenerator;
2322
import io.airlift.http.client.JsonResponseHandler;
2423
import io.airlift.http.client.Request;
25-
import io.airlift.http.client.jetty.JettyHttpClient;
2624
import io.airlift.json.JsonCodec;
2725
import io.airlift.log.Logger;
2826
import io.trino.gateway.ha.config.RequestAnalyzerConfig;
@@ -59,7 +57,7 @@ public class ExternalRoutingGroupSelector
5957
createJsonResponseHandler(jsonCodec(RoutingGroupExternalResponse.class));
6058

6159
@VisibleForTesting
62-
ExternalRoutingGroupSelector(RulesExternalConfiguration rulesExternalConfiguration, RequestAnalyzerConfig requestAnalyzerConfig)
60+
ExternalRoutingGroupSelector(RulesExternalConfiguration rulesExternalConfiguration, RequestAnalyzerConfig requestAnalyzerConfig, HttpClient httpClient)
6361
{
6462
this.excludeHeaders = ImmutableSet.<String>builder()
6563
.add("Content-Length")
@@ -76,7 +74,7 @@ public class ExternalRoutingGroupSelector
7674
throw new RuntimeException("Invalid URL provided, using "
7775
+ "routing group header as default.", e);
7876
}
79-
httpClient = new JettyHttpClient(new HttpClientConfig());
77+
this.httpClient = httpClient;
8078
}
8179

8280
@Override
@@ -87,12 +85,13 @@ public String findRoutingGroup(HttpServletRequest servletRequest)
8785
try {
8886
RoutingGroupExternalBody requestBody = createRequestBody(servletRequest);
8987
requestBodyGenerator = jsonBodyGenerator(ROUTING_GROUP_EXTERNAL_BODY_JSON_CODEC, requestBody);
90-
request = preparePost()
88+
Request.Builder requestBuilder = preparePost()
9189
.addHeader(CONTENT_TYPE, JSON_UTF_8.toString())
9290
.addHeaders(getValidHeaders(servletRequest))
9391
.setUri(uri)
94-
.setBodyGenerator(requestBodyGenerator)
95-
.build();
92+
.setBodyGenerator(requestBodyGenerator);
93+
94+
request = requestBuilder.build();
9695

9796
// Execute the request and get the response
9897
RoutingGroupExternalResponse response = httpClient.execute(request, ROUTING_GROUP_EXTERNAL_RESPONSE_JSON_RESPONSE_HANDLER);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package io.trino.gateway.ha.router;
15+
16+
import com.google.inject.BindingAnnotation;
17+
18+
import java.lang.annotation.Retention;
19+
import java.lang.annotation.Target;
20+
21+
import static java.lang.annotation.ElementType.FIELD;
22+
import static java.lang.annotation.ElementType.METHOD;
23+
import static java.lang.annotation.ElementType.PARAMETER;
24+
import static java.lang.annotation.RetentionPolicy.RUNTIME;
25+
26+
@Retention(RUNTIME)
27+
@Target({FIELD, PARAMETER, METHOD})
28+
@BindingAnnotation
29+
public @interface ForRouter
30+
{
31+
}

gateway-ha/src/main/java/io/trino/gateway/ha/router/RoutingGroupSelector.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*/
1414
package io.trino.gateway.ha.router;
1515

16+
import io.airlift.http.client.HttpClient;
1617
import io.trino.gateway.ha.config.RequestAnalyzerConfig;
1718
import io.trino.gateway.ha.config.RulesExternalConfiguration;
1819
import jakarta.servlet.http.HttpServletRequest;
@@ -46,9 +47,10 @@ static RoutingGroupSelector byRoutingRulesEngine(String rulesConfigPath, Request
4647
* Routing group selector that uses RESTful API
4748
* to determine the right routing group.
4849
*/
49-
static RoutingGroupSelector byRoutingExternal(RulesExternalConfiguration rulesExternalConfiguration, RequestAnalyzerConfig requestAnalyzerConfig)
50+
static RoutingGroupSelector byRoutingExternal(RulesExternalConfiguration rulesExternalConfiguration,
51+
RequestAnalyzerConfig requestAnalyzerConfig, HttpClient httpClient)
5052
{
51-
return new ExternalRoutingGroupSelector(rulesExternalConfiguration, requestAnalyzerConfig);
53+
return new ExternalRoutingGroupSelector(rulesExternalConfiguration, requestAnalyzerConfig, httpClient);
5254
}
5355

5456
/**

gateway-ha/src/test/java/io/trino/gateway/ha/router/TestRoutingGroupSelectorExternal.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ void testApiFailure()
133133
{
134134
RulesExternalConfiguration rulesExternalConfiguration = provideRoutingRuleExternalConfig();
135135
RoutingGroupSelector routingGroupSelector =
136-
RoutingGroupSelector.byRoutingExternal(rulesExternalConfiguration, requestAnalyzerConfig);
136+
RoutingGroupSelector.byRoutingExternal(rulesExternalConfiguration, requestAnalyzerConfig, httpClient);
137137

138138
HttpServletRequest mockRequest = prepareMockRequest();
139139
setMockHeaders(mockRequest);
@@ -162,7 +162,7 @@ void testNullUri()
162162
rulesExternalConfiguration.setUrlPath(null);
163163

164164
// Assert that a RuntimeException is thrown with message
165-
assertThatThrownBy(() -> RoutingGroupSelector.byRoutingExternal(rulesExternalConfiguration, requestAnalyzerConfig))
165+
assertThatThrownBy(() -> RoutingGroupSelector.byRoutingExternal(rulesExternalConfiguration, requestAnalyzerConfig, httpClient))
166166
.isInstanceOf(RuntimeException.class)
167167
.hasMessage("Invalid URL provided, using routing group header as default.");
168168
}
@@ -175,7 +175,7 @@ void testExcludeHeader()
175175
rulesExternalConfiguration.setExcludeHeaders(List.of("test-exclude-header"));
176176

177177
RoutingGroupSelector routingGroupSelector =
178-
RoutingGroupSelector.byRoutingExternal(rulesExternalConfiguration, requestAnalyzerConfig);
178+
RoutingGroupSelector.byRoutingExternal(rulesExternalConfiguration, requestAnalyzerConfig, httpClient);
179179

180180
// Mock headers to be read by mockRequest
181181
HttpServletRequest mockRequest = mock(HttpServletRequest.class);

0 commit comments

Comments
 (0)