robert-min / handson-go

Go-lang hands on guide
0 stars 0 forks source link

Chapter6. HTTP Server develop - Divide package #18

Open robert-min opened 1 year ago

robert-min commented 1 year ago

이전에 작성한 코드 package 단위로 분리

robert-min commented 1 year ago

config/config.go

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), } }

robert-min commented 1 year ago

handlers/handlers.go

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")
}

handlers/register.go

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})
}
robert-min commented 1 year ago

middleware/middleware.go

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)
    })
}

middleware/register.go

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)
}
robert-min commented 1 year ago

server.go

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))
}
robert-min commented 1 year ago

handlers test code


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))
    }
}
robert-min commented 1 year ago

server test code


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,
            )
        }
    }
}