forked from slackapi/bolt-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add WSGI adapter (slackapi#1085)
- Loading branch information
1 parent
dbe2333
commit 549252c
Showing
11 changed files
with
548 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
from slack_bolt import App | ||
from slack_bolt.adapter.wsgi import SlackRequestHandler | ||
|
||
app = App() | ||
|
||
|
||
@app.event("app_mention") | ||
def handle_app_mentions(body, say, logger): | ||
logger.info(body) | ||
say("What's up?") | ||
|
||
|
||
api = SlackRequestHandler(app) | ||
|
||
# pip install -r requirements.txt | ||
# export SLACK_SIGNING_SECRET=*** | ||
# export SLACK_BOT_TOKEN=xoxb-*** | ||
# gunicorn app:api -b 0.0.0.0:3000 --log-level debug | ||
# ngrok http 3000 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from slack_bolt import App | ||
from slack_bolt.adapter.wsgi import SlackRequestHandler | ||
|
||
app = App() | ||
|
||
|
||
@app.event("app_mention") | ||
def handle_app_mentions(body, say, logger): | ||
logger.info(body) | ||
say("What's up?") | ||
|
||
|
||
api = SlackRequestHandler(app) | ||
|
||
# pip install -r requirements.txt | ||
|
||
# # -- OAuth flow -- # | ||
# export SLACK_SIGNING_SECRET=*** | ||
# export SLACK_CLIENT_ID=111.111 | ||
# export SLACK_CLIENT_SECRET=*** | ||
# export SLACK_SCOPES=app_mentions:read,channels:history,im:history,chat:write | ||
|
||
# gunicorn oauth_app:api -b 0.0.0.0:3000 --log-level debug |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
gunicorn<23 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from .handler import SlackRequestHandler | ||
|
||
__all__ = ["SlackRequestHandler"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
from typing import Any, Callable, Dict, Iterable, List, Tuple | ||
|
||
from slack_bolt import App | ||
from slack_bolt.adapter.wsgi.http_request import WsgiHttpRequest | ||
from slack_bolt.adapter.wsgi.http_response import WsgiHttpResponse | ||
from slack_bolt.oauth.oauth_flow import OAuthFlow | ||
from slack_bolt.request import BoltRequest | ||
from slack_bolt.response import BoltResponse | ||
|
||
|
||
class SlackRequestHandler: | ||
def __init__(self, app: App, path: str = "/slack/events"): | ||
"""Setup Bolt as a WSGI web framework, this will make your application compatible with WSGI web servers. | ||
This can be used for production deployments. | ||
With the default settings, `http://localhost:3000/slack/events` | ||
Run Bolt with [gunicorn](https://gunicorn.org/) | ||
# Python | ||
app = App() | ||
api = SlackRequestHandler(app) | ||
# bash | ||
export SLACK_SIGNING_SECRET=*** | ||
export SLACK_BOT_TOKEN=xoxb-*** | ||
gunicorn app:api -b 0.0.0.0:3000 --log-level debug | ||
Args: | ||
app: Your bolt application | ||
path: The path to handle request from Slack (Default: `/slack/events`) | ||
""" | ||
self.path = path | ||
self.app = app | ||
|
||
def dispatch(self, request: WsgiHttpRequest) -> BoltResponse: | ||
return self.app.dispatch( | ||
BoltRequest(body=request.get_body(), query=request.query_string, headers=request.get_headers()) | ||
) | ||
|
||
def handle_installation(self, request: WsgiHttpRequest) -> BoltResponse: | ||
oauth_flow: OAuthFlow = self.app.oauth_flow | ||
return oauth_flow.handle_installation( | ||
BoltRequest(body=request.get_body(), query=request.query_string, headers=request.get_headers()) | ||
) | ||
|
||
def handle_callback(self, request: WsgiHttpRequest) -> BoltResponse: | ||
oauth_flow: OAuthFlow = self.app.oauth_flow | ||
return oauth_flow.handle_callback( | ||
BoltRequest(body=request.get_body(), query=request.query_string, headers=request.get_headers()) | ||
) | ||
|
||
def _get_http_response(self, request: WsgiHttpRequest) -> WsgiHttpResponse: | ||
if request.method == "GET": | ||
if self.app.oauth_flow is not None: | ||
if request.path == self.app.oauth_flow.install_path: | ||
bolt_response: BoltResponse = self.handle_installation(request) | ||
return WsgiHttpResponse( | ||
status=bolt_response.status, headers=bolt_response.headers, body=bolt_response.body | ||
) | ||
if request.path == self.app.oauth_flow.redirect_uri_path: | ||
bolt_response: BoltResponse = self.handle_callback(request) | ||
return WsgiHttpResponse( | ||
status=bolt_response.status, headers=bolt_response.headers, body=bolt_response.body | ||
) | ||
if request.method == "POST" and request.path == self.path: | ||
bolt_response: BoltResponse = self.dispatch(request) | ||
return WsgiHttpResponse(status=bolt_response.status, headers=bolt_response.headers, body=bolt_response.body) | ||
return WsgiHttpResponse(status=404, headers={"content-type": ["text/plain;charset=utf-8"]}, body="Not Found") | ||
|
||
def __call__( | ||
self, | ||
environ: Dict[str, Any], | ||
start_response: Callable[[str, List[Tuple[str, str]]], None], | ||
) -> Iterable[bytes]: | ||
request = WsgiHttpRequest(environ) | ||
if "HTTP" in request.protocol: | ||
response: WsgiHttpResponse = self._get_http_response( | ||
request=request, | ||
) | ||
start_response(response.status, response.get_headers()) | ||
return response.get_body() | ||
raise TypeError(f"Unsupported SERVER_PROTOCOL: {request.protocol}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
from typing import Any, Dict | ||
|
||
from .internals import ENCODING | ||
|
||
|
||
class WsgiHttpRequest: | ||
"""This Class uses the PEP 3333 standard to extract request information | ||
from the WSGI web server running the application | ||
PEP 3333: https://peps.python.org/pep-3333/ | ||
""" | ||
|
||
__slots__ = ("method", "path", "query_string", "protocol", "environ") | ||
|
||
def __init__(self, environ: Dict[str, Any]): | ||
self.method: str = environ.get("REQUEST_METHOD", "GET") | ||
self.path: str = environ.get("PATH_INFO", "") | ||
self.query_string: str = environ.get("QUERY_STRING", "") | ||
self.protocol: str = environ.get("SERVER_PROTOCOL", "") | ||
self.environ = environ | ||
|
||
def get_headers(self) -> Dict[str, str]: | ||
headers = {} | ||
for key, value in self.environ.items(): | ||
if key in {"CONTENT_LENGTH", "CONTENT_TYPE"}: | ||
name = key.lower().replace("_", "-") | ||
headers[name] = value | ||
if key.startswith("HTTP_"): | ||
name = key[len("HTTP_"):].lower().replace("_", "-") # fmt: skip | ||
headers[name] = value | ||
return headers | ||
|
||
def get_body(self) -> str: | ||
if "wsgi.input" not in self.environ: | ||
return "" | ||
content_length = int(self.environ.get("CONTENT_LENGTH", 0)) | ||
return self.environ["wsgi.input"].read(content_length).decode(ENCODING) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
from http import HTTPStatus | ||
from typing import Dict, Iterable, List, Sequence, Tuple | ||
|
||
from .internals import ENCODING | ||
|
||
|
||
class WsgiHttpResponse: | ||
"""This Class uses the PEP 3333 standard to adapt bolt response information | ||
for the WSGI web server running the application | ||
PEP 3333: https://peps.python.org/pep-3333/ | ||
""" | ||
|
||
__slots__ = ("status", "_headers", "_body") | ||
|
||
def __init__(self, status: int, headers: Dict[str, Sequence[str]] = {}, body: str = ""): | ||
_status = HTTPStatus(status) | ||
self.status = f"{_status.value} {_status.phrase}" | ||
self._headers = headers | ||
self._body = bytes(body, ENCODING) | ||
|
||
def get_headers(self) -> List[Tuple[str, str]]: | ||
headers: List[Tuple[str, str]] = [] | ||
for key, value in self._headers.items(): | ||
if key.lower() == "content-length": | ||
continue | ||
headers.append((key, value[0])) | ||
|
||
headers.append(("content-length", str(len(self._body)))) | ||
return headers | ||
|
||
def get_body(self) -> Iterable[bytes]: | ||
return [self._body] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
ENCODING = "utf-8" # The content encoding for Slack requests/responses is always utf-8 |
Empty file.
Oops, something went wrong.