golangでhttpを監視するscriptを書いて結果をslackにpostする

## golangで簡単なscriptを書いてみる
ぐぐると、簡単な1ファイルのサンプルスクリプトは出て来るが、もうちょっと拡張性のある、ライブラリ置き場や設定ファイル置き場を分けた様な構成でscriptを書いてみる事にした。

という事で、今回はgolangを使ってhttp監視をしてみる事にする。

## ディレクトリ構成

GOPATH以下の構成。基本的にGOPATH/src以下にプロジェクトを作っていく事が推奨らしい。
src以下にprojectというディレクトリを作りその下に各ディレクトリを配置。

```
src - project - config - secrets.go
              |        |
              |        - targets.go
              |
              - libs - slack - slack.go
              |
              - scripts - check_http.go
```
config : 設定ファイル置き場。secrets.goにはgit管理したくない物を入れて.gitignoreしておくと吉。

libs : slackにpostする周りのライブラリ置き場

scripts : mainの監視用script

## ソースコード
各ファイルの名前と中身、ちょっとしたコメント。

### config/secrets.go
slackのincomingのurlを書いておく。
人には知られるとまずい物なので、その他のファイルとは独立させておく。

```
package config

var (
	IncomingUrl string = "https://hooks.slack.com/services/XXXXXXXXXXXXXXXXXXXXXX"
)
```

### config/targets.go
テスト用に、3つドメインを用意してみた。最初の一つだけ成功して後は失敗するテスト。

```
package config

import (
)

type HttpConfigSlice struct {
	Http []HttpConfig
}

type HttpConfig struct {
	Name string
	Host string
	Path string
	Proto string
	Domain string
}

func HttpTargets() []HttpConfig {
	return []HttpConfig{
		HttpConfig{Name: "Google", Host: "google.com", Path: "/", Proto: "http", Domain: "google.com"},
		HttpConfig{Name: "Google", Host: "google.com", Path: "/no-such-path", Proto: "http", Domain: "google.com"},
		HttpConfig{Name: "NiseGoogle", Host: "nisegoogle.com", Path: "/", Proto: "http", Domain: "nisegoogle.com"},
	}
}
```

### libs/slack/slack.go
引数で貰ったmsgをそのままslackにPOSTするやつ。実はslackのgo用ライブラリがあったっぽいが書いた後に気づいた。
これ。https://github.com/nlopes/slack

書いちゃったので、今回は自分のを使う。

configをimportする時には、GOPATH/src以下からのpathで読める。
pathの前に名前をつけるとエイリアスになって便利。

```
package slack

import (
	"log"
	"net/http"
	"net/url"
	"strings"
	"encoding/json"
	"time"

	config "project/config"
)

type CheckConfig struct {
	Http []HttpConfig `json:"http"`
}

type HttpConfig struct {
	Name   string `json:"name"`
	Host   string `json:"host"`
	Path   string `json:"path"`
	Proto  string `json:"proto"`
	Domain string `json:"domain"`
}

type Slack struct {
	Text     string `json:"text"`
	Username string `json:"username"`
}

func Post(msg string) {
	params, _ := json.Marshal(Slack{
		msg,
		"MonitoringBot",
	})

	client := &http.Client{Timeout: 5 * time.Second}

	values := url.Values{"payload": {string(params)}}
	req, err := http.NewRequest("POST", config.IncomingUrl, strings.NewReader(values.Encode()))
	if err != nil {
		log.Print(err)
		return
	}

	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

	resp, err := client.Do(req)
	if err != nil {
		log.Print(err)
		return
	}
	defer resp.Body.Close()
}
```

###  scripts/check_http.go
監視のおおもとのやつ。単純に、ターゲット持ってきて、httpチェックをするだけ。地味にリトライ入れてみた。

```
package main

import (
	"net/http"
	"crypto/tls"
	"time"
	"fmt"

	slack "project/libs/slack"
	config "project/config"
)

func main() {
	for _, target := range config.HttpTargets() {
		check(target)
	}
}

func check(target config.HttpConfig) {
	errorNum := 0
	checkNum := 0
	fatalNum := 2
	errorMessage := ""
	for checkNum < fatalNum {
		checkNum += 1
		targetPath := target.Proto + "://" + target.Host + target.Path
		tr := &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}
		client := &http.Client{Timeout: 5 * time.Second, Transport: tr}
		req, err := http.NewRequest("GET", targetPath, nil)
		if err != nil {
			slack.Post(err.Error())
			return
		}

		req.Header.Add("Host", target.Domain)
		resp, err := client.Do(req)
		if err != nil {
			slack.Post(err.Error())
			return
		}

		if resp.StatusCode != 200 {
			errorNum += 1
			if (errorNum >= fatalNum) {
				errorMessage += targetPath + " [" + target.Name + "] " + "returns " + fmt.Sprint(resp.StatusCode) + "\n"
			}
		} else {
			break
		}

		defer resp.Body.Close()
	}

	if errorMessage != "" {
		slack.Post(errorMessage)
	}
}
```

## slack post結果
実行してみる。

```
$ go run scripts/check_http.go
```

期待通り、pathがおかしいやつが404、ドメイン変なやつが no such hostになった。
![スクリーンショット 2015-09-02 18.58.38.png](https://qiita-image-store.s3.amazonaws.com/0/62421/9129ad80-d746-8734-0cb2-31a218c5bea9.png "スクリーンショット 2015-09-02 18.58.38.png")

意外に簡単に書ける、go言語。