技術向上

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

http.Redirect【Go】

概要

文字通りhttpのredirectを行う機能です。
下記の通り、構文はシンプルです。

http.Redirect(w http.ResponseWriter, r *http.Request, url string, code int)

最後のコードはstatus codeです。「http.StatusFound」のように記述します。

今回の例は、viewとeditの2ページ構成とし、
「edit」のsaveボタンを押下すると、url末尾のPathをtitleとするテキストファイルが保存され、
そのテキストファイルの中身を読み込むhtml「view」が閲覧できる仕様とします。

http.Redirectは、上記保存完了後と、
存在しないpathのviewにアクセスしようとした場合の、2箇所で使用します。

事前にhtmlファイル(今回はview.html)を同階層に作成し、
後で示すコードを記述したmain.goを実行します。


htmlファイルの記述

まずはtemplateとなるhtmlファイルの記述方法です。
基本的には通常のhtmlと同じ書き方ですが、
goファイルから渡されるPage structの中身などを表示したい場合に、工夫が必要です。

view.html

<!-- 「.<フィールド名>」で、http.ResponseWriterに登録したgoファイルの変数を表示 -->
<h1>{{.Title}}</h1>

<!-- editは別途必要。文字列の中にも「.<フィールド名>」を記述できる -->
<p><a href="/edit/{{.Title}}">Edit</a></p>

<!-- 「printf %s .<フィールド名>」で、byte配列をキャスト -->
<div>{{printf "%s" .Body}}</div>


edit.html

<h1>Editing {{.Title}}</h1>

<!-- actionは"/save/" + title -->
<form action="/save/{{.Title}}" method="POST">
    <div>
<!-- nameは"body" -->
        <textarea name="body" rows="20" cols="80">{{printf "%s" .Body}}</textarea>        
    </div>
    <div>
        <input type="submit" value="Save"/>
    </div>
</form>

上記のように、structの場合、「.<フィールド名>」と記述すれば、
template.ParseFiles("~.html").Execute(w http.ResponseWriter, i interface{}) で登録した
goファイルからの内容を表示することができます。
ただし、byte配列の場合は「printf %s .<フィールド名>」としてstring型にキャストします。

goファイルの記述

saveHandler内の処理は、htmlファイルのtemplateに記載した、
edit.htmlのformがsubmitされた際のactionにより、実行されます。

type Page struct {
    Title string
    Body  []byte
}

func (p *Page) save() error {    // ファイルを作成するメソッド
    filename := p.Title + ".txt"
    return ioutil.WriteFile(filename, p.Body, 0600)    // Webサーバーを起動したユーザーが読み書きできる権限設定
}

func load(title string) (*Page, error) {
    filename := title + ".txt"
    body, err := ioutil.ReadFile(filename)
    if err != nil {
        return nil, err
    }
    return &Page{Title: title, Body: body}, nil
}

func renderTemplate(w http.ResponseWriter, templ string, p *Page) {
    t, _ := template.ParseFiles(templ + ".html")    // 同階層にあるhtmlファイルを読み込む
    t.Execute(w, p)    // 読み込んだ内容をhttp.ResponseWriterに登録する
}

func viewHandler(w http.ResponseWriter, r *http.Request) {    // /view/~にアクセスした場合のHandler
    title := r.URL.Path[len("/view/"):]
    p, err := load(title)
    if err != nil {
        http.Redirect(w, r, "/edit/"+title, http.StatusFound)    // 存在しない.txtファイルのtitleの場合、新規ファイルのeditにredirect
        return
    }
    renderTemplate(w, "view", p)    // templateを使用する
}

func editHandler(w http.ResponseWriter, r *http.Request) {    // /edit/~にアクセスした場合のHandler
    title := r.URL.Path[len("/edit/"):]
    p, err := load(title)
    if err != nil {
        p = &Page{Title: title}    // htmlファイルの{{.Title}}に反映される
    }
    renderTemplate(w, "edit", p)
}

func saveHandler(w http.ResponseWriter, r *http.Request) {    // /save/~にアクセスした場合のHandler
    title := r.URL.Path[len("/save/"):]
    body := r.FormValue("body")    // htmlファイル内のform name="body"の内容をstring型で返す
    p := &Page{Title: title, Body: []byte(body)}
    err := p.save()
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)    // 500エラーを返す
        return
    }
    http.Redirect(w, r, "/view/"+title, http.StatusFound)    // 成功したらviewへredirect
}

func main() {
    http.HandleFunc("/view/", viewHandler)
    http.HandleFunc("/edit/", editHandler)
    http.HandleFunc("/save/", saveHandler)
    log.Fatalln(http.ListenAndServe(":8080", nil))   // Handlerを事前に登録してからサーバーを起動。「:」の左に何も記述がない場合、ローカルサーバー。第2引数にnilを指定するとdefault設定が適用される
}


上記main.goを実行し、http://localhost:8080/view/testにアクセスします。

関連記事 tech-up.hatenablog.com