# Webhooks(翻譯) ###### tags: `telegram` `bot` >[name=shaoe.chen][time=Fri, Feb 14, 2020 4:46 PM] :::danger [官方文件](https://github.com/python-telegram-bot/python-telegram-bot/wiki/Webhooks) ::: [TOC] ## Introduction 我們的範例通常使用`Updater.start_polling`來啟動機器人。這方法使用API方法[getUpdates](https://core.telegram.org/bots/api#getupdates)來為你的機器人接收新的更新。這對中小型機器人還有測試用機器人是可以的,但是如果你的接收流量大,那響應的時間可能會慢一點。也許還有其它原因讓你切換到以webhook-based的方法來更新檢索。 第一件事:你應該有一個很好的理由從polling切換到webhook。不要單純的因為很酷而這麼做。 ## Polling vs. Webhook polling與webhook的差異在於: * polling(透過`get_updates`)定期連接到Telegram的伺服器確認是否有新的更新 * webhook是你一次傳輸給Telegram的一個URL。每當你的機器人有新的更新到來的時候,Tenegram發送這個更新到指定的URL。 ## Requirements 通過webhook檢索更新,你需要做不少事情。 ### A public IP address or domain 通常,這意味著你必須在伺服器上執行你的機器人,不管是專用機還是虛擬環境。閱讀[Where to host Telegram Bots](https://github.com/python-telegram-bot/python-telegram-bot/wiki/Where-to-host-Telegram-Bots)尋找選項清單。 確認你可以從公有網路連接到你的伺服器,不管是透過IP還是網域名稱。如果`ping`的到,那就可以繼續下一步。 ### A SSL certificate 所有與Telegram伺服器的通信都必使用SSL用HTTPS加密。使用polling,這就是由Telegram伺服器處理,但如果你想要透過Webhook來接收更新,那你就必須處理它。如果你不需要,Telegram將不會向你發送任何的更新。 有兩種方法可以做到這一點: 1. 受信的憑證頒發機構(CA)所發佈的經過驗證的憑證 2. 自簽名的憑證 如果你還沒有一個經過驗證的憑證,那就使用自簽名的憑證。這麼容易,而且沒有缺點。 #### Creating a self-signed certificate using OpenSSL 使用`openssl`建立一個自簽名的SSL憑證,執行下面命令: ```shell openssl req -newkey rsa:2048 -sha256 -nodes -keyout private.key -x509 -days 3650 -out cert.pem ``` `openssl`公用程式會問你一些細節。確認你輸入正確的完整網域名稱(FQDN)!如果你的伺服器擁有網域,輸入完成的名稱(例如,`sub.example.com`)。如果你的伺服器僅有IP位址,那就輸入IP。如果你輸入一個無效的FQDN(完整網域名稱),你將不會從Telegram收到任何更新,也不會看見任何異常。 ## Choosing a server model 實際上還有第三項要求:一個HTTP伺服器監聽weobook的連線。這一點有很多事情要考慮,取決你的需求。 ### The integrated webhook server 套件`python-telegram-bot`提供了一個基於CPython `BaseHTTPServer.HTTPServer implementation`實現的客製化HTTP server,這個server緊密的整合(集成)在`telegram.ext`模組中,而且可以使用`Updater.start_webhook`啟動。 這個webserver還負責解密HPPTS流量。這可以是設置webbook最簡單的方法。 然而,這個解決方案有一個限制。目前Telegram對webhooks僅支援四個port:443, 80, 88, 8443。因此,在一個網域/IP位置最,最多最多就只能執行四個機器人。 如果這對你而言不是問題,你可以使用下面(或類似)的程式碼以webhooks來啟動你的機器人。`listen`位址應該為`0.0.0.0`,或者如果你不允許這設置,那就設置你的server公開的IP位址。開的port可以是`443`, `80`, `88`, `8443`其中一個。對於`url_path`,建議使用你的機器人的token,這樣就沒有人可以對你的機器人發送假的更新。`key`與`cert`應該包含你早前生成的檔案路徑(Creating a self-signed certificate using OpenSSL)。`webhook_url`應該是你的webhook的實際URL。開頭包含`https://`協議,使用你設置為憑證的FQDN的網域或IP位址以及正確的port與URL路徑。 ```python updater.start_webhook(listen='0.0.0.0', port=8443, url_path='TOKEN', key='private.key', cert='cert.pem', webhook_url='https://example.com:8443/TOKEN') ``` ### Reverse proxy + integrated webhook server 要解決這個問題(僅4個port的問題),你可以使用像是nginx或haproxy的反向代理。 在這模組中,監聽公開的IP的單個伺服器應用程式(反向代理)接受所有的webhook請求,並將它們轉發到本地端執行整合webhook server的正確實例。它還執行SSL的終端負載,這意味著它解密HTTPS連接,因此webhook servers已經解密流量。這些伺服器可以在任一個port上執行,不單單在Telegram允許的那四個ports,因為Telegram單純直接連接到反向代理。 注意:在這個伺服器模型中,你必須自行呼叫`set_webhook` 取決於你使用的反向代理應用程式(或代管服務),實現的方式會有些許的不同。下面列出一些可能的設置清單。 #### Heroku 在Heroku上使用webhook對free-plan也許是有好處的,因為它將自動管理所需要的停機時間。為你設置反向代理並建立一個環境。從這個環境中,你必須提取機器人應該監聽的port。Heroku在代理端管理SSL,因此你不必要自己提供憑證。 ```python import os TOKEN = "TOKEN" PORT = int(os.environ.get('PORT', '8443')) updater = Updater(TOKEN) # add handlers updater.start_webhook(listen="0.0.0.0", port=PORT, url_path=TOKEN) updater.bot.set_webhook("https://<appname>.herokuapp.com/" + TOKEN) updater.idle() ``` #### Using nginx with one domain/port for all bots 這類似於Heroku,只是要自己設置反向代理。所有的機器人設置它們的`webhook_url`到相同的網域與port,不同的`url_path`。這個整合伺服器通常以`localhost`或`127.0.0.1`位址啟動,port可以是任意port。 注意:如果你沒有網域關聯到你的伺服器的話,`example.com`可以以任一個IP位址取代。 啟動機器人的範例程式碼: ```python updater.start_webhook(listen='127.0.0.1', port=5000, url_path='TOKEN1') updater.bot.set_webhook(webhook_url='https://example.com/TOKEN1', certificate=open('cert.pem', 'rb')) ``` `nginx`配置兩個機器人的範例(簡化至重要部份): ```nginx= server { listen 443 ssl; server_name example.com; ssl_certificate cert.pem; ssl_certificate_key private.key; location /TOKEN1 { proxy_pass http://127.0.0.1:5000/TOKEN1/; } location /TOKEN2 { proxy_pass http://127.0.0.1:5001/TOKEN2/; } } ``` #### Using haproxy with one subdomain per bot 在這個方法中,每一個機器人都被分配到各自的子網域(subdomain),如果你的伺服器有網域`example.com`,那你應該有子網域,`bot1.example.com`、`bot2.example.com`等。每個機器人需要一個憑證,並為各自的子網域設置FQDN。這個範例中的反向代理是`haproxy`。這個整合伺服器通常以`localhost`或`127.0.0.1`位址啟動,port可以是任意port。 注意:為此,你的伺服器需要網域。 啟動機器人的範例程式碼: ```python updater.start_webhook(listen='127.0.0.1', port=5000, url_path='TOKEN') updater.bot.set_webhook(webhook_url='https://bot1.example.com/TOKEN', certificate=open('cert_bot1.pem', 'rb')) ``` `haproxy`配置兩個機器人的範例(簡化至重要部份)。同樣的:兩個憑證的FQDN必須與`ssl_fc_sni`的值匹配。還有,`.pem`檔是`private.key`與`cert.pem`兩個檔案串聯在一起: ```nginx= frontend public-https bind 0.0.0.0:443 ssl crt cert_key_bot1.pem crt cert_key_bot2.pem option httpclose use_backend bot1 if { ssl_fc_sni bot1.example.com } use_backend bot2 if { ssl_fc_sni bot2.example.com } backend bot1 mode http option redispatch server bot1.example.com 127.0.0.1:5000 check inter 1000 backend bot2 mode http option redispatch server bot2.example.com 127.0.0.1:5001 check inter 1000 ``` ### Custom solutio 你根本不需要使用整合的webserver。如果你選擇這種方法,你不應該使用類別`Updater`。模組`telegram.ext`設置的時候考慮了這個選項,因此你仍然可以使用類別`Dispatcher`從它提供的訊息篩選/排序中獲利。不過你必須手工做一些工作。 通用的程式碼範本可以在下面找到。 Setup part, called once: ```python from queue import Queue # in python 2 it should be "from Queue" from threading import Thread from telegram import Bot from telegram.ext import Dispatcher def setup(token): # Create bot, update queue and dispatcher instances bot = Bot(token) update_queue = Queue() dispatcher = Dispatcher(bot, update_queue) ##### Register handlers here ##### # Start the thread thread = Thread(target=dispatcher.start, name='dispatcher') thread.start() return update_queue # you might want to return dispatcher as well, # to stop it at server shutdown, or to register more handlers: # return (update_queue, dispatcher) ``` 在webhook上呼叫使用已解密的`Update`物件(使用`Update.de_json(json.loads(text), bot)`)來解密更新: ```python def webhook(update): update_queue.put(update) ``` #### Alternative (no threading) Setup part, called once: ```python from telegram import Bot from telegram.ext import Dispatcher def setup(token): # Create bot, update queue and dispatcher instances bot = Bot(token) dispatcher = Dispatcher(bot, None, workers=0) ##### Register handlers here ##### return dispatcher ``` 在webhook上呼叫使用已解密的`Update`物件(使用`Update.de_json(json.loads(text), bot)`)來解密更新: ```python def webhook(update): dispatcher.process_update(update) ```