All Downloads are FREE. Search and download functionalities are using the official Maven repository.

build.buf.connect.impl.ProtocolClient.kt Maven / Gradle / Ivy

There is a newer version: 0.1.10
Show newest version
// Copyright 2022-2023 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package build.buf.connect.impl

import build.buf.connect.BidirectionalStreamInterface
import build.buf.connect.ClientOnlyStreamInterface
import build.buf.connect.Code
import build.buf.connect.Headers
import build.buf.connect.MethodSpec
import build.buf.connect.ProtocolClientConfig
import build.buf.connect.ProtocolClientInterface
import build.buf.connect.ResponseMessage
import build.buf.connect.ServerOnlyStreamInterface
import build.buf.connect.StreamResult
import build.buf.connect.http.Cancelable
import build.buf.connect.http.HTTPClientInterface
import build.buf.connect.http.HTTPRequest
import build.buf.connect.http.Stream
import java.net.URL
import kotlin.coroutines.resume
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.suspendCancellableCoroutine

/**
 * Concrete implementation of the `ProtocolClientInterface`.
 */
class ProtocolClient(
    // The client to use for performing requests.
    private val httpClient: HTTPClientInterface,
    // The configuration for the ProtocolClient.
    private val config: ProtocolClientConfig,
) : ProtocolClientInterface {

    override fun  unary(
        request: Input,
        headers: Headers,
        methodSpec: MethodSpec,
        onResult: (ResponseMessage) -> Unit,
    ): Cancelable {
        val serializationStrategy = config.serializationStrategy
        val requestCodec = serializationStrategy.codec(methodSpec.requestClass)
        try {
            val unaryRequest = HTTPRequest(
                url = URL("${config.host}/${methodSpec.path}"),
                contentType = "application/${requestCodec.encodingName()}",
                headers = headers,
                message = requestCodec.serialize(request)
                    .readByteArray()
            )
            val unaryFunc = config.createInterceptorChain()
            val finalRequest = unaryFunc.requestFunction(unaryRequest)
            val cancelable = httpClient.unary(finalRequest) { httpResponse ->
                val finalResponse = unaryFunc.responseFunction(httpResponse)
                val code = finalResponse.code
                val connectError = finalResponse.error?.setErrorParser(serializationStrategy.errorDetailParser())
                if (connectError != null) {
                    onResult(
                        ResponseMessage.Failure(
                            connectError,
                            code,
                            finalResponse.headers,
                            finalResponse.trailers,
                        )
                    )
                } else {
                    val responseCodec = serializationStrategy.codec(methodSpec.responseClass)
                    val responseMessage = responseCodec.deserialize(
                        finalResponse.message,
                    )
                    onResult(
                        ResponseMessage.Success(
                            responseMessage,
                            code,
                            finalResponse.headers,
                            finalResponse.trailers,
                        )
                    )
                }
            }
            return cancelable
        } catch (e: Exception) {
            throw e
        }
    }

    override suspend fun  unary(
        request: Input,
        headers: Headers,
        methodSpec: MethodSpec,
    ): ResponseMessage {
        return suspendCancellableCoroutine { continuation ->
            val cancelable = unary(request, headers, methodSpec) { responseMessage ->
                continuation.resume(responseMessage)
            }
            continuation.invokeOnCancellation {
                cancelable()
            }
        }
    }

    override suspend fun  stream(
        headers: Headers,
        methodSpec: MethodSpec,
    ): BidirectionalStreamInterface {
        return suspendCancellableCoroutine { continuation ->
            val channel = Channel>(1)
            val requestCodec = config.serializationStrategy.codec(methodSpec.requestClass)
            val responseCodec = config.serializationStrategy.codec(methodSpec.responseClass)
            val request = HTTPRequest(
                url = URL("${config.host}/${methodSpec.path}"),
                contentType = "application/connect+${requestCodec.encodingName()}",
                headers = headers,
            )
            val streamFunc = config.createStreamingInterceptorChain()
            val finalRequest = streamFunc.requestFunction(request)
            var isComplete = false
            val stream = httpClient.stream(finalRequest) { initialResult ->
                if (isComplete) {
                    // No-op on remaining handlers after a completion.
                    return@stream
                }
                // Pass through the interceptor chain.
                val streamResult = streamFunc.streamResultFunction(initialResult)
                val result: StreamResult = when (streamResult) {
                    is StreamResult.Headers -> {
                        StreamResult.Headers(streamResult.headers)
                    }

                    is StreamResult.Message -> {
                        try {
                            val message = responseCodec.deserialize(
                                streamResult.message,
                            )
                            StreamResult.Message(message)
                        } catch (e: Throwable) {
                            StreamResult.Complete(Code.UNKNOWN, e)
                        }
                    }

                    is StreamResult.Complete -> {
                        isComplete = true
                        StreamResult.Complete(
                            streamResult.connectError()?.code ?: Code.OK,
                            error = streamResult.error,
                            trailers = streamResult.trailers
                        )
                    }
                }
                channel.send(result)
            }
            continuation.invokeOnCancellation {
                stream.close()
            }
            continuation.resume(
                BidirectionalStream(
                    Stream(
                        onSend = { buffer ->
                            stream.send(streamFunc.requestBodyFunction(buffer))
                        },
                        onClose = {
                            stream.close()
                        }
                    ),
                    requestCodec,
                    channel
                )
            )
        }
    }

    override suspend fun  serverStream(
        headers: Headers,
        methodSpec: MethodSpec,
    ): ServerOnlyStreamInterface {
        val stream = stream(headers, methodSpec)
        return ServerOnlyStream(stream)
    }

    override suspend fun  clientStream(
        headers: Headers,
        methodSpec: MethodSpec,
    ): ClientOnlyStreamInterface {
        val stream = stream(headers, methodSpec)
        return ClientOnlyStream(stream)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy