HTTPサーバー

HTTPサーバー #

注意事項 #

The Go Playground では HTTP サーバーが起動できない。

他の章に合わせて The Go Playground での実行リンクを記載してはいるが、 動作確認したい場合は Go が実行可能な環境にファイルをコピーして go run などで実行すること。

HTTP サーバーを立ち上げる #

HTTP サーバーは net/http パッケージの func ListenAndServe(addr string, handler Handler) error を使って立ち上げる。

addr はサーバーのアドレスやポートを指定する。

handler はリクエストを受けてレスポンスを返すサーバー本体の処理を実装した値を指定する。

handler が nil の場合、http パッケージ組み込みのデフォルトのハンドラーが利用される。

このデフォルトのハンドラーはルーティングを担当するので、ユーザーはパスごとに起動するハンドラを定義し指定する。

package main

import (
	"io"
	"log"
	"net/http"
)

func main() {
	// Hello, world! とレスポンスを返すだけの HTTP ハンドラを定義する
	h := func(w http.ResponseWriter, req *http.Request) {
		io.WriteString(w, "Hello, world!\n")
	}
	
	// "/hello" へリクエストがあった場合に h を実行するようにデフォルトのルーターに設定する
	http.HandleFunc("/hello", h)
	
	// ポート 8080 でHTTP サーバーを起動する
	log.Fatal(http.ListenAndServe(":8080", nil))
}

play_circleRun open_in_newRun In The Playground

参考ドキュメント: net/http

HTTP サーバーのページごとの処理を登録する #

HTTP サーバーでは個々のページごとの処理を行うものを http.Handler インターフェイスとして定義している。

http.Handler はリクエストを受けてレスポンスを返す関数 ServeHTTP(w http.ResponseWriter, r *http.Request) を持つ。

HTTP サーバーへ http.Handler を登録するには http.Handle 関数を使用する。

package main

import (
	"fmt"
	"log"
	"net/http"
)

type helloHandler struct{}

func (h *helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "hello world\n")
}

func main() {
	// "/hello" へリクエストがあった場合に helloHandler で処理を行うように登録する
	http.Handle("/hello", new(helloHandler))

	// HTTP サーバーの起動
	log.Fatal(http.ListenAndServe(":8080", nil))
}

play_circleRun open_in_newRun In The Playground

参考ドキュメント: net/http

HTTP サーバーのページごとの処理をする関数を登録する #

HTTP サーバーで個々のページごとの処理をする関数を登録するには http.HandleFunc を使う。

http.Handlehttp.Handler を実装する型を用意する必要があるが、http.HandleFuncfunc(http.ResponseWriter, *http.Request) という型の関数を用意するだけでいいので手軽である。

package main

import (
	"fmt"
	"log"
	"net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "hello world\n")
}

func main() {
	// "/hello" へリクエストがあった場合に helloHandler で処理を行うように登録する
	http.HandleFunc("/hello", helloHandler)

	// HTTP サーバーの起動
	log.Fatal(http.ListenAndServe(":8080", nil))
}

play_circleRun open_in_newRun In The Playground

参考ドキュメント: net/http

クエリストリングとフォームから値を取得する #

http.RequestFormValue メソッドを使うと、クエリストリングや POST, Put のフォームから値を取得できる。

同じフィールド名で複数の値を取得したい場合は、FormValue では対応していないので、 データの参照元である http.RequestForm フィールドを直接利用する。

package main

import (
	"fmt"
	"log"
	"net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
	name := r.FormValue("name")
	fmt.Fprintf(w, "hello %s", name)
}

func main() {
	http.HandleFunc("/hello", helloHandler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

/* 

# curl コマンドでリクエストを送信してレスポンスを確認する

## GET リクエストでクエリストリングから値が取得できる
$ curl "http://localhost:8080/hello?name=aaa"
hello aaa

## POST リクエストでフォームのデータから値が取得できる
$ curl -F "name=bbb" "http://localhost:8080/hello"
hello bbb

*/

play_circleRun open_in_newRun In The Playground

参考ドキュメント: net/http

クエリストリングとフォームから複数の値を取得する #

http.RequestForm フィールドには、クエリストリングや POST, Put のフォームデータが入っている。

Form から値を取得したい場合は ParseForm メソッドで初期化をする必要がある。

以下の例はパラメータ n の値を全て足し合わせるハンドラを実装した。

package main

import (
	"fmt"
	"log"
	"net/http"
	"strconv"
)

func addHandler(w http.ResponseWriter, r *http.Request) {
	if err := r.ParseForm(); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
	}

	sum := 0
	for _, v := range r.Form["n"] {
		if vv, err := strconv.Atoi(v); err == nil {
			sum += vv
		}
	}

	fmt.Fprintf(w, "sum is %d", sum)
}

func main() {
	http.HandleFunc("/add", addHandler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

/*

# curl コマンドでリクエストを送信してレスポンスを確認する

$ curl "http://localhost:8080/add?n=10&n=20"
sum is 30

*/

play_circleRun open_in_newRun In The Playground

参考ドキュメント: net/http

HTTP リクエストメソッドごとに異なる処理をする #

GET や POST などの HTTP リクエストメソッドごとに異なる処理をしたい場合は http.RequestMethod フィールドを参照して処理を分岐させる。

package main

import (
	"fmt"
	"log"
	"net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case http.MethodGet:
		fmt.Fprintf(w, "hello GET")
	case http.MethodPost:
		fmt.Fprintf(w, "hello POST")
	case http.MethodPut:
		fmt.Fprintf(w, "hello PUT")
	case http.MethodDelete:
		fmt.Fprintf(w, "hello DELETE")
	default:
		http.Error(w, "invalid method", http.StatusBadRequest)
	}
}

func main() {
	http.HandleFunc("/hello", helloHandler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

play_circleRun open_in_newRun In The Playground

参考ドキュメント: net/http

POST リクエストのボディで JSON を受け取って処理する #

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
)

func jsonHandler(w http.ResponseWriter, r *http.Request) {
	// POST 以外をエラーとする
	if r.Method != http.MethodPost {
		http.Error(w, "invalid method", http.StatusBadRequest)
		return
	}

	// ボディからデータを取得
	data, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// JSON に変換
	var req map[string]interface{}
	err = json.Unmarshal(data, &req)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// JSON を利用してレスポンスする
	fmt.Fprintf(w, "foo:%v, bar:%v", req["foo"], req["bar"])
}

func main() {
	http.HandleFunc("/json", jsonHandler)
	log.Fatal(http.ListenAndServe(":8080", nil))
}

/*

# curl コマンドでリクエストを送信してレスポンスを確認する

$ curl -XPOST -d '{"foo":100, "bar":200}' 'http://localhost:8080/json'
foo:100, bar:200

*/

play_circleRun open_in_newRun In The Playground

参考ドキュメント: net/http , encoding/json