지난 포스트


웹 프로그래밍 with Golang 1 - Hello, World!

웹 프로그래밍 with Golang 2 - 템플릿 문법

웹 프로그래밍 with Golang 3 - 정적 파일

웹 프로그래밍 with Golang 4 - 라우팅

개요


이번 시간에는 Form과 JSON을 이용하여 간단한 Contact 사이트를 만들어보겠습니다.

HTML <form>


<form> 태그는 사용자로부터 데이터를 입력받기 위해 존재하는 문서 구획입니다.

<form><input> 태그와 함께 사용되는데 submit(제출)이 되면 직렬화(serialize)되어 서버로 전달됩니다.

Form 페이지 작성


1
2
3
4
5
6
7
8
9
10
11
<!-- form.html -->
<h1>Contact</h1>
<form method="POST">
<label>Email:</label><br />
<input type="text" name="email"><br />
<label>Subject:</label><br />
<input type="text" name="subject"><br />
<label>Message:</label><br />
<textarea name="message"></textarea><br /><hr />
<input type="submit">
</form>

https://i.ibb.co/y8PyFYT/image.png

form에 작성한 내용이 어떻게 객체화되는지 알아보기 위해 아래 스크립트를 추가해봅시다.

(테스트 후에는 제거합니다.)

1
2
3
4
5
6
7
8
9
10
11
12
<script>
const formEl = document.querySelector('form')

formEl.addEventListener('submit', evt => {
evt.preventDefault()
const formData = new FormData(evt.target)

for(var pair of formData.entries()) {
console.log(pair[0]+ '='+ pair[1]);
}
})
</script>

https://i.ibb.co/jL1cmds/image.png

https://i.ibb.co/Hp9dD1Z/image.png

HTML FormData의 경우 실제로 서버에 전달될 때에는 다음과 같이 인코딩 됩니다.

email=abc@gamil.com&subject=안녕하세요&message=sadadd

key=value로 이루어져 있으며 &로 구분합니다.

GET Method의 경우 URL에 붙으며 (Query String), POST Method의 경우 Body에 붙습니다.

이러한 인코딩 방식을 application/x-www-form-urlencoded라고 합니다.

1
2
3
4
5
6
7
8
# HTTP POST 예제

POST / HTTP/1.1
Host: foo.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 48

email=abc@gamil.com&subject=안녕하세요&message=sadadd

HTTP 요청 처리하기


Form을 이용하여 HTTP Request를 보내게 되면 완전히 페이지가 새로고침됩니다. 따라서 각 메서드별로 적절하게 응답을 해줄 필요가 있습니다. 이를 구현하기 위한 한가지 방법으로 템플릿 문법을 사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- form.html -->

{{ if .Success }}
<h1>Thanks for your message!</h1>
{{ else }}
<h1>Contact</h1>
<form method="POST">
<label>Email:</label><br />
<input type="text" name="email"><br />
<label>Subject:</label><br />
<input type="text" name="subject"><br />
<label>Message:</label><br />
<textarea name="message"></textarea><br /><hr />
<input type="submit">
</form>
{{ end }}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// main.go

package main

import (
"net/http"
"html/template"
"github.com/gorilla/mux"
"fmt"
)

type ContactDetails struct {
Email string
Subject string
Message string
}

func main() {
r := mux.NewRouter()
tmpl := template.Must(template.ParseFiles("form.html"))

r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
tmpl.Execute(w, nil)
}).Methods("GET")

r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
details := ContactDetails{
Email: r.FormValue("email"),
Subject: r.FormValue("subject"),
Message: r.FormValue("message"),
}

fmt.Printf("Email: %s, Subject: %s Message: %s\n", details.Email, details.Subject, details.Message)

tmpl.Execute(w, struct{ Success bool }{ true })
}).Methods("POST")

http.ListenAndServe(":8080", r)
}

https://i.ibb.co/zHJXcnY/2.png

https://i.ibb.co/vkMzPLL/3.png

https://i.ibb.co/By3dQ5g/image.png

브라우저에서 접속하면 기본적으로 GET 메서드를 사용한것과 동일합니다. 따라서 첫번째 핸들러 함수가 실행됩니다. POST 요청을하면 두번째 핸들러가 실행되어 “Thanks for your message!”가 출력되게 됩니다.

Fetch를 이용한 비동기 통신


Fetch API가 웹 표준(Living Standard)으로 지정되면서 Ajax(Asynchronous JavaScript And XML), 즉 비동기 통신이 대세가 되고 있습니다. 원래 비동기 통신은 XMLHttpRequest를 사용했으나, 편리성이 떨어져 jQuery같은 서브 파티 라이브러리를 주로 사용했었습니다. Fetch 덕분에 더이상 서드파티 라이브러리에 의존할 필요 없이, 편리하게 비동기 통신을 구현할 수 있습니다.

비동기 통신을 하게되면 페이지 새로고침 없이 새로운 데이터를 수신하고 갱신할 수 있습니다. 위에서 언급한 application/x-www-form-urlencoded 방식을 사용해도 되지만, 직관성을 높이기 위하여 비동기 통신에서는 보통 클라이언트와 서버 모두application/json 방식으로 데이터를 인코딩합니다.

자습 컨텐츠

Fetch API에 대해서 (MDN)

비동기 함수에 대해서 (MDN)

Fetch 구현


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!-- form.html -->
<h1>Contact</h1>
<form method="POST">
<label>Email:</label><br />
<input type="text" name="email"><br />
<label>Subject:</label><br />
<input type="text" name="subject"><br />
<label>Message:</label><br />
<textarea name="message"></textarea><br /><hr />
<input type="submit">
</form>

<script>
const formEl = document.querySelector('form')

formEl.addEventListener('submit', async evt => {
evt.preventDefault()
const formData = new FormData(evt.target)
const obj = {}

for (const [key, value] of formData) obj[key] = value // formData -> JS object

const result = await fetch('/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(obj), // JS Object -> JSON String
})

const responseData = await result.json() // Response Object -> JS object

alert(JSON.stringify(responseData))
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
"net/http"
"html/template"
"github.com/gorilla/mux"
"encoding/json"
)

type ContactDetails struct {
Email string
Subject string
Message string
}

func main() {
r := mux.NewRouter()
tmpl := template.Must(template.ParseFiles("fetch.html"))

r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
tmpl.Execute(w, nil)
}).Methods("GET")

r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
var details ContactDetails
json.NewDecoder(r.Body).Decode(&details) // read body, then decode
json.NewEncoder(w).Encode(details) // encode, then send to user
}).Methods("POST")

http.ListenAndServe(":8080", r)
}
https://i.ibb.co/yg4yRcy/4.png

참고


https://gowebexamples.com/forms/

https://gowebexamples.com/json/