우리가 만든 위키 페이지는 편집기능이 없다.
두 개의 새로운 핸들러를 추가로 생성해보자.
하나는 editHandler 얘는 수정하는 페이지를 처리하고,
하나는 saveHandler 얘는 저장하는 페이지를 처리할 핸들러다.

main 메서드에 아래 두가지를 추가하자.

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

 

editHandler 는 페이지를 로드한다. 만약에 존재하지 않는 페이지의 경우 빈 구조체를 생성해서 HTML로 보여준다.
아래 코드를 작성하자.

일단 이 기능은 잘 동작하긴 하지만 하드코딩된 HTML 부분이 보기 불편하다. 편리하게 사용할 수 있도록 바꿔보자.

func editHandler(w http.ResponseWriter, r *http.Request) {
    title := r.URL.Path[len("/edit/"):]
    p, err := loadPage(title)
    if err != nil {
        p = &Page{Title: title}
    }
    fmt.Fprintf(w, "<h1>Editing %s</h1>"+
        "<form action=\"/save/%s\" method=\"POST\">"+
        "<textarea name=\"body\">%s</textarea><br>"+
        "<input type=\"submit\" value=\"Save\">"+
        "</form>",
        p.Title, p.Title, p.Body)
}

 

html/template 패키지는 Go의 표준 라이브러리 중 하나다.
우리는 해당 패키지를 사용해서 HTML을 파일로 분리해서 사용할 수 있다. 
기본 Go 코드를 수정하지 않고도 수정페이지의 레이아웃을 변경해서 사용할 수 있다.

그럼 html/template 패키지를 import 해보자.
이제 fmt 패키지는 사용하지 않을 것이므로 해당 부분은 지우도록 한다.

import (
	"html/template"
	"io/ioutil"
	"net/http"
)

 

edit.html 이라는 파일을 만들어서 아래 코드를 작성한다.

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

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

그리고, 우리가 위에서 작성한 editHandler 을 수정하자.
이제 하드코딩된 부분을 삭제하고 아래처럼 사용할 수 있다.

func editHandler(w http.ResponseWriter, r *http.Request) {
    title := r.URL.Path[len("/edit/"):]
    p, err := loadPage(title)
    if err != nil {
        p = &Page{Title: title}
    }
    t, _ := template.ParseFiles("edit.html")
    t.Execute(w, p)
}

 

template.ParseFiles 함수는 edit.html의 내용을 읽고 * template.Template을 반환한다.
t.Execute 메소드는 템플릿을 실행하고 생성 된 HTML을 http.ResponseWriter에 기록한다.
.Title .Body 에서 . d은 p.Title , p.Body 를 의미한다.

템플릿 지시문은 이중 중괄호로 묶어서 표현한다. 
printf "% s".Body 명령어는 .Body를 바이트 스트림 대신 문자열로 출력하는 역할을 한다.

html / template 패키지는 템플릿 작업에 의해 안전하고 올바른 모양의 HTML 만 생성되도록 한다.
예를 들어 '>' 와 같은 기호는 자동으로 escape 처리하여 &gt;로 전환한다. 
사용자의 데이터가 HTML 양식을 변환시키지 않도록 보호해준다.

추가로 viewHandler에도 view.html을 만들어 호출해보자.
view.html

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

<p>[<a href="/edit/{{.Title}}">edit</a>]</p>

<div>{{printf "%s" .Body}}</div>

viewHandler를 아래처럼 수정하자.

func viewHandler(w http.ResponseWriter, r *http.Request) {
    title := r.URL.Path[len("/view/"):]
    p, _ := loadPage(title)
    t, _ := template.ParseFiles("view.html")
    t.Execute(w, p)
}


두 핸들러에서 거의 똑같은 템플릿 코드를 사용했다.
템플릿 코드를 자체 함수로 이동해서 이 중복 코드를 제거해보자.

func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
    t, _ := template.ParseFiles(tmpl + ".html")
    t.Execute(w, p)
}

그리고 아래 두 개의 핸들러도 수정하자.

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

 

func editHandler(w http.ResponseWriter, r *http.Request) {
    title := r.URL.Path[len("/edit/"):]
    p, err := loadPage(title)
    if err != nil {
        p = &Page{Title: title}
    }
    renderTemplate(w, "edit", p)
}

그럼 결과적으로 아래와 같은 코드가 될 것이다. 이 코드를 빌드하고 테스트해보자.

package main

import (
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
)

type Page struct {
	Title string
	Body  []byte
}

func (p *Page) save() error {
	filename := p.Title + ".txt"
	return ioutil.WriteFile(filename, p.Body, 0600)
}

func loadPage(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, tmpl string, p *Page) {
	t, _ := template.ParseFiles(tmpl + ".html")
	t.Execute(w, p)
}

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

func editHandler(w http.ResponseWriter, r *http.Request) {
	title := r.URL.Path[len("/edit/"):]
	p, err := loadPage(title)
	if err != nil {
		p = &Page{Title: title}
	}
	renderTemplate(w, "edit", p)
}

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

 

+ Recent posts