github.com/johnnyeven/libtools@v0.0.0-20191126065708-61829c1adf46/courier/transport_http/serve.go (about)

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