Skip to content

Commit cce33d2

Browse files
Add http-event-listener.connect-http-headers.config-file property
Introduces the `http-event-listener.connect-http-headers.config-file` property to allow defining custom HTTP headers in an external file using `key=value` pairs. This supports header names and values containing commas (`,`) or colons (`:`), which are not supported in the inline `http-event-listener.connect-http-headers` property. Only one of `http-event-listener.connect-http-headers` or `http-event-listener.connect-http-headers.config-file` may be set. An exception will be raised if both are configured.
1 parent cea84a4 commit cce33d2

File tree

5 files changed

+217
-24
lines changed

5 files changed

+217
-24
lines changed

docs/src/main/sphinx/admin/event-listeners-http.md

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ event-listener.config-files=etc/http-event-listener.properties,...
7575
[](http-event-listener-custom-headers) for more details
7676
- Empty
7777

78+
* - http-event-listener.connect-http-headers.config-file
79+
- Path of the config file containing a list of custom HTTP headers to be sent
80+
along with the events. See [](http-event-listener-custom-headers) for more
81+
details
82+
- Empty
83+
7884
* - http-event-listener.connect-http-method
7985
- Specifies the HTTP method to use for the request. Supported values
8086
are POST and PUT.
@@ -119,8 +125,23 @@ Providing headers follows the pattern of `key:value` pairs separated by commas:
119125
http-event-listener.connect-http-headers="Header-Name-1:header value 1,Header-Value-2:header value 2,..."
120126
```
121127

122-
If you need to use a comma(`,`) or colon(`:`) in a header name or value,
123-
escape it using a backslash (`\`).
128+
If your header names or values need to include special characters such as commas
129+
(`,`) or colons (`:`),define them in an external configuration file using:
130+
131+
```text
132+
http-event-listener.connect-http-headers.config-file=/path/to/headers.conf
133+
```
134+
135+
The configuration file should contain one `key=value` pair per line, for example:
136+
137+
```text
138+
Header-Name-1=header value 1
139+
Header-Name-2=header value with : colon and , comma
140+
```
141+
142+
Only one of `http-event-listener.connect-http-headers` **or**
143+
`http-event-listener.connect-http-headers.config-file` can be used at a time.
144+
If both properties are set, the system will raise an exception during startup.
124145

125-
Keep in mind that these are static, so they can not carry information
126-
taken from the event itself.
146+
Keep in mind that these headers are staticthey cannot include information
147+
dynamically taken from the event itself.

plugin/trino-http-event-listener/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,12 @@
157157
<scope>test</scope>
158158
</dependency>
159159

160+
<dependency>
161+
<groupId>io.airlift</groupId>
162+
<artifactId>testing</artifactId>
163+
<scope>test</scope>
164+
</dependency>
165+
160166
<dependency>
161167
<groupId>io.trino</groupId>
162168
<artifactId>trino-main</artifactId>

plugin/trino-http-event-listener/src/main/java/io/trino/plugin/httpquery/HttpEventListener.java

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
import com.google.common.util.concurrent.Futures;
2020
import com.google.inject.Inject;
2121
import io.airlift.bootstrap.LifeCycleManager;
22+
import io.airlift.configuration.validation.FileExists;
2223
import io.airlift.http.client.BodyGenerator;
2324
import io.airlift.http.client.HttpClient;
2425
import io.airlift.http.client.Request;
26+
import io.airlift.http.client.StatusResponseHandler.StatusResponse;
2527
import io.airlift.json.JsonCodec;
2628
import io.airlift.log.Logger;
2729
import io.airlift.units.Duration;
@@ -31,18 +33,24 @@
3133
import io.trino.spi.eventlistener.SplitCompletedEvent;
3234
import jakarta.annotation.PreDestroy;
3335

36+
import java.io.File;
37+
import java.io.FileInputStream;
38+
import java.io.IOException;
39+
import java.io.UncheckedIOException;
3440
import java.net.URI;
3541
import java.net.URISyntaxException;
3642
import java.util.Map;
43+
import java.util.Optional;
44+
import java.util.Properties;
3745
import java.util.concurrent.ScheduledExecutorService;
3846
import java.util.concurrent.TimeUnit;
3947

4048
import static com.google.common.base.Verify.verify;
49+
import static com.google.common.collect.ImmutableMap.toImmutableMap;
4150
import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
4251
import static com.google.common.net.MediaType.JSON_UTF_8;
4352
import static io.airlift.concurrent.Threads.daemonThreadsNamed;
4453
import static io.airlift.http.client.JsonBodyGenerator.jsonBodyGenerator;
45-
import static io.airlift.http.client.StatusResponseHandler.StatusResponse;
4654
import static io.airlift.http.client.StatusResponseHandler.createStatusResponseHandler;
4755
import static java.util.Objects.requireNonNull;
4856
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
@@ -73,6 +81,7 @@ public class HttpEventListener
7381
private final Duration maxDelay;
7482
private final double backoffBase;
7583
private final Map<String, String> httpHeaders;
84+
private final Optional<@FileExists File> httpHeadersConfigFile;
7685
private final URI ingestUri;
7786
private final HttpEventListenerHttpMethod httpMethod;
7887
private final ScheduledExecutorService executor;
@@ -102,6 +111,7 @@ public HttpEventListener(
102111
this.backoffBase = config.getBackoffBase();
103112
this.httpMethod = config.getHttpMethod();
104113
this.httpHeaders = ImmutableMap.copyOf(config.getHttpHeaders());
114+
this.httpHeadersConfigFile = config.getHttpHeadersConfigFile();
105115

106116
try {
107117
ingestUri = new URI(config.getIngestUri());
@@ -145,13 +155,22 @@ public void splitCompleted(SplitCompletedEvent splitCompletedEvent)
145155

146156
private void sendLog(BodyGenerator eventBodyGenerator, String queryId)
147157
{
148-
Request request = Request.builder()
158+
Request.Builder requestBuilder = Request.builder()
149159
.setMethod(httpMethod.name())
150-
.addHeaders(Multimaps.forMap(httpHeaders))
151160
.addHeader(CONTENT_TYPE, JSON_UTF_8.toString())
152161
.setUri(ingestUri)
153-
.setBodyGenerator(eventBodyGenerator)
154-
.build();
162+
.setBodyGenerator(eventBodyGenerator);
163+
164+
httpHeadersConfigFile.ifPresentOrElse(
165+
file -> {
166+
Map<String, String> fileHeaders = loadHttpHeadersFromFile(file);
167+
requestBuilder.addHeaders(Multimaps.forMap(fileHeaders));
168+
},
169+
() -> {
170+
requestBuilder.addHeaders(Multimaps.forMap(httpHeaders));
171+
});
172+
173+
Request request = requestBuilder.build();
155174

156175
attemptToSend(request, 0, Duration.valueOf("0s"), queryId);
157176
}
@@ -249,6 +268,22 @@ private Duration nextDelay(Duration delay)
249268
return newDuration;
250269
}
251270

271+
public static Map<String, String> loadHttpHeadersFromFile(File file)
272+
{
273+
Properties properties = new Properties();
274+
try (FileInputStream fis = new FileInputStream(file)) {
275+
properties.load(fis);
276+
}
277+
catch (IOException e) {
278+
throw new UncheckedIOException("Failed to read HTTP headers config file: " + file, e);
279+
}
280+
281+
return properties.entrySet().stream()
282+
.collect(toImmutableMap(
283+
e -> e.getKey().toString(),
284+
e -> e.getValue().toString()));
285+
}
286+
252287
@Override
253288
public void shutdown()
254289
{

plugin/trino-http-event-listener/src/main/java/io/trino/plugin/httpquery/HttpEventListenerConfig.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,23 @@
1111
* See the License for the specific language governing permissions and
1212
* limitations under the License.
1313
*/
14+
1415
package io.trino.plugin.httpquery;
1516

1617
import com.google.common.collect.ImmutableMap;
1718
import io.airlift.configuration.Config;
1819
import io.airlift.configuration.ConfigDescription;
20+
import io.airlift.configuration.validation.FileExists;
1921
import io.airlift.units.Duration;
22+
import jakarta.validation.constraints.AssertTrue;
2023
import jakarta.validation.constraints.Min;
2124
import jakarta.validation.constraints.NotNull;
2225

26+
import java.io.File;
2327
import java.util.EnumSet;
2428
import java.util.List;
2529
import java.util.Map;
30+
import java.util.Optional;
2631
import java.util.stream.Collectors;
2732

2833
public class HttpEventListenerConfig
@@ -35,6 +40,7 @@ public class HttpEventListenerConfig
3540
private String ingestUri;
3641
private HttpEventListenerHttpMethod httpMethod = HttpEventListenerHttpMethod.POST;
3742
private Map<String, String> httpHeaders = ImmutableMap.of();
43+
private File httpHeadersConfigFile;
3844

3945
@ConfigDescription("Will log io.trino.spi.eventlistener.QueryCreatedEvent")
4046
@Config("http-event-listener.log-created")
@@ -130,6 +136,20 @@ public HttpEventListenerConfig setHttpHeaders(List<String> httpHeaders)
130136
return this;
131137
}
132138

139+
public Optional<@FileExists File> getHttpHeadersConfigFile()
140+
{
141+
return Optional.ofNullable(httpHeadersConfigFile);
142+
}
143+
144+
@Config("http-event-listener.connect-http-headers.config-file")
145+
@ConfigDescription("Path to a properties file containing custom HTTP headers, " +
146+
"specified as key-value pairs (one per line, e.g., Header-Name=Header Value)")
147+
public HttpEventListenerConfig setHttpHeadersConfigFile(File httpHeadersConfigFile)
148+
{
149+
this.httpHeadersConfigFile = httpHeadersConfigFile;
150+
return this;
151+
}
152+
133153
@ConfigDescription("Number of retries on server error")
134154
@Config("http-event-listener.connect-retry-count")
135155
public HttpEventListenerConfig setRetryCount(int retryCount)
@@ -184,4 +204,11 @@ public Duration getMaxDelay()
184204
{
185205
return this.maxDelay;
186206
}
207+
208+
@AssertTrue(message = "Exactly one of http-event-listener.connect-http-headers.config-file or " +
209+
"http-event-listener.connect-http-headers must be set")
210+
public boolean validateHeaderConfigRedundant()
211+
{
212+
return !(httpHeadersConfigFile != null && !httpHeaders.isEmpty());
213+
}
187214
}

0 commit comments

Comments
 (0)