概要
文字通り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