github.com/profzone/eden-framework@v1.0.10/pkg/courier/transport_http/serve.go (about)

     1  package transport_http
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"github.com/profzone/envconfig"
     7  	"net/http"
     8  	_ "net/http/pprof"
     9  	"os"
    10  	"os/signal"
    11  	"reflect"
    12  	"regexp"
    13  	"sort"
    14  	"strings"
    15  	"time"
    16  
    17  	"github.com/fatih/color"
    18  	"github.com/julienschmidt/httprouter"
    19  
    20  	"github.com/profzone/eden-framework/pkg/courier"
    21  )
    22  
    23  type ServeHTTP struct {
    24  	Name         string
    25  	IP           string
    26  	Port         int
    27  	WriteTimeout envconfig.Duration
    28  	ReadTimeout  envconfig.Duration
    29  	WithCORS     bool
    30  	router       *httprouter.Router
    31  }
    32  
    33  func (s ServeHTTP) MarshalDefaults(v interface{}) {
    34  	if h, ok := v.(*ServeHTTP); ok {
    35  		if h.Name == "" {
    36  			h.Name = os.Getenv("PROJECT_NAME")
    37  		}
    38  
    39  		if h.Port == 0 {
    40  			h.Port = 8000
    41  		}
    42  
    43  		if h.ReadTimeout == 0 {
    44  			h.ReadTimeout = envconfig.Duration(15 * time.Second)
    45  		}
    46  
    47  		if h.WriteTimeout == 0 {
    48  			h.WriteTimeout = envconfig.Duration(15 * time.Second)
    49  		}
    50  	}
    51  }
    52  
    53  func (s *ServeHTTP) Serve(router *courier.Router) error {
    54  	s.MarshalDefaults(s)
    55  	s.router = s.convertRouterToHttpRouter(router)
    56  	s.router.GET("/healthz", func(http.ResponseWriter, *http.Request, httprouter.Params) {})
    57  
    58  	srv := &http.Server{
    59  		Handler:      s,
    60  		Addr:         fmt.Sprintf("%s:%d", s.IP, s.Port),
    61  		WriteTimeout: time.Duration(s.WriteTimeout),
    62  		ReadTimeout:  time.Duration(s.ReadTimeout),
    63  	}
    64  
    65  	idleConnsClosed := make(chan struct{})
    66  	go func() {
    67  		sigint := make(chan os.Signal, 1)
    68  		signal.Notify(sigint, os.Interrupt)
    69  		<-sigint
    70  		if err := srv.Shutdown(context.Background()); err != nil {
    71  			fmt.Printf("HTTP server Shutdown: %v", err)
    72  		}
    73  		close(idleConnsClosed)
    74  	}()
    75  
    76  	fmt.Printf("[Courier] listen on %s\n", srv.Addr)
    77  	return srv.ListenAndServe()
    78  }
    79  
    80  var RxHttpRouterPath = regexp.MustCompile("/:([^/]+)")
    81  
    82  func (s *ServeHTTP) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    83  	if s.WithCORS {
    84  		headers := w.Header()
    85  		setCORS(&headers)
    86  	}
    87  	s.router.ServeHTTP(w, req)
    88  }
    89  
    90  func (s *ServeHTTP) convertRouterToHttpRouter(router *courier.Router) *httprouter.Router {
    91  	routes := router.Routes()
    92  
    93  	if len(routes) == 0 {
    94  		panic(fmt.Sprintf("need to register operation before Listion"))
    95  	}
    96  
    97  	r := httprouter.New()
    98  	r.Handler(http.MethodGet, "/debug/pprof/:item", http.DefaultServeMux)
    99  
   100  	sort.Slice(routes, func(i, j int) bool {
   101  		return getPath(routes[i]) < getPath(routes[j])
   102  	})
   103  
   104  	for _, route := range routes {
   105  		method := getMethod(route)
   106  		p := getPath(route)
   107  
   108  		finalOperators, operatorTypeNames := route.EffectiveOperators()
   109  
   110  		if len(finalOperators) == 0 {
   111  			panic(fmt.Errorf(
   112  				"[Courier] No available operator %v",
   113  				route.Operators,
   114  			))
   115  		}
   116  
   117  		if method == "" {
   118  			panic(fmt.Errorf(
   119  				"[Courier] Missing method of %s\n",
   120  				color.CyanString(reflect.TypeOf(finalOperators[len(finalOperators)-1]).Name()),
   121  			))
   122  		}
   123  
   124  		lengthOfOperatorTypes := len(operatorTypeNames)
   125  
   126  		for i := range operatorTypeNames {
   127  			if i < lengthOfOperatorTypes-1 {
   128  				operatorTypeNames[i] = color.MagentaString(operatorTypeNames[i])
   129  			} else {
   130  				operatorTypeNames[i] = color.CyanString(operatorTypeNames[i])
   131  			}
   132  		}
   133  
   134  		fmt.Printf(
   135  			"[Courier] %s %s\n",
   136  			colorByMethod(method)("%s %s", method[0:3], RxHttpRouterPath.ReplaceAllString(p, "/{$1}")),
   137  			strings.Join(operatorTypeNames, " "),
   138  		)
   139  
   140  		r.Handle(method, p, CreateHttpHandler(s, finalOperators...))
   141  	}
   142  
   143  	return r
   144  }
   145  
   146  func getMethod(route *courier.Route) string {
   147  	if withHttpMethod, ok := route.Operators[len(route.Operators)-1].(IMethod); ok {
   148  		return string(withHttpMethod.Method())
   149  	}
   150  	return ""
   151  }
   152  
   153  func getPath(route *courier.Route) string {
   154  	p := "/"
   155  	for _, operator := range route.Operators {
   156  		if WithHttpPath, ok := operator.(IPath); ok {
   157  			p += WithHttpPath.Path()
   158  		}
   159  	}
   160  	return httprouter.CleanPath(p)
   161  }
   162  
   163  func colorByMethod(method string) func(f string, args ...interface{}) string {
   164  	switch method {
   165  	case http.MethodGet:
   166  		return color.BlueString
   167  	case http.MethodPost:
   168  		return color.GreenString
   169  	case http.MethodPut:
   170  		return color.YellowString
   171  	case http.MethodDelete:
   172  		return color.RedString
   173  	case http.MethodHead:
   174  		return color.WhiteString
   175  	default:
   176  		return color.BlackString
   177  	}
   178  }