Skip to content

Commit

Permalink
fix(headers): avoid retrofit and okhttp adding duplicate headers
Browse files Browse the repository at this point in the history
With the addition of the okHttp3 request interceptor `SpinnakerRequestHeaderInterceptor` in
spinnaker#1004 having retrofit1 add `X-SPINNAKER-*` headers is now redundant,
this results in the headers being added twice to all retrofit1 client requests.
  • Loading branch information
dmart committed Nov 13, 2023
1 parent bbb19fb commit e44aa56
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import com.netflix.spinnaker.kork.client.ServiceClientFactory;
import com.netflix.spinnaker.retrofit.Slf4jRetrofitLogger;
import retrofit.Endpoint;
import retrofit.RequestInterceptor;
import retrofit.RestAdapter;
import retrofit.converter.JacksonConverter;

Expand All @@ -36,22 +35,17 @@ class RetrofitServiceFactory implements ServiceClientFactory {

private final RestAdapter.LogLevel retrofitLogLevel;
private final OkHttpClientProvider clientProvider;
private final RequestInterceptor spinnakerRequestInterceptor;

RetrofitServiceFactory(
RestAdapter.LogLevel retrofitLogLevel,
OkHttpClientProvider clientProvider,
RequestInterceptor spinnakerRequestInterceptor) {
RestAdapter.LogLevel retrofitLogLevel, OkHttpClientProvider clientProvider) {
this.retrofitLogLevel = retrofitLogLevel;
this.clientProvider = clientProvider;
this.spinnakerRequestInterceptor = spinnakerRequestInterceptor;
}

@Override
public <T> T create(Class<T> type, ServiceEndpoint serviceEndpoint, ObjectMapper objectMapper) {
Endpoint endpoint = newFixedEndpoint(serviceEndpoint.getBaseUrl());
return new RestAdapter.Builder()
.setRequestInterceptor(spinnakerRequestInterceptor)
.setConverter(new JacksonConverter(objectMapper))
.setEndpoint(endpoint)
.setClient(new Ok3Client(clientProvider.getClient(serviceEndpoint)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import retrofit.RequestInterceptor;
import retrofit.RestAdapter;

@Configuration
Expand All @@ -34,10 +33,7 @@ public class RetrofitServiceFactoryAutoConfiguration {
@Bean
@Order(Ordered.LOWEST_PRECEDENCE)
ServiceClientFactory serviceClientFactory(
RestAdapter.LogLevel retrofitLogLevel,
OkHttpClientProvider clientProvider,
RequestInterceptor spinnakerRequestInterceptor) {
return new RetrofitServiceFactory(
retrofitLogLevel, clientProvider, spinnakerRequestInterceptor);
RestAdapter.LogLevel retrofitLogLevel, OkHttpClientProvider clientProvider) {
return new RetrofitServiceFactory(retrofitLogLevel, clientProvider);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,17 @@ import com.netflix.spinnaker.config.DefaultServiceClientProvider
import com.netflix.spinnaker.config.OkHttpClientComponents
import com.netflix.spinnaker.kork.client.ServiceClientFactory
import com.netflix.spinnaker.kork.client.ServiceClientProvider
import com.netflix.spinnaker.kork.common.Header
import com.netflix.spinnaker.okhttp.OkHttpClientConfigurationProperties
import com.netflix.spinnaker.okhttp.SpinnakerRequestHeaderInterceptor
import com.netflix.spinnaker.security.AuthenticatedRequest
import dev.minutest.junit.JUnit5Minutests
import dev.minutest.rootContext
import io.mockk.every
import io.mockk.mockkStatic
import okhttp3.OkHttpClient
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.springframework.boot.autoconfigure.AutoConfigurations
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
import org.springframework.boot.test.context.assertj.AssertableApplicationContext
Expand All @@ -45,6 +52,9 @@ import retrofit.http.Path
import strikt.api.expect
import strikt.assertions.isA
import strikt.assertions.isEqualTo
import strikt.assertions.isNotNull
import java.util.Optional
import java.util.concurrent.TimeUnit

class RetrofitServiceProviderTest : JUnit5Minutests {

Expand Down Expand Up @@ -78,6 +88,32 @@ class RetrofitServiceProviderTest : JUnit5Minutests {
}
}

test("auth headers are propagated once") {
mockkStatic(AuthenticatedRequest::class)
every { AuthenticatedRequest.getAuthenticationHeaders() } returns mapOf(
Header.USER.header to Optional.of("user"),
)

run { ctx: AssertableApplicationContext ->
val mockWebServer = MockWebServer()
mockWebServer.enqueue(MockResponse().setBody("response"))

val retrofitClient = ctx.getBean(ServiceClientProvider::class.java).getService(
Retrofit1Service::class.java,
DefaultServiceEndpoint("retrofit1", mockWebServer.url("/").toString())
)

retrofitClient.getSomething("user", null)
val recordedRequest = mockWebServer.takeRequest(5, TimeUnit.SECONDS)

expect {
that(recordedRequest).isNotNull()
that(recordedRequest!!.headers.values("X-SPINNAKER-USER").size).isEqualTo(1)
}

mockWebServer.shutdown()
}
}
}
}

Expand All @@ -91,8 +127,15 @@ private open class TestConfiguration {
HttpTracing.newBuilder(Tracing.newBuilder().build()).build()

@Bean
open fun okHttpClient(httpTracing: HttpTracing): OkHttpClient {
return RawOkHttpClientFactory().create(OkHttpClientConfigurationProperties(), emptyList(), httpTracing)
open fun okHttpClient(
httpTracing: HttpTracing,
spinnakerRequestHeaderInterceptor: SpinnakerRequestHeaderInterceptor,
): OkHttpClient {
return RawOkHttpClientFactory().create(
OkHttpClientConfigurationProperties(),
listOf(spinnakerRequestHeaderInterceptor),
httpTracing,
)
}

@Bean
Expand All @@ -110,7 +153,6 @@ private open class TestConfiguration {
serviceClientFactories: List<ServiceClientFactory?>, objectMapper: ObjectMapper): DefaultServiceClientProvider {
return DefaultServiceClientProvider(serviceClientFactories, objectMapper)
}

}

interface Retrofit1Service {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import com.netflix.spinnaker.config.DefaultServiceClientProvider
import com.netflix.spinnaker.kork.client.ServiceClientFactory
import com.netflix.spinnaker.kork.client.ServiceClientProvider
import com.netflix.spinnaker.okhttp.OkHttpClientConfigurationProperties
import com.netflix.spinnaker.okhttp.SpinnakerRequestInterceptor
import dev.minutest.junit.JUnit5Minutests
import dev.minutest.rootContext
import okhttp3.OkHttpClient
Expand Down Expand Up @@ -100,11 +99,6 @@ private open class TestConfiguration {
return OkHttpClientProvider(listOf(DefaultOkHttpClientBuilderProvider(okHttpClient, OkHttpClientConfigurationProperties())))
}

@Bean
open fun spinnakerRequestInterceptor(): SpinnakerRequestInterceptor {
return SpinnakerRequestInterceptor(OkHttpClientConfigurationProperties())
}

@Bean
open fun objectMapper(): ObjectMapper {
return ObjectMapper()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import com.netflix.spinnaker.okhttp.OkHttpClientConfigurationProperties;
import com.netflix.spinnaker.okhttp.OkHttpMetricsInterceptor;
import com.netflix.spinnaker.okhttp.SpinnakerRequestHeaderInterceptor;
import com.netflix.spinnaker.okhttp.SpinnakerRequestInterceptor;
import com.netflix.spinnaker.retrofit.Retrofit2ConfigurationProperties;
import com.netflix.spinnaker.retrofit.RetrofitConfigurationProperties;
import java.io.File;
Expand Down Expand Up @@ -80,11 +79,6 @@ public class OkHttpClientComponents {
private final OkHttpMetricsInterceptorProperties metricsProperties;
private final Retrofit2ConfigurationProperties retrofit2Properties;

@Bean
public SpinnakerRequestInterceptor spinnakerRequestInterceptor() {
return new SpinnakerRequestInterceptor(clientProperties);
}

@Bean
public SpinnakerRequestHeaderInterceptor spinnakerRequestHeaderInterceptor() {
return new SpinnakerRequestHeaderInterceptor(clientProperties);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@
* <p>The typical pattern is:
*
* <p>AuthenticatedRequestFilter: copies X-SPINNAKER-* incoming request headers into the MDC
* SpinnakerRequestInterceptor: copies X-SPINNAKER-* from the MDC into outgoing request headers
* SpinnakerRequestHeaderInterceptor: copies X-SPINNAKER-* from the MDC into outgoing request
* headers
*
* <p>MdcCopyingAsyncTaskExecutor makes it so SpinnakerRequestInterceptor has something to copy for
* async methods. It also makes it so log messages include X-SPINNAKER-*, specifically
* <p>MdcCopyingAsyncTaskExecutor makes it so SpinnakerRequestHeaderInterceptor has something to
* copy for async methods. It also makes it so log messages include X-SPINNAKER-*, specifically
* X-SPINNAKER-REQUEST-ID and X-SPINNAKER-REQUEST-ID to faciliate troubleshooting.
*/
public class MdcCopyingAsyncTaskExecutor implements AsyncTaskExecutor {
Expand Down

0 comments on commit e44aa56

Please sign in to comment.