telegram icon indicating copy to clipboard operation
telegram copied to clipboard

SSL with ZIO Webhook using asyncHttpClient

Open AlexGruPerm opened this issue 3 years ago • 6 comments

If I have code like this

abstract class FbBot(val conf: BotConfig)      
  extends TelegramBot[Task](conf.token, AsyncHttpClientZioBackend.usingClient(zio.Runtime.default, asyncHttpClient()))

class telegramBotZio(val config :BotConfig, private val started: Ref.Synchronized[Boolean])
  extends FbBot(config)
    with Commands[Task] {

  import com.bot4s.telegram.marshalling._
  val webhookUrl = config.webhookUrl
 ...
 ...
   def certificate: Option[InputFile] = {
    log.info(s"def certificate cert file path = ${new File(certPathStr).toPath.toString}")
    Some(InputFile(new File(certPathStr).toPath))
  }
  ...
    override def run() =
    started.updateZIO { isStarted =>
      for {
        _ <- ZIO.when(isStarted)(ZIO.fail(new Exception("Bot already started")))
        console <- ZIO.console
        _ <- console.printLine("Inside run() method")
        response <-
          request(SetWebhook(url = webhookUrl, certificate = certificate, allowedUpdates = allowedUpdates)).flatMap {
            case true => ZIO.succeed(true)
            case false =>
              ZIO.logError("Failed to set webhook")
              throw new RuntimeException("Failed to set webhook")
          }
      } yield response
    } *>
      server.make
        .flatMap(start => ZIO.logInfo(s"Server started on ${start.port}") *> ZIO.never)
        .provide(ServerChannelFactory.auto, EventLoopGroup.auto(1), Scope.default)

certificate = certificate

asyncHttpClient() know nothing about httpsContext, code here

  val httpsContext: Option[akka.http.scaladsl.HttpsConnectionContext] = Some(getHttpsContext(keystorePassword))

  def getHttpsContext(keystorePassword : Array[Char]): HttpsConnectionContext = {
    // Manual HTTPS configuration
    val password: Array[Char] = keystorePassword

    val ks: KeyStore = KeyStore.getInstance("PKCS12")
    val keystore: InputStream = new FileInputStream(config.p12certpath)

    require(keystore != null, " - Keystore required!")
    ks.load(keystore, password)

    val keyManagerFactory: KeyManagerFactory = KeyManagerFactory.getInstance("SunX509")
    keyManagerFactory.init(ks, password)

    val tmf: TrustManagerFactory = TrustManagerFactory.getInstance("SunX509")
    tmf.init(ks)

    val sslContext: SSLContext = SSLContext.getInstance("TLS")
    sslContext.init(keyManagerFactory.getKeyManagers, tmf.getTrustManagers, new SecureRandom)
    val https: HttpsConnectionContext = ConnectionContext.httpsServer(sslContext)

    https
  }

Bot starts with this info https://api.telegram.org/xxxxxxxxxx/getWebhookInfo

... "has_custom_certificate":false ..."last_error_message":"Connection timed out"

I found this link https://asynchttpclient.github.io/async-http-client/ssl.html

I will research this aspect.

AlexGruPerm avatar Aug 01 '22 19:08 AlexGruPerm

I moved creating asyncHttpClient() out from extends TelegramBot[Task]( and try pass it after setting ssl.

  /*
  we have : javax.net.ssl.SSLContext
  we need : io.netty.handler.ssl.SslContext for org.asynchttpclient.Dsl.config.setSslContext
  */
  val channel: SocketChannel = new NioSocketChannel()
  val p: ChannelPipeline = channel.pipeline()
  val sslCtx: SslContext = SslContextBuilder.forServer(keyManagerFactory).build()
  p.addLast("ssl", sslCtx.newHandler(channel.alloc()))
  val sslConfig = org.asynchttpclient.Dsl.config.setSslContext(sslCtx)
  val client = asyncHttpClient(sslConfig)
...
...
      _ <- Ref.Synchronized.make(false).flatMap { started =>
        (new telegramBotZio(config, client, started)).run()
...

but now not success.

AlexGruPerm avatar Aug 01 '22 21:08 AlexGruPerm

Unless I'm have misread your post, I think you are mixing two things together, the AsyncHttpClient (and hence the AsyncHttpClientZioBackend) does not need to know anything about your custom certificate, they are only used to communicate with telegram's API (in the example, sending a SetWebhook command for your bot).

The certificate parameter must describe the certificate that you are using to run a local http server (not client), which in this case is this part:

server.make
        .flatMap(start => ZIO.logInfo(s"Server started on ${start.port}") *> ZIO.never)
        .provide(ServerChannelFactory.auto, EventLoopGroup.auto(1), Scope.default)

Could you post a definition of server ? Did you check that this server is correctly running an https server on port 443 with the correct certificate ? Is it reachable from the public IP ?

I don't know if this could be an issue here, but maybe try to start the ZIO server before sending the WebhookRequest to Telegram.

ex0ns avatar Aug 02 '22 06:08 ex0ns

Ohh, not soo easy of investigate it. Case 1. When I use code like this

abstract class FbBot(val conf: BotConfig)
  extends TelegramBot[Task](conf.token, AsyncHttpClientZioBackend.usingClient(zio.Runtime.default, asyncHttpClient()))

class telegramBotZio(val config :BotConfig, private val started: Ref.Synchronized[Boolean])
  extends FbBot(config)
  with Commands[Task]
{
  val certPathStr :String = config.pubcertpath

  def certificate: Option[InputFile] = Some(
    InputFile(new File(certPathStr).toPath)
  )

  val port :Int = config.webhook_port
  val webhookUrl = config.webhookUrl

  val password :Array[Char] = config.keyStorePassword.toCharArray

  override def allowedUpdates: Option[Seq[UpdateType]] =
    Some(MessageUpdates ++ InlineUpdates)

  val ks: KeyStore = KeyStore.getInstance("PKCS12")
  val keystore: InputStream = new FileInputStream(config.p12certpath)

  ks.load(keystore, password)

  val keyManagerFactory: KeyManagerFactory = KeyManagerFactory.getInstance("SunX509")
  keyManagerFactory.init(ks, password)

  val trustManagerFactory: TrustManagerFactory = TrustManagerFactory.getInstance("SunX509")
  trustManagerFactory.init(ks)

  import com.bot4s.telegram.marshalling._

  private def callback: Http[Any,Throwable,Request,Response] = Http.collectZIO[Request]{
    case req @ Method.POST -> !! =>
      for {
        _ <- ZIO.logInfo("call callback")
        body    <- req.bodyAsString
        _ <- ZIO.logInfo(s"body = [$body]")
        update  <- ZIO.attempt(fromJson[Update](body))
        _ <- ZIO.logInfo(s"update = [$update]")
        handler <- receiveUpdate(update, None).catchAll{
          case ex: Throwable => ZIO.logError(s"receiveUpdate exception ${ex.getMessage} - ${ex.getCause}")
        }
        _ <- ZIO.logInfo(s"handler = [${handler}]")
      } yield Response.ok
  }

  val sslContext: io.netty.handler.ssl.SslContext = SslContextBuilder.
    forServer(keyManagerFactory)
    .startTls(true)
    .build()

  val sslOptions: ServerSSLOptions = ServerSSLOptions(sslContext)

  private def server: Server[Any,Throwable] =
      Server.port(8443) ++ Server.app(callback) ++ Server.ssl(sslOptions)

  override def run():ZIO[Any,Throwable,Nothing] =
    started.updateZIO { isStarted =>
      for {
        _ <- ZIO.when(isStarted)(ZIO.fail(new Exception("Bot already started")))
        response <-
          request(SetWebhook(url = webhookUrl, certificate = certificate, allowedUpdates = allowedUpdates)).flatMap {
            case true => ZIO.succeed(true)
            case false =>
              ZIO.logError("Failed to set webhook")
              throw new RuntimeException("Failed to set webhook")
          }
      } yield response
    } *>
      server.make
        .flatMap(start => ZIO.logInfo(s"Server started on ${start.port}") *> ZIO.never)
        .provide(ServerChannelFactory.auto, EventLoopGroup.auto(1), Scope.default)
        
         onCommand("/hello") { implicit msg =>
      onCommandLog(msg) *>
        reply("hello command").ignore
    }   
}

Bot as ZLayer

//1. trait
abstract class FbBotZio(conf: BotConfig) {

  def startBot: ZIO[Any, Nothing, Unit] =
    for {
      _ <- ZIO.logInfo("Begin start bot")
      _ <- Ref.Synchronized.make(false).flatMap { started =>
        new telegramBotZio(conf, started).run()
      }.catchAll {
        case ex: Throwable => ZIO.logError(s" Exception FbBotZio.runBot ${ex.getMessage} - ${ex.getCause} ")
      }
      _ <- ZIO.logInfo("End bot")
    } yield ()

}

//3. Service implementations (classes) should accept all dependencies in constructor
case class FbBotZioImpl(conf: BotConfig) extends FbBotZio(conf) {
  super.startBot
}

//4. converting service implementation into ZLayer
object FbBotZioImpl {
  val layer: ZLayer[BotConfig, Nothing, FbBotZioImpl] = //ZLayer.succeed(FbBotZioImpl)
    ZLayer {
      for {
        conf <- ZIO.service[BotConfig]
      } yield FbBotZioImpl(conf)
    }

}

and main app

object Main extends ZIOAppDefault {

  val configBot: ZIO[String, Throwable, BotConfig] =
    for {
      configParam <- ZIO.service[String]
      configFilename: String = System.getProperty("user.dir") + File.separator + configParam
      fileConfig = ConfigFactory.parseFile(new io.File(configFilename))
      botConfig = BotConfigHelper.getConfig(fileConfig)
    } yield botConfig

  val botEffect: ZIO[BotConfig with FbBotZio, Throwable, Unit] =
    for {
      bot <- ZIO.service[FbBotZio]
      _ <- bot.startBot
    } yield ()

  val MainApp: ZIO[BotConfig, Throwable, Unit] = for {
    _ <- ZIO.logInfo("Begin MainApp")
    conf <- ZIO.service[BotConfig]
    _ <- botEffect.provide(
      ZLayer.succeed(conf),
      FbBotZioImpl.layer
    )
  } yield ()

  def botConfigZLayer(confParam: ZIOAppArgs): ZLayer[Any, Throwable, BotConfig] =
    ZLayer {
      for {
        botConfig <- confParam.getArgs.toList match {
          case List(configFile) => configBot.provide(ZLayer.succeed(configFile))
          case _ => ZIO.fail(new Exception("There is no config file in input parameter. "))
        }
        _ <- ZIO.logInfo(s"BotConfig = ${botConfig}")
      } yield botConfig
    }

  override def run: URIO[ZIOAppArgs, ExitCode] =
    for {
      args <- ZIO.service[ZIOAppArgs]
      botConfig = botConfigZLayer(args)
      res <- MainApp.provide(botConfig).exitCode
    } yield res

}

There is no important is webhook established before or not. I have the same output

C:\jdk-18.0.2\bin\java.exe
.....................
........................
timestamp=2022-08-09T21:08:37.162672700Z level=INFO thread=#zio-fiber-6 message="BotConfig = BotConfig(886094130:AAENizOGvqrl7vrLVeOZwKfJdIJyQEU_123,https://myip:8443,8443,333333,E:\PROJECTS\min_bot4s_zio\tbot\src\main\resources\mts.pem,E:\PROJECTS\min_bot4s_zio\tbot\src\main\resources\mts.p12)" location=app.Main.botConfigZLayer file=Main.scala line=53
timestamp=2022-08-09T21:08:37.168673300Z level=INFO thread=#zio-fiber-6 message="Begin MainApp" location=app.Main.MainApp file=Main.scala line=38
timestamp=2022-08-09T21:08:37.195672800Z level=INFO thread=#zio-fiber-6 message="Begin start bot" location=service.FbBotZio.startBot file=telegBotZio.scala line=11
02:08:37.323 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.logging.InternalLoggerFactory - Using SLF4J as the default logging framework
02:08:37.329 [ZScheduler-Worker-1] DEBUG io.netty.util.ResourceLeakDetector - -Dio.netty.leakDetection.level: simple
02:08:37.329 [ZScheduler-Worker-1] DEBUG io.netty.util.ResourceLeakDetector - -Dio.netty.leakDetection.targetRecords: 4
02:08:37.333 [ZScheduler-Worker-1] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@108f7124
02:08:37.357 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.PlatformDependent0 - -Dio.netty.noUnsafe: false
02:08:37.357 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.PlatformDependent0 - Java version: 18
02:08:37.359 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.theUnsafe: available
02:08:37.359 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.copyMemory: available
02:08:37.360 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.storeFence: available
02:08:37.360 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.PlatformDependent0 - java.nio.Buffer.address: available
02:08:37.361 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.PlatformDependent0 - direct buffer constructor: unavailable: Reflective setAccessible(true) disabled
02:08:37.361 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.PlatformDependent0 - java.nio.Bits.unaligned: available, true
02:08:37.362 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.PlatformDependent0 - jdk.internal.misc.Unsafe.allocateUninitializedArray(int): unavailable: class io.netty.util.internal.PlatformDependent0$7 cannot access class jdk.internal.misc.Unsafe (in module java.base) because module java.base does not export jdk.internal.misc to unnamed module @3abd3847
02:08:37.363 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.PlatformDependent0 - java.nio.DirectByteBuffer.<init>(long, int): unavailable
02:08:37.363 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.PlatformDependent - sun.misc.Unsafe: available
02:08:37.374 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.PlatformDependent - maxDirectMemory: 4278190080 bytes (maybe)
02:08:37.374 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.PlatformDependent - -Dio.netty.tmpdir: C:\Users\yakushev\AppData\Local\Temp (java.io.tmpdir)
02:08:37.374 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.PlatformDependent - -Dio.netty.bitMode: 64 (sun.arch.data.model)
02:08:37.375 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.PlatformDependent - Platform: Windows
02:08:37.376 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.PlatformDependent - -Dio.netty.maxDirectMemory: -1 bytes
02:08:37.376 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.PlatformDependent - -Dio.netty.uninitializedArrayAllocationThreshold: -1
02:08:37.378 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.CleanerJava9 - java.nio.ByteBuffer.cleaner(): available
02:08:37.378 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.PlatformDependent - -Dio.netty.noPreferDirect: false
02:08:37.386 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.PlatformDependent - org.jctools-core.MpscChunkedArrayQueue: available
02:08:37.644 [ZScheduler-Worker-1] DEBUG io.netty.handler.ssl.JdkSslContext - Default protocols (JDK): [TLSv1.3, TLSv1.2] 
02:08:37.644 [ZScheduler-Worker-1] DEBUG io.netty.handler.ssl.JdkSslContext - Default cipher suites (JDK): [TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384]
02:08:37.662 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.InternalThreadLocalMap - -Dio.netty.threadLocalMap.stringBuilder.initialSize: 1024
02:08:37.662 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.InternalThreadLocalMap - -Dio.netty.threadLocalMap.stringBuilder.maxSize: 4096
02:08:37.667 [ZScheduler-Worker-1] DEBUG io.netty.channel.MultithreadEventLoopGroup - -Dio.netty.eventLoopThreads: 8
02:08:37.674 [ZScheduler-Worker-1] DEBUG io.netty.channel.nio.NioEventLoop - -Dio.netty.noKeySetOptimization: false
02:08:37.674 [ZScheduler-Worker-1] DEBUG io.netty.channel.nio.NioEventLoop - -Dio.netty.selectorAutoRebuildThreshold: 512
02:08:37.708 [ZScheduler-Worker-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.numHeapArenas: 8
02:08:37.708 [ZScheduler-Worker-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.numDirectArenas: 8
02:08:37.708 [ZScheduler-Worker-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.pageSize: 8192
02:08:37.708 [ZScheduler-Worker-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxOrder: 9
02:08:37.708 [ZScheduler-Worker-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.chunkSize: 4194304
02:08:37.708 [ZScheduler-Worker-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.smallCacheSize: 256
02:08:37.708 [ZScheduler-Worker-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.normalCacheSize: 64
02:08:37.708 [ZScheduler-Worker-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxCachedBufferCapacity: 32768
02:08:37.708 [ZScheduler-Worker-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.cacheTrimInterval: 8192
02:08:37.708 [ZScheduler-Worker-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.cacheTrimIntervalMillis: 0
02:08:37.708 [ZScheduler-Worker-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.useCacheForAllThreads: false
02:08:37.708 [ZScheduler-Worker-1] DEBUG io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxCachedByteBuffersPerChunk: 1023
02:08:37.717 [ZScheduler-Worker-1] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled
02:08:37.717 [ZScheduler-Worker-1] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 0
02:08:37.717 [ZScheduler-Worker-1] DEBUG io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384
02:08:38.020 [ZScheduler-Worker-1] DEBUG io.netty.handler.ssl.OpenSsl - netty-tcnative not in the classpath; OpenSslEngine will be unavailable.

Process finished with exit code 0

and application is finishing here without any information.

case 2) I delete webhook manually with

E:\PROJECTS\min_bot4s_zio\tbot\src\main\resources>curl  --ssl-no-revoke "https://api.telegram.org/bot886094130:AAENizOGvqrl7vrLVeOZwKfJdIJyQEU_123/deleteWebhook"
{"ok":true,"result":true,"description":"Webhook was deleted"}

If I change run() into next code

  override def run(): ZIO[Any, Throwable, Unit] = {
    for {
      srv <- server.withSsl(sslOptions).make
        .flatMap(start => ZIO.logInfo(s"Server started on ${start.port} ") *> ZIO.never)
        .provide(ServerChannelFactory.auto, EventLoopGroup.auto(1), Scope.default).forkDaemon
      cln <- started.updateZIO { isStarted =>
        for {
          _ <- ZIO.logInfo(s"certificate path is ${certPathStr}")
          _ <- ZIO.when(isStarted)(ZIO.fail(new Exception("Bot already started")))
          _ <- ZIO.when(!isStarted)(ZIO.logInfo(s"Bot not started yet, starting it .... webhookUrl=${webhookUrl}"))
          response <- request(SetWebhook(url = webhookUrl, certificate = certificate, allowedUpdates = allowedUpdates)).flatMap {
            case true =>
              ZIO.logInfo("SetWebhook success.") *>
                ZIO.succeed(true)
            case false =>
              ZIO.logError("Failed to set webhook")
              throw new RuntimeException("Failed to set webhook")
          }
        } yield response
      }.forkDaemon

      _ <- srv.join
      _ <- cln.join
    } yield ()
  }

When I run whole application, output is

timestamp=2022-08-09T21:15:17.700129500Z level=INFO thread=#zio-fiber-6 message="BotConfig = BotConfig(886094130:AAENizOGvqrl7vrLVeOZwKfJdIJyQEU_123,https://myip:8443,8443,333333,E:\PROJECTS\min_bot4s_zio\tbot\src\main\resources\mts.pem,E:\PROJECTS\min_bot4s_zio\tbot\src\main\resources\mts.p12)" location=app.Main.botConfigZLayer file=Main.scala line=53
timestamp=2022-08-09T21:15:17.706129400Z level=INFO thread=#zio-fiber-6 message="Begin MainApp" location=app.Main.MainApp file=Main.scala line=38
timestamp=2022-08-09T21:15:17.731131200Z level=INFO thread=#zio-fiber-6 message="Begin start bot" location=service.FbBotZio.startBot file=telegBotZio.scala line=11
02:15:17.860 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.logging.InternalLoggerFactory - Using SLF4J as the default logging framework
02:15:17.867 [ZScheduler-Worker-1] DEBUG io.netty.util.ResourceLeakDetector - -Dio.netty.leakDetection.level: simple
02:15:17.867 [ZScheduler-Worker-1] DEBUG io.netty.util.ResourceLeakDetector - -Dio.netty.leakDetection.targetRecords: 4
02:15:17.871 [ZScheduler-Worker-1] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@594c6308
02:15:17.889 [ZScheduler-Worker-1] DEBUG io.netty.util.internal.PlatformDependent0 - -Dio.netty.noUnsafe: false
... io.netty.output....
timestamp=2022-08-09T21:15:18.703132800Z level=INFO thread=#zio-fiber-10 message="certificate path is E:\PROJECTS\min_bot4s_zio\tbot\src\main\resources\mts.pem" location=service.telegramBotZio.run file=BotCore.scala line=99
timestamp=2022-08-09T21:15:18.714133600Z level=INFO thread=#zio-fiber-10 message="Bot not started yet, starting it .... webhookUrl=https://myip:8443" location=service.telegramBotZio.run file=BotCore.scala line=101
02:15:18.810 [ZScheduler-1] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.processId: 14248 (auto-detected)
02:15:18.813 [ZScheduler-1] DEBUG io.netty.util.NetUtil - -Djava.net.preferIPv4Stack: false
02:15:18.813 [ZScheduler-1] DEBUG io.netty.util.NetUtil - -Djava.net.preferIPv6Addresses: false
02:15:18.826 [ZScheduler-1] DEBUG io.netty.util.NetUtilInitializations - Loopback interface: lo (Software Loopback Interface 1, 127.0.0.1)
02:15:18.827 [ZScheduler-1] DEBUG io.netty.util.NetUtil - Failed to get SOMAXCONN from sysctl and file \proc\sys\net\core\somaxconn. Default: 200
02:15:18.854 [ZScheduler-1] DEBUG io.netty.channel.DefaultChannelId - -Dio.netty.machineId: a0:d3:c1:ff:fe:44:24:00 (auto-detected)
timestamp=2022-08-09T21:15:18.920132200Z level=INFO thread=#zio-fiber-9 message="Server started on 8443 " location=service.telegramBotZio.run file=BotCore.scala line=95

and no reaction on telegram messages in chat, no more output in console...

In this time I manually set webhook

E:\PROJECTS\min_bot4s_zio\tbot\src\main\resources>curl -F "url=https://myip:8443/" --ssl-no-revoke -F "[email protected]" "https://api.telegram.org/bot886094130:AAENizOGvqrl7vrLVeOZwKfJdIJyQEU_123/setwebhook"
{"ok":true,"result":true,"description":"Webhook was set"}

In IDEA I see a lot of messages

[nioEventLoopGroup-4-1] WARN io.netty.channel.DefaultChannelPipeline - An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
io.netty.handler.codec.DecoderException: javax.net.ssl.SSLHandshakeException: Received fatal alert: protocol_version
...
and my message to bot and a lot of reposnse like this:

02:20:45.482 [nioEventLoopGroup-4-1] DEBUG com.bot4s.telegram.clients.SttpClient - curl \
  --request POST \
  --url 'https://api.telegram.org/bot886094130:AAENizOGvqrl7vrLVeOZwKfJdIJyQEU_123/SendMessage' \
  --header 'Content-Type: application/json; charset=utf-8' \
  --data-raw '{"chat_id":322134338,"text":"start command!"}' \
  --location \
  --max-redirs 32

and sometimes bet respnse into chat image

I will continue my research.

AlexGruPerm avatar Aug 09 '22 21:08 AlexGruPerm

Looks like I found that request(SetWebhook not works (not send request to telegram server). I add withSsl to server, and start it before SetWebhook manually.

  val sslContext: io.netty.handler.ssl.SslContext = SslContextBuilder.
    forServer(keyManagerFactory)
    .build()

  val sslOptions: ServerSSLOptions = ServerSSLOptions(sslContext)

  private def server: Server[Any,Throwable] =
      Server.port(8443) ++ Server.app(callback) ++ Server.ssl(sslOptions)

  override def run(): ZIO[Any, Throwable, Unit] = {
    server.withSsl(sslOptions).make
      .flatMap(start => ZIO.logInfo(s"Server started on ${start.port}") *> ZIO.never)
      .provide(ServerChannelFactory.auto, EventLoopGroup.auto(1), Scope.default) *>
    started.updateZIO { isStarted =>
      for {
        _ <- ZIO.when(isStarted)(ZIO.fail(new Exception("Bot already started")))
        response <-
          request(SetWebhook(url = webhookUrl, certificate = certificate, allowedUpdates = allowedUpdates)).flatMap {
            case true => ZIO.succeed(true)
            case false =>
              ZIO.logError("Failed to set webhook")
              throw new RuntimeException("Failed to set webhook")
          }
      } yield response
    }
  }

  onCommand("/hello") { implicit msg =>
    onCommandLog(msg) *>
      reply("hello command").ignore
  }

  onCommand("/start") { implicit msg =>
    onCommandLog(msg) *>
      reply("start command!").ignore
  }

  onCommand("/begin") { implicit msg =>
    onCommandLog(msg) *>
      reply("begin command!").ignore
  }
  1. I delete web hook manually and start application
E:\PROJECTS\min_bot4s_zio\tbot\src\main\resources>curl  --ssl-no-revoke "https://api.telegram.org/bot886094130:AAENizOGvqrl7vrLVeOZwKfJdIJyQEU_123/deleteWebhook"
{"ok":true,"result":true,"description":"Webhook is already deleted"}

output

timestamp=2022-08-09T22:01:20.064258300Z level=INFO thread=#zio-fiber-6 message="BotConfig = BotConfig(886094130:AAENizOGvqrl7vrLVeOZwKfJdIJyQEU_123,https://myip:8443,8443,333333,E:\PROJECTS\min_bot4s_zio\tbot\src\main\resources\mts.pem,E:\PROJECTS\min_bot4s_zio\tbot\src\main\resources\mts.p12)" location=app.Main.botConfigZLayer file=Main.scala line=53
timestamp=2022-08-09T22:01:20.070257100Z level=INFO thread=#zio-fiber-6 message="Begin MainApp" location=app.Main.MainApp file=Main.scala line=38
timestamp=2022-08-09T22:01:20.096270600Z level=INFO thread=#zio-fiber-6 message="Begin start bot" location=service.FbBotZio.startBot file=telegBotZio.scala line=11
03:01:20.232 [ZScheduler-0] DEBUG io.netty.util.internal.logging.InternalLoggerFactory - Using SLF4J as the default logging framework
...
timestamp=2022-08-09T22:01:21.186950800Z level=INFO thread=#zio-fiber-6 message="Server started on 8443" location=service.telegramBotZio.run file=BotCore.scala line=86

There is no errors, application dosn't crash. No reactions on telegram messages. and getInfo

E:\PROJECTS\min_bot4s_zio\tbot\src\main\resources>curl  --ssl-no-revoke "https://api.telegram.org/bot886094130:AAENizOGvqrl7vrLVeOZwKfJdIJyQEU_123/getWebhookInfo"
{"ok":true,"result":{"url":"","has_custom_certificate":false,"pending_update_count":4,"allowed_updates":["message","edited_message","inline_query","chosen_inline_result"]}}

If in this time I set web hook manually

E:\PROJECTS\min_bot4s_zio\tbot\src\main\resources>curl -F "url=https://myip:8443/" --ssl-no-revoke -F "[email protected]" "https://api.telegram.org/bot886094130:AAENizOGvqrl7vrLVeOZwKfJdIJyQEU_123/setwebhook"
{"ok":true,"result":true,"description":"Webhook was set"}

applicaiton is respawns and starts responding to chat messages

image

I think that request(SetWebhook(url = webhookUrl, certificate = certificate, allowedUpdates = allowedUpdates)).flatMap not work at all or don't use certificate when call setWebHook

AlexGruPerm avatar Aug 09 '22 22:08 AlexGruPerm

Also If I write

  override def run(): ZIO[Any, Throwable, Unit] = {
    server.withSsl(sslOptions).make
      .flatMap(start => ZIO.logInfo(s"Server started on ${start.port}") *> ZIO.never)
      .provide(ServerChannelFactory.auto, EventLoopGroup.auto(1), Scope.default) *>
      started.updateZIO { isStarted =>
        for {
          _ <- ZIO.logInfo(s"isStarted = $isStarted")
          _ <- ZIO.when(isStarted)(ZIO.fail(new Exception("Bot already started")))
          response <-
            request(SetWebhook(url = webhookUrl, certificate = certificate, allowedUpdates = allowedUpdates)).flatMap {
              case true =>
                ZIO.logInfo("webhook successful set") *>
                ZIO.succeed(true)
              case false =>
                ZIO.logError("Failed to set webhook")
                throw new RuntimeException("Failed to set webhook")
            }
          _ <- ZIO.logInfo(s"response = $response")
        } yield response
      }
  }

I don't see in output, no Bot already started, no isStarted no webhook successful set, no Failed to set webhook, no Failed to set webhook, no response=?

looks like request(SetWebhook(url = webhookUrl,....)).flatMap out of ZIO. Maybe ZIO.never make effect here.

AlexGruPerm avatar Aug 09 '22 22:08 AlexGruPerm

When we start server before SetWebhook,

  override def run(): ZIO[Any, Throwable, Unit] = {
    server.withSsl(sslOptions).make
      .flatMap(start => ZIO.logInfo(s"Server started on ${start.port}") *> ZIO.never)
      .provide(ServerChannelFactory.auto, EventLoopGroup.auto(1), Scope.default) *>
    ZIO.logInfo("we are here")

we don't see we are here

AlexGruPerm avatar Aug 09 '22 22:08 AlexGruPerm

I think this is because you never reach that because of the flatmap with the zio.never.

I'm a bit lost with all you trial and tests, could you maybe create a repository with the issue that you have and the code that you wrote ?

I think that you should be careful with the ZIO.never stuff, maybe you need to fork or forkDaemon because otherwise you will never reach the other part of the code (that does the init of the bot) Thanks

ex0ns avatar Aug 17 '22 08:08 ex0ns

I made repository https://github.com/AlexGruPerm/min_bot4s_zio (commit for issues-242)

  1. Delete webhook manually with
curl  --ssl-no-revoke "https://api.telegram.org/bot886094130:AAENizOGvqrl7vrLVeOZwKfJdIJyQEU_123/deleteWebhook"
  1. Run application. It runs but not work with telegram, not set webhook.

  2. In this time set webhook manually

curl -F "url=https://91.236.15.73:8443/" --ssl-no-revoke -F "[email protected]" "https://api.telegram.org/bot886094130:AAENizOGvqrl7vrLVeOZwKfJdIJyQEU_123/setwebhook"

and in this time bot begin work (request/response).

AlexGruPerm avatar Aug 18 '22 14:08 AlexGruPerm

I think you forgot to add the common repository, could you push that as well, I'll try to make your code work

ex0ns avatar Aug 18 '22 14:08 ex0ns

Sorry, I don't undestand what means "common" reposotory? It seems that I don't have it.

AlexGruPerm avatar Aug 18 '22 14:08 AlexGruPerm

When I compile your project, the following classes are missing common.BotConfig and common.BotConfigHelper. sbt give me the following error:

min_bot4s_zio/tbot/src/main/scala/app/Main.scala:4:8: not found: object common
[error] import common.{BotConfig, BotConfigHelper}

ex0ns avatar Aug 18 '22 14:08 ex0ns

My pardon, I fixed it.

AlexGruPerm avatar Aug 18 '22 14:08 AlexGruPerm

Config structure control.conf (in main/resource)

telegram {
  token="886094130:AAENizOGvqrl7vrLVeOZwKfJdIJyQEU_123"
  webhook_port=8443
  webhookUrl="https://1.2.3.4:8443"
  keyStorePassword=123456
  pubcertpath="E:\\PROJECTS\\min_bot4s_zio\\tbot\\src\\main\\resources\\mts.pem"
  p12certpath="E:\\PROJECTS\\min_bot4s_zio\\tbot\\src\\main\\resources\\mts.p12"
}

and CLI aruments of application is: tbot\src\main\resources\control.conf

AlexGruPerm avatar Aug 18 '22 14:08 AlexGruPerm

How to make certificate and pem,p12 I wrote here: https://github.com/bot4s/telegram/issues/222

AlexGruPerm avatar Aug 18 '22 14:08 AlexGruPerm

Ok so there was multiple things. The first one is that some of ZIO's defect were not printed and just thrown away, making the code difficult to debug. For some reasons, InputFile(Path( is raising an exception in our client, I don't know why that is, and I don't have the time to look at it right now. In the meantime, you can change your code by:

  def certificate: Option[InputFile] = Some(
    InputFile("certificate", Files.readAllBytes(Paths.get(certPathStr)))
  )

I have opened two PR to show you what I've changed:

https://github.com/AlexGruPerm/min_bot4s_zio/pulls, I think that you will be most interested by https://github.com/AlexGruPerm/min_bot4s_zio/pull/1

also a side note about your logging, as you are using forkDaemon the order of the execution of the before and `after is not guaranteed in the following code:

      startedBefore <- started.get
      _ <- ZIO.logInfo(s"started = [$startedBefore] BEFORE updateZIO")

      cln <- started.updateZIO { isStarted =>
        for {
          _ <- ZIO.logInfo(s"isStarted = $isStarted")
       ...
          } yield response
      }.forkDaemon

      startedAfter <- started.get
      _ <- ZIO.logInfo(s"started = [$startedAfter] AFTER updateZIO")

What you should do instead is:

    for {
      srv <- server.withSsl(sslOptions).make
        .flatMap(start => ZIO.logInfo(s"Server started on ${start.port} ") *> ZIO.never)
        .catchAllDefect(ex => ZIO.logError(s"Server error ${ex.getMessage} - ${ex.getCause}"))
        .provide(ServerChannelFactory.auto, EventLoopGroup.auto(1), Scope.default).forkDaemon

      cln <- {
        for {
            startedBefore <- started.get
            _ <- ZIO.logInfo(s"started = [$startedBefore] BEFORE updateZIO")
            _ <- started.updateZIO { isStarted =>
              for {
                _ <- ZIO.when(isStarted)(ZIO.fail(new Exception("Bot already started")))
                _ <- ZIO.when(!isStarted)(ZIO.logInfo(s"Bot not started yet, starting it .... webhookUrl=${webhookUrl}"))
                response <- request(SetWebhook(url = webhookUrl, certificate = certificate, allowedUpdates = allowedUpdates)).flatMap {
                  case true =>
                    ZIO.logInfo("SetWebhook success.") *>
                      ZIO.succeed(true)
                  case false =>
                    ZIO.logError("Failed to set webhook")
                    throw new RuntimeException("Failed to set webhook")
                }
              } yield response
            }
          startedAfter <- started.get
          _ <- ZIO.logInfo(s"started = [$startedAfter] AFTER updateZIO")
      } yield startedAfter
    }.forkDaemon

That way the both logInfo are also schedule on the same fiber and will print in the correct order. Also, as you can see, nesting for comprehension is a red flag and means that it should probably be extracted into its own function ;)

startBot.forkDaemon

for instance.

Hope it helps

ex0ns avatar Aug 18 '22 16:08 ex0ns

Thank you too much for problem solving and teaching me. It was really helpful.

AlexGruPerm avatar Aug 19 '22 05:08 AlexGruPerm