일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- mysql
- SWIFT
- perfect
- nGrinder
- kitura
- Jupyter
- sshpass
- insert
- appium
- STF_PortForwarding
- postgres
- ftp
- PYTHON
- openpyxl
- centos
- STF
- Materials
- GoCD
- appium server
- 실행권한
- ubuntu
- create table
- ssh
- nmap
- 28015
- rethinkdb
- postgresql
- Jupyter Notebook
- port forwarding
- nohup
- Today
- Total
don't stop believing
Handler와 Multiplexer 이해하기 본문
Multiplexer를 검색하다가 정리가 잘된 글을 찾았습니다.
https://www.integralist.co.uk/posts/understanding-golangs-func-type/
확인차 코드를 정리하여 기록해 보겠습니다.
Golang으로 Web app 개발을 하면 처음 만나는 Hello World 코드는 아래와 같습니다.
package main import ( "fmt" "net/http" ) func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello %s", r.URL.Path[1:]) } func main() { http.HandleFunc("/World", handler) http.ListenAndServe(":8080", nil) }
server.go 파일에 위 코드를 넣고 바로 실행해 봅니다.
$ go run server.go
그리고 브라우저를 열고 localhost:8080/World를 호출해 봅니다.
처음 Golang을 배우고 기본 코드를 따라하게 될때 HandleFunc가 무엇인지, ListenAndServe의 두 번째 파라메타에 왜 nil이 들어가는지 모릅니다.
그렇다고 그걸 모른체 따라해서는 안되며 계속 궁금증을 가져야 합니다.
여러 프로그램책에서 카고 컬트 프로그래밍이라는 것을 언급합니다.
카고 컬트 프로그래밍
2차 세계대전 중에 연합군은 전쟁을 돕기 위해 태평양 섬에 공군 기지를 지었습니다. 많은 양의 보급품과 군 장비 지원으로 인해 군인과 원주민의 삶을 크게 바꾸었습니다. 주민들은 비행기가 제조된 옷과 통조림 식품 그리고 기타 물품을 싣고 오는 것들을 처음 보았습니다. 전쟁이 끝나자, 기지는 버려졌고 화물(cargo)이 더는 도착하지 않았습니다. 그래서 섬 주민들은 군인들을 흉내내기 위해 항공 교통 관제사나 군인, 그리고 선원처럼 복장을 차려입고 활주로 비슷한 곳을 만들어 놓고 막대기를 이용해 착륙 신호를 항공기에 보냈습니다. 항공기에서 화물이 낙하산으로 떨어지도록 하기 위한 행진을 계속했습니다. 그러나 그들이 기다리는 비행기는 오지 않았습니다.
카고 예찬자들은 이와 비슷한 관행에 대해 카고 컬트 프로그래밍(cargo cult programming)이라는 이름을 부여했습니다. 카고 컬트 프로그래머는 착륙 신호가 무엇을 의미하는지 정확히 모른 채 착륙 신호를 흔듭니다. 이들은 인터넷에서 찾은 코드가 어떻게 동작하는지 이해하지 않고 (스택오버플로우와 같은 사이트 등에서) 복사하여 붙여넣기를 합니다. 결과적으로 코드를 변경하거나 확장할 수 없게 됩니다. 또한, 카고 컬트 프로그래머는 종종 프레임워크가 사용하는 특정 패턴이나 관례를 사용하는 이유를 이해하지 못한 채 웹 프레임워크를 사용하기도 합니다.
Go로 Web Server를 만드는 4개지 방법을 설명합니다.
1. No request parsing (serve same content regardless of request)
2. Manual request parsing
3. Multiplexer
4. Global multiplexer
사실상 3가지 방법입니다. 첫 번째와 두 번째는 실질적으로 같습니다.
1. No request parsing
가장 기본적인 방법 입니다.
package main import ( "fmt" "log" "net/http" ) type pounds float32 func (p pounds) String() string { return fmt.Sprintf("£%.2f", p) } type database map[string]pounds func (d database) ServeHTTP(w http.ResponseWriter, r *http.Request) { for item, price := range d { fmt.Fprintf(w, "%s: %s\n", item, price) } } func main() { db := database{ "foo": 1, "bar": 2, } log.Fatal(http.ListenAndServe("localhost:8000", db)) }
먼저 db라는 변수에 임시로 사용할 map 타입의 database를 정의합니다.
그리고 pounds라는 타입을 만드는데 이건 float32를 참조합니다. String() 함수는 pounds (float32)를 상속받아 string으로 변환해 리턴합니다. 마찬가지로 ServeHTTP 함수도 database를 상속받아 처리합니다.
ListenAndServe는 두 번째 인자로 ServeHTTP를 받습니다.
godoc으로 ListenAndServe를 확인하면 두 번째 인자로 Handler 타입을 받는 것을 확인할 수 있습니다.
$ godoc net/http ListenAndServe | less
func ListenAndServe(addr string, handler Handler) error
ListenAndServe listens on the TCP network address addr and then calls
Serve with handler to handle requests on incoming connections. Accepted
connections are configured to enable TCP keep-alives.
The handler is typically nil, in which case the DefaultServeMux is used.
ListenAndServe always returns a non-nil error.
그럼 Handler 타입도 확인해 보겠습니다.
$ godoc net/http Handler | less
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
A Handler responds to an HTTP request.
ServeHTTP should write reply headers and data to the ResponseWriter and
then return. Returning signals that the request is finished; it is not
valid to use the ResponseWriter or read from the Request.Body after or
concurrently with the completion of the ServeHTTP call.
Handler는 interface이며 ServeHTTP를 가지고 있습니다. 따라서, 위 코드에서 ServeHTTP 함수를 가지고 있는 database를 ListenAdnServe의 두 번째 인자로 넣어주게 됩니다.
위 코드를 바로 실행하고 브라우저에서 아래와 같이 호출해 보겠습니다.
- http://localhost:8000/
- http://localhost:8000/abc
- http://localhost:8000/xyz
모두 동일한 내용이 출력됩니다.
이번에는 위 코드를 좀 더 추가해 보겠습니다. ServeHTTP 함수 안에서 요청되는 경로를 구분해 처리하겠습니다.
package main import ( "fmt" "log" "net/http" ) type pounds float32 func (p pounds) String() string { return fmt.Sprintf("£%.2f", p) } type database map[string]pounds func (d database) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/foo": fmt.Fprintf(w, "foo: %s\n", d["foo"]) case "/bar": fmt.Fprintf(w, "bar: %s\n", d["bar"]) default: w.WriteHeader(http.StatusNotFound) fmt.Fprintf(w, "No page found for: %s\n", r.URL) } } func main() { db := database{ "foo": 1, "bar": 2, } log.Fatal(http.ListenAndServe("localhost:8000", db)) }
ServeHTTP 함수 안에 switch 문을 넣어 request되는 경로에 따라 다르게 출력했습니다.
그리고 지정된 경로가 아닐 경우 404 메시지를 보냅니다. 404 메시지를 지정해 보내려면 WriteHeader()를 사용합니다.
참고로 WriteHeader의 source를 확인하고 싶다면 godoc -src 로 확인할 수 있습니다.
$ godoc -src net/http WriteHeader | less
코드를 실행하고 지정된 경로를 호출해 봅니다.
다른 경로를 호출했을 경우 아래와 같이 호출된 경로를 출력하고 header에는 404 메시지를 보냅니다.
3. Multiplexer
위 코드에서 switch로 분기된 부분을 함수로 만들어 주겠습니다. 그리고 이번에는 ServeHTTP 대신 ServeMux를 사용하겠습니다.
ServeMux는 HandleFunc를 제공하며 개발 함수를 바로 사용할 수 있습니다.
package main import ( "fmt" "log" "net/http" ) type pounds float32 func (p pounds) String() string { return fmt.Sprintf("£%.2f", p) } type database map[string]pounds func (d database) foo(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "foo: %s\n", d["foo"]) } func (d database) bar(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "bar: %s\n", d["bar"]) } func (d database) baz(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "baz: %s\n", d["baz"]) } func main() { db := database{ "foo": 1, "bar": 2, "baz": 3, } mux := http.NewServeMux() mux.Handle("/foo", http.HandlerFunc(db.foo)) mux.Handle("/bar", http.HandlerFunc(db.bar)) // Convenience method for longer form mux.Handle mux.HandleFunc("/baz", db.baz) log.Fatal(http.ListenAndServe("localhost:8000", mux)) }
ServeMux 인스턴스는 Multiplexer이며 ListenAdnServe의 두 번째 인자로 들어갈 수 있습니다. ServeMux를 사용하려면 http.NewServeMux를 선언해야 합니다.
HandlerFunc()과 HandleFunc()의 차이를 알고 싶다면 godoc을 확인해 보세요.
$ godoc net/http HandlerFunc | less
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)
ServeHTTP calls f(w, r).
$ godoc net/http HandleFunc | less
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
HandleFunc registers the handler function for the given pattern in the
DefaultServeMux. The documentation for ServeMux explains how patterns
are matched.
type ServeMux struct {
// contains filtered or unexported fields
}
ServeMux is an HTTP request multiplexer. It matches the URL of each
incoming request against a list of registered patterns and calls the
handler for the pattern that most closely matches the URL.
4. Global multiplexer
Web server를 만들다 보면 기능별로 여러개의 package를 만들어 사용하게 됩니다. ServeMux를 각 package별로 만들어야 되는데 그 대신에 Go의 Global DefaultServeMux를 사용하는 방법이 있습니다. DefaultServeMux를 사용하면 ListenAndServe의 두 번째 인자에 nil이 들어가게 됩니다.
package main import ( "fmt" "log" "net/http" ) type pounds float32 func (p pounds) String() string { return fmt.Sprintf("£%.2f", p) } type database map[string]pounds func (d database) foo(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "foo: %s\n", d["foo"]) } func (d database) bar(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "bar: %s\n", d["bar"]) } func (d database) baz(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "baz: %s\n", d["baz"]) } func main() { db := database{ "foo": 1, "bar": 2, "baz": 3, } http.HandleFunc("/foo", db.foo) http.HandleFunc("/bar", db.bar) http.HandleFunc("/baz", db.baz) log.Fatal(http.ListenAndServe("localhost:8000", nil)) }
여까지 코드로 확인한 Handler와 Multiplexer에 대한 설명이었습니다.
'Golang > Web App' 카테고리의 다른 글
Header와 body 확인하기 (1) | 2019.02.09 |
---|---|
체이닝 함수 (0) | 2019.02.06 |
핸들링 요청과 핸들링 함수 (0) | 2019.02.02 |
첫 번째 웹 서버(Hello Tongchun!) (0) | 2019.01.31 |