golang で HTTP Server を停止する方法

個人的に嵌ったので、覚え書きです。
間違っていたら教えて下さい…。

Go を始めてから2週間ほど経ちました。
Go を書いていると心が満たされます。

# 問題
## http.Server
さて、 Go 言語で最もシンプルな HTTP Server の実装は以下の通りだと思います。

```go:simple_server.go
package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello, world.")
	})
	http.ListenAndServe(":8080", nil)
}
```

しかし、これでは何かと不便です。

* サーバを停止する手段が提供されていません。
* `http.ListenAndServe` が処理をブロックします。

## テスト
まずは、テストを書いてみましょう。

```go:simple_server_test.go
package main

import (
	"io/ioutil"
	"net/http"
	"testing"
)

func TestServer(t *testing.T) {
	go main()

	resp, err := http.Get("http://localhost:8080")
	if err != nil {
		t.Fatal(err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		t.Fatal("status:", resp.StatusCode)
	}

	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Fatal(err)
	}

	if string(respBody) != "Hello, world.\n" {
		t.Fatal("body:", string(respBody))
	}
}
```

こうなりますね。

## テストケースが複数の場合
例えば、ここでパスが増えて、テストケース (Test*** 関数) を増やしたくなった場合を考えます。
次のように `go main()` を再度書きますか？

```go:simple_server_test.go
package main

import (
	"io/ioutil"
	"net/http"
	"testing"
)

func TestServer(t *testing.T) {
	go main()

	resp, err := http.Get("http://localhost:8080")
	if err != nil {
		t.Fatal(err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		t.Fatal("status:", resp.StatusCode)
	}

	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Fatal(err)
	}

	if string(respBody) != "Hello, world.\n" {
		t.Fatal("body:", string(respBody))
	}
}

func TestServer2(t *testing.T) {
	go main()

	/* 略 */
}
```

すると、以下のエラーを吐いて死にます。

```
panic: http: multiple registrations for /
```

`http.HandleFunc` に同じパスを渡したので panic を起こしました。

## http.ServeMux
それでは、 `http.ServeMux` を使って `simple_server.go` を書き直します。

```go:simple_server.go
package main

import (
	"fmt"
	"net/http"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello, world.")
	})
	http.ListenAndServe(":8080", mux)
}
```

これでテストは通るので、一見上手く行ったかのように思えますが、サーバのコードに例外処理を追加しましょう。

```go:simple_server.go
package main

import (
	"fmt"
	"net/http"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello, world.")
	})
	err := http.ListenAndServe(":8080", mux)
	if err != nil {
		panic(err)
	}
}
```

もう一度テストを走らせると、以下のエラーが吐かれます。


```
panic: listen tcp :8080: bind: address already in use
```

`TestServer` 関数を抜けても Goroutine (ゴルーチン) は止まらないので、同じポートを再度 listen しようとする為です。
そこで、テストケース毎にサーバを起動・停止する必要があります。

# 解答
## サーバ
```go:simple_server.go
package main

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"syscall"
)

func routes() (mux *http.ServeMux) {
	mux = http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello, world.")
	})

	return
}

func server(addr string) (listener net.Listener, ch chan error) {
	ch = make(chan error)

	listener, err := net.Listen("tcp", addr)
	if err != nil {
		panic(err)
	}

	go func() {
		mux := routes()
		ch <- http.Serve(listener, mux)
	}()

	return
}

func main() {
	listener, ch := server(":8080")
	fmt.Println("Server started at", listener.Addr())

	// シグナルハンドリング (Ctrl + C)
	sig := make(chan os.Signal)
	signal.Notify(sig, syscall.SIGINT)
	go func() {
		log.Println(<-sig)
		listener.Close()
	}()

	log.Println(<-ch)
}
```

## テスト
追記: `httptest.Server` を使って書き直しました。

```go:simple_server_test.go
package main

import (
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestServer(t *testing.T) {
	server := httptest.NewServer(routes())
	defer server.Close()

	resp, err := http.Get(server.URL)
	if err != nil {
		t.Fatal(err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		t.Fatal("status:", resp.StatusCode)
	}

	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Fatal(err)
	}

	if string(respBody) != "Hello, world.\n" {
		t.Fatal("body:", string(respBody))
	}
}

func TestServer2(t *testing.T) {
	server := httptest.NewServer(routes())
	defer server.Close()

	/* 略 */
}
```

これで、無事に HTTP Server を停止させることが出来ました。
channel (チャネル) 経由でエラーを受け取ることも出来ます。

# 参考
* [HTTP Serverの止め方 - すずけんメモ](http://suzuken.hatenablog.jp/entry/2015/04/01/111520)
* [golangでのシグナルハンドリング - 年中アイス](http://reiki4040.hatenablog.com/entry/2014/10/03/002239)
* [go test で出来ること - Qiita](http://qiita.com/taizo/items/82930518430f940721a0)