# 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)
```