WAS(Web Application Server)
Node.js 서버처럼 Go 언어로 만든 서버도 동적 컨텐츠를 처리하는 WAS(Web Application Server)로써 동작하게 만들 수 있다. 보통 앞에 NginX, Apache 와 같은 범용적인 http 서버를 앞에다가 두고 뒤에 별도로 WAS 를 실행시켜두는 것이 일반적인 구성일 것이다. 물론 Go 로 만든 서버를 직접 80 포트에 바인딩하는 것도 가능하지만 요청이 많아질 경우 과부하가 걸릴 가능성도 있어서 많은 트래픽이 발생하는 경우에는 권장하는 방법은 아니다.
이 포스트에서 말하는 웹 서버는 Go 언어로 만든 서버를 의미할 것이며 이러한 서버를 만드는 행위는 Node.js http 모듈을 사용하여 만드는 것만큼 상당히 단순하다고 볼 수 있다. 개인적으로 사용 편의성은 Node.js 보다 훨씬 더 좋은 듯하다.
net/http
http
패키지는 웹 서버를 구축하기 위한 도구들이 담겨있는 패키지다. http 패키지 문서에서 꽤나 많은 함수와 타입 목록들을 볼 수 있지만, 간단히 소스코드를 작성하여 살펴보면 다음과 같다.
package main
import (
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, Go!"))
})
if err := http.ListenAndServe(":3000", nil); err != nil {
log.Fatal(err)
}
}
http.ResponseWriter
타입은io.Writer
인터페이스를 충족하기 때문에 해당 인터페이스를 받는 함수나 메서드에 사용할 수 있다.
논리적 경로인 URL 의 루트인 /
경로에 대한 라우팅과 그에 대한 로직을 설정한다. http.ListenAndServe()
함수를 쓰면 서버를 시작할 수 있으며 시작하는 순간 블러킹되기 때문에 서버를 시작할 때 어떤 문제가 발생하지 않는 이상은 error
가 반환되지 않는다.
http.HandleFunc(), http.Handle()
요청을 핸들링 하기 위한 함수로는 http.HandleFunc()
도 있겠지만, http.Handle()
도 있다. http.HandleFunc()
의 경우 파라매터로 함수를 받지만, http.Handle()
의 경우 Handler
타입을 받는다. Handle
는 인터페이스이며 해당 인터페이스를 만족하기만 하면 요청을 핸들링할 수 있다.
http.Handler
$ go doc http.Handler
package http // import "net/http"
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
http.HandlerFunc
http.HandlerFunc
는 인터페이스가 아닌 타입이며 http.Handler
인터페이스를 만족한다.
$ go doc http.HandlerFunc
package http // import "net/http"
type HandlerFunc func(ResponseWriter, *Request)
The HandlerFunc type is an adapter to allow the use of ordinary functions as
HTTP handlers. If f is a function with the appropriate signature,
HandlerFunc(f) is a Handler that calls f.
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request)
따라서 해당 타입을 사용하면 http.Handle()
을 사용해서도 핸들러를 넘길 수 있게된다.
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, Go!"))
}))
html/template
html/template
를 사용하면 Content-Type: text/html
을 응답으로 반환할 수 있다. html/template
의 기반은 text/template
이며 둘은 패키지 이름은 같지만 패키지 경로가 다르므로 서로 다른 패키지임을 주의하자. html/template
패키지는 text/template
패키지에 비해 웹에서 사용할 수 있도록 이스케이프와 같은 보안요소를 제공한다.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.New("index").Parse("<h1>Hello, Go!</h1>")
if err != nil {
http.Error(
w,
http.StatusText(http.StatusInternalServerError),
http.StatusInternalServerError)
}
if err = tmpl.Execute(w, nil); err != nil {
http.Error(
w,
http.StatusText(http.StatusInternalServerError),
http.StatusInternalServerError)
}
})
error
처리가 다소 지저분해 보이긴 한다만, Go 의 에러처리 방식은 어플리케이션 관점에서 보면 더욱 견고하게 구성할 수 있도록 만들어준다.
템플릿에 데이터 넘기기
template.New()
함수를 호출하여 새로운 템플릿을 생성하고, HTML String 을 파싱하거나 template.ParseFiles()
메서드를 사용하여 html 파일도 파싱할 수 있다. template.Execute()
메서드의 두번째 파라매터로는 템플릿에 넘길 데이터를 줄 수 있다. 예를 들면 다음과 같이 템플릿을 만들고 데이터를 넘길 수 있다.
tmpl, _ := template.New("index").Parse("<h1>{{.}}</h1>")
tmpl.Execute(w, "Hello, Go!")
{{.}}
와 같이 사용하면 .
이 템플릿에 넘겨준 데이터로 변환된다. 조건문과 반복문도 사용이 가능하며 일반적인 사용법으로는 템플릿에 데이터를 넘길때 구조체의 객체를 넘기는 것이다. 이러한 템플릿의 사용법은 html/template
패키지의 문서에 가보면 자세히 알 수 있다.