-
Notifications
You must be signed in to change notification settings - Fork 651
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
20 changed files
with
560 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
libraries/apollo-debug-server/api/android/apollo-debug-server.api
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
public final class com/apollographql/apollo3/debugserver/ApolloDebugServer { | ||
public static final field INSTANCE Lcom/apollographql/apollo3/debugserver/ApolloDebugServer; | ||
public final fun registerApolloClient (Lcom/apollographql/apollo3/ApolloClient;Ljava/lang/String;)V | ||
public static synthetic fun registerApolloClient$default (Lcom/apollographql/apollo3/debugserver/ApolloDebugServer;Lcom/apollographql/apollo3/ApolloClient;Ljava/lang/String;ILjava/lang/Object;)V | ||
public final fun unregisterApolloClient (Lcom/apollographql/apollo3/ApolloClient;)V | ||
} | ||
|
7 changes: 7 additions & 0 deletions
7
libraries/apollo-debug-server/api/jvm/apollo-debug-server.api
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
public final class com/apollographql/apollo3/debugserver/ApolloDebugServer { | ||
public static final field INSTANCE Lcom/apollographql/apollo3/debugserver/ApolloDebugServer; | ||
public final fun registerApolloClient (Lcom/apollographql/apollo3/ApolloClient;Ljava/lang/String;)V | ||
public static synthetic fun registerApolloClient$default (Lcom/apollographql/apollo3/debugserver/ApolloDebugServer;Lcom/apollographql/apollo3/ApolloClient;Ljava/lang/String;ILjava/lang/Object;)V | ||
public final fun unregisterApolloClient (Lcom/apollographql/apollo3/ApolloClient;)V | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import com.android.build.gradle.tasks.BundleAar | ||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile | ||
|
||
plugins { | ||
id("org.jetbrains.kotlin.multiplatform") | ||
id("com.android.library") | ||
alias(libs.plugins.apollo.published) | ||
id("com.google.devtools.ksp") | ||
} | ||
|
||
apolloLibrary( | ||
javaModuleName = "com.apollographql.apollo3.debugserver", | ||
withLinux = false, | ||
withApple = false, | ||
withJs = false, | ||
) | ||
|
||
kotlin { | ||
sourceSets { | ||
findByName("commonMain")?.apply { | ||
kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin") | ||
|
||
dependencies { | ||
implementation(project(":apollo-normalized-cache")) | ||
|
||
// apollo-execution is not published: we bundle it into the aar artifact | ||
compileOnly(project(":apollo-execution")) | ||
api(project(":apollo-ast")) | ||
} | ||
} | ||
|
||
findByName("androidMain")?.apply { | ||
dependencies { | ||
implementation(libs.androidx.startup.runtime) | ||
} | ||
} | ||
} | ||
} | ||
|
||
val shadow = configurations.create("shadow") { | ||
isCanBeConsumed = false | ||
isCanBeResolved = true | ||
} | ||
|
||
dependencies { | ||
add("kspCommonMainMetadata", project(":apollo-ksp")) | ||
add("kspCommonMainMetadata", apollo.apolloKspProcessor(file("src/androidMain/resources/schema.graphqls"), "apolloDebugServer", "com.apollographql.apollo3.debugserver.internal.graphql")) | ||
add(shadow.name, project(":apollo-execution")) { | ||
isTransitive = false | ||
} | ||
} | ||
|
||
configurations.getByName("compileOnly").extendsFrom(shadow) | ||
|
||
android { | ||
compileSdk = libs.versions.android.sdkversion.compile.get().toInt() | ||
namespace = "com.apollographql.apollo3.debugserver" | ||
|
||
defaultConfig { | ||
minSdk = libs.versions.android.sdkversion.min.get().toInt() | ||
} | ||
} | ||
|
||
// KMP ksp configuration inspired by https://medium.com/@actiwerks/setting-up-kotlin-multiplatform-with-ksp-7f598b1681bf | ||
tasks.withType<KotlinCompile>().configureEach { | ||
dependsOn("kspCommonMainKotlinMetadata") | ||
} | ||
|
||
tasks.configureEach { | ||
if (name.endsWith("sourcesJar", ignoreCase = true)) { | ||
dependsOn("kspCommonMainKotlinMetadata") | ||
} | ||
} | ||
|
||
// apollo-execution is not published: we bundle it into the aar artifact | ||
val jarApolloExecution = tasks.register<Jar>("jarApolloExecution") { | ||
archiveBaseName.set("apollo-execution") | ||
from(provider { | ||
shadow.files.map { zipTree(it) } | ||
}) | ||
} | ||
|
||
tasks.withType<BundleAar>().configureEach { | ||
from(jarApolloExecution) { | ||
into("libs") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
POM_ARTIFACT_ID=apollo-debug-server | ||
POM_NAME=Apollo Kotlin Debug Server | ||
POM_DESCRIPTION=Apollo Kotlin debug server, used by tools to inspect running applications using Apollo. |
14 changes: 14 additions & 0 deletions
14
libraries/apollo-debug-server/src/androidMain/AndroidManifest.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> | ||
<application> | ||
<provider | ||
android:name="androidx.startup.InitializationProvider" | ||
android:authorities="${applicationId}.androidx-startup" | ||
android:exported="false" | ||
tools:node="merge"> | ||
<!-- This entry makes ApolloDebugServerInitializer discoverable. --> | ||
<meta-data | ||
android:name="com.apollographql.apollo3.debugserver.internal.initializer.ApolloDebugServerInitializer" | ||
android:value="androidx.startup" /> | ||
</provider> | ||
</application> | ||
</manifest> |
12 changes: 12 additions & 0 deletions
12
...roidMain/kotlin/com/apollographql/apollo3/debugserver/internal/graphql/GraphQL.android.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.apollographql.apollo3.debugserver.internal.graphql | ||
|
||
import com.apollographql.apollo3.debugserver.internal.server.Server | ||
import okio.buffer | ||
import okio.source | ||
import kotlin.reflect.KClass | ||
|
||
internal actual fun getExecutableSchema(): String = Server::class.java.classLoader!! | ||
.getResourceAsStream("schema.graphqls")!! | ||
.source() | ||
.buffer() | ||
.readUtf8() |
16 changes: 16 additions & 0 deletions
16
...om/apollographql/apollo3/debugserver/internal/initializer/ApolloDebugServerInitializer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.apollographql.apollo3.debugserver.internal.initializer | ||
|
||
import android.content.Context | ||
import androidx.startup.Initializer | ||
|
||
internal class ApolloDebugServerInitializer : Initializer<Unit> { | ||
override fun create(context: Context) { | ||
packageName = context.packageName | ||
} | ||
|
||
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList() | ||
|
||
companion object { | ||
lateinit var packageName: String | ||
} | ||
} |
105 changes: 105 additions & 0 deletions
105
...ndroidMain/kotlin/com/apollographql/apollo3/debugserver/internal/server/Server.android.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package com.apollographql.apollo3.debugserver.internal.server | ||
|
||
import android.net.LocalServerSocket | ||
import android.net.LocalSocket | ||
import com.apollographql.apollo3.ApolloClient | ||
import com.apollographql.apollo3.debugserver.internal.graphql.GraphQL | ||
import com.apollographql.apollo3.debugserver.internal.initializer.ApolloDebugServerInitializer | ||
import kotlinx.coroutines.CancellationException | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.SupervisorJob | ||
import kotlinx.coroutines.asCoroutineDispatcher | ||
import kotlinx.coroutines.cancel | ||
import kotlinx.coroutines.launch | ||
import java.io.BufferedReader | ||
import java.io.PrintStream | ||
import java.util.concurrent.Executors | ||
|
||
internal actual fun createServer( | ||
apolloClients: Map<ApolloClient, String>, | ||
): Server = AndroidServer(apolloClients) | ||
|
||
private class AndroidServer( | ||
apolloClients: Map<ApolloClient, String>, | ||
) : Server { | ||
companion object { | ||
private const val SOCKET_NAME_PREFIX = "apollo_debug_" | ||
} | ||
|
||
private var localServerSocket: LocalServerSocket? = null | ||
private val dispatcher = Executors.newCachedThreadPool().asCoroutineDispatcher() | ||
private val coroutineScope = CoroutineScope(SupervisorJob() + dispatcher) | ||
|
||
private val graphQL = GraphQL(apolloClients) | ||
|
||
override fun start() { | ||
if (localServerSocket != null) error("Already started") | ||
val localServerSocket = LocalServerSocket("$SOCKET_NAME_PREFIX${ApolloDebugServerInitializer.packageName}") | ||
this.localServerSocket = localServerSocket | ||
coroutineScope.launch { | ||
while (true) { | ||
val clientSocket = try { | ||
localServerSocket.accept() | ||
} catch (_: Exception) { | ||
// Server socket has been closed (stop() was called) | ||
break | ||
} | ||
launch { handleClient(clientSocket) } | ||
} | ||
} | ||
} | ||
|
||
private fun handleClient(clientSocket: LocalSocket) { | ||
try { | ||
val bufferedReader = clientSocket.inputStream.bufferedReader() | ||
val printWriter = PrintStream(clientSocket.outputStream.buffered(), true) | ||
val httpRequest = readHttpRequest(bufferedReader) | ||
if (httpRequest.method == "OPTIONS") { | ||
printWriter.print("HTTP/1.1 204 No Content\r\nConnection: close\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: *\r\nAccess-Control-Allow-Headers: *\r\n\r\n") | ||
return | ||
} | ||
printWriter.print("HTTP/1.1 200 OK\r\nConnection: close\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: application/json\r\n\r\n") | ||
printWriter.print(graphQL.executeGraphQL(httpRequest.body ?: "")) | ||
} catch (e: CancellationException) { | ||
// Expected when the server is closed | ||
throw e | ||
} catch (_: Exception) { | ||
// I/O error or otherwise: ignore | ||
} finally { | ||
runCatching { clientSocket.close() } | ||
} | ||
} | ||
|
||
private class HttpRequest( | ||
val method: String, | ||
val path: String, | ||
val headers: List<Pair<String, String>>, | ||
val body: String?, | ||
) | ||
|
||
private fun readHttpRequest(bufferedReader: BufferedReader): HttpRequest { | ||
val (method, path) = bufferedReader.readLine().split(" ") | ||
val headers = mutableListOf<Pair<String, String>>() | ||
while (true) { | ||
val line = bufferedReader.readLine() | ||
if (line.isEmpty()) break | ||
val (key, value) = line.split(": ") | ||
headers.add(key to value) | ||
} | ||
val contentLength = headers.firstOrNull { it.first.equals("Content-Length", ignoreCase = true) }?.second?.toLongOrNull() ?: 0 | ||
val body = if (contentLength <= 0) { | ||
null | ||
} else { | ||
val buffer = CharArray(contentLength.toInt()) | ||
bufferedReader.read(buffer) | ||
String(buffer) | ||
} | ||
return HttpRequest(method, path, headers, body) | ||
} | ||
|
||
override fun stop() { | ||
runCatching { localServerSocket?.close() } | ||
coroutineScope.cancel() | ||
dispatcher.close() | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
libraries/apollo-debug-server/src/androidMain/resources/schema.graphqls
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
type Query { | ||
apolloClients: [ApolloClient!]! | ||
apolloClient(id: ID!): ApolloClient | ||
} | ||
|
||
type ApolloClient { | ||
id: ID! | ||
displayName: String! | ||
normalizedCaches: [NormalizedCache!]! | ||
normalizedCache(id: ID!): NormalizedCache | ||
} | ||
|
||
type NormalizedCache { | ||
id: ID! | ||
displayName: String! | ||
recordCount: Int! | ||
records: [Record!]! | ||
} | ||
|
||
type Record { | ||
key: String! | ||
size: Int! | ||
fields: Fields! | ||
} | ||
|
||
scalar Fields |
34 changes: 34 additions & 0 deletions
34
...g-server/src/commonMain/kotlin/com/apollographql/apollo3/debugserver/ApolloDebugServer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package com.apollographql.apollo3.debugserver | ||
|
||
import com.apollographql.apollo3.ApolloClient | ||
import com.apollographql.apollo3.debugserver.internal.server.Server | ||
import com.apollographql.apollo3.debugserver.internal.server.createServer | ||
|
||
object ApolloDebugServer { | ||
private val apolloClients = mutableMapOf<ApolloClient, String>() | ||
private var server: Server? = null | ||
|
||
fun registerApolloClient(apolloClient: ApolloClient, id: String = "client") { | ||
if (apolloClients.containsKey(apolloClient)) error("Client '$apolloClient' already registered") | ||
if (apolloClients.containsValue(id)) error("Name '$id' already registered") | ||
apolloClients[apolloClient] = id | ||
startOrStopServer() | ||
} | ||
|
||
fun unregisterApolloClient(apolloClient: ApolloClient) { | ||
apolloClients.remove(apolloClient) | ||
startOrStopServer() | ||
} | ||
|
||
private fun startOrStopServer() { | ||
if (apolloClients.isEmpty()) { | ||
server?.stop() | ||
} else { | ||
if (server == null) { | ||
server = createServer(apolloClients).apply { | ||
start() | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.