> 最近有個自動化腳本的任務開發,開發完成只有,發現調用有點麻煩。因為腳本也需要提供給其他運維人員使用,所以腳本只能放到運維平臺服務器上,而其他人員對這個服務器又不是很熟悉,導致使用麻煩。
> 所以直接想到從運維平臺開一個頁面進行調用此腳本,如果單純只是執行,那是很簡單的。如果想實時看到腳本執行輸出,那就需要想點辦法了。
> 從網上查到,基本上有兩種方式:①客戶端 ajax 輪詢 ②服務端 websocket 主動推送
> 本篇為我選擇的 websocket 方案的使用記錄
# 1. WebSocket 服務端
運維平臺使用 django 開發的,所以首先查詢了一下 django 中集成 websocket 方法;查到可以使用 [channels](https://channels.readthedocs.io/en/latest/),調研中又發現另一個 [websocketd](http://websocketd.com/),發現后者更加輕量、簡單,所以直接使用了后者。
## 1.1 安裝啟動
安裝和啟動都特別簡單,直接按照官網的示例。
* 安裝:直接下載二進制文件,它就是一個單文件;下載后放到服務器的 `/usr/bin/` 目錄即可
* 啟動:`$ websocketd --port=8080 my-program`
## 1.2 本地測試
官網示例:https://github.com/joewalnes/websocketd/wiki/Ten-minute-tutorial
下面主要是我自己的示例,與官網比較,添加了參數處理和調用外部腳本
### 1.2.1 本地啟動:
```bash
$ websocketd --devconsole --port=9000 --address=127.0.0.1 --dir=./script
Sun, 18 Oct 2020 14:40:18 +0800 | INFO | server | | Serving from directory : /home/shengan/popop/script
Sun, 18 Oct 2020 14:40:18 +0800 | INFO | server | | Starting WebSocket server : ws://127.0.0.1:9000/
Sun, 18 Oct 2020 14:40:18 +0800 | INFO | server | | Developer console enabled : http://127.0.0.1:9000/
Sun, 18 Oct 2020 14:40:21 +0800 | ACCESS | session | url:'http://127.0.0.1:9000/ws/test.sh?p1=5__1' id:'1603003221372852300' remote:'127.0.0.1' command:'/home/shengan/popop/script/ws/test.sh' origin:'http://127.0.0.1:9000' | CONNECT
Sun, 18 Oct 2020 14:40:31 +0800 | ACCESS | session | url:'http://127.0.0.1:9000/ws/test.sh?p1=5__1' id:'1603003221372852300' remote:'127.0.0.1' command:'/home/shengan/popop/script/ws/test.sh' origin:'http://127.0.0.1:9000' pid:'173' | DISCONNECT
```
### 1.2.2 參數介紹:
* --devconsole:開啟瀏覽器測試,可以不用編寫客戶端代碼,直接提供頁面進行測試
* --address:綁定地址,默認是 all,上線后記得修改或直接去掉這個參數
* --dir:不用指定一個腳本,此參數指定一個目錄,此目錄可以放置多個腳本任務
### 1.2.3 頁面測試示例:
使用瀏覽器登錄 locahost:9000 即可進入開發測試頁面;填寫要執行的腳本和其參數 `ws/test.sh?p1=5__1`
我的測試腳本 `test.sh` 在 `script/ws` 目錄,所以我的填寫的是 `ws/test.sh`;后面 `?p1=5__1` 是參數,就是正常的 url 地址和參數
正常的參數是 `?p1=5&p2=1` 為了方便處理,我將多個參數都合并成一個參數了,使用雙下劃線連接。

執行完成也可以看到服務端日志的輸出,在上面的本地啟動中可以看到
### 1.2.4 腳本內容
```bash
$ cat test.py
#!/bin/bash
export LC_ALL="en_US.UTF-8"
export LC_CTYPE="en_US.UTF-8"
echo $QUERY_STRING
# 分割參數環境變量,獲取參數
param=${QUERY_STRING#*=}
cnt=${param%__*}
ss=${param#*__}
for ((COUNT = 1; COUNT <= $cnt; COUNT++)); do
echo $COUNT
sleep $ss
done
pipenv run python -u script/test.py $cnt $ss
```
```python
$ cat test.py
import time
import sys
cnt = sys.argv[1]
ss = sys.argv[2]
cnt = int(cnt)
ss = float(ss)
def test():
for i in range(cnt):
print(f'python {i}')
# sys.stdout.flush()
time.sleep(ss)
if __name__ == '__main__':
test()
```
重點:
* `$QUERY_STRING`:為 websocketd 提供的環境變量,記錄參數;更多環境變量:https://github.com/joewalnes/websocketd/wiki/Environment-variables
* `python -u xxx.py`:-u 關閉 python 腳本輸出緩沖問題,不關閉的話,在調用 test.py 時并不會實時打印 test.py 的輸出,而是會在 test.py 執行完成之后一次性打印其輸出。參考:https://medium.com/@bramblexu/three-ways-to-close-buffer-for-stdout-stdin-stderr-in-python-8be694bd2737
## 1.3 Nginx 代理
```
server {
listen 80;
server_name 127.0.0.1;
location /ws/ {
proxy_pass http://127.0.0.1:9000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
```
注意后面三行也必不可少,參考:https://github.com/joewalnes/websocketd/wiki/Websocketd-behind-Nginx
啟動 nginx 代理之后,上面的訪問鏈接就可以改成 http://localhost/ws/test.sh?p1=5__1 了,效果一樣。
## 1.4 線上環境使用
websocketd 的啟動非常適合使用 supervisor 來啟動管理,配置文件如下:
```
[program:websocketd]
command=websocketd --port=9000 --dir=./script
directory=/popop/current
user=root
numprocs=1
stdout_logfile=/var/log/popop/websocketd.log
stderr_logfile=/var/log/popop/websocketd.log
autostart=true
autorestart=true
startsecs=10
```
注意點:
* 線上服務如果使用 https,則客戶端連接地址為 `wss://xxx.com/ws/test.sh?p1=5__1`
* 啟動方式的 --address 參數已經去掉了