技術向上

プログラミングの学び、気になるテクノロジーやビジネストレンドを発信

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()を用いてルーティングするのが、一般的でしょう。