golang版Exping「pexpo」にhttp pingの追加とその実装。

## ここまでのしんちょく
前記事:[golang版Exping「pexpo」を書きました。
](https://qiita.com/A_Resas/items/c904ddebdd7096f1a6bc)

-Hもしくは-Sをつけるとpexpoがhttp pingモードになります。
-Hがhttp ping, -Sがhttps pingで、-H, -S両方使うとping-listにそのどっちも混ざっててもイケます。
https://github.com/nnao45/pexpo
![pexpo_HS_1.20_wins.gif](https://qiita-image-store.s3.amazonaws.com/0/124502/1c326dd1-380f-2d59-8a5f-f8388b5356d8.gif)

## もう追加機能なの？
仕事中にロードバランサの生存確認とかヘルスチェックした時、別にそんなAPIのテストとかするわけでもないし、毎回curlの長いワンライナーを書くのが面倒だな・・・と思った時にpexpoがcurlしてくれりゃいいじゃん、あ、oとかxとか入れてるとこちょうど半角三文字じゃん！と思ったので。

## curlなの？
いえ、goです（execでcurlなんか使ったら互換性もスピードもタイムアウト機能も無くなってしまう笑）。fastping()の代わりにイカの関数でGETリクエストを送ってるだけです。
net/httpでの実装の仕様上、リダイレクト先とかまで見ます（curl -L的な）。
レスポンス無しの場合は000になるのはcurlと同じです。
別になんてことない、なんもないGETです。

```go:
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
```go:
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
				}
			}
		}
	}
}
```

```go
func drawLoop(stop, restart chan struct{}) {
	for {
		//なんやかんや
		select {
			case <-stop:
				<-restart

			/*Default behavior*/
			default:
			}
		//なんやかんや
```

### After

```go:
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
				}
			}
		}
	}
}
```

```go
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とか割りと頑張ってたりとか（タブをいくつ入れようがちゃんと１つのスペースとして読み込んだりしますし、-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)