telegram
telegram copied to clipboard
SSL with ZIO Webhook using asyncHttpClient
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.
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.
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.
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

I will continue my research.
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
}
- 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

I think that request(SetWebhook(url = webhookUrl, certificate = certificate, allowedUpdates = allowedUpdates)).flatMap not work at all or don't use certificate when call setWebHook
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.
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
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
I made repository https://github.com/AlexGruPerm/min_bot4s_zio (commit for issues-242)
- Delete webhook manually with
curl --ssl-no-revoke "https://api.telegram.org/bot886094130:AAENizOGvqrl7vrLVeOZwKfJdIJyQEU_123/deleteWebhook"
-
Run application. It runs but not work with telegram, not set webhook.
-
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).
I think you forgot to add the common repository, could you push that as well, I'll try to make your code work
Sorry, I don't undestand what means "common" reposotory? It seems that I don't have it.
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}
My pardon, I fixed it.
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
How to make certificate and pem,p12 I wrote here: https://github.com/bot4s/telegram/issues/222
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
Thank you too much for problem solving and teaching me. It was really helpful.