18
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Golang での Web アプリ開発で、理解を早める 5 ステップ

Last updated at Posted at 2020-01-05

Web アプリとは?

知っていると思いますが、共通認識に違いがないか確認するために書いています。
以下、「Go プログラミング 実践入門」(英題: Go Web Programming) から抜粋です。

  1. HTTP を介し、HTTP リクエストメッセージの形でクライアントから入力を受け取る
  2. HTTP リクエストメッセージを処理し、必要な処理を行う
  3. 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) を実装すれば、インターフェースを満たすので、このメソッドを持つ全ての型は第二引数に渡すことができます。

下記は、ブラウザ画面に「吾輩は猫である」と表示するサンプルです。

main.go
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 によって、出力結果を変える処理を入れてみましょう。

main.go
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 では マルチプレクサ を使ってルーティングをします。以下がマルチプレクサを使って修正したサンプルコードになります。

main.go
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 を使うこともできます。

main.go
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 は、

  1. URL パス と
  2. 引数として (http.ResponseWriter, *http.Request) をとる、ハンドラ関数

を紐づけます。

STEP 1 で、「ハンドラのように振る舞う関数のこと」と説明しましたが、ハンドラ関数を使うことで、Handler のインターフェースを満たすために型を定義し ServeHTTP メソッドを実装する必要がなくなり、下記のようにシンプルにコードを書き直すことができます。

main.go
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 の流れで読みば理解しやすかったのではないでしょうか?

この知識をベースに引き続き、知識を増やしていただければと思います。

18
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?