Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added gzip support to web #305

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions web/src/main/java/com/rudderstack/web/WebService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ interface WebService {
body: String?,
endpoint: String,
responseClass: Class<T>,
isGzipEnabled: Boolean = false,
): Future<HttpResponse<T>>

/**
Expand All @@ -132,6 +133,7 @@ interface WebService {
body: String?,
endpoint: String,
responseTypeAdapter: RudderTypeAdapter<T>,
isGzipEnabled: Boolean = false,
): Future<HttpResponse<T>>

/**
Expand All @@ -151,6 +153,7 @@ interface WebService {
body: String?,
endpoint: String,
responseClass: Class<T>,
isGzipEnabled: Boolean = false,
callback: (HttpResponse<T>) -> Unit,
)

Expand All @@ -171,6 +174,7 @@ interface WebService {
body: String?,
endpoint: String,
responseTypeAdapter: RudderTypeAdapter<T>,
isGzipEnabled: Boolean = false,
callback: (HttpResponse<T>) -> Unit,
)

Expand All @@ -181,4 +185,11 @@ interface WebService {
* @see HttpInterceptor
*/
fun setInterceptor(httpInterceptor: HttpInterceptor)

/**
* Performs Cleanup. Also shuts down the executor service. Do not call this method if the
* same executor service is being used by other objects. In that case handle the executor
* shutdown yourself.
*/
fun shutdown(shutdownExecutor: Boolean = true)
}
128 changes: 79 additions & 49 deletions web/src/main/java/com/rudderstack/web/internal/WebServiceImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.rudderstack.rudderjsonadapter.RudderTypeAdapter
import com.rudderstack.web.HttpInterceptor
import com.rudderstack.web.HttpResponse
import com.rudderstack.web.WebService
import com.rudderstack.web.utils.GzipUtils
import java.io.BufferedInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
Expand All @@ -38,8 +39,7 @@ class WebServiceImpl internal constructor(
private val baseUrl: String

private enum class HttpMethod {
POST,
GET
POST, GET
}

init {
Expand All @@ -53,7 +53,7 @@ class WebServiceImpl internal constructor(
responseClass: Class<T>
): Future<HttpResponse<T>> {
return executor.submit(Callable {
httpCall(headers, query, null, endpoint, HttpMethod.GET, responseClass)
httpCall(headers, query, null, endpoint, HttpMethod.GET, responseClass, false)
})
}

Expand All @@ -64,7 +64,7 @@ class WebServiceImpl internal constructor(
responseTypeAdapter: RudderTypeAdapter<T>
): Future<HttpResponse<T>> {
return executor.submit(Callable {
httpCall(headers, query, null, endpoint, HttpMethod.GET, responseTypeAdapter)
httpCall(headers, query, null, endpoint, HttpMethod.GET, responseTypeAdapter, false)
})
}

Expand All @@ -77,7 +77,7 @@ class WebServiceImpl internal constructor(
) {
executor.execute {
callback.invoke(
httpCall(headers, query, null, endpoint, HttpMethod.GET, responseTypeAdapter)
httpCall(headers, query, null, endpoint, HttpMethod.GET, responseTypeAdapter, false)
)
}
}
Expand All @@ -92,7 +92,7 @@ class WebServiceImpl internal constructor(

executor.execute {
callback.invoke(
httpCall(headers, query, null, endpoint, HttpMethod.GET, responseClass)
httpCall(headers, query, null, endpoint, HttpMethod.GET, responseClass, false)
)
}
}
Expand All @@ -102,10 +102,11 @@ class WebServiceImpl internal constructor(
query: Map<String, String>?,
body: String?,
endpoint: String,
responseClass: Class<T>
responseClass: Class<T>,
isGzipEnabled: Boolean
): Future<HttpResponse<T>> {
return executor.submit(Callable {
httpCall(headers, query, body, endpoint, HttpMethod.POST, responseClass)
httpCall(headers, query, body, endpoint, HttpMethod.POST, responseClass, isGzipEnabled)
})
}

Expand All @@ -114,10 +115,19 @@ class WebServiceImpl internal constructor(
query: Map<String, String>?,
body: String?,
endpoint: String,
responseTypeAdapter: RudderTypeAdapter<T>
responseTypeAdapter: RudderTypeAdapter<T>,
isGzipEnabled: Boolean
): Future<HttpResponse<T>> {
return executor.submit(Callable {
httpCall(headers, query, body, endpoint, HttpMethod.POST, responseTypeAdapter)
httpCall(
headers,
query,
body,
endpoint,
HttpMethod.POST,
responseTypeAdapter,
isGzipEnabled
)
})
}

Expand All @@ -127,11 +137,20 @@ class WebServiceImpl internal constructor(
body: String?,
endpoint: String,
responseClass: Class<T>,
isGzipEnabled: Boolean,
callback: (HttpResponse<T>) -> Unit
) {
executor.execute {
callback.invoke(
httpCall(headers, query, body, endpoint, HttpMethod.POST, responseClass)
httpCall(
headers,
query,
body,
endpoint,
HttpMethod.POST,
responseClass,
isGzipEnabled
)
)
}
}
Expand All @@ -142,6 +161,7 @@ class WebServiceImpl internal constructor(
body: String?,
endpoint: String,
responseTypeAdapter: RudderTypeAdapter<T>,
isGzipEnabled: Boolean,
callback: (HttpResponse<T>) -> Unit
) {
executor.execute {
Expand All @@ -152,7 +172,8 @@ class WebServiceImpl internal constructor(
body,
endpoint,
HttpMethod.POST,
responseTypeAdapter
responseTypeAdapter,
isGzipEnabled
)
)
}
Expand All @@ -163,20 +184,26 @@ class WebServiceImpl internal constructor(
_interceptor = httpInterceptor
}

override fun shutdown(shutdownExecutor: Boolean) {
if(shutdownExecutor)
executor.shutdown()
_interceptor = null
}

// @Throws(Throwable::class)
private fun <T : Any> httpCall(
headers: Map<String, String>?,
query: Map<String, String>?,
body: String?,
endpoint: String,
type: HttpMethod,

responseClass: Class<T>
responseClass: Class<T>,
isGzipEncoded: Boolean
): HttpResponse<T> {
return rawHttpCall(headers, query, body, endpoint, type, deserializer = { json ->
jsonAdapter.readJson(json, responseClass)
?: throw IllegalArgumentException("Json adapter not able to parse response body")
})
?: throw IllegalArgumentException("Json adapter not able to parse response body")
}, isGzipEncoded = isGzipEncoded)
}

@Throws(IOException::class)
Expand All @@ -186,17 +213,17 @@ class WebServiceImpl internal constructor(
body: String?,
endpoint: String,
type: HttpMethod,
typeAdapter: RudderTypeAdapter<T>
typeAdapter: RudderTypeAdapter<T>,
isGzipEncoded: Boolean
): HttpResponse<T> {
return rawHttpCall(headers, query, body, endpoint, type, deserializer = { json ->
if (json.isEmpty()) {
//TODO add logger
// logger.debug("Empty response body")
null
} else
jsonAdapter.readJson(json, typeAdapter)
?: throw IllegalArgumentException("Json adapter not able to parse response body")
})
} else jsonAdapter.readJson(json, typeAdapter)
?: throw IllegalArgumentException("Json adapter not able to parse response body")
}, isGzipEncoded = isGzipEncoded)
}


Expand All @@ -206,15 +233,23 @@ class WebServiceImpl internal constructor(
body: String?,
endpoint: String,
type: HttpMethod,
isGzipEncoded: Boolean,
deserializer: (String) -> T?
): HttpResponse<T> {
try {

val httpConnection =
createHttpConnection(endpoint, headers, type, query, body, defaultTimeout) {
//call interceptor if any changes to HttpConnection required
_interceptor?.intercept(it) ?: it
}
val httpConnection = createHttpConnection(
endpoint,
headers,
type,
query,
body,
defaultTimeout,
isGzipEncoded
) {
//call interceptor if any changes to HttpConnection required
_interceptor?.intercept(it) ?: it
}
// create connection
httpConnection.connect()

Expand All @@ -233,9 +268,7 @@ class WebServiceImpl internal constructor(
return Utils.NetworkResponses.SUCCESS
}*/
HttpResponse(
httpConnection.responseCode,
deserializer.invoke(baos.toString()),
null
httpConnection.responseCode, deserializer.invoke(baos.toString()), null
)

} else {
Expand All @@ -256,10 +289,7 @@ class WebServiceImpl internal constructor(
// RudderLogger.logError(ex)
ex.printStackTrace()
return HttpResponse(
status = HttpResponse.HTTP_STATUS_NONE,
body = null,
errorBody = null,
error = ex
status = HttpResponse.HTTP_STATUS_NONE, body = null, errorBody = null, error = ex
)
}

Expand All @@ -269,9 +299,12 @@ class WebServiceImpl internal constructor(
@Throws(IOException::class)
private fun createHttpConnection(
endpoint: String,
headers: Map<String, String>?, type: HttpMethod,
headers: Map<String, String>?,
type: HttpMethod,
query: Map<String, String>?,
body: String?, defaultTimeout: Int,
body: String?,
defaultTimeout: Int,
isGzipEncoded: Boolean,
onHttpConnectionCreated: (HttpURLConnection) -> HttpURLConnection
): HttpURLConnection {
//the url to hit
Expand All @@ -290,15 +323,10 @@ class WebServiceImpl internal constructor(
}

// set content type for network request if not present
if (headers?.containsKey("Content-Type") == false)
httpConn.setRequestProperty("Content-Type", "application/json")
// set authorization header
/*httpConnection.setRequestProperty(
"Authorization",
String.format(Locale.US, "Basic %s", this.authHeaderString)
)*/
// set anonymousId header for definitive routing
// httpConnection.setRequestProperty("AnonymousId", this.anonymousIdHeaderString)
if (headers?.containsKey("Content-Type") == false) httpConn.setRequestProperty(
"Content-Type", "application/json"
)

// set request method
httpConn.requestMethod = when (type) {
HttpMethod.GET -> "GET"
Expand All @@ -310,7 +338,10 @@ class WebServiceImpl internal constructor(
if (type == HttpMethod.POST) {
// set connection object to return output
httpConn.doOutput = true
val os = httpConn.outputStream
val os = if (isGzipEncoded) GzipUtils.getGzipOutputStream(httpConn.outputStream)
?: //TODO add logger.debug("Gzip compression not supported")
httpConn.outputStream
else httpConn.outputStream
val osw = OutputStreamWriter(os, "UTF-8")

body?.apply {
Expand All @@ -323,10 +354,9 @@ class WebServiceImpl internal constructor(
return httpConn
}

private fun Map<String, String>.createQueryString() =
takeIf { isNotEmpty() }?.map {
"${it.key}=${it.value}"
}?.reduce { acc, s -> "$acc&$s" } ?: ""
private fun Map<String, String>.createQueryString() = takeIf { isNotEmpty() }?.map {
"${it.key}=${it.value}"
}?.reduce { acc, s -> "$acc&$s" } ?: ""

private val String.validatedBaseUrl
get() = if (this.endsWith('/')) this else "$this/"
Expand Down
41 changes: 41 additions & 0 deletions web/src/main/java/com/rudderstack/web/utils/GzipUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Creator: Debanjan Chatterjee on 13/09/23, 12:11 pm Last modified: 13/09/23, 12:11 pm
* Copyright: All rights reserved Ⓒ 2023 http://rudderstack.com
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain a
* copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package com.rudderstack.web.utils

import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream

object GzipUtils {
fun getGzipOutputStream(outputStream: OutputStream?): OutputStream? {
try {
return GZIPOutputStream(outputStream)
} catch (e: IOException) {
e.printStackTrace()
}
return null
}
fun getGzipInputStream(inputStream: InputStream?): InputStream? {
try {
return GZIPInputStream(inputStream)
} catch (e: IOException) {
e.printStackTrace()
}
return null
}

}
Loading