Goのhttp clientを試す

Goのhttp clientを試してみたメモです。


Goでhttp clientはどう書くのか調べてみると、
標準のnet/httpパッケージが十分高機能で使えるようでした。

http - The Go Programming Language


GET, POST(form), POST(json)をそれぞれ試してみます。

GET

GETを受けるサーバを作成します。
リクエストのメソッド、ヘッダ、パラメータを標準出力するようにしました。
リクエスト受信後に文字列をレスポンスとして返します。

server.go

func handleDog(w http.ResponseWriter, req *http.Request) {
	// header
	method := req.Method
	fmt.Println("[method] " + method)
	for k, v := range req.Header {
		fmt.Print("[header] " + k)
		fmt.Println(": " + strings.Join(v, ","))
	}

	// GET
	if method == "GET" {
		req.ParseForm()
		for k, v := range req.Form {
			fmt.Print("[param] " + k)
			fmt.Println(": " + strings.Join(v, ","))
		}
		fmt.Fprint(w, "Recieved Get request!!")
	}
}

http.HandleFunc()でルーティングを行います。
"/dog"でアクセスされた場合に,handleDog()へハンドリングします。
http.ListenAndServe()を実行するとサーバが起動します。
port 8080で待ち受けます。

func main() {
	http.HandleFunc("/dog", handleDog)
	http.ListenAndServe(":8080", nil)
}

GETするクライアントを作成します。
http.Get()でURLを指定するだけなので、シンプルです。
レスポンスのステータス、ヘッダ、ボディを標準出力するようにしてみます。

client.go

func get() {
	// Get request
	res, err := http.Get("http://localhost:8080/dog?id=123&order=desc")
	if err != nil {
		log.Fatal(err)
	}

	// header
	fmt.Printf("[status] %d\n", res.StatusCode)
	for k, v := range res.Header {
		fmt.Print("[header] " + k)
		fmt.Println(": " + strings.Join(v, ","))
	}

	// body
	defer res.Body.Close()
	body, error := ioutil.ReadAll(res.Body)
	if error != nil {
		log.Fatal(error)
	}
	fmt.Println("[body] " + string(body))
}

実行してみます。

サーバ側ログ

$ go run server.go
[method] GET
[header] User-Agent: Go-http-client/1.1
[header] Accept-Encoding: gzip
[param] id: 123
[param] order: desc

クライアント側ログ

$ go run client.go
[status] 200
[header] Date: Tue, 24 Apr 2018 00:07:14 GMT
[header] Content-Length: 22
[header] Content-Type: text/plain; charset=utf-8
[body] Recieved Get request!!


POST(form)

POSTでformを送信してみます。

同様にPOSTを受けるサーバを作成します。
リクエストのメソッド、ヘッダ、パラメータ、生のform、URL decodeしたformを標準出力するようにしました。
リクエスト受信後に文字列をレスポンスとして返します。

server.go

func handleDog(w http.ResponseWriter, req *http.Request) {
	// header
	method := req.Method
	fmt.Println("[method] " + method)
	for k, v := range req.Header {
		fmt.Print("[header] " + k)
		fmt.Println(": " + strings.Join(v, ","))
	}

	// POST (form)
	if method == "POST" {
		defer req.Body.Close()
		body, err := ioutil.ReadAll(req.Body)
		if err != nil {
			log.Fatal(err)
		}

		fmt.Println("[request body row] " + string(body))
		decoded, error := url.QueryUnescape(string(body))
		if error != nil {
			log.Fatal(error)
		}
		fmt.Println("[request body decoded] ", decoded)
		fmt.Fprint(w, "Recieved Post(form) request!!")
	}
}

POSTするクライアントを作成します。
formの内容はhttp.PostForm()の引数に渡すだけです。
レスポンスのステータス、ヘッダ、ボディを標準出力するようにしてみます。

client.go

func postAsForm() {
	// form values
	values := url.Values{}
	values.Add("id", "123")
	values.Add("name", "ポメラニアン")
	values.Encode()

	res, err := http.PostForm("http://localhost:8080/dog", values)
	if err != nil {
		log.Fatal(err)
	}

	// header
	fmt.Printf("[status] %d\n", res.StatusCode)
	for k, v := range res.Header {
		fmt.Print("[header] " + k)
		fmt.Println(": " + strings.Join(v, ","))
	}

	// body
	defer res.Body.Close()
	body, error := ioutil.ReadAll(res.Body)
	if error != nil {
		log.Fatal(error)
	}
	fmt.Println("[body] " + string(body))
}

実行してみます。

サーバ側ログ

$ go run server.go
[method] POST
[header] User-Agent: Go-http-client/1.1
[header] Content-Length: 66
[header] Content-Type: application/x-www-form-urlencoded
[header] Accept-Encoding: gzip
[request body row] id=123&name=%E3%83%9D%E3%83%A1%E3%83%A9%E3%83%8B%E3%82%A2%E3%83%B3
[request body decoded]  id=123&name=ポメラニアン

クライアント側ログ

$ go run client.go
[status] 200
[header] Date: Tue, 24 Apr 2018 00:07:14 GMT
[header] Content-Length: 29
[header] Content-Type: text/plain; charset=utf-8
[body] Recieved Post(form) request!!


POST(json)

POSTでjsonを送信してみます。

下記のDogの構造体を作成し、jsonシリアライズしてPOST送信してみます。
tagで`json:"xxx"`と記述すると、jsonパッケージでのシリアライズ・デシリアライズjsonのキーとして使用されます。

type Dog struct {
	Id   int    `json:"id"`
	Name string `json:"name"`
}

POSTを受けるサーバを作成します。
リクエストのメソッド、ヘッダ、パラメータ、生のbody、デシリアライズしたbodyを出力するようにしました。
リクエスト受信後に文字列をレスポンスとして返します。

server.go

func handleDogJson(w http.ResponseWriter, req *http.Request) {
	// header
	method := req.Method
	fmt.Println("[method] " + method)
	for k, v := range req.Header {
		fmt.Print("[header] " + k)
		fmt.Println(": " + strings.Join(v, ","))
	}

	// POST (json)
	if method == "POST" {
		defer req.Body.Close()
		body, err := ioutil.ReadAll(req.Body)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println("[request body row] " + string(body))

		// Unmarshal
		var dog Dog
		error := json.Unmarshal(body, &dog)
		if error != nil {
			log.Fatal(error)
		}
		fmt.Printf("[request body decoded] %+v\n", dog)
		fmt.Fprint(w, "Recieved Post(json) request!!")
	}
}

POSTするクライアントを作成します。
Dogをjsonシリアライズし、http.Post()の第2引数でContentTypeとして"application/json"を指定します。
レスポンスを標準出力するようにしてみます。

client.go

func postAsJson() {
	// json values
	values, err := json.Marshal(Dog{Id: 2, Name: "柴犬"})

	res, err := http.Post("http://localhost:8080/dog_json", "application/json", bytes.NewBuffer(values))
	if err != nil {
		log.Fatal(err)
	}

	// header
	fmt.Printf("[status] %d\n", res.StatusCode)
	for k, v := range res.Header {
		fmt.Print("[header] " + k)
		fmt.Println(": " + strings.Join(v, ","))
	}

	// body
	defer res.Body.Close()
	body, error := ioutil.ReadAll(res.Body)
	if error != nil {
		log.Fatal(error)
	}
	fmt.Println("[body] " + string(body))
}

実行してみます。

サーバ側ログ

$ go run server.go
[method] POST
[header] User-Agent: Go-http-client/1.1
[header] Content-Length: 24
[header] Content-Type: application/json
[header] Accept-Encoding: gzip
[request body row] {"id":2,"name":"柴犬"}
[request body decoded] {Id:2 Name:柴犬}

クライアント側ログ

$ go run client.go
[status] 200
[header] Date: Tue, 24 Apr 2018 00:07:14 GMT
[header] Content-Length: 29
[header] Content-Type: text/plain; charset=utf-8
[body] Recieved Post(json) request!!


コードは下記にあげました。
github.com

終わり。