2017/04/21更新

[Go] net/httpパッケージでWebサーバー(handlerの書き方、静的ファイル配信、Basic認証、など)

このエントリーをはてなブックマークに追加            

こんにちは、@yoheiMuneです。
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.HandleFunchttp.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の脳にすべく、情報をブログに貯めています。気になった方は、本ブログのRSSTwitterをフォローして頂けると幸いです ^ ^。

最後までご覧頂きましてありがとうございました!





こんな記事もいかがですか?

RSS画像

もしご興味をお持ち頂けましたら、ぜひRSSへの登録をお願い致します。