Skip to content

Commit 6327c89

Browse files
committed
grpc: Adjust client/server DSL and provide documentation
Signed-off-by: Johannes Zottele <[email protected]>
1 parent 52b0657 commit 6327c89

File tree

10 files changed

+275
-67
lines changed

10 files changed

+275
-67
lines changed

grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcClient.kt

Lines changed: 119 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,20 @@ public class GrpcClient internal constructor(
123123
}
124124

125125
/**
126-
* Constructor function for the [GrpcClient] class.
126+
* Creates and configures a gRPC client instance.
127+
*
128+
* This function initializes a new gRPC client with the specified target server
129+
* and allows optional customization of the client's configuration through a configuration block.
130+
*
131+
* @param hostname The gRPC server hostname to connect to.
132+
* @param port The gRPC server port to connect to.
133+
* @param configure An optional configuration block to customize the [GrpcClientConfiguration].
134+
* This can include setting up interceptors, specifying credentials, customizing message codec
135+
* resolution, and overriding default authority.
136+
*
137+
* @return A new instance of [GrpcClient] configured with the specified target and options.
138+
*
139+
* @see [GrpcClientConfiguration]
127140
*/
128141
public fun GrpcClient(
129142
hostname: String,
@@ -134,8 +147,22 @@ public fun GrpcClient(
134147
return GrpcClient(ManagedChannelBuilder(hostname, port, config.credentials), config)
135148
}
136149

150+
137151
/**
138-
* Constructor function for the [GrpcClient] class.
152+
* Creates and configures a gRPC client instance.
153+
*
154+
* This function initializes a new gRPC client with the specified target server
155+
* and allows optional customization of the client's configuration through a configuration block.
156+
*
157+
* @param target The gRPC server endpoint to connect to, typically specified in
158+
* the format `hostname:port`.
159+
* @param configure An optional configuration block to customize the [GrpcClientConfiguration].
160+
* This can include setting up interceptors, specifying credentials, customizing message codec
161+
* resolution, and overriding default authority.
162+
*
163+
* @return A new instance of [GrpcClient] configured with the specified target and options.
164+
*
165+
* @see [GrpcClientConfiguration]
139166
*/
140167
public fun GrpcClient(
141168
target: String,
@@ -155,30 +182,105 @@ private fun GrpcClient(
155182
return GrpcClient(channel, config.messageCodecResolver, config.interceptors)
156183
}
157184

185+
186+
/**
187+
* Configuration class for a gRPC client, providing customization options
188+
* for client behavior, including interceptors, credentials, codec resolution,
189+
* and authority overrides.
190+
*/
158191
public class GrpcClientConfiguration internal constructor() {
159-
internal var messageCodecResolver: MessageCodecResolver = EmptyMessageCodecResolver
160-
internal var credentials: ClientCredentials? = null
161-
internal var overrideAuthority: String? = null
162192
internal val interceptors: MutableList<ClientInterceptor> = mutableListOf()
163193

164-
public fun usePlaintext() {
165-
credentials = createInsecureClientCredentials()
166-
}
194+
/**
195+
* Configurable resolver used to determine the appropriate codec for a given Kotlin type
196+
* during message serialization and deserialization in gRPC calls.
197+
*
198+
* Custom implementations of [MessageCodecResolver] can be provided to handle specific serialization
199+
* for arbitrary types.
200+
* For custom types prefer using the [kotlinx.rpc.grpc.codec.WithCodec] annotation.
201+
*
202+
* @see MessageCodecResolver
203+
* @see kotlinx.rpc.grpc.codec.SourcedMessageCodec
204+
* @see kotlinx.rpc.grpc.codec.WithCodec
205+
*/
206+
public var messageCodecResolver: MessageCodecResolver = EmptyMessageCodecResolver
167207

168-
public fun useCredentials(credentials: ClientCredentials) {
169-
this@GrpcClientConfiguration.credentials = credentials
170-
}
171208

172-
public fun overrideAuthority(authority: String) {
173-
overrideAuthority = authority
174-
}
209+
/**
210+
* Configures the client credentials used for secure gRPC requests made by the client.
211+
*
212+
* By default, the client uses default TLS credentials.
213+
* To use custom TLS credentials, use the [tls] constructor function which returns a
214+
* [TlsClientCredentials] instance.
215+
*
216+
* To use plaintext communication, use the [plaintext] constructor function.
217+
* Should only be used for testing or for APIs where the use of such API or
218+
* the data exchanged is not sensitive.
219+
*
220+
* ```
221+
* GrpcClient("localhost", 50051) {
222+
* credentials = plaintext() // for testing purposes only!
223+
* }
224+
* ```
225+
*/
226+
public var credentials: ClientCredentials? = null
227+
228+
/**
229+
* Overrides the authority used with TLS and HTTP virtual hosting.
230+
* It does not change what the host is actually connected to.
231+
* Is commonly in the form `host:port`.
232+
*/
233+
public var overrideAuthority: String? = null
175234

176-
public fun useMessageCodecResolver(messageCodecResolver: MessageCodecResolver) {
177-
this.messageCodecResolver = messageCodecResolver
178-
}
179235

236+
/**
237+
* Adds one or more client-side interceptors to the current gRPC client configuration.
238+
* Interceptors enable extended customization of gRPC calls
239+
* by observing or altering the behaviors of requests and responses.
240+
*
241+
* The order of interceptors added via this method is significant.
242+
* Interceptors are executed in the order they are added,
243+
* while one interceptor has to invoke the next interceptor to proceed with the call.
244+
*
245+
* @param interceptors Interceptors to be added to the current configuration.
246+
* Each provided instance of [ClientInterceptor] may perform operations such as modifying headers,
247+
* observing call metadata, logging, or transforming data flows.
248+
*
249+
* @see ClientInterceptor
250+
* @see ClientCallScope
251+
*/
180252
public fun intercept(vararg interceptors: ClientInterceptor) {
181253
this.interceptors.addAll(interceptors)
182254
}
183255

256+
/**
257+
* Provides insecure client credentials for the gRPC client configuration.
258+
*
259+
* Typically, this would be used for local development, testing, or other
260+
* environments where security is not a concern.
261+
*
262+
* @return An insecure [ClientCredentials] instance that must be passed to [credentials].
263+
*/
264+
public fun plaintext(): ClientCredentials = createInsecureClientCredentials()
265+
266+
/**
267+
* Configures and creates secure client credentials for the gRPC client.
268+
*
269+
* This method takes a configuration block in which TLS-related parameters,
270+
* such as trust managers and key managers, can be defined. The resulting
271+
* credentials are used to establish secure communication between the gRPC client
272+
* and server, ensuring encrypted transmission of data and mutual authentication
273+
* if configured.
274+
*
275+
* Alternatively, you can use the [TlsClientCredentials] constructor.
276+
*
277+
* @param configure A configuration block that allows setting up the TLS parameters
278+
* using the [TlsClientCredentialsBuilder].
279+
* @return A secure [ClientCredentials] instance that must be passed to [credentials].
280+
*
281+
* @see credentials
282+
*/
283+
public fun tls(configure: TlsClientCredentialsBuilder.() -> Unit): ClientCredentials =
284+
TlsClientCredentials(configure)
285+
184286
}

grpc/grpc-core/src/commonMain/kotlin/kotlinx/rpc/grpc/GrpcServer.kt

Lines changed: 122 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -198,41 +198,147 @@ public class GrpcServer internal constructor(
198198
}
199199
}
200200

201+
201202
/**
202-
* Constructor function for the [GrpcServer] class.
203+
* Creates and configures a gRPC server instance.
204+
*
205+
* This function initializes a gRPC server with the provided port and a configuration block
206+
* ([GrpcServerConfiguration]).
207+
*
208+
* To start the server, call the [GrpcServer.start] method.
209+
* To clean up resources, call the [GrpcServer.shutdown] or [GrpcServer.shutdownNow] methods.
210+
*
211+
* ```kt
212+
* GrpcServer(port) {
213+
* credentials = tls(myCertChain, myPrivateKey)
214+
* services {
215+
* registerService(MyService())
216+
* registerService(MyOtherService())
217+
* }
218+
* }
219+
* ```
220+
*
221+
* @param port The port number where the gRPC server will listen for incoming connections.
222+
* This must be a valid and available port on the host system.
223+
* @param parentContext The parent coroutine context used for managing server-related operations.
224+
* Defaults to an empty coroutine context if not specified.
225+
* @param configure A configuration lambda receiver,
226+
* allowing customization of server behavior such as credentials, interceptors,
227+
* codecs, and service registration logic.
228+
* @return A fully configured `GrpcServer` instance, which must be started explicitly to handle requests.
203229
*/
204230
public fun GrpcServer(
205231
port: Int,
206232
parentContext: CoroutineContext = EmptyCoroutineContext,
207233
configure: GrpcServerConfiguration.() -> Unit = {},
208-
builder: RpcServer.() -> Unit = {},
209234
): GrpcServer {
210235
val config = GrpcServerConfiguration().apply(configure)
211236
val serverBuilder = ServerBuilder(port, config.credentials).apply {
212237
config.fallbackHandlerRegistry?.let { fallbackHandlerRegistry(it) }
213238
}
214239
return GrpcServer(port, serverBuilder, config.interceptors, config.messageCodecResolver, parentContext)
215-
.apply(builder)
240+
.apply(config.serviceBuilder)
216241
.apply { build() }
217242
}
218243

244+
/**
245+
* A configuration class for setting up a gRPC server.
246+
*
247+
* This class provides an API to configure various server parameters, such as message codecs,
248+
* security credentials, server-side interceptors, and service registration.
249+
*/
219250
public class GrpcServerConfiguration internal constructor() {
220-
internal var messageCodecResolver: MessageCodecResolver = EmptyMessageCodecResolver
221-
internal var credentials: ServerCredentials? = null
222-
internal val interceptors: MutableList<ServerInterceptor> = mutableListOf()
223-
internal var fallbackHandlerRegistry: HandlerRegistry? = null
224-
internal var services: ServerBuilder<*>? = null
225-
226-
public fun useCredentials(credentials: ServerCredentials) {
227-
this.credentials = credentials
228-
}
229-
230-
public fun useMessageCodecResolver(messageCodecResolver: MessageCodecResolver) {
231-
this.messageCodecResolver = messageCodecResolver
232-
}
233251

252+
internal val interceptors: MutableList<ServerInterceptor> = mutableListOf()
253+
internal var serviceBuilder: RpcServer.() -> Unit = { }
254+
255+
256+
/**
257+
* Sets the credentials to be used by the gRPC server for secure communication.
258+
*
259+
* By default, the server does not have any credentials configured and the communication is plaintext.
260+
* To set up transport-layer security provide a [TlsServerCredentials] by constructing it with the
261+
* [tls] function.
262+
*
263+
* @see TlsServerCredentials
264+
* @see tls
265+
*/
266+
public var credentials: ServerCredentials? = null
267+
268+
/**
269+
* Sets a custom [MessageCodecResolver] to be used by the gRPC server for resolving the appropriate
270+
* codec for message serialization and deserialization.
271+
*
272+
* When not explicitly set, a default [EmptyMessageCodecResolver] is used, which may not perform
273+
* any specific resolution.
274+
* Provide a custom [MessageCodecResolver] to resolve codecs based on the message's `KType`.
275+
*/
276+
public var messageCodecResolver: MessageCodecResolver = EmptyMessageCodecResolver
277+
278+
279+
/**
280+
* Sets a custom [HandlerRegistry] to be used by the gRPC server for resolving service implementations
281+
* that were not registered before via the [services] configuration block.
282+
*
283+
* If not set, unknown services not registered will cause a `UNIMPLEMENTED` status
284+
* to be returned to the client.
285+
*/
286+
public var fallbackHandlerRegistry: HandlerRegistry? = null
287+
288+
/**
289+
* Registers one or more server-side interceptors for the gRPC server.
290+
*
291+
* Interceptors allow observing and modifying incoming gRPC calls before they reach the service
292+
* implementation logic.
293+
* They are commonly used to implement cross-cutting concerns like
294+
* authentication, logging, metrics, or custom request/response transformations.
295+
*
296+
* @param interceptors One or more instances of [ServerInterceptor] to be applied to incoming calls.
297+
* @see ServerInterceptor
298+
*/
234299
public fun intercept(vararg interceptors: ServerInterceptor) {
235300
this.interceptors.addAll(interceptors)
236301
}
237302

303+
/**
304+
* Configures the gRPC server to register services.
305+
*
306+
* This method allows defining a block of logic to configure an [RpcServer] instance,
307+
* where multiple services can be registered:
308+
* ```kt
309+
* GrpcServer(port) {
310+
* services {
311+
* registerService(MyService())
312+
* registerService(MyOtherService())
313+
* }
314+
* }
315+
* ```
316+
*
317+
* @param block A lambda with [RpcServer] as its receiver, allowing service registration.
318+
*/
319+
public fun services(block: RpcServer.() -> Unit) {
320+
serviceBuilder = block
321+
}
322+
323+
/**
324+
* Configures and creates TLS (Transport Layer Security) credentials for the gRPC server.
325+
*
326+
* This method allows specifying the server's certificate chain, private key, and additional
327+
* configurations needed for setting up a secure communication channel over TLS.
328+
*
329+
* @param certificateChain A string representing the PEM-encoded certificate chain for the server.
330+
* @param privateKey A string representing the PKCS#8 formatted private key corresponding to the certificate.
331+
* @param configure A lambda to further customize the [TlsServerCredentialsBuilder], enabling configurations
332+
* like setting trusted root certificates or enabling client authentication.
333+
* @return An instance of [ServerCredentials] representing the configured TLS credentials that must be passed
334+
* to [credentials].
335+
*
336+
* @see credentials
337+
*/
338+
public fun tls(
339+
certificateChain: String,
340+
privateKey: String,
341+
configure: TlsServerCredentialsBuilder.() -> Unit,
342+
): ServerCredentials =
343+
TlsServerCredentials(certificateChain, privateKey, configure)
238344
}

grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/BaseGrpcServiceTest.kt

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,18 @@ abstract class BaseGrpcServiceTest {
3131
val server = GrpcServer(
3232
port = PORT,
3333
parentContext = coroutineContext,
34-
configure = {
35-
useMessageCodecResolver(resolver)
36-
},
37-
builder = {
34+
) {
35+
messageCodecResolver = resolver
36+
services {
3837
registerService(kClass) { impl }
3938
}
40-
)
39+
}
4140

4241
server.start()
4342

4443
val client = GrpcClient("localhost", PORT) {
45-
useMessageCodecResolver(messageCodecResolver)
46-
usePlaintext()
44+
messageCodecResolver = resolver
45+
credentials = plaintext()
4746
}
4847

4948
val service = client.withService(kClass)

grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/CoreClientTest.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import kotlinx.coroutines.delay
88
import kotlinx.coroutines.runBlocking
99
import kotlinx.coroutines.test.runTest
1010
import kotlinx.coroutines.withTimeout
11-
import kotlinx.rpc.grpc.GrpcServer
1211
import kotlinx.rpc.grpc.GrpcMetadata
12+
import kotlinx.rpc.grpc.GrpcServer
1313
import kotlinx.rpc.grpc.ManagedChannel
1414
import kotlinx.rpc.grpc.ManagedChannelBuilder
1515
import kotlinx.rpc.grpc.Status
@@ -274,8 +274,11 @@ class GreeterServiceImpl : GreeterService {
274274
fun runServer() = runTest {
275275
val server = GrpcServer(
276276
port = PORT,
277-
builder = { registerService<GreeterService> { GreeterServiceImpl() } }
278-
)
277+
) {
278+
services {
279+
registerService<GreeterService> { GreeterServiceImpl() }
280+
}
281+
}
279282

280283
try {
281284
server.start()

grpc/grpc-core/src/commonTest/kotlin/kotlinx/rpc/grpc/test/RawClientServerTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ class RawClientServerTest {
110110
val serverScope = CoroutineScope(serverJob)
111111

112112
val client = GrpcClient("localhost", PORT) {
113-
usePlaintext()
113+
credentials = plaintext()
114114
}
115115

116116
val descriptor = methodDescriptor(

0 commit comments

Comments
 (0)