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 }