mirror of
https://github.com/openflocon/Flocon.git
synced 2026-05-10 01:13:21 +00:00
feature: Enhance grpc json printing (#117)
* Enhance json printing for grpc calls * Remove gson dependency * Split grpc interceptor into three modules for both protobuf libraries * Remove unwanted versioned files * Abstract the grpc interceptor to avoid injecting the formatter * fixed readme * fixed pom for lit * added to publish yml --------- Co-authored-by: Florent Champigny <florent@bere.al> Co-authored-by: Florent CHAMPIGNY <champigny.florent@gmail.com>
This commit is contained in:
parent
a893b32ef1
commit
288372bdd8
30 changed files with 522 additions and 226 deletions
2
FloconAndroid/.idea/gradle.xml
generated
2
FloconAndroid/.idea/gradle.xml
generated
|
|
@ -15,6 +15,8 @@
|
|||
<option value="$PROJECT_DIR$/flocon-base" />
|
||||
<option value="$PROJECT_DIR$/flocon-no-op" />
|
||||
<option value="$PROJECT_DIR$/grpc-interceptor" />
|
||||
<option value="$PROJECT_DIR$/grpc-interceptor-base" />
|
||||
<option value="$PROJECT_DIR$/grpc-interceptor-lite" />
|
||||
<option value="$PROJECT_DIR$/ktor-interceptor" />
|
||||
<option value="$PROJECT_DIR$/okhttp-interceptor" />
|
||||
</set>
|
||||
|
|
|
|||
|
|
@ -66,19 +66,20 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
val useMaven = false
|
||||
val useMaven = true
|
||||
dependencies {
|
||||
if(useMaven) {
|
||||
val floconVersion = "1.0.5"
|
||||
val floconVersion = "1.1.2"
|
||||
implementation("io.github.openflocon:flocon:$floconVersion")
|
||||
//implementation("io.github.openflocon:flocon-no-op:$floconVersion")
|
||||
implementation("io.github.openflocon:flocon-grpc-interceptor:$floconVersion")
|
||||
implementation("io.github.openflocon:flocon-grpc-interceptor-lite:$floconVersion")
|
||||
implementation("io.github.openflocon:flocon-okhttp-interceptor:$floconVersion")
|
||||
implementation("io.github.openflocon:flocon-ktor-interceptor:$floconVersion")
|
||||
} else {
|
||||
debugImplementation(project(":flocon"))
|
||||
releaseImplementation(project(":flocon-no-op"))
|
||||
implementation(project(":okhttp-interceptor"))
|
||||
implementation(project(":grpc-interceptor"))
|
||||
implementation(project(":grpc-interceptor-lite"))
|
||||
implementation(project(":ktor-interceptor"))
|
||||
}
|
||||
|
||||
|
|
@ -106,7 +107,7 @@ dependencies {
|
|||
|
||||
// region grpc
|
||||
implementation(libs.grpc.android)
|
||||
implementation(libs.grpc.kotin.stub)
|
||||
implementation(libs.grpc.kotlin.stub)
|
||||
implementation(libs.grpc.protobuf.lite)
|
||||
implementation(libs.grpc.okhttp)
|
||||
implementation(libs.protobuf.kotlin.lite)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
package io.github.openflocon.flocon.myapplication.grpc
|
||||
|
||||
import io.github.openflocon.flocon.grpc.FloconGrpcInterceptor
|
||||
import io.github.openflocon.flocon.grpc.lite.FloconGrpcInterceptor
|
||||
import io.grpc.CallOptions
|
||||
import io.grpc.ManagedChannel
|
||||
import io.grpc.ManagedChannelBuilder
|
||||
import io.grpc.examples.helloworld.GreeterGrpcKt
|
||||
import io.grpc.examples.helloworld.helloRequest
|
||||
import kotlin.getValue
|
||||
|
||||
object GrpcController {
|
||||
val channel: ManagedChannel by lazy {
|
||||
|
|
@ -19,7 +18,7 @@ object GrpcController {
|
|||
.build()
|
||||
}
|
||||
|
||||
val geeterClient by lazy {
|
||||
val greeterClient by lazy {
|
||||
GreeterGrpcKt.GreeterCoroutineStub(
|
||||
channel = channel,
|
||||
callOptions = CallOptions.DEFAULT,
|
||||
|
|
@ -31,7 +30,7 @@ object GrpcController {
|
|||
val request = helloRequest {
|
||||
name = "florent"
|
||||
}
|
||||
val response = geeterClient.sayHello(request)
|
||||
val response = greeterClient.sayHello(request)
|
||||
return response.message
|
||||
} catch (t: Throwable) {
|
||||
t.printStackTrace()
|
||||
|
|
@ -51,7 +51,8 @@ androidx-material3 = { group = "androidx.compose.material3", name = "material3"
|
|||
# for grpc
|
||||
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
|
||||
grpc-android = { group = "io.grpc", name = "grpc-android", version.ref = "grpc" }
|
||||
grpc-kotin-stub = { group = "io.grpc", name = "grpc-kotlin-stub", version.ref = "grpcKotlin" }
|
||||
grpc-okhttp = { group = "io.grpc", name = "grpc-okhttp", version.ref = "grpc" }
|
||||
grpc-kotlin-stub = { group = "io.grpc", name = "grpc-kotlin-stub", version.ref = "grpcKotlin" }
|
||||
grpc-protobuf-lite = { group = "io.grpc", name = "grpc-protobuf-lite", version.ref = "grpc" }
|
||||
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android" }
|
||||
kotlinx-coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version.ref = "kotlinxCoroutinesBom" }
|
||||
|
|
@ -65,7 +66,7 @@ okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp" }
|
|||
org-jetbrains-kotlinx-kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android" }
|
||||
org-jetbrains-kotlinx-kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core" }
|
||||
protobuf-kotlin-lite = { group = "com.google.protobuf", name = "protobuf-kotlin-lite", version.ref = "protobuf"}
|
||||
grpc-okhttp = { group = "io.grpc", name = "grpc-okhttp", version.ref = "grpc" }
|
||||
protobuf-util = { group = "com.google.protobuf", name = "protobuf-java-util", version.ref = "protobuf" }
|
||||
squareup-okhttp = { module = "com.squareup.okhttp3:okhttp" }
|
||||
|
||||
[plugins]
|
||||
|
|
|
|||
1
FloconAndroid/grpc-interceptor-base/.gitignore
vendored
Normal file
1
FloconAndroid/grpc-interceptor-base/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
88
FloconAndroid/grpc-interceptor-base/build.gradle.kts
Normal file
88
FloconAndroid/grpc-interceptor-base/build.gradle.kts
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
id("com.vanniktech.maven.publish") version "0.34.0"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.github.openflocon.flocon.grpc.base"
|
||||
compileSdk = 36
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 24
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":flocon-base"))
|
||||
|
||||
implementation(platform(libs.kotlinx.coroutines.bom))
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
|
||||
implementation(libs.grpc.android)
|
||||
}
|
||||
|
||||
|
||||
mavenPublishing {
|
||||
publishToMavenCentral(automaticRelease = true)
|
||||
|
||||
if (project.hasProperty("signing.required") && project.property("signing.required") == "false") {
|
||||
// Skip signing
|
||||
} else {
|
||||
signAllPublications()
|
||||
}
|
||||
|
||||
coordinates(
|
||||
groupId = project.property("floconGroupId") as String,
|
||||
artifactId = "flocon-grpc-interceptor-base",
|
||||
version = System.getenv("PROJECT_VERSION_NAME") ?: project.property("floconVersion") as String
|
||||
)
|
||||
|
||||
|
||||
pom {
|
||||
name = "Flocon Grpc Interceptor"
|
||||
description = project.property("floconDescription") as String
|
||||
inceptionYear = "2025"
|
||||
url = "https://github.com/openflocon/Flocon"
|
||||
licenses {
|
||||
license {
|
||||
name = "The Apache License, Version 2.0"
|
||||
url = "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
distribution = "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = "openflocon"
|
||||
name = "Open Flocon"
|
||||
url = "https://github.com/openflocon"
|
||||
}
|
||||
}
|
||||
scm {
|
||||
url = "https://github.com/openflocon/Flocon"
|
||||
connection = "scm:git:git://github.com/openflocon/Flocon.git"
|
||||
developerConnection = "scm:git:ssh://git@github.com/openflocon/Flocon.git"
|
||||
}
|
||||
}
|
||||
}
|
||||
0
FloconAndroid/grpc-interceptor-base/consumer-rules.pro
Normal file
0
FloconAndroid/grpc-interceptor-base/consumer-rules.pro
Normal file
21
FloconAndroid/grpc-interceptor-base/proguard-rules.pro
vendored
Normal file
21
FloconAndroid/grpc-interceptor-base/proguard-rules.pro
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package io.github.openflocon.flocon.grpc
|
||||
|
||||
interface FloconGrpcBaseFormatter {
|
||||
|
||||
fun <T> format(message: T): String
|
||||
}
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
package io.github.openflocon.flocon.grpc
|
||||
|
||||
import io.github.openflocon.flocon.FloconApp
|
||||
import io.github.openflocon.flocon.FloconLogger
|
||||
import io.github.openflocon.flocon.grpc.model.RequestHolder
|
||||
import io.github.openflocon.flocon.grpc.model.toHeaders
|
||||
import io.github.openflocon.flocon.plugins.network.FloconNetworkPlugin
|
||||
import io.github.openflocon.flocon.plugins.network.model.FloconNetworkRequest
|
||||
import io.github.openflocon.flocon.plugins.network.model.FloconNetworkResponse
|
||||
import io.grpc.CallOptions
|
||||
import io.grpc.Channel
|
||||
import io.grpc.ClientCall
|
||||
import io.grpc.ClientInterceptor
|
||||
import io.grpc.ForwardingClientCall
|
||||
import io.grpc.ForwardingClientCallListener
|
||||
import io.grpc.Metadata
|
||||
import io.grpc.MethodDescriptor
|
||||
import io.grpc.Status
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.UUID
|
||||
|
||||
abstract class FloconGrpcBaseInterceptor : ClientInterceptor {
|
||||
|
||||
private val floconGrpcPlugin = FloconGrpcPlugin()
|
||||
|
||||
abstract val floconGrpcFormatter: FloconGrpcBaseFormatter
|
||||
|
||||
override fun <ReqT : Any?, RespT : Any?> interceptCall(
|
||||
method: MethodDescriptor<ReqT, RespT>,
|
||||
callOptions: CallOptions,
|
||||
next: Channel,
|
||||
): ClientCall<ReqT, RespT> {
|
||||
val networkPlugin = FloconApp.instance?.client?.networkPlugin
|
||||
if (networkPlugin == null) {
|
||||
// do not intercept if no network plugin, just call
|
||||
return next.newCall(method, callOptions)
|
||||
}
|
||||
|
||||
val callId = UUID.randomUUID().toString()
|
||||
return LoggingForwardingClientCall(
|
||||
floconGrpcPlugin = floconGrpcPlugin,
|
||||
floconNetworkPlugin = networkPlugin,
|
||||
callId = callId,
|
||||
method = method,
|
||||
next = next,
|
||||
formatter = floconGrpcFormatter,
|
||||
callOptions = callOptions,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class LoggingForwardingClientCall<ReqT, RespT>(
|
||||
private val floconNetworkPlugin: FloconNetworkPlugin,
|
||||
private val floconGrpcPlugin: FloconGrpcPlugin,
|
||||
private val callId: String,
|
||||
private val method: MethodDescriptor<ReqT, RespT>,
|
||||
private val next: Channel,
|
||||
private val formatter: FloconGrpcBaseFormatter,
|
||||
callOptions: CallOptions,
|
||||
) : ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(
|
||||
next.newCall(
|
||||
method,
|
||||
callOptions,
|
||||
),
|
||||
) {
|
||||
|
||||
val requestHolder = RequestHolder()
|
||||
|
||||
private var headers: Metadata? = null
|
||||
|
||||
override fun start(responseListener: Listener<RespT>, headers: Metadata) {
|
||||
this.headers = headers
|
||||
super.start(
|
||||
LoggingClientCallListener(
|
||||
floconGrpcPlugin = floconGrpcPlugin,
|
||||
callId = callId,
|
||||
requestHolder = requestHolder,
|
||||
responseListener = responseListener,
|
||||
formatter = formatter,
|
||||
),
|
||||
headers,
|
||||
)
|
||||
}
|
||||
|
||||
override fun sendMessage(message: ReqT) {
|
||||
val request = FloconNetworkRequest(
|
||||
url = next.authority(),
|
||||
method = method.fullMethodName,
|
||||
body = formatter.format(message),
|
||||
startTime = System.currentTimeMillis(),
|
||||
headers = headers?.toHeaders().orEmpty(),
|
||||
size = 0, // TODO
|
||||
isMocked = false, // cannot mock grpc
|
||||
)
|
||||
requestHolder.request.complete(request)
|
||||
floconGrpcPlugin.reportRequest(
|
||||
callId = callId,
|
||||
request = request
|
||||
)
|
||||
floconNetworkPlugin.badQualityConfig?.let {
|
||||
executeBadQuality(it)
|
||||
}
|
||||
super.sendMessage(message)
|
||||
}
|
||||
}
|
||||
|
||||
private class LoggingClientCallListener<RespT>(
|
||||
private val floconGrpcPlugin: FloconGrpcPlugin,
|
||||
private val callId: String,
|
||||
responseListener: ClientCall.Listener<RespT>,
|
||||
private val requestHolder: RequestHolder,
|
||||
private val formatter: FloconGrpcBaseFormatter,
|
||||
) : ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT>(
|
||||
responseListener,
|
||||
) {
|
||||
|
||||
private var headers: Metadata? = null
|
||||
private var message: RespT? = null
|
||||
|
||||
override fun onClose(status: Status, trailers: Metadata) {
|
||||
try {
|
||||
runBlocking { requestHolder.request.await() }.let { request ->
|
||||
status.cause?.let { cause ->
|
||||
floconGrpcPlugin.reportResponse(
|
||||
callId = callId,
|
||||
request = request,
|
||||
response = FloconNetworkResponse(
|
||||
body = null,
|
||||
headers = emptyMap(),
|
||||
httpCode = null,
|
||||
contentType = "grpc",
|
||||
size = null,
|
||||
grpcStatus = null,
|
||||
error = cause.message ?: cause.javaClass.simpleName,
|
||||
),
|
||||
)
|
||||
} ?: run {
|
||||
floconGrpcPlugin.reportResponse(
|
||||
callId = callId,
|
||||
request = request,
|
||||
response = FloconNetworkResponse(
|
||||
body = formatter.format(message),
|
||||
headers = (this.headers ?: trailers).toHeaders(),
|
||||
httpCode = null,
|
||||
contentType = "grpc",
|
||||
size = 0L,
|
||||
grpcStatus = status.code.toString(),
|
||||
error = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
FloconLogger.logError("cannot find request for callId $callId", t)
|
||||
}
|
||||
super.onClose(status, trailers)
|
||||
}
|
||||
|
||||
override fun onMessage(message: RespT) {
|
||||
super.onMessage(message)
|
||||
this.message = message
|
||||
}
|
||||
|
||||
override fun onHeaders(headers: Metadata?) {
|
||||
super.onHeaders(headers)
|
||||
this.headers = headers
|
||||
}
|
||||
}
|
||||
1
FloconAndroid/grpc-interceptor-lite/.gitignore
vendored
Normal file
1
FloconAndroid/grpc-interceptor-lite/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
86
FloconAndroid/grpc-interceptor-lite/build.gradle.kts
Normal file
86
FloconAndroid/grpc-interceptor-lite/build.gradle.kts
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
id("com.vanniktech.maven.publish") version "0.34.0"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.github.openflocon.flocon.grpc.lite"
|
||||
compileSdk = 36
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 24
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":grpc-interceptor-base"))
|
||||
|
||||
implementation(libs.grpc.android)
|
||||
implementation(libs.gson)
|
||||
}
|
||||
|
||||
|
||||
mavenPublishing {
|
||||
publishToMavenCentral(automaticRelease = true)
|
||||
|
||||
if (project.hasProperty("signing.required") && project.property("signing.required") == "false") {
|
||||
// Skip signing
|
||||
} else {
|
||||
signAllPublications()
|
||||
}
|
||||
|
||||
coordinates(
|
||||
groupId = project.property("floconGroupId") as String,
|
||||
artifactId = "flocon-grpc-interceptor-lite",
|
||||
version = System.getenv("PROJECT_VERSION_NAME") ?: project.property("floconVersion") as String
|
||||
)
|
||||
|
||||
|
||||
pom {
|
||||
name = "Flocon Grpc Interceptor Lite"
|
||||
description = project.property("floconDescription") as String
|
||||
inceptionYear = "2025"
|
||||
url = "https://github.com/openflocon/Flocon"
|
||||
licenses {
|
||||
license {
|
||||
name = "The Apache License, Version 2.0"
|
||||
url = "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
distribution = "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id = "openflocon"
|
||||
name = "Open Flocon"
|
||||
url = "https://github.com/openflocon"
|
||||
}
|
||||
}
|
||||
scm {
|
||||
url = "https://github.com/openflocon/Flocon"
|
||||
connection = "scm:git:git://github.com/openflocon/Flocon.git"
|
||||
developerConnection = "scm:git:ssh://git@github.com/openflocon/Flocon.git"
|
||||
}
|
||||
}
|
||||
}
|
||||
0
FloconAndroid/grpc-interceptor-lite/consumer-rules.pro
Normal file
0
FloconAndroid/grpc-interceptor-lite/consumer-rules.pro
Normal file
21
FloconAndroid/grpc-interceptor-lite/proguard-rules.pro
vendored
Normal file
21
FloconAndroid/grpc-interceptor-lite/proguard-rules.pro
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package io.github.openflocon.flocon.grpc.lite
|
||||
|
||||
import com.google.gson.ExclusionStrategy
|
||||
import com.google.gson.FieldAttributes
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import io.github.openflocon.flocon.grpc.FloconGrpcBaseFormatter
|
||||
|
||||
class FloconGrpcFormatter : FloconGrpcBaseFormatter {
|
||||
|
||||
private val excluded = setOf(
|
||||
"unknownFields",
|
||||
"memoizedHashCode",
|
||||
"bitField",
|
||||
"memoizedSerializedSize",
|
||||
"bytes",
|
||||
)
|
||||
|
||||
private val defaultFieldExcluder: (name: String) -> Boolean = { name ->
|
||||
var isExcluded = false
|
||||
excluded.forEach { toExclude ->
|
||||
if (name.startsWith(prefix = toExclude)) {
|
||||
isExcluded = true
|
||||
}
|
||||
}
|
||||
isExcluded
|
||||
}
|
||||
|
||||
private val gson = buildGsonInstance(defaultFieldExcluder)
|
||||
|
||||
override fun <T> format(message: T): String =
|
||||
gson.toJson(message)
|
||||
|
||||
private fun buildGsonInstance(
|
||||
excluder: (name: String) -> Boolean,
|
||||
): Gson {
|
||||
return GsonBuilder().setPrettyPrinting()
|
||||
.setFieldNamingStrategy {
|
||||
it.name.removeSuffix("_")
|
||||
}
|
||||
.setExclusionStrategies(object : ExclusionStrategy {
|
||||
override fun shouldSkipField(f: FieldAttributes): Boolean {
|
||||
return excluder(f.name)
|
||||
}
|
||||
|
||||
override fun shouldSkipClass(clazz: Class<*>): Boolean {
|
||||
return false
|
||||
}
|
||||
}).create()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package io.github.openflocon.flocon.grpc.lite
|
||||
|
||||
import io.github.openflocon.flocon.grpc.FloconGrpcBaseFormatter
|
||||
import io.github.openflocon.flocon.grpc.FloconGrpcBaseInterceptor
|
||||
|
||||
class FloconGrpcInterceptor : FloconGrpcBaseInterceptor() {
|
||||
|
||||
override val floconGrpcFormatter: FloconGrpcBaseFormatter = FloconGrpcFormatter()
|
||||
}
|
||||
|
|
@ -35,13 +35,10 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":flocon-base"))
|
||||
api(project(":grpc-interceptor-base"))
|
||||
|
||||
implementation(platform(libs.kotlinx.coroutines.bom))
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
|
||||
implementation(libs.gson)
|
||||
implementation(libs.grpc.android)
|
||||
implementation(libs.protobuf.util)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package io.github.openflocon.flocon.grpc
|
||||
|
||||
import com.google.protobuf.MessageOrBuilder
|
||||
import com.google.protobuf.util.JsonFormat
|
||||
|
||||
class FloconGrpcFormatter : FloconGrpcBaseFormatter {
|
||||
|
||||
private val printer = JsonFormat.printer().alwaysPrintFieldsWithNoPresence()
|
||||
|
||||
override fun <T> format(message: T): String =
|
||||
(message as? MessageOrBuilder)?.let { printer.print(it) } ?: ""
|
||||
}
|
||||
|
|
@ -1,212 +1,6 @@
|
|||
package io.github.openflocon.flocon.grpc
|
||||
|
||||
import com.google.gson.ExclusionStrategy
|
||||
import com.google.gson.FieldAttributes
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import io.github.openflocon.flocon.FloconApp
|
||||
import io.github.openflocon.flocon.FloconLogger
|
||||
import io.github.openflocon.flocon.grpc.model.RequestHolder
|
||||
import io.github.openflocon.flocon.grpc.model.toHeaders
|
||||
import io.github.openflocon.flocon.plugins.network.FloconNetworkPlugin
|
||||
import io.github.openflocon.flocon.plugins.network.model.FloconNetworkRequest
|
||||
import io.github.openflocon.flocon.plugins.network.model.FloconNetworkResponse
|
||||
import io.grpc.CallOptions
|
||||
import io.grpc.Channel
|
||||
import io.grpc.ClientCall
|
||||
import io.grpc.ClientInterceptor
|
||||
import io.grpc.ForwardingClientCall
|
||||
import io.grpc.ForwardingClientCallListener
|
||||
import io.grpc.Metadata
|
||||
import io.grpc.MethodDescriptor
|
||||
import io.grpc.Status
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.UUID
|
||||
class FloconGrpcInterceptor : FloconGrpcBaseInterceptor() {
|
||||
|
||||
private val excluded = setOf(
|
||||
"unknownFields",
|
||||
"memoizedHashCode",
|
||||
"bitField",
|
||||
"memoizedSerializedSize",
|
||||
"bytes",
|
||||
)
|
||||
|
||||
private val defaultFieldExcluder: (name: String) -> Boolean = { name ->
|
||||
var isExcluded = false
|
||||
excluded.forEach { toExclude ->
|
||||
if (name.startsWith(prefix = toExclude)) {
|
||||
isExcluded = true
|
||||
}
|
||||
}
|
||||
isExcluded
|
||||
}
|
||||
|
||||
class FloconGrpcInterceptor(
|
||||
private val shouldExcludeField: (name: String) -> Boolean = defaultFieldExcluder,
|
||||
) : ClientInterceptor {
|
||||
|
||||
private val floconGrpcPlugin = FloconGrpcPlugin()
|
||||
|
||||
private val gson = buildGsonInstance(shouldExcludeField)
|
||||
|
||||
override fun <ReqT : Any?, RespT : Any?> interceptCall(
|
||||
method: MethodDescriptor<ReqT, RespT>,
|
||||
callOptions: CallOptions,
|
||||
next: Channel,
|
||||
): ClientCall<ReqT, RespT> {
|
||||
val networkPlugin = FloconApp.instance?.client?.networkPlugin
|
||||
if (networkPlugin == null) {
|
||||
// do not intercept if no network plugin, just call
|
||||
return next.newCall(method, callOptions)
|
||||
}
|
||||
|
||||
val callId = UUID.randomUUID().toString()
|
||||
return LoggingForwardingClientCall(
|
||||
floconGrpcPlugin = floconGrpcPlugin,
|
||||
floconNetworkPlugin = networkPlugin,
|
||||
callId = callId,
|
||||
method = method,
|
||||
next = next,
|
||||
callOptions = callOptions,
|
||||
gson = gson,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class LoggingForwardingClientCall<ReqT, RespT>(
|
||||
private val floconNetworkPlugin: FloconNetworkPlugin,
|
||||
private val floconGrpcPlugin: FloconGrpcPlugin,
|
||||
private val callId: String,
|
||||
private val method: MethodDescriptor<ReqT, RespT>,
|
||||
private val next: Channel,
|
||||
callOptions: CallOptions,
|
||||
private val gson: Gson,
|
||||
) : ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(
|
||||
next.newCall(
|
||||
method,
|
||||
callOptions,
|
||||
),
|
||||
) {
|
||||
|
||||
val requestHolder = RequestHolder()
|
||||
|
||||
private var headers: Metadata? = null
|
||||
|
||||
override fun start(responseListener: Listener<RespT>, headers: Metadata) {
|
||||
this.headers = headers
|
||||
super.start(
|
||||
LoggingClientCallListener(
|
||||
floconGrpcPlugin = floconGrpcPlugin,
|
||||
callId = callId,
|
||||
requestHolder = requestHolder,
|
||||
responseListener = responseListener,
|
||||
gson = gson
|
||||
),
|
||||
headers,
|
||||
)
|
||||
}
|
||||
|
||||
override fun sendMessage(message: ReqT) {
|
||||
val request = FloconNetworkRequest(
|
||||
url = next.authority(),
|
||||
method = method.fullMethodName,
|
||||
body = message?.toJson(gson = gson) ?: "",
|
||||
startTime = System.currentTimeMillis(),
|
||||
headers = headers?.toHeaders().orEmpty(),
|
||||
size = 0, // TODO
|
||||
isMocked = false, // cannot mock grpc
|
||||
)
|
||||
requestHolder.request.complete(request)
|
||||
floconGrpcPlugin.reportRequest(
|
||||
callId = callId,
|
||||
request = request
|
||||
)
|
||||
floconNetworkPlugin.badQualityConfig?.let {
|
||||
executeBadQuality(it)
|
||||
}
|
||||
super.sendMessage(message)
|
||||
}
|
||||
}
|
||||
|
||||
private class LoggingClientCallListener<RespT>(
|
||||
private val floconGrpcPlugin: FloconGrpcPlugin,
|
||||
private val callId: String,
|
||||
responseListener: ClientCall.Listener<RespT>,
|
||||
private val gson: Gson,
|
||||
private val requestHolder: RequestHolder,
|
||||
) : ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT>(
|
||||
responseListener,
|
||||
) {
|
||||
|
||||
private var headers: Metadata? = null
|
||||
private var message: RespT? = null
|
||||
|
||||
override fun onClose(status: Status, trailers: Metadata) {
|
||||
try {
|
||||
runBlocking { requestHolder.request.await() }.let { request ->
|
||||
status.cause?.let { cause ->
|
||||
floconGrpcPlugin.reportResponse(
|
||||
callId = callId,
|
||||
request = request,
|
||||
response = FloconNetworkResponse(
|
||||
body = null,
|
||||
headers = emptyMap(),
|
||||
httpCode = null,
|
||||
contentType = "grpc",
|
||||
size = null,
|
||||
grpcStatus = null,
|
||||
error = cause.message ?: cause.javaClass.simpleName,
|
||||
),
|
||||
)
|
||||
} ?: run {
|
||||
floconGrpcPlugin.reportResponse(
|
||||
callId = callId,
|
||||
request = request,
|
||||
response = FloconNetworkResponse(
|
||||
body = message?.toJson(gson),
|
||||
headers = (this.headers ?: trailers).toHeaders(),
|
||||
httpCode = null,
|
||||
contentType = "grpc",
|
||||
size = 0L,
|
||||
grpcStatus = status.code.toString(),
|
||||
error = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
FloconLogger.logError("cannot find request for callId $callId", t)
|
||||
}
|
||||
super.onClose(status, trailers)
|
||||
}
|
||||
|
||||
override fun onMessage(message: RespT) {
|
||||
super.onMessage(message)
|
||||
this.message = message
|
||||
}
|
||||
|
||||
override fun onHeaders(headers: Metadata?) {
|
||||
super.onHeaders(headers)
|
||||
this.headers = headers
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildGsonInstance(
|
||||
excluder: (name: String) -> Boolean,
|
||||
): Gson {
|
||||
return GsonBuilder().setPrettyPrinting()
|
||||
.setFieldNamingStrategy {
|
||||
it.name.removeSuffix("_")
|
||||
}
|
||||
.setExclusionStrategies(object : ExclusionStrategy {
|
||||
override fun shouldSkipField(f: FieldAttributes): Boolean {
|
||||
return excluder(f.name)
|
||||
}
|
||||
|
||||
override fun shouldSkipClass(clazz: Class<*>): Boolean {
|
||||
return false
|
||||
}
|
||||
}).create()
|
||||
}
|
||||
|
||||
private fun Any.toJson(gson: Gson): String = gson.toJson(this)
|
||||
override val floconGrpcFormatter: FloconGrpcBaseFormatter = FloconGrpcFormatter()
|
||||
}
|
||||
|
|
@ -2,7 +2,9 @@
|
|||
:flocon-base:assembleRelease \
|
||||
:flocon:assembleRelease \
|
||||
:flocon-no-op:assembleRelease \
|
||||
:grpc-interceptor-base:assembleRelease \
|
||||
:grpc-interceptor:assembleRelease \
|
||||
:grpc-interceptor-lite:assembleRelease \
|
||||
:okhttp-interceptor:assembleRelease \
|
||||
:ktor-interceptor:assembleRelease
|
||||
|
||||
|
|
@ -10,7 +12,9 @@
|
|||
:flocon-base:publishToMavenLocal \
|
||||
:flocon:publishToMavenLocal \
|
||||
:flocon-no-op:publishToMavenLocal \
|
||||
:grpc-interceptor-base:publishToMavenLocal \
|
||||
:grpc-interceptor:publishToMavenLocal \
|
||||
:grpc-interceptor-lite:publishToMavenLocal \
|
||||
:okhttp-interceptor:publishToMavenLocal \
|
||||
:ktor-interceptor:publishToMavenLocal \
|
||||
-Psigning.required=false
|
||||
|
|
@ -21,4 +21,6 @@ include(":flocon")
|
|||
include(":flocon-no-op")
|
||||
include(":okhttp-interceptor")
|
||||
include(":grpc-interceptor")
|
||||
include(":grpc-interceptor-base")
|
||||
include(":grpc-interceptor-lite")
|
||||
include(":ktor-interceptor")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue