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

Support EventListener to decorate HttpTransaction recordings #918

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.chuckerteam.chucker.api

import com.chuckerteam.chucker.internal.data.entity.HttpTransaction
import okhttp3.Call
import okhttp3.EventListener
import okhttp3.Handshake
import okhttp3.Protocol
import java.io.IOException
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.Proxy

internal class ChuckerEventListener constructor(
private val onCallEnded: ((Call) -> Unit)
) : EventListener() {

val httpTransaction = HttpTransaction()

override fun callStart(call: Call) {
super.callStart(call)
httpTransaction.callStartDate = System.currentTimeMillis()
}

override fun callEnd(call: Call) {
super.callEnd(call)
onCallEnded(call)
httpTransaction.callEndDate = System.currentTimeMillis()
}

override fun callFailed(call: Call, ioe: IOException) {
super.callFailed(call, ioe)
onCallEnded(call)
}

override fun canceled(call: Call) {
super.canceled(call)
onCallEnded(call)
}

override fun connectStart(call: Call, inetSocketAddress: InetSocketAddress, proxy: Proxy) {
super.connectStart(call, inetSocketAddress, proxy)
httpTransaction.connectStartDate = System.currentTimeMillis()
}

override fun connectEnd(
call: Call,
inetSocketAddress: InetSocketAddress,
proxy: Proxy,
protocol: Protocol?
) {
super.connectEnd(call, inetSocketAddress, proxy, protocol)
httpTransaction.connectEndDate = System.currentTimeMillis()
}

override fun dnsStart(call: Call, domainName: String) {
super.dnsStart(call, domainName)
httpTransaction.dnsStartDate = System.currentTimeMillis()
}

override fun dnsEnd(call: Call, domainName: String, inetAddressList: List<InetAddress>) {
super.dnsEnd(call, domainName, inetAddressList)
httpTransaction.dnsEndDate = System.currentTimeMillis()
}

override fun secureConnectStart(call: Call) {
super.secureConnectStart(call)
httpTransaction.secureConnectStartDate = System.currentTimeMillis()
}

override fun secureConnectEnd(call: Call, handshake: Handshake?) {
super.secureConnectEnd(call, handshake)
httpTransaction.secureConnectEndDate = System.currentTimeMillis()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package com.chuckerteam.chucker.api

import android.content.Context
import androidx.annotation.VisibleForTesting
import com.chuckerteam.chucker.internal.data.entity.HttpTransaction
import com.chuckerteam.chucker.internal.support.CacheDirectoryProvider
import com.chuckerteam.chucker.internal.support.HttpTransactionFactory
import com.chuckerteam.chucker.internal.support.PlainTextDecoder
import com.chuckerteam.chucker.internal.support.RealChuckerEventListenerFactory
import com.chuckerteam.chucker.internal.support.RequestProcessor
import com.chuckerteam.chucker.internal.support.ResponseProcessor
import com.chuckerteam.chucker.internal.support.SimpleHttpTransactionFactory
import okhttp3.EventListener
import okhttp3.Interceptor
import okhttp3.Response
import java.io.IOException
Expand Down Expand Up @@ -53,6 +56,8 @@ public class ChuckerInterceptor private constructor(
decoders,
)

private var httpTransactionFactory: HttpTransactionFactory = SimpleHttpTransactionFactory()

init {
if (builder.createShortcut) {
Chucker.createShortcut(builder.context)
Expand All @@ -64,9 +69,16 @@ public class ChuckerInterceptor private constructor(
headersToRedact.addAll(headerName)
}

public fun useEventListener(): EventListener.Factory {
val realEventListenerFactory = RealChuckerEventListenerFactory()
httpTransactionFactory = realEventListenerFactory

return realEventListenerFactory
}

@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val transaction = HttpTransaction()
val transaction = httpTransactionFactory.getHttpTransaction(chain.call())
val request = chain.request()

requestProcessor.process(request, transaction)
Expand All @@ -87,14 +99,15 @@ public class ChuckerInterceptor private constructor(
*
* @param context An Android [Context].
*/
public class Builder(internal var context: Context) {
public class Builder(providedContext: Context) {
internal var collector: ChuckerCollector? = null
internal var maxContentLength = MAX_CONTENT_LENGTH
internal var cacheDirectoryProvider: CacheDirectoryProvider? = null
internal var alwaysReadResponseBody = false
internal var headersToRedact = emptySet<String>()
internal var decoders = emptyList<BodyDecoder>()
internal var createShortcut = true
internal val context = providedContext.applicationContext

/**
* Sets the [ChuckerCollector] to customize data retention.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ internal class HttpTransaction(
@ColumnInfo(name = "responseImageData") var responseImageData: ByteArray?,
@ColumnInfo(name = "graphQlDetected") var graphQlDetected: Boolean = false,
@ColumnInfo(name = "graphQlOperationName") var graphQlOperationName: String?,
@ColumnInfo(name = "callStartDate") var callStartDate: Long?,
@ColumnInfo(name = "callEndDate") var callEndDate: Long?,
@ColumnInfo(name = "connectStartDate") var connectStartDate: Long?,
@ColumnInfo(name = "connectEndDate") var connectEndDate: Long?,
@ColumnInfo(name = "secureConnectStartDate") var secureConnectStartDate: Long?,
@ColumnInfo(name = "secureConnectEndDate") var secureConnectEndDate: Long?,
@ColumnInfo(name = "dnsStartDate") var dnsStartDate: Long?,
@ColumnInfo(name = "dnsEndDate") var dnsEndDate: Long?,
) {

@Ignore
Expand Down Expand Up @@ -85,7 +93,15 @@ internal class HttpTransaction(
responseHeadersSize = null,
responseBody = null,
responseImageData = null,
graphQlOperationName = null
graphQlOperationName = null,
callStartDate = null,
callEndDate = null,
connectStartDate = null,
connectEndDate = null,
secureConnectStartDate = null,
secureConnectEndDate = null,
dnsStartDate = null,
dnsEndDate = null,
)

enum class Status {
Expand All @@ -109,6 +125,42 @@ internal class HttpTransaction(

val durationString: String?
get() = tookMs?.let { "$it ms" }
val callDurationString: String?
get() {
val start = callStartDate ?: return null
val end = callEndDate ?: return null
return (end - start).let { "$it ms" }
}

val connectDuration: Long?
get() {
val start = connectStartDate ?: return null
val end = connectEndDate ?: return null
return (end - start)
}
val connectDurationString: String?
get() = connectDuration?.let { "$it ms" }


val secureConnectDuration: Long?
get() {
val start = secureConnectStartDate ?: return null
val end = secureConnectEndDate ?: return null
return (end - start)
}

val secureConnectDurationString: String?
get() = secureConnectDuration?.let { "$it ms" }

val dnsDuration: Long?
get() {
val start = dnsStartDate ?: return null
val end = dnsEndDate ?: return null
return (end - start)
}

val dnsDurationString: String?
get() = dnsDuration?.let { "$it ms" }

val requestSizeString: String
get() = formatBytes(requestPayloadSize ?: 0)
Expand Down Expand Up @@ -211,6 +263,7 @@ internal class HttpTransaction(
contentType.contains("xml", ignoreCase = true) -> FormatUtils.formatXml(body)
contentType.contains("x-www-form-urlencoded", ignoreCase = true) ->
FormatUtils.formatUrlEncodedForm(body)

else -> body
}
}
Expand Down Expand Up @@ -295,6 +348,14 @@ internal class HttpTransaction(
(isResponseBodyEncoded == other.isResponseBodyEncoded) &&
(responseImageData?.contentEquals(other.responseImageData ?: byteArrayOf()) != false) &&
(graphQlOperationName == other.graphQlOperationName) &&
(graphQlDetected == other.graphQlDetected)
(graphQlDetected == other.graphQlDetected) &&
(callStartDate == other.callStartDate) &&
(callEndDate == other.callEndDate) &&
(connectStartDate == other.connectStartDate) &&
(connectEndDate == other.connectEndDate) &&
(secureConnectStartDate == other.secureConnectStartDate) &&
(secureConnectEndDate == other.secureConnectEndDate) &&
(dnsStartDate == other.dnsStartDate) &&
(dnsEndDate == other.dnsEndDate)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ internal data class Timings(
) {
constructor(transaction: HttpTransaction) : this(
wait = transaction.tookMs ?: 0,
dns = transaction.dnsDuration,
connect = transaction.connectDuration ?: 0,
ssl = transaction.secureConnectDuration
)

fun getTime(): Long {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ internal class HttpTransactionDatabaseRepository(private val database: ChuckerDa

override suspend fun insertTransaction(transaction: HttpTransaction) {
val id = transactionDao.insert(transaction)
transaction.id = id ?: 0
transaction.id = id
}

override suspend fun updateTransaction(transaction: HttpTransaction): Int {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import androidx.room.Room
import androidx.room.RoomDatabase
import com.chuckerteam.chucker.internal.data.entity.HttpTransaction

@Database(entities = [HttpTransaction::class], version = 9, exportSchema = false)
@Database(entities = [HttpTransaction::class], version = 10, exportSchema = false)
internal abstract class ChuckerDatabase : RoomDatabase() {

abstract fun transactionDao(): HttpTransactionDao
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal interface HttpTransactionDao {
fun getFilteredTuples(codeQuery: String, pathQuery: String): LiveData<List<HttpTransactionTuple>>

@Insert
suspend fun insert(transaction: HttpTransaction): Long?
suspend fun insert(transaction: HttpTransaction): Long

@Update(onConflict = OnConflictStrategy.REPLACE)
suspend fun update(transaction: HttpTransaction): Int
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.chuckerteam.chucker.internal.support

import com.chuckerteam.chucker.api.ChuckerEventListener
import com.chuckerteam.chucker.internal.data.entity.HttpTransaction
import okhttp3.Call
import okhttp3.EventListener
import java.util.concurrent.ConcurrentHashMap

internal class RealChuckerEventListenerFactory : EventListener.Factory, HttpTransactionFactory {

private val httpTransactionHolder = ConcurrentHashMap<Call, ChuckerEventListener>()

override fun create(call: Call): EventListener {
val eventListener = ChuckerEventListener { httpTransactionHolder.remove(it) }
httpTransactionHolder[call] = eventListener

return eventListener
}

override fun getHttpTransaction(call: Call): HttpTransaction {
return httpTransactionHolder[call]?.httpTransaction
?: error("HttpTransaction required before the Call was created")
}

}

internal interface HttpTransactionFactory {
fun getHttpTransaction(call: Call): HttpTransaction
}

internal class SimpleHttpTransactionFactory : HttpTransactionFactory {
override fun getHttpTransaction(call: Call): HttpTransaction {
return HttpTransaction()
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import com.chuckerteam.chucker.R
import com.chuckerteam.chucker.databinding.ChuckerFragmentTransactionOverviewBinding
import com.chuckerteam.chucker.internal.data.entity.HttpTransaction
Expand Down Expand Up @@ -37,9 +36,8 @@ internal class TransactionOverviewFragment : Fragment() {
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
menu.findItem(R.id.save_body).isVisible = false
viewModel.doesUrlRequireEncoding.observe(
viewLifecycleOwner,
Observer { menu.findItem(R.id.encode_url).isVisible = it }
)
viewLifecycleOwner
) { menu.findItem(R.id.encode_url).isVisible = it }

super.onCreateOptionsMenu(menu, inflater)
}
Expand All @@ -48,9 +46,8 @@ internal class TransactionOverviewFragment : Fragment() {
super.onViewCreated(view, savedInstanceState)

viewModel.transaction.combineLatest(viewModel.encodeUrl).observe(
viewLifecycleOwner,
Observer { (transaction, encodeUrl) -> populateUI(transaction, encodeUrl) }
)
viewLifecycleOwner
) { (transaction, encodeUrl) -> populateUI(transaction, encodeUrl) }
}

private fun populateUI(transaction: HttpTransaction?, encodeUrl: Boolean) {
Expand Down Expand Up @@ -84,6 +81,10 @@ internal class TransactionOverviewFragment : Fragment() {
requestTime.text = transaction?.requestDateString
responseTime.text = transaction?.responseDateString
duration.text = transaction?.durationString
callDuration.text = transaction?.callDurationString
connectDuration.text = transaction?.connectDurationString
secureConnectDuration.text = transaction?.secureConnectDurationString
dnsDuration.text = transaction?.dnsDurationString
requestSize.text = transaction?.requestSizeString
responseSize.text = transaction?.responseSizeString
totalSize.text = transaction?.totalSizeString
Expand Down
Loading