ここまでのしんちょく
前記事:golang版Exping「pexpo」を書きました。
-Hもしくは-Sをつけるとpexpoがhttp pingモードになります。
-Hがhttp ping, -Sがhttps pingで、-H, -S両方使うとping-listにそのどっちも混ざっててもイケます。
https://github.com/nnao45/pexpo
もう追加機能なの?
仕事中にロードバランサの生存確認とかヘルスチェックした時、別にそんなAPIのテストとかするわけでもないし、毎回curlの長いワンライナーを書くのが面倒だな・・・と思った時にpexpoがcurlしてくれりゃいいじゃん、あ、oとかxとか入れてるとこちょうど半角三文字じゃん!と思ったので。
curlなの?
いえ、goです(execでcurlなんか使ったら互換性もスピードもタイムアウト機能も無くなってしまう笑)。fastping()の代わりにイカの関数でGETリクエストを送ってるだけです。
net/httpでの実装の仕様上、リダイレクト先とかまで見ます(curl -L的な)。
レスポンス無しの場合は000になるのはcurlと同じです。
別になんてことない、なんもないGETです。
func curlCheck(url string) string {
var out string
var res string
receiver := make(chan string, EDGE_X)
done := make(chan struct{}, 0)
if *httping && *sslping {
if !strings.Contains(url, "https://") && !strings.Contains(url, "http://") {
url = "https://" + url
}
} else {
if *sslping{
if !strings.Contains(url, "https://") {
url = "https://" + url
}
} else if *httping{
if !strings.Contains(url, "http://") {
url = "http://" + url
}
}
}
time_start := time.Now()
c_timeout := time.Duration(*timeout * time.Second)
go func() {
client := http.Client{
Timeout: c_timeout,
}
resp, err := client.Get(url)
if err != nil {
<-done
defer close(done)
}
out = strconv.Itoa(resp.StatusCode) + " " + url + " " + time.Since(time_start).String()
receiver <- out
defer close(receiver)
defer resp.Body.Close()
}()
timer := time.NewTimer(*timeout)
for {
timer.Reset(*timeout)
select {
case res = <-receiver:
return res
case <-timer.C:
if *sslping {
res = "000 " + url + " ssl...no_response"
} else {
res = "000 " + url + " http...no_response"
}
return res
case <-done:
if *sslping {
res = "000 " + url + " ssl...no_response"
} else {
res = "000 " + url + " http...no_response"
}
return res
}
}
}
↑そういえばこのgolangのインデントってあんまカラーリングしてないみたいに見える笑
実装上、迷った所でありんすが、タイムアウトが二重なんじゃないか?というwhy you askに関してはごもっとも・・・ですが、まぁせっかく-t(timeout)オプションを作って、しかもtime.Duration型というちょうどよく型も合ってるんだからという事でチャンネルのタイムアウトも判定に入れました。
(それにresp, err
のerrにタイムアウト以外のなんか入ってた時とかも、別にまぁ少なくとも4XXコードじゃないだろうし全部no responseってことでハンドリングしても問題ないだろうなぁと思ったので)
あと現状、HTTP 200 OKだけ青くしてます。
他に変えた所は?
すみませんコソコソ変えてはいますが、上記以外だと細かい所の少し大きな所がイカで、それ以外はほぼ変わってないです。通して、PINGの動作に関してはいじっていません。
- 今更バージョン表記をしました(現在最新1.20)。
- グローバル変数を消滅させました。これで怖い人に怒られませんね(機能に関係無い笑)。
- キー割り込みのチャンネルの仕様を若干変えました。後述。
- Ctrl+Sで右上の文字に変化をさせてバグでなく意図的に一時停止させた事が分かるように(分かりやすくて便利ね)。
キー割り込みのチャンネルについて。
Before
func main() {
//なんやかんや
sleep := false
go keyEventLoop(killKey)
go drawLoop(stop, restart)
for {
select {
case wait := <-killKey:
switch wait {
case termbox.KeyEsc, termbox.KeyCtrlC:
return
case termbox.KeyCtrlS:
if sleep == false {
fill(maxX-44, 0, 45, 1, termbox.Cell{Ch: ' '})
drawLineColor(maxX-48, 0, "Stop Now!! Crtl+S: Restart, Esc or Ctrl+C: Exit.", termbox.ColorYellow)
stop <- struct{}{}
sleep = true
} else if sleep == true {
fill(maxX-48, 0, 49, 1, termbox.Cell{Ch: ' '})
drawLine(maxX-44, 0, "Ctrl+S: Stop & Restart, Esc or Ctrl+C: Exit.")
restart <- struct{}{}
sleep = false
}
}
}
}
}
func drawLoop(stop, restart chan struct{}) {
for {
//なんやかんや
select {
case <-stop:
<-restart
/*Default behavior*/
default:
}
//なんやかんや
After
func main() {
//なんやかんや
sleep := false
go keyEventLoop(killKey)
go drawLoop(stop, restart, received)
loop:
<-received
for {
select {
case wait := <-killKey:
switch wait {
case termbox.KeyEsc, termbox.KeyCtrlC:
return
case termbox.KeyCtrlS:
if sleep == false {
fill(maxX-44, 0, 45, 1, termbox.Cell{Ch: ' '})
drawLineColor(maxX-48, 0, "Stop Now!! Crtl+S: Restart, Esc or Ctrl+C: Exit.", termbox.ColorYellow)
stop <- struct{}{}
sleep = true
goto loop
} else if sleep == true {
fill(maxX-48, 0, 49, 1, termbox.Cell{Ch: ' '})
drawLine(maxX-44, 0, "Ctrl+S: Stop & Restart, Esc or Ctrl+C: Exit.")
restart <- struct{}{}
sleep = false
goto loop
}
}
}
}
}
func drawLoop(stop, restart, received chan struct{}) {
for {
//なんやかんや
select {
case <-stop:
received <- struct{}{}
<-restart
received <- struct{}{}
/*Default behavior*/
default:
}
//なんやかんや
最初はただ一方的にmain側から<- stopもしくは<- restartを送るだけでしたが、現在のコードでは<- receivedでdrawLoop側からも「受け取った報告」をmainに投げて、それを受け取らない限りはキー割り込みは拒否するようにしました。
よく考えたらマルチスレッドの設計として最初の実装が安直過ぎた、かも。
Ctrl+Sを連打すると結構挙動の安定性が変わったし(Ctrl+Sを連打するとかツールの性質上ないとは思うけど笑)、誤受信もほぼほぼ0%になったので、まぁ直して良かったかと。
ちなみにclose(received)
とか使わずにわざわざ値を入れる理由ですが、close関数を入れてこういう連打にでも耐えるように設計すると、「チャンネルが閉じてない事を確認」する必要になり(閉じてるチャンネルをclose()すると、それはpanicや!)、個人的に今回のような高速な繰り返しに差し込むケースにはあんまり好きな書き方ではないからです(結構周り見ると安直に書かれてる気がするけど。。。。)。
おわりに。
使ってくれてる人、とても嬉しいです、ありがとう御座います。
結構いいねが集まり、感動しました。
ping-listのsyntaxとか割りと頑張ってたりとか(タブをいくつ入れようがちゃんと1つのスペースとして読み込んだりしますし、-H, -Sオプションを使った時にhttp://とか付いてなかったら付けてくれたり、付いてたらそのままGETしたり)、実はその辺の細かい所も気を遣って作っているので、まぁ使ってやってください。
後golangを使ったNWエンジニアとして、もう少し、パケットをいじったりとか出来るといいなと思っております。
ではあでゅう。
参考記事
How to set timeout for http.Get() requests in Golang?(https://stackoverflow.com/questions/16895294/how-to-set-timeout-for-http-get-requests-in-golang)
curl-to-Go(https://mholt.github.io/curl-to-go/)
golang の channel を使ったテクニックあれこれ(https://mattn.kaoriya.net/software/lang/go/20160706165757.htm)
マルチスレッドについて書かれた投稿(https://github.com/astaxie/build-web-application-with-golang/blob/master/ja/02.7.md)