github.com/zntrio/harp/v2@v2.0.9/pkg/sdk/platform/server.go (about) 1 // Licensed to Elasticsearch B.V. under one or more contributor 2 // license agreements. See the NOTICE file distributed with 3 // this work for additional information regarding copyright 4 // ownership. Elasticsearch B.V. licenses this file to you under 5 // the Apache License, Version 2.0 (the "License"); you may 6 // not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, 12 // software distributed under the License is distributed on an 13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 // KIND, either express or implied. See the License for the 15 // specific language governing permissions and limitations 16 // under the License. 17 18 package platform 19 20 import ( 21 "context" 22 "fmt" 23 "net" 24 "net/http" 25 "os" 26 "os/signal" 27 "syscall" 28 "time" 29 30 "github.com/dchest/uniuri" 31 "github.com/oklog/run" 32 "go.uber.org/zap" 33 34 "github.com/zntrio/harp/v2/pkg/sdk/log" 35 "github.com/zntrio/harp/v2/pkg/sdk/platform/diagnostic" 36 "github.com/zntrio/harp/v2/pkg/sdk/platform/reloader" 37 ) 38 39 // ----------------------------------------------------------------------------- 40 41 // Server represents platform server. 42 type Server struct { 43 Debug bool 44 Name string 45 Version string 46 Revision string 47 Instrumentation InstrumentationConfig 48 Network string 49 Address string 50 Builder func(ln net.Listener, group *run.Group) 51 } 52 53 // Serve starts the server listening process. 54 func Serve(ctx context.Context, srv *Server) error { 55 // Generate an instance identifier 56 appID := uniuri.NewLen(64) 57 58 // Prepare logger 59 log.Setup(ctx, &log.Options{ 60 Debug: srv.Debug, 61 AppName: srv.Name, 62 AppID: appID, 63 Version: srv.Version, 64 Revision: srv.Revision, 65 LogLevel: srv.Instrumentation.Logs.Level, 66 }) 67 68 // Preparing instrumentation 69 instrumentationRouter := instrumentServer(ctx, srv) 70 71 // Configure graceful restart 72 upg := reloader.Create(ctx) 73 74 var group run.Group 75 76 // Instrumentation server 77 { 78 ln, err := upg.Listen(srv.Instrumentation.Network, srv.Instrumentation.Listen) 79 if err != nil { 80 return fmt.Errorf("platform: unable to start instrumentation server: %w", err) 81 } 82 83 server := &http.Server{ 84 Handler: instrumentationRouter, 85 // Set timeouts to avoid Slowloris attacks. 86 ReadHeaderTimeout: time.Second * 20, 87 WriteTimeout: time.Second * 60, 88 ReadTimeout: time.Second * 60, 89 IdleTimeout: time.Second * 120, 90 } 91 92 group.Add( 93 func() error { 94 log.For(ctx).Info("Starting instrumentation server", zap.String("address", ln.Addr().String())) 95 return server.Serve(ln) 96 }, 97 func(e error) { 98 log.For(ctx).Info("Shutting instrumentation server down") 99 100 ctxShutdown, cancel := context.WithTimeout(ctx, 60*time.Second) 101 defer cancel() 102 103 log.CheckErrCtx(ctx, "Error raised while shutting down the server", server.Shutdown(ctxShutdown)) 104 log.SafeClose(server, "Unable to close instrumentation server") 105 }, 106 ) 107 } 108 109 // Initialiaze network listener 110 ln, err := upg.Listen(srv.Network, srv.Address) 111 if err != nil { 112 return fmt.Errorf("unable to start server listener: %w", err) 113 } 114 115 // Initialize the component 116 srv.Builder(ln, &group) 117 118 // Setup signal handler 119 { 120 var ( 121 cancelInterrupt = make(chan struct{}) 122 ch = make(chan os.Signal, 2) 123 ) 124 defer close(ch) 125 126 group.Add( 127 func() error { 128 signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) 129 130 select { 131 case <-ch: 132 log.For(ctx).Info("Captured signal") 133 case <-cancelInterrupt: 134 } 135 136 return nil 137 }, 138 func(e error) { 139 close(cancelInterrupt) 140 signal.Stop(ch) 141 }, 142 ) 143 } 144 145 // Register graceful restart handler 146 upg.SetupGracefulRestart(ctx, group) 147 148 // Run goroutine group 149 return group.Run() 150 } 151 152 func instrumentServer(ctx context.Context, srv *Server) *http.ServeMux { 153 instrumentationRouter := http.NewServeMux() 154 155 // Register common features 156 if srv.Instrumentation.Diagnostic.Enabled { 157 cancelFunc, err := diagnostic.Register(ctx, &srv.Instrumentation.Diagnostic.Config, instrumentationRouter) 158 if err != nil { 159 log.For(ctx).Fatal("Unable to register diagnostic instrumentation", zap.Error(err)) 160 } 161 defer cancelFunc() 162 } 163 164 return instrumentationRouter 165 }