Skip to content

Commit c52a9e9

Browse files
Pluieswendigo
authored andcommitted
Add support for --extra-header to Trino CLI and JDBC
Allows passing arbitrary headers to Trino clients (JDBC/CLI/library)
1 parent 1718f8f commit c52a9e9

File tree

10 files changed

+157
-0
lines changed

10 files changed

+157
-0
lines changed

client/trino-cli/src/main/java/io/trino/cli/ClientOptions.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import static io.trino.client.uri.PropertyName.EXTERNAL_AUTHENTICATION;
5757
import static io.trino.client.uri.PropertyName.EXTERNAL_AUTHENTICATION_REDIRECT_HANDLERS;
5858
import static io.trino.client.uri.PropertyName.EXTRA_CREDENTIALS;
59+
import static io.trino.client.uri.PropertyName.EXTRA_HEADERS;
5960
import static io.trino.client.uri.PropertyName.HTTP_LOGGING_LEVEL;
6061
import static io.trino.client.uri.PropertyName.HTTP_PROXY;
6162
import static io.trino.client.uri.PropertyName.KERBEROS_CONFIG_PATH;
@@ -204,6 +205,10 @@ public class ClientOptions
204205
@Option(names = "--client-tags", paramLabel = "<tags>", description = "Client tags", converter = ClientTagsConverter.class)
205206
public Optional<Set<String>> clientTags;
206207

208+
@PropertyMapping(EXTRA_HEADERS)
209+
@Option(names = "--extra-header", paramLabel = "<header>", description = "Additional HTTP header to add to HTTP requests (property can be used multiple times; format is key=value)")
210+
public final List<ExtraHeader> extraHeaders = new ArrayList<>();
211+
207212
@PropertyMapping(TRACE_TOKEN)
208213
@Option(names = "--trace-token", paramLabel = "<token>", description = "Trace token")
209214
public Optional<String> traceToken;
@@ -422,6 +427,9 @@ public TrinoUri getTrinoUri(Map<PropertyName, String> restrictedProperties)
422427
if (!sessionProperties.isEmpty()) {
423428
builder.setSessionProperties(toProperties(sessionProperties));
424429
}
430+
if (!extraHeaders.isEmpty()) {
431+
builder.setExtraHeaders(toExtraHeaders(extraHeaders));
432+
}
425433
if (!resourceEstimates.isEmpty()) {
426434
builder.setResourceEstimates(toResourceEstimates(resourceEstimates));
427435
}
@@ -498,6 +506,15 @@ public static URI parseServer(String server)
498506
}
499507
}
500508

509+
public static Map<String, String> toExtraHeaders(List<ExtraHeader> extraHeaders)
510+
{
511+
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
512+
for (ExtraHeader extraHeader : extraHeaders) {
513+
builder.put(extraHeader.getHeader(), extraHeader.getValue());
514+
}
515+
return builder.buildOrThrow();
516+
}
517+
501518
private static Map<String, String> toProperties(List<ClientSessionProperty> sessionProperties)
502519
{
503520
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
@@ -605,6 +622,58 @@ public int hashCode()
605622
}
606623
}
607624

625+
public static final class ExtraHeader
626+
{
627+
private final String header;
628+
private final String value;
629+
630+
public ExtraHeader(String headerAndValue)
631+
{
632+
List<String> nameValue = NAME_VALUE_SPLITTER.splitToList(headerAndValue);
633+
checkArgument(nameValue.size() == 2, "Header and value: %s", headerAndValue);
634+
635+
this.header = nameValue.get(0);
636+
this.value = nameValue.get(1);
637+
checkArgument(!header.isEmpty(), "Header name is empty");
638+
checkArgument(!value.isEmpty(), "Header value is empty");
639+
}
640+
641+
public ExtraHeader(String header, String value)
642+
{
643+
this.header = header;
644+
this.value = value;
645+
}
646+
647+
public String getHeader()
648+
{
649+
return header;
650+
}
651+
652+
public String getValue()
653+
{
654+
return value;
655+
}
656+
657+
@Override
658+
public boolean equals(Object o)
659+
{
660+
if (this == o) {
661+
return true;
662+
}
663+
if (o == null || getClass() != o.getClass()) {
664+
return false;
665+
}
666+
ExtraHeader other = (ExtraHeader) o;
667+
return Objects.equals(header, other.header) && Objects.equals(value, other.value);
668+
}
669+
670+
@Override
671+
public int hashCode()
672+
{
673+
return Objects.hash(header, value);
674+
}
675+
}
676+
608677
public static final class ClientSessionProperty
609678
{
610679
private static final Splitter NAME_SPLITTER = Splitter.on('.');

client/trino-cli/src/main/java/io/trino/cli/Trino.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import io.trino.cli.ClientOptions.ClientExtraCredential;
2121
import io.trino.cli.ClientOptions.ClientResourceEstimate;
2222
import io.trino.cli.ClientOptions.ClientSessionProperty;
23+
import io.trino.cli.ClientOptions.ExtraHeader;
2324
import org.jline.utils.AttributedStringBuilder;
2425
import org.jline.utils.AttributedStyle;
2526
import picocli.CommandLine;
@@ -62,6 +63,7 @@ public static CommandLine createCommandLine(Object command)
6263
.registerConverter(ClientResourceEstimate.class, ClientResourceEstimate::new)
6364
.registerConverter(ClientSessionProperty.class, ClientSessionProperty::new)
6465
.registerConverter(ClientExtraCredential.class, ClientExtraCredential::new)
66+
.registerConverter(ExtraHeader.class, ExtraHeader::new)
6567
.registerConverter(HostAndPort.class, HostAndPort::fromString)
6668
.registerConverter(Duration.class, Duration::valueOf)
6769
.setResourceBundle(new TrinoResourceBundle())

client/trino-cli/src/test/java/io/trino/cli/TestClientOptions.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,16 @@ public void testExtraCredentials()
246246
new ClientOptions.ClientExtraCredential("test.token.bar", "bar")));
247247
}
248248

249+
@Test
250+
public void testExtraHeaders()
251+
{
252+
Console console = createConsole("--extra-header", "X-Trino-Routing-Group=foo", "--extra-header", "x-foo=bar");
253+
ClientOptions options = console.clientOptions;
254+
assertThat(options.extraHeaders).isEqualTo(ImmutableList.of(
255+
new ClientOptions.ExtraHeader("X-Trino-Routing-Group", "foo"),
256+
new ClientOptions.ExtraHeader("x-foo", "bar")));
257+
}
258+
249259
@Test
250260
public void testSessionProperties()
251261
{

client/trino-client/src/main/java/io/trino/client/ClientSession.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public class ClientSession
5151
private final ZoneId timeZone;
5252
private final Locale locale;
5353
private final Map<String, String> resourceEstimates;
54+
private final Map<String, String> extraHeaders;
5455
private final Map<String, String> properties;
5556
private final Map<String, String> preparedStatements;
5657
private final Map<String, ClientSelectedRole> roles;
@@ -87,6 +88,7 @@ private ClientSession(
8788
String source,
8889
Optional<String> traceToken,
8990
Set<String> clientTags,
91+
Map<String, String> extraHeaders,
9092
String clientInfo,
9193
Optional<String> catalog,
9294
Optional<String> schema,
@@ -112,6 +114,7 @@ private ClientSession(
112114
this.source = requireNonNull(source, "source is null");
113115
this.traceToken = requireNonNull(traceToken, "traceToken is null");
114116
this.clientTags = ImmutableSet.copyOf(requireNonNull(clientTags, "clientTags is null"));
117+
this.extraHeaders = ImmutableMap.copyOf(requireNonNull(extraHeaders, "extraHeaders is null"));
115118
this.clientInfo = clientInfo;
116119
this.catalog = catalog;
117120
this.schema = schema;
@@ -198,6 +201,11 @@ public Set<String> getClientTags()
198201
return clientTags;
199202
}
200203

204+
public Map<String, String> getExtraHeaders()
205+
{
206+
return extraHeaders;
207+
}
208+
201209
public String getClientInfo()
202210
{
203211
return clientInfo;
@@ -295,6 +303,7 @@ public String toString()
295303
.add("sessionUser", sessionUser)
296304
.add("authorizationUser", authorizationUser)
297305
.add("clientTags", clientTags)
306+
.add("extraHeaders", extraHeaders)
298307
.add("clientInfo", clientInfo)
299308
.add("catalog", catalog)
300309
.add("schema", schema)
@@ -324,6 +333,7 @@ public static final class Builder
324333
private String source;
325334
private Optional<String> traceToken = Optional.empty();
326335
private Set<String> clientTags = ImmutableSet.of();
336+
private Map<String, String> extraHeaders = ImmutableMap.of();
327337
private String clientInfo;
328338
private String catalog;
329339
private String schema;
@@ -354,6 +364,7 @@ private Builder(ClientSession clientSession)
354364
source = clientSession.getSource();
355365
traceToken = clientSession.getTraceToken();
356366
clientTags = clientSession.getClientTags();
367+
extraHeaders = clientSession.getExtraHeaders();
357368
clientInfo = clientSession.getClientInfo();
358369
catalog = clientSession.getCatalog().orElse(null);
359370
schema = clientSession.getSchema().orElse(null);
@@ -419,6 +430,12 @@ public Builder clientTags(Set<String> clientTags)
419430
return this;
420431
}
421432

433+
public Builder extraHeaders(Map<String, String> extraHeaders)
434+
{
435+
this.extraHeaders = extraHeaders;
436+
return this;
437+
}
438+
422439
public Builder clientInfo(String clientInfo)
423440
{
424441
this.clientInfo = clientInfo;
@@ -526,6 +543,7 @@ public ClientSession build()
526543
source,
527544
traceToken,
528545
clientTags,
546+
extraHeaders,
529547
clientInfo,
530548
Optional.ofNullable(catalog),
531549
Optional.ofNullable(schema),

client/trino-client/src/main/java/io/trino/client/OkHttpUtil.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import java.security.cert.X509Certificate;
5656
import java.util.Arrays;
5757
import java.util.List;
58+
import java.util.Map;
5859
import java.util.Optional;
5960
import java.util.concurrent.TimeUnit;
6061

@@ -109,6 +110,17 @@ public static Interceptor tokenAuth(String accessToken)
109110
.build());
110111
}
111112

113+
public static Interceptor extraHeaders(Map<String, String> extraHeaders)
114+
{
115+
requireNonNull(extraHeaders, "extraHeaders is null");
116+
117+
return chain -> {
118+
okhttp3.Request.Builder builder = chain.request().newBuilder();
119+
extraHeaders.forEach(builder::addHeader);
120+
return chain.proceed(builder.build());
121+
};
122+
}
123+
112124
public static void setupTimeouts(OkHttpClient.Builder clientBuilder, int timeout, TimeUnit unit)
113125
{
114126
clientBuilder

client/trino-client/src/main/java/io/trino/client/uri/ConnectionProperties.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ enum SslVerificationMode
9999
public static final ConnectionProperty<String, Map<String, String>> EXTRA_CREDENTIALS = new ExtraCredentials();
100100
public static final ConnectionProperty<String, String> CLIENT_INFO = new ClientInfo();
101101
public static final ConnectionProperty<String, Set<String>> CLIENT_TAGS = new ClientTags();
102+
public static final ConnectionProperty<String, Map<String, String>> EXTRA_HEADERS = new ExtraHeaders();
102103
public static final ConnectionProperty<String, String> TRACE_TOKEN = new TraceToken();
103104
public static final ConnectionProperty<String, Map<String, String>> SESSION_PROPERTIES = new SessionProperties();
104105
public static final ConnectionProperty<String, String> SOURCE = new Source();
@@ -139,6 +140,7 @@ enum SslVerificationMode
139140
.add(EXTERNAL_AUTHENTICATION_TIMEOUT)
140141
.add(EXTERNAL_AUTHENTICATION_TOKEN_CACHE)
141142
.add(EXTRA_CREDENTIALS)
143+
.add(EXTRA_HEADERS)
142144
.add(HOSTNAME_IN_CERTIFICATE)
143145
.add(HTTP_LOGGING_LEVEL)
144146
.add(HTTP_PROXY)
@@ -789,6 +791,29 @@ public static String toString(Map<String, String> values)
789791
}
790792
}
791793

794+
private static class ExtraHeaders
795+
extends AbstractConnectionProperty<String, Map<String, String>>
796+
{
797+
public ExtraHeaders()
798+
{
799+
super(PropertyName.EXTRA_HEADERS, NOT_REQUIRED, ALLOWED, converter(ExtraHeaders::parseExtraHeaders, ExtraHeaders::toString));
800+
}
801+
802+
// Extra credentials consists of a list of credential name value pairs.
803+
// E.g., `jdbc:trino://example.net:8080/?extraHeaders=abc:xyz;foo:bar` will create credentials `abc=xyz` and `foo=bar`
804+
public static Map<String, String> parseExtraHeaders(String extraHeadersString)
805+
{
806+
return new MapPropertyParser(PropertyName.EXTRA_CREDENTIALS.toString()).parse(extraHeadersString);
807+
}
808+
809+
public static String toString(Map<String, String> values)
810+
{
811+
return values.entrySet().stream()
812+
.map(entry -> entry.getKey() + ":" + entry.getValue())
813+
.collect(Collectors.joining(";"));
814+
}
815+
}
816+
792817
private static class SessionProperties
793818
extends AbstractConnectionProperty<String, Map<String, String>>
794819
{

client/trino-client/src/main/java/io/trino/client/uri/HttpClientFactory.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
import static io.trino.client.KerberosUtil.defaultCredentialCachePath;
3232
import static io.trino.client.OkHttpUtil.basicAuth;
33+
import static io.trino.client.OkHttpUtil.extraHeaders;
3334
import static io.trino.client.OkHttpUtil.setupAlternateHostnameVerification;
3435
import static io.trino.client.OkHttpUtil.setupCookieJar;
3536
import static io.trino.client.OkHttpUtil.setupHttpLogging;
@@ -87,6 +88,10 @@ public static OkHttpClient.Builder toHttpClientBuilder(TrinoUri uri, String user
8788
builder.addNetworkInterceptor(tokenAuth(uri.getAccessToken().get()));
8889
}
8990

91+
if (!uri.getExtraHeaders().isEmpty()) {
92+
builder.addNetworkInterceptor(extraHeaders(uri.getExtraHeaders()));
93+
}
94+
9095
if (uri.isExternalAuthenticationEnabled()) {
9196
if (!uri.isUseSecureConnection()) {
9297
throw new RuntimeException("TLS/SSL required for authentication using external authorization");

client/trino-client/src/main/java/io/trino/client/uri/PropertyName.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public enum PropertyName
3131
CLIENT_INFO("clientInfo"),
3232
CLIENT_TAGS("clientTags"),
3333
DISABLE_COMPRESSION("disableCompression"),
34+
EXTRA_HEADERS("extraHeaders"),
3435
DNS_RESOLVER("dnsResolver"),
3536
DNS_RESOLVER_CONTEXT("dnsResolverContext"),
3637
ENCODING("encoding"),

client/trino-client/src/main/java/io/trino/client/uri/TrinoUri.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import static io.trino.client.uri.ConnectionProperties.EXTERNAL_AUTHENTICATION_TIMEOUT;
6262
import static io.trino.client.uri.ConnectionProperties.EXTERNAL_AUTHENTICATION_TOKEN_CACHE;
6363
import static io.trino.client.uri.ConnectionProperties.EXTRA_CREDENTIALS;
64+
import static io.trino.client.uri.ConnectionProperties.EXTRA_HEADERS;
6465
import static io.trino.client.uri.ConnectionProperties.HOSTNAME_IN_CERTIFICATE;
6566
import static io.trino.client.uri.ConnectionProperties.HTTP_LOGGING_LEVEL;
6667
import static io.trino.client.uri.ConnectionProperties.HTTP_PROXY;
@@ -244,6 +245,11 @@ public Map<String, String> getSessionProperties()
244245
return resolveWithDefault(SESSION_PROPERTIES, ImmutableMap.of());
245246
}
246247

248+
public Map<String, String> getExtraHeaders()
249+
{
250+
return resolveWithDefault(EXTRA_HEADERS, ImmutableMap.of());
251+
}
252+
247253
public Optional<String> getSource()
248254
{
249255
return resolveOptional(SOURCE);
@@ -521,6 +527,7 @@ public ClientSession.Builder toClientSessionBuilder()
521527
.timeZone(getTimeZone())
522528
.locale(getLocale())
523529
.properties(getSessionProperties())
530+
.extraHeaders(getExtraHeaders())
524531
.credentials(getExtraCredentials())
525532
.transactionId(null)
526533
.resourceEstimates(getResourceEstimates())
@@ -1009,6 +1016,11 @@ public Builder setSessionProperties(Map<String, String> sessionProperties)
10091016
return setProperty(SESSION_PROPERTIES, requireNonNull(sessionProperties, "sessionProperties is null"));
10101017
}
10111018

1019+
public Builder setExtraHeaders(Map<String, String> extraHeaders)
1020+
{
1021+
return setProperty(EXTRA_HEADERS, requireNonNull(extraHeaders, "extraHeaders is null"));
1022+
}
1023+
10121024
public Builder setSource(String source)
10131025
{
10141026
return setProperty(SOURCE, requireNonNull(source, "source is null"));

docs/src/main/sphinx/client/cli.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ mode:
161161
* - `--editing-mode`
162162
- Sets key bindings in the CLI to be compatible with VI or
163163
EMACS editors. Defaults to `EMACS`.
164+
* - ``--extra-header``
165+
- Arbitrary headers to attach to authenticated HTTP requests made by the CLI. Property can be
166+
used multiple times with the format ``header=value``.
164167
* - `--extra-credential`
165168
- Extra credentials (property can be used multiple times; format is key=value)
166169
* - `--http-proxy`

0 commit comments

Comments
 (0)