技術向上

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

httpのhandlerに引数を指定する方法【Go】

htmlのtemplate【Go】 - 技術向上などで紹介した方法ですと、
各handlerで共通した、titleを取得する処理が行われています。
引数にtitleを渡すことで効率化できそうです。

...

func viewHandler(w http.ResponseWriter, r *http.Request) {
    title := r.URL.Path[len("/view/"):]
    ...
}

func editHandler(w http.ResponseWriter, r *http.Request) {
    title := r.URL.Path[len("/edit/"):]
    ...
}

func saveHandler(w http.ResponseWriter, r *http.Request) {
    title := r.URL.Path[len("/save/"):]
    ...
}

func main() {
    http.HandleFunc("/view/", viewHandler)
    http.HandleFunc("/edit/", editHandler)
    http.HandleFunc("/save/", saveHandler)
    log.Fatalln(http.ListenAndServe(":8080", nil))
}


しかし、呼び出し元であるhttp.HandleFuncの第2引数には、
handler func(ResponseWriter, *Request)が指定されており、
呼び出し元でviewHandler(title)のようにhandler func(ResponseWriter, *Request)に対して新たな引数を指定することはできません。

そこで『「新たな引数を含む関数」を引数として、func(ResponseWriter, *Request)を返す関数』を作成し、 http.HandleFuncの第2引数に指定します。
ただし、「新たな引数」は呼び出し元では指定しません。titleはrequestのurlから取得できるからです。

...

func viewHandler(w http.ResponseWriter, r *http.Request, title string) {    // 第3引数titleを指定
    p, err := load(title)
    if err != nil {
        http.Redirect(w, r, "/edit/"+title, http.StatusFound)
        return
    }
    renderTemplate(w, "view", p)
}

func editHandler(w http.ResponseWriter, r *http.Request, title string) {
    p, err := load(title)
    if err != nil {
        p = &Page{Title: title}
    }
    renderTemplate(w, "edit", p)
}

func saveHandler(w http.ResponseWriter, r *http.Request, title string) {
    body := r.FormValue("body")
    p := &Page{Title: title, Body: []byte(body)}
    err := p.save()
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    http.Redirect(w, r, "/view/"+title, http.StatusFound)
}

var validPath = regexp.MustCompile("^/(view|edit|save)/([a-zA-Z0-9]+)$")    // 正規表現をキャッシュ。「^」は先頭、「$」は末尾を意味する

func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {    // 3つの引数をもつfunctionを引数にとり、func(ResponseWriter, *Request)を返す
    return func(w http.ResponseWriter, r *http.Request) {
        m := validPath.FindStringSubmatch(r.URL.Path)    // キャッシュした正規表現にマッチするpathを[]stringで格納
        if m == nil {
            http.NotFound(w, r)    // マッチしない場合は、404エラーを返す
            return
        }
        fn(w, r, m[2])    // 第3引数に[]string([全体、view|edit|save、title])の3番目titleを指定
    }
}

func main() {
    http.HandleFunc("/view/", makeHandler(viewHandler))    // makeHandlerを呼び出す
    http.HandleFunc("/edit/", makeHandler(editHandler))
    http.HandleFunc("/save/", makeHandler(saveHandler))
    log.Fatalln(http.ListenAndServe(":8080", nil))
}


htmlファイルも含めた全体の構成はhttp.Redirect【Go】 - 技術向上を、
正規表現についてはregex(正規表現)【Go】 - 技術向上を参照ください。