[Go] net/httpパッケージでWebサーバー(handlerの書き方、静的ファイル配信、Basic認証、など)
こんにちは、@yoheiMuneです。
Go言語を案件で使うべく、絶賛お勉強中です。今日は
https://github.com/yoheiMune/MyGoProject/tree/master/mch01
本格的に作るなら、サードパーティ製のechoライブラリが人気ですが、言語ネイティブの機能を使うことで学ぶことも多いので、今回は使ってみた次第です(それを忘れないようにブログに残しておきたい)。
以降では、
ここでGo言語だと無名関数(クロージャー)も使えるので、下記のように書くこともできます。
https://golang.org/pkg/net/http/#Request
https://golang.org/pkg/net/http/
https://golang.org/pkg/net/http/#Request
https://golang.org/pkg/net/url/#URL
最後になりますが本ブログでは、Go言語・Python・Linux・Node.js・フロントエンド・インフラ・開発関連・Swift・Java・機械学習など雑多に情報発信をしていきます。自分の第2の脳にすべく、情報をブログに貯めています。気になった方は、本ブログのRSSやTwitterをフォローして頂けると幸いです ^ ^。
最後までご覧頂きましてありがとうございました!
Go言語を案件で使うべく、絶賛お勉強中です。今日は
net/http
パッケージを使った、Webサーバーの実装方法をブログに残したいと思います。目次
今回のソースコード
この記事で使うコードの全体は、下記に置きました。必要あればご参照ください。https://github.com/yoheiMune/MyGoProject/tree/master/mch01
net/httpパッケージとは
net/httpパッケージとはその名の通りHTTPを扱うパッケージで、HTTPクライアントとHTTPサーバーを実装するために必要な機能が提供されています。今回のブログではHTTPサーバー用の機能を使って、Webサーバーを作ってみたいと思います。本格的に作るなら、サードパーティ製のechoライブラリが人気ですが、言語ネイティブの機能を使うことで学ぶことも多いので、今回は使ってみた次第です(それを忘れないようにブログに残しておきたい)。
以降では、
net/http
を用いたWebサーバーの作り方をみていきたいと思います。net/httpを用いたWebサーバーを開発する
それでは1つずつ、機能を拡張しながらみていきたいと思います。サーバーの起動
サーバーの起動は以下のように行うことができます。数行でできて簡単です。// mainHTTPSample.go package main import ( "net/http" ) func main() { // 8080ポートで起動 http.ListenAndServe(":8080", nil) }そして以下のようにGoプログラムを起動します。
$ go run mainHTTPSample.goこれでサーバーが起動しました。試しに
http://localhost:8080
にアクセスすると起動していることがわかります。ただ、現状は何もコンテンツを提供していないので404が返却されます。リクエストを処理する(HandleFuncの利用)
レスポンスを返す方法はいくつか存在しますが、http.HandleFunc
関数を使って処理を定義するのが一般的です。func main() { // 「/a」に対して処理を追加 http.HandleFunc("/a", handler) // 8080ポートで起動 http.ListenAndServe(":8080", nil) } // リクエストを処理する関数 func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello World from Go.") }
http.HandleFunc
関数を使い、第1引数にパスを、第2引数に処理を指定することでリクエストを処理できるようになります。ここでGo言語だと無名関数(クロージャー)も使えるので、下記のように書くこともできます。
func main() { // クロージャー的なのでもOK. http.HandleFunc("/b", func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello from Closure") }) // 8080ポートで起動 http.ListenAndServe(":8080", nil) }この辺は柔軟に書けるので便利ですね。
リクエストを処理する(Handleの利用)
レスポンスを返すもう一つの方法としてHandle
関数を使って処理する方法があります。type String string // String に ServeHTTP 関数を追加 func (s String) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, s) } func main() { // DuckTyping的に、ServeHTTP関数があれば良い. http.Handle("/c", String("Duck Typing!!!")) // 8080ポートで起動 http.ListenAndServe(":8080", nil) }これはGo言語でダックタイピング(詳細はWiki参照)ができる特性を生かした実装方法で、
ServeHTTP(w http.ResponseWriter, r *http.Request)
という関数を持った構造体をhttp.Handle
の第2引数に渡すことで、リクエストを処理しています。http.HandleFunc
とhttp.Handle
は似ていますが、第2引数に指定できる形式が異なります。POSTメソッドのみに対応
特定のHTTPメソッドのみに対応したい場合、自分で頑張って実装します。func main() { // POSTのみ許可. http.HandleFunc("/onlyPost", handleOnlyPost) // 8080ポートで起動 http.ListenAndServe(":8080", nil) } func handleOnlyPost(w http.ResponseWriter, r *http.Request) { // HTTPメソッドをチェック(POSTのみ許可) if r.Method != http.MethodPost { w.WriteHeader(http.StatusMethodNotAllowed) // 405 w.Write([]byte("POSTだけだよー")) return } w.Write([]byte("OK")) }上記のように
Request
に様々な情報が入っているので、それを使ってチェックを行います。Requestの詳細は下記をご参照ください。https://golang.org/pkg/net/http/#Request
パラメータを受け取る
クエリパラメータやFormデータなどはRequest
から取得することができます。func main() { // パラメータ受け取り http.HandleFunc("/params", handleParams) // 8080ポートで起動 http.ListenAndServe(":8080", nil) } func handleParams(w http.ResponseWriter, r *http.Request) { // クエリパラメータ取得してみる fmt.Fprintf(w, "クエリ:%s\n", r.URL.RawQuery) // Bodyデータを扱う場合には、事前にパースを行う r.ParseForm() // Formデータを取得. form := r.PostForm fmt.Fprintf(w, "フォーム:\n%v\n", form) // または、クエリパラメータも含めて全部. params := r.Form fmt.Fprintf(w, "フォーム2:\n%v\n", params) }上記の他にもいくつか取得方法がありますので詳しくはRequestのリファレンスをご確認ください。
静的ファイルの配信
静的ファイルを配信したい場合には、それ専用の便利な機能が提供されています。今回は以下のような構成であるとします。. ├── mainHTTPSample.go └── contents # contentsフォルダに静的ファイルがある ├── sample1.txt └── sample2.txt静的ファイルの配信は以下のように実装します。
func main() { // 静的ファイル配信. // ディレクトリ名をURLパスに使う場合 // 例:http://localhost:8080/contents/sample1.txt http.Handle("/contents/", http.FileServer(http.Dir("./"))) // ディレクトリ名とURLパスを変える場合 // 例:http://localhost:8080/mysecret/sample1.txt http.Handle("/mysecret/", http.StripPrefix("/mysecret/", http.FileServer(http.Dir("./contents")))) // 8080ポートで起動 http.ListenAndServe(":8080", nil) }実装方法としては
http.FileServer
関数を使って静的ファイル配信を行なっています。Basic認証
最後にBasic認証の実装方法ですが、Request#BasicAuth
の関数を利用します。func main() { // basic認証. http.HandleFunc("/basicAuth", handleBasicAuth) // 8080ポートで起動 http.ListenAndServe(":8080", nil) } func handleBasicAuth(w http.ResponseWriter, r *http.Request) { // Basic認証のデータ取得. username, password, ok := r.BasicAuth() // そもそもそんなヘッダーがないなどのエラー. if ok == false { w.Header().Set("WWW-Authenticate", `Basic realm="SECRET AREA"`) w.WriteHeader(http.StatusUnauthorized) // 401 fmt.Fprintf(w, "%d Not authorized.", http.StatusUnauthorized) return } // Basic認証のヘッダーはあるけど、値が不正な場合. if username != "my" || password != "secret" { w.Header().Set("WWW-Authenticate", `Basic realm="SECRET AREA"`) w.WriteHeader(http.StatusUnauthorized) // 401 fmt.Fprintf(w, "%d Not authorized.", http.StatusUnauthorized) return } // OK fmt.Fprint(w, "OK") }上記のコードだと冗長に同じことを書いちゃっていますが、雰囲気としては上記のような感じで実装できます。ここで
w.Header().Set("WWW-Authenticate", `Basic realm="SECRET AREA"`)
をレスポンスヘッダで返すことで、ブラウザがユーザーにBasic認証のダイアログなどを表示してくれるようになります。参考資料
以下の情報を参照しました。Go言語のリファレンスはサンプル付きでわかりやすくていいですね。https://golang.org/pkg/net/http/
https://golang.org/pkg/net/http/#Request
https://golang.org/pkg/net/url/#URL
最後に
今回の実装を進めるにあたり、Go言語の良さがだんだんと感じられてきました。嬉しい。今後もたくさんコードを書いてメキメキとスキルをつけ、ブログにもGo言語について書きたいと思います。最後になりますが本ブログでは、Go言語・Python・Linux・Node.js・フロントエンド・インフラ・開発関連・Swift・Java・機械学習など雑多に情報発信をしていきます。自分の第2の脳にすべく、情報をブログに貯めています。気になった方は、本ブログのRSSやTwitterをフォローして頂けると幸いです ^ ^。
最後までご覧頂きましてありがとうございました!