Skip to content

Commit b4449b1

Browse files
authored
implement request factory (#176)
* implement request factory * add unit tests
1 parent 86a4efa commit b4449b1

File tree

10 files changed

+116
-25
lines changed

10 files changed

+116
-25
lines changed

core/src/main/java/com/segment/analytics/kotlin/core/Configuration.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ data class Configuration(
3636
val defaultSettings: Settings = Settings(),
3737
var autoAddSegmentDestination: Boolean = true,
3838
var apiHost: String = DEFAULT_API_HOST,
39-
var cdnHost: String = DEFAULT_CDN_HOST
39+
var cdnHost: String = DEFAULT_CDN_HOST,
40+
var requestFactory: RequestFactory = RequestFactory()
4041
) {
4142
fun isValid(): Boolean {
4243
return writeKey.isNotBlank() && application != null

core/src/main/java/com/segment/analytics/kotlin/core/HTTPClient.kt

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,18 @@ import java.net.HttpURLConnection
1010
import java.net.MalformedURLException
1111
import java.net.URL
1212
import java.util.zip.GZIPOutputStream
13-
class HTTPClient(private val writeKey: String) {
13+
class HTTPClient(
14+
private val writeKey: String,
15+
private val requestFactory: RequestFactory = RequestFactory()
16+
) {
1417

1518
fun settings(cdnHost: String): Connection {
16-
val connection: HttpURLConnection =
17-
openConnection("https://$cdnHost/projects/$writeKey/settings")
18-
connection.setRequestProperty("Content-Type", "application/json; charset=utf-8")
19-
val responseCode = connection.responseCode
20-
if (responseCode != HttpURLConnection.HTTP_OK) {
21-
connection.disconnect()
22-
throw IOException("HTTP " + responseCode + ": " + connection.responseMessage)
23-
}
19+
val connection: HttpURLConnection = requestFactory.settings(cdnHost, writeKey)
2420
return connection.createGetConnection()
2521
}
2622

2723
fun upload(apiHost: String): Connection {
28-
val connection: HttpURLConnection = openConnection("https://$apiHost/b")
29-
connection.setRequestProperty("Content-Type", "text/plain")
30-
connection.doOutput = true
31-
connection.setChunkedStreamingMode(0)
24+
val connection: HttpURLConnection = requestFactory.upload(apiHost)
3225
return connection.createPostConnection()
3326
}
3427

@@ -129,4 +122,46 @@ internal class HTTPException(
129122
fun is4xx(): Boolean {
130123
return responseCode in 400..499
131124
}
125+
}
126+
127+
open class RequestFactory {
128+
open fun settings(cdnHost: String, writeKey: String): HttpURLConnection {
129+
val connection: HttpURLConnection = openConnection("https://$cdnHost/projects/$writeKey/settings")
130+
connection.setRequestProperty("Content-Type", "application/json; charset=utf-8")
131+
val responseCode = connection.responseCode
132+
if (responseCode != HttpURLConnection.HTTP_OK) {
133+
connection.disconnect()
134+
throw IOException("HTTP " + responseCode + ": " + connection.responseMessage)
135+
}
136+
return connection
137+
}
138+
139+
open fun upload(apiHost: String): HttpURLConnection {
140+
val connection: HttpURLConnection = openConnection("https://$apiHost/b")
141+
connection.setRequestProperty("Content-Type", "text/plain")
142+
connection.doOutput = true
143+
connection.setChunkedStreamingMode(0)
144+
return connection
145+
}
146+
147+
/**
148+
* Configures defaults for connections opened with [.upload], and [ ][.projectSettings].
149+
*/
150+
open fun openConnection(url: String): HttpURLConnection {
151+
val requestedURL: URL = try {
152+
URL(url)
153+
} catch (e: MalformedURLException) {
154+
throw IOException("Attempted to use malformed url: $url", e)
155+
}
156+
val connection = requestedURL.openConnection() as HttpURLConnection
157+
connection.connectTimeout = 15_000 // 15s
158+
connection.readTimeout = 20_1000 // 20s
159+
160+
connection.setRequestProperty(
161+
"User-Agent",
162+
"analytics-kotlin/$LIBRARY_VERSION"
163+
)
164+
connection.doInput = true
165+
return connection
166+
}
132167
}

core/src/main/java/com/segment/analytics/kotlin/core/Settings.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ internal fun Analytics.fetchSettings(
106106
writeKey: String,
107107
cdnHost: String
108108
): Settings? = try {
109-
val connection = HTTPClient(writeKey).settings(cdnHost)
109+
val connection = HTTPClient(writeKey, this.configuration.requestFactory).settings(cdnHost)
110110
val settingsString =
111111
connection.inputStream?.bufferedReader()?.use(BufferedReader::readText) ?: ""
112112
log("Fetched Settings: $settingsString")

core/src/main/java/com/segment/analytics/kotlin/core/compat/ConfigurationBuilder.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.segment.analytics.kotlin.core.compat
22

33
import com.segment.analytics.kotlin.core.Configuration
4+
import com.segment.analytics.kotlin.core.RequestFactory
45

56
/**
67
* This class serves as a helper class for Java compatibility, which makes the
@@ -32,5 +33,7 @@ class ConfigurationBuilder (writeKey: String) {
3233

3334
fun setCdnHost(cdnHost: String) = apply { configuration.cdnHost = cdnHost}
3435

36+
fun setRequestFactory(requestFactory: RequestFactory) = apply { configuration.requestFactory = requestFactory }
37+
3538
fun build() = configuration
3639
}

core/src/main/java/com/segment/analytics/kotlin/core/platform/EventPipeline.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ internal class EventPipeline(
3131

3232
private var uploadChannel: Channel<String>
3333

34-
private val httpClient: HTTPClient = HTTPClient(apiKey)
34+
private val httpClient: HTTPClient = HTTPClient(apiKey, analytics.configuration.requestFactory)
3535

3636
private val storage get() = analytics.storage
3737

core/src/test/kotlin/com/segment/analytics/kotlin/core/HTTPClientTests.kt

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,21 @@ package com.segment.analytics.kotlin.core
33
import com.segment.analytics.kotlin.core.Constants.LIBRARY_VERSION
44
import io.mockk.clearConstructorMockk
55
import io.mockk.every
6+
import io.mockk.mockk
67
import io.mockk.spyk
78
import org.junit.jupiter.api.Assertions.assertEquals
89
import org.junit.jupiter.api.Assertions.assertTrue
910
import org.junit.jupiter.api.Assertions.fail
1011
import org.junit.jupiter.api.Test
1112
import org.junit.jupiter.api.TestInstance
13+
import java.io.ByteArrayOutputStream
1214
import java.io.IOException
1315
import java.io.InputStream
16+
import java.io.OutputStream
1417
import java.net.HttpURLConnection
18+
import java.net.MalformedURLException
1519
import java.net.URL
20+
import java.net.http.HttpClient
1621

1722
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
1823
class HTTPClientTests {
@@ -80,4 +85,53 @@ class HTTPClientTests {
8085
}
8186
}
8287

88+
@Test
89+
fun `custom requestFactory takes effect`() {
90+
val httpClient = HTTPClient("123", object : RequestFactory() {
91+
override fun settings(cdnHost: String, writeKey: String): HttpURLConnection {
92+
return openConnection("https://cdn.test.com")
93+
}
94+
95+
override fun upload(apiHost: String): HttpURLConnection {
96+
return openConnection("https://api.test.com").apply { doOutput = true }
97+
}
98+
99+
override fun openConnection(url: String): HttpURLConnection {
100+
val requestedURL: URL = try {
101+
URL(url)
102+
} catch (e: MalformedURLException) {
103+
throw IOException("Attempted to use malformed url: $url", e)
104+
}
105+
106+
return object : HttpURLConnection(requestedURL) {
107+
override fun connect() {
108+
109+
}
110+
111+
override fun disconnect() {
112+
}
113+
114+
override fun usingProxy() = false
115+
116+
override fun getOutputStream(): OutputStream {
117+
return ByteArrayOutputStream()
118+
}
119+
}
120+
}
121+
})
122+
123+
httpClient.settings("cdn-settings.segment.com/v1").connection.let {
124+
assertEquals(
125+
"https://cdn.test.com",
126+
it.url.toString()
127+
)
128+
}
129+
130+
httpClient.upload("api.segment.io/v1").connection.let {
131+
assertEquals(
132+
"https://api.test.com",
133+
it.url.toString()
134+
)
135+
}
136+
}
83137
}

core/src/test/kotlin/com/segment/analytics/kotlin/core/compat/ConfigurationBuilderTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ internal class ConfigurationBuilderTest {
123123
.setAutoAddSegmentDestination(expected.autoAddSegmentDestination)
124124
.setApiHost(expected.apiHost)
125125
.setCdnHost(expected.cdnHost)
126+
.setRequestFactory(expected.requestFactory)
126127
.build()
127128

128129
assertEquals(expected, config)

samples/java-android-app/build.gradle

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@ plugins {
33
}
44

55
android {
6-
compileSdkVersion 31
7-
buildToolsVersion "31.0.0"
6+
compileSdkVersion 33
87

98
defaultConfig {
109
applicationId "com.segment.analytics.javaandroidapp"
1110
minSdkVersion 16
12-
targetSdkVersion 31
11+
targetSdkVersion 33
1312
versionCode 1
1413
versionName "1.0"
1514

samples/kotlin-android-app-destinations/build.gradle

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@ plugins {
55
}
66

77
android {
8-
compileSdkVersion 31
9-
buildToolsVersion "31.0.0"
8+
compileSdkVersion 33
109

1110
defaultConfig {
1211
multiDexEnabled true
1312
applicationId "com.segment.analytics.kotlin.destinations"
1413
minSdkVersion 21
15-
targetSdkVersion 31
14+
targetSdkVersion 33
1615
versionCode 3
1716
versionName "2.0"
1817

samples/kotlin-android-app/build.gradle

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,13 @@ plugins {
66
}
77

88
android {
9-
compileSdkVersion 31
10-
buildToolsVersion "31.0.0"
9+
compileSdkVersion 33
1110

1211
defaultConfig {
1312
multiDexEnabled true
1413
applicationId "com.segment.analytics.next"
1514
minSdkVersion 19
16-
targetSdkVersion 31
15+
targetSdkVersion 33
1716
versionCode 3
1817
versionName "2.0"
1918

0 commit comments

Comments
 (0)