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

kalix.spring.impl.WebClientProviderHolder.scala Maven / Gradle / Ivy

There is a newer version: 1.4.1
Show newest version
/*
 * Copyright 2021 Lightbend 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 kalix.spring.impl

import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentMap

import scala.jdk.CollectionConverters.MapHasAsScala
import scala.jdk.OptionConverters.RichOptional

import akka.actor.ActorSystem
import akka.actor.ClassicActorSystemProvider
import akka.actor.ExtendedActorSystem
import akka.actor.Extension
import akka.actor.ExtensionId
import akka.actor.ExtensionIdProvider
import akka.annotation.InternalApi
import com.typesafe.config.Config
import kalix.devtools.impl.DevModeSettings
import kalix.devtools.impl.HostAndPort
import kalix.javasdk.JsonSupport
import kalix.javasdk.impl.ProxyInfoHolder
import kalix.spring.WebClientProvider
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.http.codec.json.Jackson2JsonEncoder
import org.springframework.web.reactive.function.client.ExchangeFilterFunctions
import org.springframework.web.reactive.function.client.WebClient

/**
 * INTERNAL API
 */
@InternalApi
private[kalix] object WebClientProviderHolder extends ExtensionId[WebClientProviderHolder] with ExtensionIdProvider {
  override def get(system: ActorSystem): WebClientProviderHolder = super.get(system)

  override def get(system: ClassicActorSystemProvider): WebClientProviderHolder = super.get(system)

  override def createExtension(system: ExtendedActorSystem): WebClientProviderHolder =
    new WebClientProviderHolder(system)
  override def lookup: ExtensionId[_ <: Extension] = this

}

class WebClientProviderHolder(system: ExtendedActorSystem) extends Extension {
  val webClientProvider = new WebClientProviderImpl(system)
}

/**
 * INTERNAL API
 */
@InternalApi
private[kalix] class WebClientProviderImpl(system: ExtendedActorSystem) extends WebClientProvider {

  private val proxyInfoHolder = ProxyInfoHolder(system)
  private val clients: ConcurrentMap[String, WebClient] = new ConcurrentHashMap()

  private val devModeSettings = DevModeSettings.fromConfig(system.settings.config).portMappings

  private val MaxCrossServiceResponseContentLength =
    system.settings.config.getBytes("kalix.cross-service.max-content-length").toInt

  override def webClientFor(host: String): WebClient = {

    // differently from the gRPC client, we don't need to create an extra config on the fly
    // we can read the dev-mode settings directly and use it to override the host and port
    val (mappedHost, mappedPort) =
      devModeSettings
        .get(host)
        .map(HostAndPort.extract)
        .getOrElse((host, 80))

    clients.computeIfAbsent(
      host,
      _ => {
        val remoteAddHeader = proxyInfoHolder.remoteIdentificationHeader
        buildClient(mappedHost, mappedPort, remoteAddHeader)
      })
  }

  val localWebClient: WebClient = {
    val localAddHeader = proxyInfoHolder.localIdentificationHeader
    val clientOpt =
      for {
        host <- proxyInfoHolder.proxyHostname
        port <- proxyInfoHolder.proxyPort
      } yield buildClient(host, port, localAddHeader)

    clientOpt.getOrElse {
      throw new IllegalStateException(
        "Service proxy hostname and/or port are not set by proxy at discovery, too old proxy version?")
    }
  }

  private def buildClient(host: String, port: Int, identificationHeader: Option[(String, String)]) = {

    val builder =
      WebClient.builder
        .baseUrl(s"http://$host:$port")
        .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
        .codecs(configurer => {
          configurer.defaultCodecs.jackson2JsonEncoder(
            new Jackson2JsonEncoder(JsonSupport.getObjectMapper, MediaType.APPLICATION_JSON))
        })
        .filter(ExchangeFilterFunctions.limitResponseSize(MaxCrossServiceResponseContentLength))

    identificationHeader.foreach { case (key, value) =>
      builder.defaultHeader(key, value)
    }

    builder.build()

  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy