Open robert-min opened 1 year ago
package config
import ( "io" "log" )
type AppConfig struct { Logger *log.Logger }
func InitConfig(w io.Writer) AppConfig { return AppConfig{ Logger: log.New(w, "", log.Ldate|log.Ltime|log.Lshortfile), } }
package handlers
import (
"fmt"
"net/http"
"github.com/handson-go/chap6/complex_server/config"
)
type app struct {
conf config.AppConfig
handler func(w http.ResponseWriter, r *http.Request, conf config.AppConfig)
}
func (a app) ServeHTTP(w http.ResponseWriter, r *http.Request) {
a.handler(w, r, a.conf)
}
func apiHandler(w http.ResponseWriter, r *http.Request, conf config.AppConfig) {
fmt.Fprintf(w, "Hello, world!")
}
func healthzHandler(w http.ResponseWriter, r *http.Request, conf config.AppConfig) {
if r.Method != "GET" {
conf.Logger.Printf("error=\"Invalid request\" path=%s method=%s", r.URL.Path, r.Method)
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
fmt.Fprintf(w, "ok")
}
func panicHandler(w http.ResponseWriter, r *http.Request, conf config.AppConfig) {
panic("I panicked")
}
package handlers
import (
"net/http"
"github.com/handson-go/chap6/complex_server/config"
)
func Register(mux *http.ServeMux, conf config.AppConfig) {
mux.Handle("/healthz", &app{conf: conf, handler: healthzHandler})
mux.Handle("/api", &app{conf: conf, handler: apiHandler})
mux.Handle("/panic", &app{conf: conf, handler: panicHandler})
}
package middleware
import (
"fmt"
"net/http"
"time"
"github.com/handson-go/chap6/complex_server/config"
)
func loggingMiddleware(h http.Handler, c config.AppConfig) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t1 := time.Now()
h.ServeHTTP(w, r)
requestDuration := time.Now().Sub(t1).Seconds()
c.Logger.Printf("protocol=%s path=%s method=%s duration=%f", r.Proto, r.URL.Path, r.Method, requestDuration)
})
}
func panicMiddleware(h http.Handler, c config.AppConfig) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rValue := recover(); rValue != nil {
c.Logger.Println("panic detected", rValue)
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "Unexpected server error occured")
}
}()
h.ServeHTTP(w, r)
})
}
package middleware
import (
"net/http"
"github.com/handson-go/chap6/complex_server/config"
)
func RegisterMiddleware(mux *http.ServeMux, c config.AppConfig) http.Handler {
return loggingMiddleware(panicMiddleware(mux, c), c)
}
package main
import (
"io"
"log"
"net/http"
"os"
"github.com/handson-go/chap6/complex_server/config"
"github.com/handson-go/chap6/complex_server/handlers"
"github.com/handson-go/chap6/complex_server/middleware"
)
func setupServer(mux *http.ServeMux, w io.Writer) http.Handler {
conf := config.InitConfig(w)
handlers.Register(mux, conf)
return middleware.RegisterMiddleware(mux, conf)
}
func main() {
listenAddr := os.Getenv("LISTEN_ADDR")
if len(listenAddr) == 0 {
listenAddr = ":8080"
}
mux := http.NewServeMux()
wrappedMux := setupServer(mux, os.Stdout)
log.Fatal(http.ListenAndServe(listenAddr, wrappedMux))
}
func TestApiHandler(t *testing.T) {
r := httptest.NewRequest("GET", "/api", nil)
w := httptest.NewRecorder()
b := new(bytes.Buffer)
c := config.InitConfig(b)
apiHandler(w, r, c)
resp := w.Result()
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("Error response body: %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected response status: %v, Got: %v\n", http.StatusOK, resp.StatusCode)
}
expectedResponsBody := "Hello, world!"
if string(body) != expectedResponsBody {
t.Errorf("Expected response: %s, Got: %s\n", expectedResponsBody, string(body))
}
}
func TestSetupServer(t *testing.T) {
b := new(bytes.Buffer)
mux := http.NewServeMux()
wrappedMux := setupServer(mux, b)
ts := httptest.NewServer(wrappedMux)
defer ts.Close()
resp, err := http.Get(ts.URL + "/panic")
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != http.StatusInternalServerError {
t.Errorf(
"Expected response status to be: %v, Got: %v",
http.StatusInternalServerError,
resp.StatusCode,
)
}
logs := b.String()
expectedLogFragments := []string{
"path=/panic method=GET duration=",
"panic detected",
}
for _, log := range expectedLogFragments {
if !strings.Contains(logs, log) {
t.Errorf(
"Expected logs to contain: %s, Got: %s",
log, logs,
)
}
}
}
이전에 작성한 코드 package 단위로 분리