http.ListenAndServe【Go】
簡易Web Application作成の流れ
データベースではなくtxtファイルを使う、ごくごく簡易的なWeb Applicationを作成していきます。
流れと概要は下記の通りです。
- .txtファイルを作成
- アクセスするURLは上記ファイルのタイトルを含む
- http.RequestのPathからファイルのタイトルを取得しファイルを読み込む
- 読み込んだファイルの内容をアクセス先のbodyに書き出す
事前にファイルを作成し、
Webのサーバーを起動する(事前に内容を設定)という流れになります。
読み込む.txtファイルの作成
type Page struct { Title string Body []byte } func (p *Page) save() error { // ファイルを作成するメソッド filename := p.Title + ".text" // 拡張子は.txt return ioutil.WriteFile(filename, p.Body, 0600) // Webサーバーを起動したユーザーが読み書きできる権限設定 } func main() { p1 := Page{Title: "test", Body: []byte("This is the test.")} if err := p1.save(); err != nil { log.Fatalln(err) } }
Webサーバーの起動
上記手順を踏んだ後、ファイルが同階層にある前提で、Webサーバーを起動します。
type Page struct { Title string Body []byte } func load(title string) (*Page, error) { filename := title + ".text" body, err := ioutil.ReadFile(filename) if err != nil { return nil, err } return &Page{Title: title, Body: body}, nil } func viewHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[len("/view/"):] // http.Requestからurlのpathを取得し、/view/以降の文字列をtitleに格納 p, _ := load(title) // ファイルを読み込む fmt.Fprintf(w, "<h1>%s</h1><div>%s</div>", p.Title, p.Body) // io.Writerインターフェース、内容、可変長引数を引数にとるファイル書き込み関数 } func main() { http.HandleFunc("/view/", viewHandler) // /view/~にアクセスされたら、viewHandlerを実行 log.Fatalln(http.ListenAndServe(":8080", nil)) // 問題があればエラーを返す。ポートは8080、:の前に何も記述しない場合はローカルホスト。第2引数はnilなのでdefaultのhandlerが実行される }
http.HandleFunc()によるHandlerの設定は、http.ListenAndServeによるWebサーバーの起動よりも前に、完了する必要があります。
また、http.HandleFunc()の第1引数が「/」で終わっていない場合、完全一致でしかルーティングをしないため、注意が必要です。
"/view"と第1引数が指定されている場合に、「/view/something/」にアクセスすると、404NotFoundエラーになります。
http.ListenAndServe()の第2引数にnilを指定すると、defaultのhandlerが実行され、
事前に設定したpattern(http.HandleFunc()による設定)以外と「/」にアクセスした場合に、404エラーを返します。
最初のステップで作成したファイル名「test」を使って、
http://localhosthttp://localhost:8080/view/test
にアクセスすると、viewHandlerに定義した内容が表示されます。
なお、Handelerは下記のように複数登録することが可能です。
簡易的な例示のため、処理内容がほぼ同じですが、本来はそれぞれ必要な処理が入ることかと思います。
... func viewHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[len("/view/"):] p, _ := load(title) fmt.Fprintf(w, "<h1>%s</h1><div>%s</div>", p.Title, p.Body) } func editHandler(w http.ResponseWriter, r *http.Request) { title := r.URL.Path[len("/edit/"):] p, _ := load(title) fmt.Fprintf(w, "<h1>%s</h1><div>%s</div>", p.Title, p.Body) } func main() { http.HandleFunc("/view/", viewHandler) http.HandleFunc("/edit/", editHandler) log.Fatalln(http.ListenAndServe(":8080", nil)) }
http.ListenAndServeの中身
http.ListenAndServeは、tcp接続の基本的な流れであるListen→Acceptを隠蔽しています。
tcpの基本的な流れは、こちらにまとめています。
TCP write【Go】 - 技術向上
先述したように、第2引数のhandlerはnilとすることができます。
ListenAndServe func(addr string, handler Handler) error
nilとせずにhandlerを登録する場合は、Handler型のものを登録するわけですが、
Handler型は次のように定義されています。
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
これはinterface型なので、
ServeHTTP(ResponseWriter, *Request)をメソッドに持つものは
Handler型とみなされる、ということを意味します。
handlerを登録する場合のいささか”原始的な”例を、他の部分を省略してお見せします。
type hotdog int // 型は何でもよい func (m hotdog) ServeHTTP(w http.ResponseWriter, r *http.ReadRequest) { // Handler型となるようメソッドを指定 fmt.Fprintln(w, "Awsome!") // response内容に第2引数の文字列を書き込む } func main() { var d hotdog http.ListenAndServe(":8080", d) // Handlerとみなされるdを第2引数に指定できる。localhost:8080にアクセスすると"Awsome!"が表示される }
これにルーティングを付加するには次のようにします。
type hotdog int func (h hotdog) ServeHTTP(w http.ResponseWriter, req *http.Request) { fmt.Fprint(w, "woo foo!") } type hotcat string func (h hotcat) ServeHTTP(w http.ResponseWriter, req *http.Request) { fmt.Fprint(w, "meao.") } func main() { var d hotdog var c hotcat mux := http.NewServeMux() // 登録済みURLと照合して、Handlerを実行するServeMuxを新たに作成 mux.Handle("/dog/", d) // 「/dog」または「/dog/~」にアクセスした場合、Handler d (hotdog)を実行 mux.Handle("/cat", c) // 「/cat」にアクセスした場合、Handler c (hotcat)を実行 http.ListenAndServe(":8080", mux) }
ServeMuxを新たに作成してhandleパターンを追加し、そのServeMuxをHandlerに指定した上で、サーバーを起動しています。
ListenAndServeの第2引数にnilを指定すると、デフォルト設定がされたServeMux、DefaultServeMuxが実行されます。
これが、ListenAndServeの第2引数にnilを指定できる理由です。
ListenAndServeの第2引数にnilを指定すると次のようにできます。
type hotdog int func (h hotdog) ServeHTTP(w http.ResponseWriter, req *http.Request) { fmt.Fprint(w, "woo foo!") } type hotcat string func (h hotcat) ServeHTTP(w http.ResponseWriter, req *http.Request) { fmt.Fprint(w, "meao.") } func main() { var d hotdog var c hotcat http.Handle("/dog/", d) // 「/dog」または「/dog/~」にアクセスした場合、Handler d (hotdog)を実行 http.Handle("/cat", c) // 「/cat」にアクセスした場合、Handler c (hotcat)を実行 http.ListenAndServe(":8080", nil) }
http packageにはHandle()という関数があるので、それを使用します。
これはDefaultServeMuxにHandleパターンを登録する関数です。
さらに改良します。
type hotdogとtype hotcatは、特に利用用途がありません。
ServeHTTPメソッドを定義することでHandlerになれることを示すためのものでした。
それらを取り除いて、次のように関数にします。
func d(w http.ResponseWriter, req *http.Request) { fmt.Fprint(w, "woo foo!") } func c(w http.ResponseWriter, req *http.Request) { fmt.Fprint(w, "meao.") } func main() { http.HandleFunc("/dog/", d) http.HandleFunc("/cat", c) http.ListenAndServe(":8080", nil) }
ここでHandleFunc()の登場です。
HandleFunc()はHandle()と同じくDefaultServeMuxにHandleパターンを登録する関数です。
違いは第2引数にあります。
HandleFunc()の第2引数にはHandlerではなく関数が指定されています。
ただの関数ではなく、ResponseWriter、*Requestを第1、第2引数にもつ関数です。
Handlerは必要なくなったので削除し、同じ引数を持っていたServeHTTP()メソッドを関数に書き換えています。
このようにHandleFunc()を用いてルーティングするのが、一般的でしょう。