Skip to content

Commit 691cb72

Browse files
authored
Add interceptors support to DefaultHttpClient and WpRequestExecutor (#1041)
Allow OkHttp interceptors to be passed to DefaultHttpClient while preserving hostname verification functionality. Add a convenience constructor to WpRequestExecutor that accepts interceptors directly. This enables clients to inject interceptors (e.g., for network debugging) without needing to manage OkHttpClient configuration. Changes: - Add `interceptors` parameter to `DefaultHttpClient` - Refactor to use `buildClient()` method for client construction - Add convenience constructor to `WpRequestExecutor`
1 parent 8670f3d commit 691cb72

File tree

15 files changed

+131
-29
lines changed

15 files changed

+131
-29
lines changed

native/kotlin/api/android/src/androidTest/kotlin/rs/wordpress/api/android/UsersEndpointAndroidTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import java.net.URL
1111
class UsersEndpointAndroidTest {
1212
// https://developer.android.com/studio/run/emulator-networking
1313
private val siteUrl = "http://10.0.2.2"
14-
private val client = WpApiClient(URL(siteUrl), WpAuthenticationProvider.none())
14+
private val client = WpApiClient(URL(siteUrl), WpAuthenticationProvider.none(), emptyList())
1515

1616
@Test
1717
fun testUserListRequest() = runTest {

native/kotlin/api/kotlin/src/integrationTest/kotlin/ApiUrlDiscoveryTest.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import kotlin.test.assertContains
2525

2626
@Execution(ExecutionMode.CONCURRENT)
2727
class ApiUrlDiscoveryTest {
28-
private val loginClient: WpLoginClient = WpLoginClient()
28+
private val loginClient: WpLoginClient = WpLoginClient(emptyList())
2929

3030
@Test
3131
fun testLocalSite() = runTest {
@@ -186,7 +186,7 @@ class ApiUrlDiscoveryTest {
186186
val invalid =
187187
ApiDiscoveryAuthenticationMiddleware(username = "invalid", password = "invalid")
188188
val client = WpLoginClient(
189-
WpRequestExecutor(), WpApiMiddlewarePipeline(middlewares = listOf(invalid))
189+
WpRequestExecutor(emptyList()), WpApiMiddlewarePipeline(middlewares = listOf(invalid))
190190
)
191191
val reason = client.apiDiscovery("https://basic-auth.wpmt.co")
192192
.assertFailureFindApiRoot().getRequestExecutionErrorReason()
@@ -204,7 +204,7 @@ class ApiUrlDiscoveryTest {
204204
)
205205

206206
val client = WpLoginClient(
207-
WpRequestExecutor(), WpApiMiddlewarePipeline(middlewares = listOf(valid))
207+
WpRequestExecutor(emptyList()), WpApiMiddlewarePipeline(middlewares = listOf(valid))
208208
)
209209

210210
assertEquals(
@@ -283,7 +283,7 @@ class ApiUrlDiscoveryTest {
283283

284284
@Test // Spec Example 17 (with exception)
285285
fun testInvalidHttpsWithExceptionWorks() = runTest {
286-
val httpClient = WpHttpClient.DefaultHttpClient()
286+
val httpClient = WpHttpClient.DefaultHttpClient(emptyList())
287287
val executor = WpRequestExecutor(httpClient)
288288
httpClient.addAllowedAlternativeNamesForHostname(
289289
"vanilla.wpmt.co",

native/kotlin/api/kotlin/src/integrationTest/kotlin/AuthProviderTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class AuthProviderTest {
2121
val authProvider = WpAuthenticationProvider.staticWithUsernameAndPassword(
2222
username = testCredentials.adminUsername, password = testCredentials.adminPassword
2323
)
24-
val client = WpApiClient(testCredentials.apiRootUrl, authProvider)
24+
val client = WpApiClient(testCredentials.apiRootUrl, authProvider, emptyList())
2525

2626
val currentUser = client.request { requestBuilder ->
2727
requestBuilder.users().retrieveMeWithEditContext()
@@ -52,7 +52,7 @@ class AuthProviderTest {
5252

5353
val dynamicAuthProvider = DynamicAuthProvider()
5454
val authProvider = WpAuthenticationProvider.dynamic(dynamicAuthProvider)
55-
val client = WpApiClient(testCredentials.apiRootUrl, authProvider)
55+
val client = WpApiClient(testCredentials.apiRootUrl, authProvider, emptyList())
5656

5757
// Assert that initial unauthorized request fails
5858
assert(client.request { requestBuilder ->
@@ -75,7 +75,7 @@ class AuthProviderTest {
7575
val modifiableAuthenticationProvider =
7676
ModifiableAuthenticationProvider(authentication = WpAuthentication.None)
7777
val authProvider = WpAuthenticationProvider.modifiable(modifiableAuthenticationProvider)
78-
val client = WpApiClient(testCredentials.apiRootUrl, authProvider)
78+
val client = WpApiClient(testCredentials.apiRootUrl, authProvider, emptyList())
7979

8080
// Assert that request fails without authentication
8181
assert(client.request { requestBuilder ->

native/kotlin/api/kotlin/src/integrationTest/kotlin/IntegrationTestHelpers.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ fun defaultApiClient(): WpApiClient {
2121
val authProvider = WpAuthenticationProvider.staticWithUsernameAndPassword(
2222
username = testCredentials.adminUsername, password = testCredentials.adminPassword
2323
)
24-
return WpApiClient(testCredentials.apiRootUrl, authProvider)
24+
return WpApiClient(testCredentials.apiRootUrl, authProvider, emptyList())
2525
}
2626

2727
fun <T> WpRequestResult<T>.assertSuccess() {

native/kotlin/api/kotlin/src/integrationTest/kotlin/MediaEndpointTest.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ class MediaEndpointTest {
9494
password = TestCredentials.INSTANCE.adminPassword
9595
)
9696
val requestExecutor = WpRequestExecutor(
97+
interceptors = emptyList(),
9798
fileResolver = FileResolverMock(),
9899
uploadListener = uploadListener
99100
)
@@ -144,6 +145,7 @@ class MediaEndpointTest {
144145
username = testCredentials.adminUsername, password = testCredentials.adminPassword
145146
)
146147
val requestExecutor = WpRequestExecutor(
148+
interceptors = emptyList(),
147149
fileResolver = FileResolverMock()
148150
)
149151
return WpApiClient(

native/kotlin/api/kotlin/src/integrationTest/kotlin/PluginsEndpointTest.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,16 @@ class PluginsEndpointTest {
2020
testCredentials.apiRootUrl, WpAuthenticationProvider.staticWithUsernameAndPassword(
2121
username = testCredentials.adminUsername,
2222
password = testCredentials.adminPassword
23-
)
23+
),
24+
emptyList()
2425
)
2526
private val clientAsSubscriber = WpApiClient(
2627
testCredentials.apiRootUrl,
2728
WpAuthenticationProvider.staticWithUsernameAndPassword(
2829
username = testCredentials.subscriberUsername,
2930
password = testCredentials.subscriberPassword
30-
)
31+
),
32+
emptyList()
3133
)
3234

3335
@Test

native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/JetpackApiClient.kt

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package rs.wordpress.api.kotlin
33
import kotlinx.coroutines.CoroutineDispatcher
44
import kotlinx.coroutines.Dispatchers
55
import kotlinx.coroutines.withContext
6+
import okhttp3.Interceptor
67
import uniffi.wp_api.ApiUrlResolver
78
import uniffi.wp_api.ParsedUrl
89
import uniffi.wp_api.RequestExecutor
@@ -18,14 +19,14 @@ import java.net.URL
1819
class JetpackApiClient(
1920
apiUrlResolver: ApiUrlResolver,
2021
authProvider: WpAuthenticationProvider,
21-
private val requestExecutor: RequestExecutor = WpRequestExecutor(),
22+
private val requestExecutor: RequestExecutor,
2223
private val appNotifier: WpAppNotifier = EmptyAppNotifier(),
2324
private val dispatcher: CoroutineDispatcher = Dispatchers.IO
2425
) {
2526
constructor(
2627
wpOrgSiteApiRootUrl: URL,
2728
authProvider: WpAuthenticationProvider,
28-
requestExecutor: RequestExecutor = WpRequestExecutor(),
29+
requestExecutor: RequestExecutor,
2930
appNotifier: WpAppNotifier = EmptyAppNotifier(),
3031
dispatcher: CoroutineDispatcher = Dispatchers.IO
3132
) : this(
@@ -36,6 +37,24 @@ class JetpackApiClient(
3637
dispatcher
3738
)
3839

40+
/**
41+
* Convenience constructor that accepts a list of OkHttp interceptors.
42+
* Uses [WpRequestExecutor] internally with the provided interceptors.
43+
*/
44+
constructor(
45+
wpOrgSiteApiRootUrl: URL,
46+
authProvider: WpAuthenticationProvider,
47+
interceptors: List<Interceptor>,
48+
appNotifier: WpAppNotifier = EmptyAppNotifier(),
49+
dispatcher: CoroutineDispatcher = Dispatchers.IO
50+
) : this(
51+
wpOrgSiteApiRootUrl,
52+
authProvider,
53+
requestExecutor = WpRequestExecutor(interceptors),
54+
appNotifier,
55+
dispatcher
56+
)
57+
3958
// Don't expose `WpRequestBuilder` directly so we can control how it's used
4059
private val requestBuilder by lazy {
4160
UniffiJetpackApiClient(

native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpApiClient.kt

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package rs.wordpress.api.kotlin
33
import kotlinx.coroutines.CoroutineDispatcher
44
import kotlinx.coroutines.Dispatchers
55
import kotlinx.coroutines.withContext
6+
import okhttp3.Interceptor
67
import uniffi.wp_api.ApiUrlResolver
78
import uniffi.wp_api.ParsedUrl
89
import uniffi.wp_api.RequestExecutor
@@ -18,14 +19,14 @@ import java.net.URL
1819
class WpApiClient(
1920
apiUrlResolver: ApiUrlResolver,
2021
authProvider: WpAuthenticationProvider,
21-
private val requestExecutor: RequestExecutor = WpRequestExecutor(),
22+
private val requestExecutor: RequestExecutor,
2223
private val appNotifier: WpAppNotifier = EmptyAppNotifier(),
2324
private val dispatcher: CoroutineDispatcher = Dispatchers.IO
2425
) {
2526
constructor(
2627
wpOrgSiteApiRootUrl: URL,
2728
authProvider: WpAuthenticationProvider,
28-
requestExecutor: RequestExecutor = WpRequestExecutor(),
29+
requestExecutor: RequestExecutor,
2930
appNotifier: WpAppNotifier = EmptyAppNotifier(),
3031
dispatcher: CoroutineDispatcher = Dispatchers.IO
3132
) : this(
@@ -36,6 +37,24 @@ class WpApiClient(
3637
dispatcher
3738
)
3839

40+
/**
41+
* Convenience constructor that accepts a list of OkHttp interceptors.
42+
* Uses [WpRequestExecutor] internally with the provided interceptors.
43+
*/
44+
constructor(
45+
wpOrgSiteApiRootUrl: URL,
46+
authProvider: WpAuthenticationProvider,
47+
interceptors: List<Interceptor>,
48+
appNotifier: WpAppNotifier = EmptyAppNotifier(),
49+
dispatcher: CoroutineDispatcher = Dispatchers.IO
50+
) : this(
51+
wpOrgSiteApiRootUrl,
52+
authProvider,
53+
requestExecutor = WpRequestExecutor(interceptors),
54+
appNotifier,
55+
dispatcher
56+
)
57+
3958
// Don't expose `WpRequestBuilder` directly so we can control how it's used
4059
private val requestBuilder by lazy {
4160
UniffiWpApiClient(

native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpComApiClient.kt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package rs.wordpress.api.kotlin
33
import kotlinx.coroutines.CoroutineDispatcher
44
import kotlinx.coroutines.Dispatchers
55
import kotlinx.coroutines.withContext
6+
import okhttp3.Interceptor
67
import uniffi.wp_api.RequestExecutor
78
import uniffi.wp_api.UniffiWpComApiClient
89
import uniffi.wp_api.WpApiClientDelegate
@@ -13,10 +14,27 @@ import uniffi.wp_api.WpAuthenticationProvider
1314

1415
class WpComApiClient(
1516
authProvider: WpAuthenticationProvider,
16-
private val requestExecutor: RequestExecutor = WpRequestExecutor(),
17+
private val requestExecutor: RequestExecutor,
1718
private val appNotifier: WpAppNotifier = EmptyAppNotifier(),
1819
private val dispatcher: CoroutineDispatcher = Dispatchers.IO
1920
) {
21+
22+
/**
23+
* Convenience constructor that accepts a list of OkHttp interceptors.
24+
* Uses [WpRequestExecutor] internally with the provided interceptors.
25+
*/
26+
constructor(
27+
authProvider: WpAuthenticationProvider,
28+
interceptors: List<Interceptor>,
29+
appNotifier: WpAppNotifier = EmptyAppNotifier(),
30+
dispatcher: CoroutineDispatcher = Dispatchers.IO
31+
) : this(
32+
authProvider,
33+
requestExecutor = WpRequestExecutor(interceptors),
34+
appNotifier,
35+
dispatcher
36+
)
37+
2038
// Don't expose `WpRequestBuilder` directly so we can control how it's used
2139
private val requestBuilder by lazy {
2240
UniffiWpComApiClient(

native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpHttpClient.kt

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,34 @@
11
package rs.wordpress.api.kotlin
22

3+
import okhttp3.Interceptor
34
import okhttp3.OkHttpClient
45
import javax.net.ssl.HostnameVerifier
56
import javax.net.ssl.SSLSession
67

78
sealed class WpHttpClient {
89
abstract fun getClient(): OkHttpClient
910

10-
class DefaultHttpClient : WpHttpClient() {
11-
private var client: OkHttpClient = OkHttpClient()
12-
11+
class DefaultHttpClient(
12+
private val interceptors: List<Interceptor>
13+
) : WpHttpClient() {
1314
private var allowedHostnames: Map<String, List<String>> = emptyMap()
1415

16+
private var client: OkHttpClient = buildClient()
17+
1518
fun addAllowedAlternativeNamesForHostname(hostname: String, allowedNames: List<String>) {
1619
// Preserve the previous records for this key
1720
val previousList = allowedHostnames[hostname].orEmpty()
1821
allowedHostnames = allowedHostnames.plus(Pair(hostname, allowedNames.plus(previousList)))
19-
updateClient()
22+
client = buildClient()
2023
}
2124

22-
private fun updateClient() {
23-
client = client.newBuilder()
24-
.hostnameVerifier(WpRequestExecutorHostnameVerifier(allowedHostnames))
25-
.build()
25+
private fun buildClient(): OkHttpClient {
26+
return OkHttpClient.Builder().apply {
27+
this@DefaultHttpClient.interceptors.forEach { addInterceptor(it) }
28+
if (allowedHostnames.isNotEmpty()) {
29+
hostnameVerifier(WpRequestExecutorHostnameVerifier(allowedHostnames))
30+
}
31+
}.build()
2632
}
2733

2834
override fun getClient() = client

0 commit comments

Comments
 (0)