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

Add example for sending pre-serialized messages #429

Open
bubenheimer opened this issue Sep 8, 2023 · 3 comments
Open

Add example for sending pre-serialized messages #429

bubenheimer opened this issue Sep 8, 2023 · 3 comments
Assignees

Comments

@bubenheimer
Copy link

grpc-java 1.58.0 added a documentation example1 for how to transparently construct and send a response message with a pre-serialized byte[] (ByteArray) payload. (Without deserializing to the corresponding protobuf type first, then serializing again to the same byte[].)

I am looking for an equivalent example for grpc-kotlin, like via io.grpc.kotlin.ServerCalls, I imagine someone may have done this already.

I'd like to use this on the server for a response from a bidi service, in essentially the same scenario as in the original grpc-java request2.

Footnotes

  1. https://github.com/grpc/grpc-java/blob/master/examples/src/main/java/io/grpc/examples/preserialized/PreSerializedServer.java

  2. https://github.com/grpc/grpc-java/issues/9707

@bubenheimer
Copy link
Author

At least the server side of this example looks pretty straightforward to carry over to grpc-kotlin, presuming knowledge of ServerCalls, generated Kotlin service stubs, and the original Java example.

@jamesward
Copy link
Collaborator

Maybe @lowasser can point us in the right direction.

@bubenheimer
Copy link
Author

The more general parts of the server code could look as below:

/**
 *  Rewrites the ServerServiceDefinition replacing one method's definition.
 */
private fun <ReqT, RespT> replaceMethod(
    def: ServerServiceDefinition,
    newDesc: MethodDescriptor<ReqT, RespT>,
    newHandler: ServerCallHandler<ReqT, RespT>
): ServerServiceDefinition {
    // There are two data structures involved. The first is the "descriptor" which describes the
    // service and methods as a schema. This is the same on client and server. The second is the
    // "definition" which includes the handlers to execute methods. This is specific to the server
    // and is generated by "bind." This adjusts both the descriptor and definition.

    // Descriptor
    val newServiceDesc = def.serviceDescriptor.let { desc ->
        ServiceDescriptor.newBuilder(desc.name)
            .setSchemaDescriptor(desc.schemaDescriptor)
            .addMethod(newDesc) // Add the modified method
            .apply {
                // Copy methods other than the modified one
                desc.methods.forEach { md ->
                    if (newDesc.fullMethodName != md.fullMethodName) {
                        addMethod(md)
                    }
                }
            }
            .build()
    }

    // Definition
    return ServerServiceDefinition.builder(newServiceDesc)
        .addMethod(newDesc, newHandler) // Add the modified method
        .apply {
            // Copy methods other than the modified one
            def.methods.forEach { smd ->
                if (newDesc.fullMethodName != smd.methodDescriptor.fullMethodName) {
                    addMethod(smd)
                }
            }
        }
        .build()
}

/**
 * A marshaller that produces a ByteArray instead of decoding into typical POJOs. It can be used for
 * any message type.
 */
object ByteArrayMarshaller : MethodDescriptor.Marshaller<ByteArray> {
    override fun parse(stream: InputStream): ByteArray = stream.readBytes()

    override fun stream(b: ByteArray) = ByteArrayInputStream(b)
}

I've ended up using custom code for my own limited needs instead of the general replaceMethod()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants