github.com/artisanhe/tools@v1.0.1-0.20210607022958-19a8fef2eb04/courier/transport_http/serve.go (about)

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