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

akka.grpc.internal.NettyClientUtils.scala Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (C) 2009-2019 Lightbend Inc. 
 */

package akka.grpc.internal

import java.lang.reflect.Field
import java.util.concurrent.{ ThreadLocalRandom, TimeUnit }

import akka.Done
import akka.annotation.InternalApi
import akka.discovery.Lookup
import akka.grpc.GrpcClientSettings
import io.grpc.netty.shaded.io.grpc.netty.{ GrpcSslContexts, NegotiationType, NettyChannelBuilder }
import io.grpc.netty.shaded.io.netty.handler.ssl._
import io.grpc.{ CallOptions, ManagedChannel }
import javax.net.ssl.SSLContext

import scala.concurrent.duration.FiniteDuration
import scala.concurrent.{ ExecutionContext, Future, Promise }

/**
 * INTERNAL API
 */
@InternalApi
object NettyClientUtils {

  /**
   * INTERNAL API
   */
  @InternalApi
  def createChannel(settings: GrpcClientSettings)(implicit ec: ExecutionContext): InternalChannel = {
    val promise = Promise[Done]()
    val mc: Future[ManagedChannel] = settings.serviceDiscovery.lookup(
      Lookup(settings.serviceName, settings.servicePortName, settings.serviceProtocol),
      settings.resolveTimeout).flatMap { targets =>
        if (targets.addresses.nonEmpty) {
          val target = targets.addresses(ThreadLocalRandom.current().nextInt(targets.addresses.size))
          var builder =
            NettyChannelBuilder
              .forAddress(target.host, target.port.getOrElse(settings.defaultPort))
              .flowControlWindow(NettyChannelBuilder.DEFAULT_FLOW_CONTROL_WINDOW)

          if (!settings.useTls)
            builder = builder.usePlaintext()
          else {
            builder = settings.sslContext
              .map(javaCtx => builder.negotiationType(NegotiationType.TLS).sslContext(nettyHttp2SslContext(javaCtx)))
              .getOrElse(builder.negotiationType(NegotiationType.PLAINTEXT))

            builder = settings.overrideAuthority.map(builder.overrideAuthority(_)).getOrElse(builder)
          }

          builder = settings.userAgent.map(builder.userAgent(_)).getOrElse(builder)
          builder = settings.channelBuilderOverrides(builder)

          val channel = builder.build()
          ChannelUtils.monitorChannel(promise, channel, settings.connectionAttempts)
          Future.successful(channel)
        } else {
          Future.failed(new IllegalStateException("No targets returned for name: " + settings.serviceName))
        }
      }
    new InternalChannel(mc, promise)
  }

  /**
   * INTERNAL API
   *
   * Given a Java [[SSLContext]], create a Netty [[SslContext]] that can be used to build
   * a Netty HTTP/2 channel.
   */
  @InternalApi
  private def nettyHttp2SslContext(javaSslContext: SSLContext): SslContext = {
    // FIXME: Create a JdkSslContext using a normal constructor. Need to work out sensible values for all args first.
    // In the meantime, use a Netty SslContextBuild to create a JdkSslContext, then use reflection to patch the
    // object's internal SSLContext. It's not pretty, but it gets something working for now.

    // Create a Netty JdkSslContext object with all the correct ciphers, protocol settings, etc initialized.
    val nettySslContext: JdkSslContext = GrpcSslContexts
      .configure(GrpcSslContexts.forClient, SslProvider.JDK)
      .build.asInstanceOf[JdkSslContext]

    // Patch the SSLContext value inside the JdkSslContext object
    val nettySslContextField: Field = classOf[JdkSslContext].getDeclaredField("sslContext")
    nettySslContextField.setAccessible(true)
    nettySslContextField.set(nettySslContext, javaSslContext)

    nettySslContext
  }
  /**
   * INTERNAL API
   */
  @InternalApi def callOptions(settings: GrpcClientSettings): CallOptions = {
    settings.callCredentials.map(CallOptions.DEFAULT.withCallCredentials).getOrElse(CallOptions.DEFAULT)
  }

  /**
   * INTERNAL API
   */
  @InternalApi private[akka] def callOptionsWithDeadline(defaultOptions: CallOptions, settings: GrpcClientSettings): CallOptions =
    settings.deadline match {
      case d: FiniteDuration => defaultOptions.withDeadlineAfter(d.toMillis, TimeUnit.MILLISECONDS)
      case _ => defaultOptions
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy