Web アプリとは?
知っていると思いますが、共通認識に違いがないか確認するために書いています。
以下、「Go プログラミング 実践入門」(英題: Go Web Programming) から抜粋です。
- HTTP を介し、HTTP リクエストメッセージの形でクライアントから入力を受け取る
- HTTP リクエストメッセージを処理し、必要な処理を行う
- HTML を生成し、HTTP レスポンスメッセージに入れて返す
Golang での Web アプリ作成に必要な項目
ざっくりと、以下が Golang で Web アプリ開発をする際に実施する項目になります。
- マルチプレクサ (multiplexer) を作成
- 例:
mux := http.NewServeMux()
- 注意:
sync.Mutex
とは全く別だよ!(自分自身、勘違いしてたので。。)
- 例:
- Handler (ハンドラ)、またはハンドラ関数を作成
- ルーティング設定
- ある URL へのリクエストを、どのハンドラ/ハンドラ関数に処理させるのかを登録
- 例:
http.Handle("/", Handle)
またはhttp.HandleFunc("/", ハンドラ関数)
を利用
-
http.ListenAndServe
でウェブサーバー起動
ひとまず、Golang によるウェブアプリ開発の全体像を把握できたと思います。
それでは、本格的にウェブアプリ開発をする前に知っておきたいことをステップ分けで説明していきます。
STEP 1: 言葉の意味を知る
詳しくは、以降のサンプルコードで説明しますので、ここで理解できなくても問題ありません。 「そんなのがあるのね」程度で読み進めてください。
-
Handler (ハンドラ) は、HTTP リクエストに応じて処理をするもの
-
超重要: メソッド
ServeHTTP(ResponseWriter, *Request)
を持つインターフェース型 - 任意の型にメソッド
ServeHTTP(ResponseWriter, *Request)
を実装することでインターフェースを満たす (= implement) ことになります - Golang では、Java のように明示的に implement する必要はありません
-
超重要: メソッド
-
ハンドラ関数 は、ハンドラのように振る舞う関数のこと。つまりは関数で、引数に
(http.ResponseWriter, *http.Request)
をとります。 -
マルチプレクサ (multiplexer) は、どの URL にどんな処理をさせるかを設定できるもの
- 言語によって言い方が変わるようで、Python (Flask, Django) ではルーティング (routing) と言っています
- http.HandleFunc は、URL のパスとハンドラ関数を DefaultServeMux に登録する関数のこと
- http.HandlerFunc ("r" がついていることに注意) は、ハンドラ関数を Handler に変換できる 型 の名前
STEP 2: ServeHTTP を知る
http.ListenAndServe(addr string, handler Handler)
で、ウェブサーバーが立ち上がります。
- 第一引数に、文字列でポート番号を (":" を忘れずに!)、
- 第二引数に、
Handler
型を渡します。-
Handler
は、ServeHTTP(ResponseWriter, *Request)
を実装すれば、インターフェースを満たすので、このメソッドを持つ全ての型は第二引数に渡すことができます。
-
下記は、ブラウザ画面に「吾輩は猫である」と表示するサンプルです。
package main
import (
"fmt"
"net/http"
)
// cat 型を定義
type cat int
// ServeHTTP を実装
func (c cat) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 何かしらの処理を書く
fmt.Fprintln(w, "吾輩は猫である")
}
func main() {
var c cat
// cat 型に ServeHTTP メソッドを実装したので、c を第二引数に渡せる
http.ListenAndServe(":8080", c)
}
コンソールで go run main.go
を実行し、ブラウザから localhost:8080
にアクセスすると「吾輩は猫である」と表示されます。
STEP 3: マルチプレクサを知る
STEP 2 のコードでは、1 つのレスポンスしか返ってこないため、つまらないですね。
コードを変更して URL によって、出力結果を変える処理を入れてみましょう。
package main
import (
"fmt"
"net/http"
)
type cat int
func (c cat) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// localhost:8080/<path> の <path> で処理を変える (= ルーティング)
switch r.URL.Path {
case "/cat":
fmt.Fprintln(w, "吾輩は猫である")
case "/dog":
fmt.Fprintln(w, "吾輩は犬である")
}
}
func main() {
var c cat
http.ListenAndServe(":8080", c)
}
このコードだと、cat
型に localhost:8080/dog
の処理を書いているため、変ですね。
また、Golang では マルチプレクサ を使ってルーティングをします。以下がマルチプレクサを使って修正したサンプルコードになります。
package main
import (
"fmt"
"net/http"
)
// cat 型を定義
type cat int
func (c cat) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "吾輩は猫である")
}
// dog 型を定義
type dog int
func (d dog) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "吾輩は犬である")
}
func main() {
var c cat
var d dog
// マルチプレクサを作成
mux := http.NewServeMux()
// URL パスとハンドラを登録
mux.Handle("/cat", c)
mux.Handle("/dog", d)
http.ListenAndServe(":8080", mux)
}
作成したマルチプレクサ (= mux) に Handle メソッドを使って、URL パスとハンドラを登録しています。
作成されたマルチプレクサには既にメソッド ServeHTTP(ResponseWriter, *Request)
が実装 されていますので、Handler
のインターフェースを満たしています。なので、http.ListenAndServe(":8080", mux)
と第二引数に作成したマルチプレクサを渡すことができます。
STEP 4: DefaultServeMux を知る
下記のように http.ListenAndServe(":8080", nil)
と第二引数に nil
を渡すことで DefaultServeMux を使うこともできます。
func main() {
var c cat
var d dog
// DefaultServeMux に登録する
http.Handle("/cat", c)
http.Handle("/dog", d)
// 第二引数に nil を渡して DefaultServeMux を使う
http.ListenAndServe(":8080", nil)
}
http.Handle
を使うことで、DefaultServeMux に URL パスとハンドラを登録することができます。
ただ、こちらの記事 によると、セキュリティリスクがあるので (global に保存されるので、悪意のあるパッケージからアクセスできてしまう)、DefaultServeMux は使うべきではないと説明されています。
色んな場所でサンプルコードとしてよく見るので、「こういう書き方もあるんだ」程度で知っておきましょう。
STEP 5: HandleFunc を知る
HandleFunc
は、
- URL パス と
- 引数として
(http.ResponseWriter, *http.Request)
をとる、ハンドラ関数
を紐づけます。
STEP 1 で、「ハンドラのように振る舞う関数のこと」と説明しましたが、ハンドラ関数を使うことで、Handler
のインターフェースを満たすために型を定義し ServeHTTP メソッドを実装する必要がなくなり、下記のようにシンプルにコードを書き直すことができます。
package main
import (
"fmt"
"net/http"
)
func cat(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "吾輩は猫である")
}
func dog(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "吾輩は犬である")
}
func main() {
mux := http.NewServeMux()
// 第二引数にハンドラ関数を渡す
mux.HandleFunc("/cat", cat)
mux.HandleFunc("/dog", dog)
http.ListenAndServe(":8080", mux)
}
すごくシンプルになり、コードも読みやすくなりましたね。
ちなみに、http.HandlerFunc
("r" がついていることに注意) でハンドラ関数をハンドラに変換しても同様の結果になります。
// 関数 cat を ハンドラに変換。
// もう関数ではないので、ハンドラを引数にとる mux.Handle を使う
mux.Handle("/cat", http.HandlerFunc(cat))
まとめ
ドキュメント (Go Doc - net/http) にしっかりと書かれていることですが、この STEP の流れで読みば理解しやすかったのではないでしょうか?
この知識をベースに引き続き、知識を増やしていただければと思います。