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 }