Go言語学習の3日目です。
今日はWebサービスのレスポンスタイムを計測するCLIツールを作成しました。
作った機能
go run main.go と叩くと、レスポンスのステータスやレスポンスが返ってくるまでの時間を表示する機能を作りました。また、結果をjsonファイルに保存できるようにしました。
os.Argsによるコマンドライン引数の取得
前回Todoアプリを作成したときは、コマンドライン引数はflagパッケージで取得しました。今回は、os.Argsで取得しました。
想定では、go run main.go と叩いたとき、os.Args[0]でが取得できると考えていましたが、どうではありませんでした。
fmt.Printf("%v\n", os.Args)
// go run main.go a b c
// 実行結果
// [C:\Users\R247E~1.SAD\AppData\Local\Temp\go-build1367157334\b001\exe\main.exe a b c]
go run main.goを実行した時、main.goを一時的なフォルダにビルドして、.exeを一時ファイルに出力、その.exeを即座に実行して、終わったら削除という流れになります。os.Args[0]はGoが一時的に作った.exeのパスになる(つまり、実行されたバイナリのパス)
作成したファイルと次回の課題
saveResultメソッドの条件分岐の書き方が適当になってしまった。あと、エラーメッセージをfmt.Errorfでラップしたい。また、ログファイルとしてログを出力させたい。
また、goroutineを使って、複数URLを並行チェックできるようにしたい。
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"time"
)
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: go run main.go <URL>")
return
}
url := os.Args[1]
start := time.Now()
resp, err := http.Get(url)
elapsed := time.Since(start)
if err != nil {
fmt.Printf("[×]%s - Error: %s\n", url, err)
return
}
defer resp.Body.Close()
result := Result{
Url: url,
ResponseCode: resp.StatusCode,
StatusText: http.StatusText(resp.StatusCode),
ResponseTime: elapsed.Milliseconds(),
}
if err := saveResult(result); err != nil {
log.Printf("Failed to save result: %v\n", err)
}
fmt.Printf("[✓]%s -%d %s -%dms\n",
url,
resp.StatusCode,
http.StatusText(resp.StatusCode),
elapsed.Milliseconds(),
)
}
type Result struct {
Url string
ResponseCode int
StatusText string
ResponseTime int64
}
func saveResult(NewResult Result) error {
var results []Result
file, err := os.Open("results.json")
if err != nil && os.IsNotExist(err) {
os.Create("results.json")
} else if err != nil {
return fmt.Errorf("read error: %w", err)
} else {
defer file.Close()
byteValue, err := io.ReadAll(file)
if err != nil {
return err
}
err = json.Unmarshal(byteValue, &results)
if err != nil {
return err
}
}
results = append(results, NewResult)
file, err = os.Create("results.json")
if err != nil {
return err
}
encoder := json.NewEncoder(file)
return encoder.Encode(results)
}