github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/api/server/server.go (about) 1 package server 2 3 import ( 4 "fmt" 5 "net/http" 6 "os" 7 8 "github.com/go-acme/lego/v3/providers/dns/cloudflare" 9 "github.com/gorilla/mux" 10 "github.com/tickoalcantara12/micro/v3/plugin" 11 pb "github.com/tickoalcantara12/micro/v3/proto/api" 12 "github.com/tickoalcantara12/micro/v3/service" 13 "github.com/tickoalcantara12/micro/v3/service/api" 14 "github.com/tickoalcantara12/micro/v3/service/api/auth" 15 "github.com/tickoalcantara12/micro/v3/service/api/handler" 16 aapi "github.com/tickoalcantara12/micro/v3/service/api/handler/api" 17 "github.com/tickoalcantara12/micro/v3/service/api/handler/event" 18 ahttp "github.com/tickoalcantara12/micro/v3/service/api/handler/http" 19 "github.com/tickoalcantara12/micro/v3/service/api/handler/rpc" 20 "github.com/tickoalcantara12/micro/v3/service/api/handler/web" 21 "github.com/tickoalcantara12/micro/v3/service/api/resolver" 22 "github.com/tickoalcantara12/micro/v3/service/api/resolver/grpc" 23 "github.com/tickoalcantara12/micro/v3/service/api/resolver/host" 24 "github.com/tickoalcantara12/micro/v3/service/api/resolver/path" 25 "github.com/tickoalcantara12/micro/v3/service/api/resolver/subdomain" 26 "github.com/tickoalcantara12/micro/v3/service/api/router" 27 regRouter "github.com/tickoalcantara12/micro/v3/service/api/router/registry" 28 httpapi "github.com/tickoalcantara12/micro/v3/service/api/server/http" 29 "github.com/tickoalcantara12/micro/v3/service/logger" 30 "github.com/tickoalcantara12/micro/v3/service/registry" 31 "github.com/tickoalcantara12/micro/v3/service/store" 32 "github.com/tickoalcantara12/micro/v3/util/acme" 33 "github.com/tickoalcantara12/micro/v3/util/acme/autocert" 34 "github.com/tickoalcantara12/micro/v3/util/acme/certmagic" 35 "github.com/tickoalcantara12/micro/v3/util/helper" 36 "github.com/tickoalcantara12/micro/v3/util/opentelemetry" 37 "github.com/tickoalcantara12/micro/v3/util/opentelemetry/jaeger" 38 "github.com/tickoalcantara12/micro/v3/util/sync/memory" 39 "github.com/tickoalcantara12/micro/v3/util/wrapper" 40 "github.com/opentracing/opentracing-go" 41 "github.com/urfave/cli/v2" 42 ) 43 44 var ( 45 Name = "api" 46 Address = ":8080" 47 Handler = "meta" 48 Resolver = "micro" 49 APIPath = "/" 50 ProxyPath = "/{service:[a-zA-Z0-9]+}" 51 Namespace = "" 52 ACMEProvider = "autocert" 53 ACMEChallengeProvider = "cloudflare" 54 ACMECA = acme.LetsEncryptProductionCA 55 ) 56 57 var ( 58 Flags = []cli.Flag{ 59 &cli.StringFlag{ 60 Name: "address", 61 Usage: "Set the api address e.g 0.0.0.0:8080", 62 EnvVars: []string{"MICRO_API_ADDRESS"}, 63 }, 64 &cli.StringFlag{ 65 Name: "handler", 66 Usage: "Specify the request handler to be used for mapping HTTP requests to services; {api, event, http, rpc}", 67 EnvVars: []string{"MICRO_API_HANDLER"}, 68 }, 69 &cli.StringFlag{ 70 Name: "namespace", 71 Usage: "Set the namespace used by the API e.g. com.example", 72 EnvVars: []string{"MICRO_API_NAMESPACE"}, 73 }, 74 &cli.StringFlag{ 75 Name: "resolver", 76 Usage: "Set the hostname resolver used by the API {host, path, grpc}", 77 EnvVars: []string{"MICRO_API_RESOLVER"}, 78 }, 79 &cli.BoolFlag{ 80 Name: "enable_cors", 81 Usage: "Enable CORS, allowing the API to be called by frontend applications", 82 EnvVars: []string{"MICRO_API_ENABLE_CORS"}, 83 Value: true, 84 }, 85 &cli.BoolFlag{ 86 Name: "enable_acme", 87 Usage: "Enables ACME support via Let's Encrypt. ACME hosts should also be specified.", 88 EnvVars: []string{"MICRO_API_ENABLE_ACME"}, 89 }, 90 &cli.StringFlag{ 91 Name: "acme_hosts", 92 Usage: "Comma separated list of hostnames to manage ACME certs for", 93 EnvVars: []string{"MICRO_API_ACME_HOSTS"}, 94 }, 95 &cli.StringFlag{ 96 Name: "acme_provider", 97 Usage: "The provider that will be used to communicate with Let's Encrypt. Valid options: autocert, certmagic", 98 EnvVars: []string{"MICRO_API_ACME_PROVIDER"}, 99 }, 100 &cli.BoolFlag{ 101 Name: "enable_tls", 102 Usage: "Enable TLS support. Expects cert and key file to be specified", 103 EnvVars: []string{"MICRO_API_ENABLE_TLS"}, 104 }, 105 &cli.StringFlag{ 106 Name: "tls_cert_file", 107 Usage: "Path to the TLS Certificate file", 108 EnvVars: []string{"MICRO_API_TLS_CERT_FILE"}, 109 }, 110 &cli.StringFlag{ 111 Name: "tls_key_file", 112 Usage: "Path to the TLS Key file", 113 EnvVars: []string{"MICRO_API_TLS_KEY_FILE"}, 114 }, 115 &cli.StringFlag{ 116 Name: "tls_client_ca_file", 117 Usage: "Path to the TLS CA file to verify clients against", 118 EnvVars: []string{"MICRO_API_TLS_CLIENT_CA_FILE"}, 119 }, 120 } 121 ) 122 123 func Run(ctx *cli.Context) error { 124 if len(ctx.String("server_name")) > 0 { 125 Name = ctx.String("server_name") 126 } 127 if len(ctx.String("address")) > 0 { 128 Address = ctx.String("address") 129 } 130 if len(ctx.String("handler")) > 0 { 131 Handler = ctx.String("handler") 132 } 133 if len(ctx.String("resolver")) > 0 { 134 Resolver = ctx.String("resolver") 135 } 136 if len(ctx.String("acme_provider")) > 0 { 137 ACMEProvider = ctx.String("acme_provider") 138 } 139 if len(ctx.String("namespace")) > 0 { 140 Namespace = ctx.String("namespace") 141 } 142 if len(ctx.String("api_handler")) > 0 { 143 Handler = ctx.String("api_handler") 144 } 145 if len(ctx.String("api_address")) > 0 { 146 Address = ctx.String("api_address") 147 } 148 // initialise service 149 srv := service.New(service.Name(Name)) 150 151 // Init API 152 var opts []api.Option 153 154 if ctx.Bool("enable_acme") { 155 hosts := helper.ACMEHosts(ctx) 156 opts = append(opts, api.EnableACME(true)) 157 opts = append(opts, api.ACMEHosts(hosts...)) 158 switch ACMEProvider { 159 case "autocert": 160 opts = append(opts, api.ACMEProvider(autocert.NewProvider())) 161 case "certmagic": 162 if ACMEChallengeProvider != "cloudflare" { 163 logger.Fatal("The only implemented DNS challenge provider is cloudflare") 164 } 165 166 apiToken := os.Getenv("CF_API_TOKEN") 167 if len(apiToken) == 0 { 168 logger.Fatal("env variables CF_API_TOKEN and CF_ACCOUNT_ID must be set") 169 } 170 171 storage := certmagic.NewStorage( 172 memory.NewSync(), 173 store.DefaultStore, 174 ) 175 176 config := cloudflare.NewDefaultConfig() 177 config.AuthToken = apiToken 178 config.ZoneToken = apiToken 179 challengeProvider, err := cloudflare.NewDNSProviderConfig(config) 180 if err != nil { 181 logger.Fatal(err.Error()) 182 } 183 184 opts = append(opts, 185 api.ACMEProvider( 186 certmagic.NewProvider( 187 acme.AcceptToS(true), 188 acme.CA(ACMECA), 189 acme.Cache(storage), 190 acme.ChallengeProvider(challengeProvider), 191 acme.OnDemand(false), 192 ), 193 ), 194 ) 195 default: 196 logger.Fatalf("%s is not a valid ACME provider\n", ACMEProvider) 197 } 198 } else if ctx.Bool("enable_tls") { 199 config, err := helper.TLSConfig(ctx) 200 if err != nil { 201 fmt.Println(err.Error()) 202 return err 203 } 204 205 opts = append(opts, api.EnableTLS(true)) 206 opts = append(opts, api.TLSConfig(config)) 207 } 208 209 if ctx.Bool("enable_cors") { 210 opts = append(opts, api.EnableCORS(true)) 211 } 212 213 // create the router 214 var h http.Handler 215 r := mux.NewRouter() 216 h = r 217 218 // return version and list of services 219 r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 220 if r.Method == "OPTIONS" { 221 return 222 } 223 224 response := fmt.Sprintf(`{"version": "%s"}`, ctx.App.Version) 225 w.Write([]byte(response)) 226 }) 227 228 // strip favicon.ico 229 r.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {}) 230 231 // resolver options 232 ropts := []resolver.Option{ 233 resolver.WithServicePrefix(Namespace), 234 resolver.WithHandler(Handler), 235 } 236 237 // default resolver 238 rr := NewResolver(ropts...) 239 240 switch Resolver { 241 case "subdomain": 242 rr = subdomain.NewResolver(rr) 243 case "host": 244 rr = host.NewResolver(ropts...) 245 case "path": 246 rr = path.NewResolver(ropts...) 247 case "grpc": 248 rr = grpc.NewResolver(ropts...) 249 } 250 251 switch Handler { 252 case "rpc": 253 logger.Infof("Registering API RPC Handler at %s", APIPath) 254 rt := regRouter.NewRouter( 255 router.WithHandler(rpc.Handler), 256 router.WithResolver(rr), 257 router.WithRegistry(registry.DefaultRegistry), 258 ) 259 rp := rpc.NewHandler( 260 handler.WithNamespace(Namespace), 261 handler.WithRouter(rt), 262 handler.WithClient(srv.Client()), 263 ) 264 r.PathPrefix(APIPath).Handler(rp) 265 case "api": 266 logger.Infof("Registering API Request Handler at %s", APIPath) 267 rt := regRouter.NewRouter( 268 router.WithHandler(aapi.Handler), 269 router.WithResolver(rr), 270 router.WithRegistry(registry.DefaultRegistry), 271 ) 272 ap := aapi.NewHandler( 273 handler.WithNamespace(Namespace), 274 handler.WithRouter(rt), 275 handler.WithClient(srv.Client()), 276 ) 277 r.PathPrefix(APIPath).Handler(ap) 278 case "event": 279 logger.Infof("Registering API Event Handler at %s", APIPath) 280 rt := regRouter.NewRouter( 281 router.WithHandler(event.Handler), 282 router.WithResolver(rr), 283 router.WithRegistry(registry.DefaultRegistry), 284 ) 285 ev := event.NewHandler( 286 handler.WithNamespace(Namespace), 287 handler.WithRouter(rt), 288 handler.WithClient(srv.Client()), 289 ) 290 r.PathPrefix(APIPath).Handler(ev) 291 case "http": 292 logger.Infof("Registering API HTTP Handler at %s", ProxyPath) 293 rt := regRouter.NewRouter( 294 router.WithHandler(ahttp.Handler), 295 router.WithResolver(rr), 296 router.WithRegistry(registry.DefaultRegistry), 297 ) 298 ht := ahttp.NewHandler( 299 handler.WithNamespace(Namespace), 300 handler.WithRouter(rt), 301 handler.WithClient(srv.Client()), 302 ) 303 r.PathPrefix(ProxyPath).Handler(ht) 304 case "web": 305 logger.Infof("Registering API Web Handler at %s", APIPath) 306 rt := regRouter.NewRouter( 307 router.WithHandler(web.Handler), 308 router.WithResolver(rr), 309 router.WithRegistry(registry.DefaultRegistry), 310 ) 311 w := web.NewHandler( 312 handler.WithNamespace(Namespace), 313 handler.WithRouter(rt), 314 handler.WithClient(srv.Client()), 315 ) 316 r.PathPrefix(APIPath).Handler(w) 317 default: 318 logger.Infof("Registering API Default Handler at %s", APIPath) 319 rt := regRouter.NewRouter( 320 router.WithResolver(rr), 321 router.WithRegistry(registry.DefaultRegistry), 322 ) 323 r.PathPrefix(APIPath).Handler(Meta(srv.Client(), rt, Namespace)) 324 } 325 326 // register all the http handler plugins 327 for _, p := range plugin.Plugins() { 328 if v := p.Handler(); v != nil { 329 h = v(h) 330 } 331 } 332 333 reporterAddress := ctx.String("tracing_reporter_address") 334 if len(reporterAddress) == 0 { 335 reporterAddress = jaeger.DefaultReporterAddress 336 } 337 // Create a new Jaeger opentracer: 338 openTracer, traceCloser, err := jaeger.New( 339 opentelemetry.WithServiceName("API"), 340 opentelemetry.WithTraceReporterAddress(reporterAddress), 341 ) 342 logger.Infof("Setting jaeger global tracer to %s", reporterAddress) 343 defer traceCloser.Close() // Make sure we flush any pending traces before shutdown: 344 if err != nil { 345 logger.Warnf("Unable to prepare a Jaeger tracer: %s", err) 346 } else { 347 // Set the global default opentracing tracer: 348 opentracing.SetGlobalTracer(openTracer) 349 } 350 opentelemetry.DefaultOpenTracer = openTracer 351 352 // append the opentelemetry wrapper 353 h = wrapper.HTTPWrapper(h) 354 355 // append the auth wrapper 356 h = auth.Wrapper(rr, Namespace)(h) 357 358 // create a new api server with wrappers 359 api := httpapi.NewServer(Address) 360 // initialise 361 api.Init(opts...) 362 // register the http handler 363 api.Handle("/", h) 364 365 // Start API 366 if err := api.Start(); err != nil { 367 logger.Fatal(err) 368 } 369 370 // register the rpc handler 371 pb.RegisterApiHandler(srv.Server(), &handler.APIHandler{}) 372 373 // Run server 374 if err := srv.Run(); err != nil { 375 logger.Fatal(err) 376 } 377 378 // Stop API 379 if err := api.Stop(); err != nil { 380 logger.Fatal(err) 381 } 382 383 return nil 384 }