slackapi / bolt-python

A framework to build Slack apps using Python
https://slack.dev/bolt-python/
MIT License
1.04k stars 238 forks source link

Failed to Connect in Async Socket Mode #1112

Closed chatGPTxx closed 1 month ago

chatGPTxx commented 1 month ago

Hi, I'm new to Slack bolt, I have successfully connected to slack api with normal SocketMode, but when I try with Async mode it keeps failing. here is my working normal conneciton:

from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler

app = App(token="my_bot_token")

if __name__ == "__main__":
  handler = SocketModeHandler(app, "my_app_token")
  handler.start()

My below Async Socket is failing:

import asyncio

from slack_bolt.app.async_app import AsyncApp
from slack_bolt.adapter.socket_mode.aiohttp import AsyncSocketModeHandler

app = AsyncApp(token="my_bot_token")

async def main():
  handler = AsyncSocketModeHandler(app, "my_app_token")
  await handler.start_async()

if __name__ == "__main__":
  asyncio.run(main())

I got error: aiohttp\connector.py, line 1111, in _start_tls_connect raise client_error(req.connection_key, exc) from exc aiohttp.client_exceptions.ClientConnectionError: Cannot connect to host wss-primary.slack.com:443 ssl:default [None] Failed to connect (error: Cannot connect to host wss-primary.slack.com:443 ssl:default [None]); Retrying...

Could you please advise what's the possible root cause?

Reproducible in:

python 3.11

The slack_bolt version

slack-bolt 1.19.1

Python runtime version

python 3.11

OS info

Windows 11

Steps to reproduce:

see above

Expected result:

both normal and async should work

Actual result:

async failing

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

WilliamBergamin commented 1 month ago

Hi @chatGPTxx thanks for writing in 💯

You can refer to our async socket mode example for more details on implementation

You seem to be using the handler.start() method when you should be awaiting for the handler.start_async() method

Here is an example of a main function

async def main():
    handler = AsyncSocketModeHandler(app, os.environ["SLACK_APP_TOKEN"])
    await handler.start_async()
chatGPTxx commented 1 month ago

Hi @WilliamBergamin thanks for your reply! sorry for the typos (I've updated the codes), and I did follow the async mode example I tried both handler.start_async() and handler.connect_async() all failed with Cannot connect to host wss-primary.slack.com:443 ssl:default [None] from stacktrace, most are from aiohttp package that I won't specify here. The error one from slack bolt is: slack_sdk\socket_mode\aiohttp__init__.py, line 374 self.current_session = await self.aiohttp_client_sessions.ws_connect(

Please advise, thanks!

import asyncio

from slack_bolt.app.async_app import AsyncApp
from slack_bolt.adapter.socket_mode.aiohttp import AsyncSocketModeHandler

app = AsyncApp(token="my_bot_token")

async def main():
  handler = AsyncSocketModeHandler(app, "my_app_token")
  await handler.start_async()

if __name__ == "__main__":
  asyncio.run(main())
chatGPTxx commented 1 month ago

Since the sync socket mode works, async socket mode fails. And the code is running in my firm internal env and befind a proxy. Is it possible that the URL wss-primary.slack.com:443 blocking by the firm proxy policy which caused the failures? Are sync socket and async socket connecting to different slack URL?

chatGPTxx commented 1 month ago

Here are additional debug logs: For working SocketModeHandler:


DEBUG:slack_bolt.App:Sending a request - url: https://slack.com/api/auth.test, query_params: {}, body_params: {}, files: {}, json_body: None, headers: {'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': '(redacted)', 'User-Agent': 'Bolt/1.19.1 Python/3.11.0 slackclient/3.31.0 Windows/10'}
DEBUG:slack_bolt.App:Received the following response - status: 200, headers: {'date': 'Mon, 22 Jul 2024 02:35:52 GMT', 'server': 'Apache', 'vary': 'Accept-Encoding', 'x-slack-req-id': '6ac90a5c52e676ac633a54dc0acd4ef0', 'x-content-type-options': 'nosniff', 'x-xss-protection': '0', 'pragma': 'no-cache', 'cache-control': 'private, no-cache, no-store, must-revalidate', 'expires': 'Sat, 26 Jul 1997 05:00:00 GMT', 'content-type': 'application/json; charset=utf-8', 'x-oauth-scopes': 'app_mentions:read,im:history,channels:history,chat:write', 'access-control-expose-headers': 'x-slack-req-id, retry-after', 'access-control-allow-headers': 'slack-route, x-slack-version-ts, x-b3-traceid, x-b3-spanid, x-b3-parentspanid, x-b3-sampled, x-b3-flags', 'strict-transport-security': 'max-age=31536000; includeSubDomains; preload', 'referrer-policy': 'no-referrer', 'x-slack-unique-id': 'Zp3FiP5ALYTKRzu0OgaQVQAAABI', 'x-slack-backend': 'r', 'access-control-allow-origin': '*', 'content-length': '221', 'via': '1.1 slack-prod.tinyspeck.com, envoy-www-iad-gydgzwqe, envoy-edge-iad-fvrzdyrq', 'x-envoy-attempt-count': '1', 'x-envoy-upstream-service-time': '35', 'x-backend': 'main_normal main_canary_with_overflow main_control_with_overflow', 'x-server': 'slack-www-hhvm-main-iad-oyke', 'x-slack-shared-secret-outcome': 'no-match', 'x-edge-backend': 'envoy-www', 'x-slack-edge-shared-secret-outcome': 'no-match', 'Connection': 'close'}, body: {"ok":true,"url":"https://dummy.slack.com/","team":"dummy","user":"dummy_bot","team_id":"dummy","user_id":"dummy","bot_id":"dummy","enterprise_id":"dummy","is_enterprise_install":false}
DEBUG:slack_bolt.App:Sending a request - url: https://slack.com/api/apps.connections.open, query_params: {}, body_params: {}, files: {}, json_body: None, headers: {'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': '(redacted)', 'User-Agent': 'Bolt/1.19.1 Python/3.11.0 slackclient/3.31.0 Windows/10'}
DEBUG:slack_bolt.App:Received the following response - status: 200, headers: {'date': 'Mon, 22 Jul 2024 02:35:54 GMT', 'server': 'Apache', 'vary': 'Accept-Encoding', 'x-slack-req-id': '514b2a7ab338da611507f9bfaa306e7b', 'x-content-type-options': 'nosniff', 'x-xss-protection': '0', 'x-robots-tag': 'noindex,nofollow', 'pragma': 'no-cache', 'cache-control': 'private, no-cache, no-store, must-revalidate', 'expires': 'Sat, 26 Jul 1997 05:00:00 GMT', 'content-type': 'application/json; charset=utf-8', 'x-accepted-oauth-scopes': 'connections:write', 'x-oauth-scopes': 'connections:write,authorizations:read,app_configurations:write', 'access-control-expose-headers': 'x-slack-req-id, retry-after', 'access-control-allow-headers': 'slack-route, x-slack-version-ts, x-b3-traceid, x-b3-spanid, x-b3-parentspanid, x-b3-sampled, x-b3-flags', 'strict-transport-security': 'max-age=31536000; includeSubDomains; preload', 'referrer-policy': 'no-referrer', 'x-slack-unique-id': 'Zp3FikihHaUW9aUDqB4kaQAAAAg', 'x-slack-backend': 'r', 'access-control-allow-origin': '*', 'content-length': '173', 'via': '1.1 slack-prod.tinyspeck.com, envoy-www-iad-fujozqmu, envoy-edge-iad-tuwwloha', 'x-envoy-attempt-count': '1', 'x-envoy-upstream-service-time': '803', 'x-backend': 'main_normal main_canary_with_overflow main_control_with_overflow', 'x-server': 'slack-www-hhvm-main-iad-xeni', 'x-slack-shared-secret-outcome': 'no-match', 'x-edge-backend': 'envoy-www', 'x-slack-edge-shared-secret-outcome': 'no-match', 'Connection': 'close'}, body: {"ok":true,"url":"wss:\/\/wss-primary.slack.com\/link\/?ticket=dummy&app_id=dummy"}
INFO:slack_bolt.App:A new session has been established (session id: d6cc8813-b96c-4115-ba74-43741b645bc3)
INFO:slack_bolt.App:Bolt app is running!

For issue AsyncSocketModeHandler:


DEBUG:slack_sdk.web.async_base_client:Sending a request - url: POST https://slack.com/api/apps.connections.open, params: {}, files: {}, data: {}, json: {}, proxy: {}, headers: {'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': '(redacted)', 'User-Agent': 'Python/3.11.0 slackclient/3.31.0 Windows/10'}
DEBUG:slack_sdk.web.async_base_client:Received the following response - status: 200, headers: {'Date': 'Mon, 22 Jul 2024 02:02:57 GMT', 'Server': 'Apache', 'Vary': 'Accept-Encoding', 'x-slack-req-id': 'f7689dfb86e769681bbad13b170e50aa', 'x-content-type-options': 'nosniff', 'x-xss-protection': '0', 'x-robots-tag': 'noindex,nofollow', 'Pragma': 'no-cache', 'Cache-Control': 'private, no-cache, no-store, must-revalidate', 'Expires': 'Sat, 26 Jul 1997 05:00:00 GMT', 'Content-Type': 'application/json; charset=utf-8', 'x-accepted-oauth-scopes': 'connections:write', 'x-oauth-scopes': 'connections:write,authorizations:read,app_configurations:write', 'Access-Control-Expose-Headers': 'x-slack-req-id, retry-after', 'Access-Control-Allow-Headers': 'slack-route, x-slack-version-ts, x-b3-traceid, x-b3-spanid, x-b3-parentspanid, x-b3-sampled, x-b3-flags', 'strict-transport-security': 'max-age=31536000; includeSubDomains; preload', 'referrer-policy': 'no-referrer', 'x-slack-unique-id': 'Zp290e42muobxpYXV6K7EgAAEBo', 'x-slack-backend': 'r', 'Access-Control-Allow-Origin': '*', 'Via': '1.1 slack-prod.tinyspeck.com, envoy-www-iad-ouiwmlbn, envoy-edge-iad-ujwxxrmg', 'Content-Encoding': 'gzip', 'Content-Length': '165', 'x-envoy-attempt-count': '1', 'x-envoy-upstream-service-time': '21', 'x-backend': 'main_normal main_canary_with_overflow main_control_with_overflow', 'x-server': 'slack-www-hhvm-main-iad-ficv', 'x-slack-shared-secret-outcome': 'no-match', 'x-edge-backend': 'envoy-www', 'x-slack-edge-shared-secret-outcome': 'no-match'}, body: {'ok': True, 'url': 'wss://wss-primary.slack.com/link/?ticket=dummy&app_id=dummy'}
ERROR:slack_bolt.AsyncApp:Failed to connect (error: Cannot connect to host wss-primary.slack.com:443 ssl:default [The semaphore timeout period has expired]); Retrying...

According to Slack support team, the Slack server log indicates "No websocket connection for (app_id) via http/1.1"

WilliamBergamin commented 1 month ago

Thank you for the additional details

In the debug logs I see the following, wss://wss-primary.slack.com/link/?ticket=dummy&app_id=dummy are the dummy values redacted?

I used the following app.py to test the behavior

import logging

logging.basicConfig(level=logging.DEBUG)

import os

from slack_bolt.app.async_app import AsyncApp
from slack_bolt.adapter.socket_mode.async_handler import AsyncSocketModeHandler

# Install the Slack app and get xoxb- token in advance
app = AsyncApp(token=os.environ["SLACK_BOT_TOKEN"])

@app.command("/hello-socket-mode")
async def hello_command(ack, body):
    user_id = body["user_id"]
    await ack(f"Hi <@{user_id}>!")

# export SLACK_APP_TOKEN=xapp-***
# export SLACK_BOT_TOKEN=xoxb-***

async def main():
    handler = AsyncSocketModeHandler(app, os.environ["SLACK_APP_TOKEN"])
    await handler.start_async()

if __name__ == "__main__":
    import asyncio

    asyncio.run(main())

The app worked as expect, I was not able to reproduce your behavior, this may be related to a proxy/firewall issue 🤔

WilliamBergamin commented 1 month ago

In your working SocketModeHandler project are you passing any proxy_headers?

This seems to be a valid field for the synchronous implementation but not valid for the async implementation

chatGPTxx commented 1 month ago

Thanks @WilliamBergamin! Those dummy values in wss://wss-primary.slack.com/link/?ticket=dummy&app_id=dummy were masked by me manually as I don't know if they're sensitive information. For SocketModeHandler I'm using environment variables to set proxies not via passing proxy_header. And I do the same for AsyncSocketModeHandler. I'm expecting to use AsyncSocketModeHandler since I'm using FastAPI framework. I did find a workaround by using separated event loop for SocketModeHandler, but not sure if it impacts my app performance. Will need to test out

WilliamBergamin commented 1 month ago

🤔 Are you using the async FastAPI adapter for bolt (Like this example) and trying to use the async socketmode adapter for development?

Using FastAPI framework with an asgi webserver like uvicorn in combination with an AsyncSocketModeHandler may lead to complications, AsyncSocketModeHandler is designed to be its own standalone webserver

seratch commented 1 month ago

The aiohttp implementation also sets proxy url string from env variables:

But the auto resolution might not work for your case due to some specific reason. Can you try manually setting the proxy string when initializing AsyncSocketModeHandler? You can use os.environ in your code.

chatGPTxx commented 1 month ago

Sorry for the late reply, I ended up using SocketModeHandler instead. And then wrapped with asyncio.to_thread it looks good at least not blocking other requests. For the AsyncSocketModeHandler I think it's mainly due to proxy setting issue with aiohttp lib, but I tried many different ways of configuring proxy including using environment varible, setting proxy string parameter and etc. Unfortunately non of them work.

seratch commented 1 month ago

Thanks for letting us know how it went on your side! Let us close this issue now.