Set default User-Agent in HTTP client to "Graylog" (#21864)

* Set default User-Agent in HTTP client to "Graylog"

* Add changelog

* Add our default user agent only if none is set

This allows custom user agent strings on a per-request basis.

* Update changelog wording

---------

Co-authored-by: Bernd Ahlers <bernd@users.noreply.github.com>
This commit is contained in:
Sebastian Röbke
2025-03-17 12:56:15 +01:00
committed by GitHub
parent bf6b1e72a6
commit 830531bcb0
6 changed files with 73 additions and 13 deletions

View File

@@ -0,0 +1,5 @@
type = "c"
message = "Set default `User-Agent` header in HTTP client to `Graylog`."
issues = [""]
pulls = ["21864"]

View File

@@ -85,6 +85,9 @@ public abstract class BaseConfiguration extends PathConfiguration implements Com
@Parameter("disable_native_system_stats_collector")
private boolean disableNativeSystemStatsCollector = false;
@Parameter(value = "http_user_agent")
private String httpUserAgent = "Graylog";
@Parameter(value = "http_proxy_uri")
private URI httpProxyUri;
@@ -179,6 +182,10 @@ public abstract class BaseConfiguration extends PathConfiguration implements Com
return disableNativeSystemStatsCollector;
}
public String getHttpUserAgent() {
return httpUserAgent;
}
public URI getHttpProxyUri() {
return httpProxyUri;
}

View File

@@ -18,9 +18,15 @@ package org.graylog2.shared.bindings.providers;
import com.github.joschi.jadconfig.util.Duration;
import com.google.common.base.Splitter;
import com.google.common.net.HttpHeaders;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.inject.Provider;
import jakarta.inject.Singleton;
import okhttp3.Authenticator;
import okhttp3.Challenge;
import okhttp3.Credentials;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
@@ -31,12 +37,6 @@ import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.inject.Provider;
import jakarta.inject.Singleton;
import java.io.IOException;
import java.net.URI;
import java.util.List;
@@ -51,6 +51,7 @@ import static java.util.Objects.requireNonNull;
/**
* Provider for a configured {@link okhttp3.OkHttpClient}.
*
* @see org.graylog2.plugin.BaseConfiguration#getHttpUserAgent()
* @see org.graylog2.plugin.BaseConfiguration#getHttpConnectTimeout()
* @see org.graylog2.plugin.BaseConfiguration#getHttpReadTimeout()
* @see org.graylog2.plugin.BaseConfiguration#getHttpWriteTimeout()
@@ -59,6 +60,29 @@ import static java.util.Objects.requireNonNull;
@Singleton
public class OkHttpClientProvider implements Provider<OkHttpClient> {
private static final Logger LOG = LoggerFactory.getLogger(OkHttpClientProvider.class);
static class UserAgentInterceptor implements Interceptor {
private final String userAgent;
public UserAgentInterceptor(String userAgent) {
this.userAgent = userAgent;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
// Add our default user agent, but only if none is set
if (originalRequest.header(HttpHeaders.USER_AGENT) == null) {
final var builder = originalRequest.newBuilder();
final var newRequest = builder.header(HttpHeaders.USER_AGENT, userAgent).build();
return chain.proceed(newRequest);
} else {
return chain.proceed(originalRequest);
}
}
}
protected final String userAgent;
protected final Duration connectTimeout;
protected final Duration readTimeout;
protected final Duration writeTimeout;
@@ -67,12 +91,14 @@ public class OkHttpClientProvider implements Provider<OkHttpClient> {
private final ProxySelectorProvider proxySelectorProvider;
@Inject
public OkHttpClientProvider(@Named("http_connect_timeout") Duration connectTimeout,
public OkHttpClientProvider(@Named("http_user_agent") String userAgent,
@Named("http_connect_timeout") Duration connectTimeout,
@Named("http_read_timeout") Duration readTimeout,
@Named("http_write_timeout") Duration writeTimeout,
@Named("http_proxy_uri") @Nullable URI httpProxyUri,
TrustManagerAndSocketFactoryProvider trustManagerAndSocketFactoryProvider,
ProxySelectorProvider proxySelectorProvider) {
this.userAgent = requireNonNull(userAgent);
this.connectTimeout = requireNonNull(connectTimeout);
this.readTimeout = requireNonNull(readTimeout);
this.writeTimeout = requireNonNull(writeTimeout);
@@ -85,6 +111,7 @@ public class OkHttpClientProvider implements Provider<OkHttpClient> {
public OkHttpClient get() {
final OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder()
.retryOnConnectionFailure(true)
.addInterceptor(new UserAgentInterceptor(userAgent))
.connectTimeout(connectTimeout.getQuantity(), connectTimeout.getUnit())
.writeTimeout(writeTimeout.getQuantity(), writeTimeout.getUnit())
.readTimeout(readTimeout.getQuantity(), readTimeout.getUnit());

View File

@@ -291,11 +291,11 @@ public class OkHttpClientProviderTest {
final ProxySelectorProvider proxyProvider = new ProxySelectorProvider(server.url("/").uri(), null);
ProxySelectorProvider spyProxyProvider = Mockito.spy(proxyProvider);
final OkHttpClientProvider provider = new OkHttpClientProvider(
"GraylogTest",
Duration.milliseconds(100L),
Duration.milliseconds(100L),
Duration.milliseconds(100L),
server.url("/").uri(),
null, spyProxyProvider);
server.url("/").uri(), null, spyProxyProvider);
OkHttpClientProvider spyClientProvider = Mockito.spy(provider);
@@ -312,6 +312,23 @@ public class OkHttpClientProviderTest {
.matches(proxy -> proxy.equals(testProxy));
}
@Test
public void testDefaultUserAgent() throws IOException, InterruptedException {
server.enqueue(successfulMockResponse());
client(server.url("/").uri()).newCall(request()).execute();
final RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getHeader(HttpHeaders.USER_AGENT)).isEqualTo("GraylogTest");
}
@Test
public void testCustomUserAgent() throws IOException, InterruptedException {
server.enqueue(successfulMockResponse());
final var request = request().newBuilder().header(HttpHeaders.USER_AGENT, "foo").build();
client(server.url("/").uri()).newCall(request).execute();
final RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getHeader(HttpHeaders.USER_AGENT)).isEqualTo("foo");
}
private MockResponse successfulMockResponse() {
return new MockResponse().setResponseCode(200).setBody("Test");
}
@@ -334,11 +351,11 @@ public class OkHttpClientProviderTest {
private OkHttpClient client(URI proxyURI) {
final OkHttpClientProvider provider = new OkHttpClientProvider(
"GraylogTest",
Duration.milliseconds(100L),
Duration.milliseconds(100L),
Duration.milliseconds(100L),
proxyURI,
null, new ProxySelectorProvider(proxyURI, null));
proxyURI, null, new ProxySelectorProvider(proxyURI, null));
return provider.get();
}

View File

@@ -142,11 +142,11 @@ public class ParameterizedHttpClientProviderTest {
private OkHttpClientProvider client(URI proxyURI) {
final OkHttpClientProvider provider = new OkHttpClientProvider(
"GraylogTest",
Duration.milliseconds(500L),
Duration.milliseconds(500L),
Duration.milliseconds(500L),
proxyURI,
null, null);
proxyURI, null, null);
return provider;
}

View File

@@ -624,6 +624,10 @@ mongodb_max_connections = 1000
# This should define the fully qualified base url to your web interface exactly the same way as it is accessed by your users.
#transport_email_web_interface_url = https://graylog.example.com
# The User-Agent header for outgoing HTTP connections.
# Default: Graylog
#http_user_agent = Graylog
# The default connect timeout for outgoing HTTP connections.
# Values must be a positive duration (and between 1 and 2147483647 when converted to milliseconds).
# Default: 5s