github.com/iron-io/functions@v0.0.0-20180820112432-d59d7d1c40b2/api/server/server.go (about) 1 package server 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "net" 9 "net/http" 10 "os" 11 "path" 12 "sync" 13 14 "github.com/Sirupsen/logrus" 15 "github.com/gin-gonic/gin" 16 "github.com/iron-io/functions/api" 17 "github.com/iron-io/functions/api/datastore" 18 "github.com/iron-io/functions/api/models" 19 "github.com/iron-io/functions/api/mqs" 20 "github.com/iron-io/functions/api/runner" 21 "github.com/iron-io/functions/api/runner/task" 22 "github.com/iron-io/functions/api/server/internal/routecache" 23 "github.com/iron-io/runner/common" 24 "github.com/spf13/viper" 25 "github.com/ucirello/supervisor" 26 ) 27 28 const ( 29 EnvLogLevel = "log_level" 30 EnvMQURL = "mq_url" 31 EnvDBURL = "db_url" 32 EnvPort = "port" // be careful, Gin expects this variable to be "port" 33 EnvAPIURL = "api_url" 34 ) 35 36 type Server struct { 37 Datastore models.Datastore 38 Runner *runner.Runner 39 Router *gin.Engine 40 MQ models.MessageQueue 41 Enqueue models.Enqueue 42 43 apiURL string 44 45 specialHandlers []SpecialHandler 46 appListeners []AppListener 47 middlewares []Middleware 48 runnerListeners []RunnerListener 49 50 mu sync.Mutex // protects hotroutes 51 hotroutes *routecache.Cache 52 tasks chan task.Request 53 singleflight singleflight // singleflight assists Datastore 54 } 55 56 const cacheSize = 1024 57 58 // NewFromEnv creates a new IronFunctions server based on env vars. 59 func NewFromEnv(ctx context.Context) *Server { 60 ds, err := datastore.New(viper.GetString(EnvDBURL)) 61 if err != nil { 62 logrus.WithError(err).Fatalln("Error initializing datastore.") 63 } 64 65 mq, err := mqs.New(viper.GetString(EnvMQURL)) 66 if err != nil { 67 logrus.WithError(err).Fatal("Error initializing message queue.") 68 } 69 70 apiURL := viper.GetString(EnvAPIURL) 71 72 return New(ctx, ds, mq, apiURL) 73 } 74 75 // New creates a new IronFunctions server with the passed in datastore, message queue and API URL 76 func New(ctx context.Context, ds models.Datastore, mq models.MessageQueue, apiURL string, opts ...ServerOption) *Server { 77 metricLogger := runner.NewMetricLogger() 78 funcLogger := runner.NewFuncLogger() 79 80 rnr, err := runner.New(ctx, funcLogger, metricLogger) 81 if err != nil { 82 logrus.WithError(err).Fatalln("Failed to create a runner") 83 return nil 84 } 85 86 tasks := make(chan task.Request) 87 s := &Server{ 88 Runner: rnr, 89 Router: gin.New(), 90 Datastore: ds, 91 MQ: mq, 92 hotroutes: routecache.New(cacheSize), 93 tasks: tasks, 94 Enqueue: DefaultEnqueue, 95 apiURL: apiURL, 96 } 97 98 s.Router.Use(prepareMiddleware(ctx)) 99 s.bindHandlers(ctx) 100 s.setupMiddlewares() 101 102 for _, opt := range opts { 103 opt(s) 104 } 105 return s 106 } 107 108 // todo: remove this or change name 109 func prepareMiddleware(ctx context.Context) gin.HandlerFunc { 110 return func(c *gin.Context) { 111 ctx, _ := common.LoggerWithFields(ctx, extractFields(c)) 112 113 if appName := c.Param(api.CApp); appName != "" { 114 c.Set(api.AppName, appName) 115 } 116 117 if routePath := c.Param(api.CRoute); routePath != "" { 118 c.Set(api.Path, routePath) 119 } 120 121 // todo: can probably replace the "ctx" value with the Go 1.7 context on the http.Request 122 c.Set("ctx", ctx) 123 c.Request = c.Request.WithContext(ctx) 124 c.Next() 125 } 126 } 127 128 func DefaultEnqueue(ctx context.Context, mq models.MessageQueue, task *models.Task) (*models.Task, error) { 129 ctx, _ = common.LoggerWithFields(ctx, logrus.Fields{"call_id": task.ID}) 130 return mq.Push(ctx, task) 131 } 132 133 func (s *Server) cacheget(appname, path string) (*models.Route, bool) { 134 s.mu.Lock() 135 defer s.mu.Unlock() 136 route, ok := s.hotroutes.Get(appname, path) 137 if !ok { 138 return nil, false 139 } 140 return route, ok 141 } 142 143 func (s *Server) cacherefresh(route *models.Route) { 144 s.mu.Lock() 145 defer s.mu.Unlock() 146 s.hotroutes.Refresh(route) 147 } 148 149 func (s *Server) cachedelete(appname, path string) { 150 s.mu.Lock() 151 defer s.mu.Unlock() 152 s.hotroutes.Delete(appname, path) 153 } 154 155 func (s *Server) handleRunnerRequest(c *gin.Context) { 156 s.handleRequest(c, s.Enqueue) 157 } 158 159 func (s *Server) handleTaskRequest(c *gin.Context) { 160 ctx, _ := common.LoggerWithFields(c, nil) 161 switch c.Request.Method { 162 case "GET": 163 task, err := s.MQ.Reserve(ctx) 164 if err != nil { 165 logrus.WithError(err).Error() 166 c.JSON(http.StatusInternalServerError, simpleError(models.ErrRoutesList)) 167 return 168 } 169 c.JSON(http.StatusAccepted, task) 170 case "DELETE": 171 body, err := ioutil.ReadAll(c.Request.Body) 172 if err != nil { 173 logrus.WithError(err).Error() 174 c.JSON(http.StatusInternalServerError, err) 175 return 176 } 177 var task models.Task 178 if err = json.Unmarshal(body, &task); err != nil { 179 logrus.WithError(err).Error() 180 c.JSON(http.StatusInternalServerError, err) 181 return 182 } 183 184 if err := s.MQ.Delete(ctx, &task); err != nil { 185 logrus.WithError(err).Error() 186 c.JSON(http.StatusInternalServerError, err) 187 return 188 } 189 c.JSON(http.StatusAccepted, task) 190 } 191 } 192 193 func extractFields(c *gin.Context) logrus.Fields { 194 fields := logrus.Fields{"action": path.Base(c.HandlerName())} 195 for _, param := range c.Params { 196 fields[param.Key] = param.Value 197 } 198 return fields 199 } 200 201 func (s *Server) Start(ctx context.Context) { 202 ctx = contextWithSignal(ctx, os.Interrupt) 203 s.startGears(ctx) 204 close(s.tasks) 205 } 206 207 func (s *Server) setupMiddlewares() { 208 SetupJwtAuth(s) 209 } 210 211 func (s *Server) startGears(ctx context.Context) { 212 // By default it serves on :8080 unless a 213 // PORT environment variable was defined. 214 listen := fmt.Sprintf(":%d", viper.GetInt(EnvPort)) 215 listener, err := net.Listen("tcp", listen) 216 if err != nil { 217 logrus.WithError(err).Fatalln("Failed to serve functions API.") 218 } 219 logrus.Infof("Serving Functions API on address `%s`", listen) 220 221 svr := &supervisor.Supervisor{ 222 MaxRestarts: supervisor.AlwaysRestart, 223 Log: func(msg interface{}) { 224 logrus.Debug("supervisor: ", msg) 225 }, 226 } 227 228 svr.AddFunc(func(ctx context.Context) { 229 go func() { 230 err := http.Serve(listener, s.Router) 231 if err != nil { 232 logrus.Fatalf("Error serving API: %v", err) 233 } 234 }() 235 <-ctx.Done() 236 }) 237 238 svr.AddFunc(func(ctx context.Context) { 239 runner.RunAsyncRunner(ctx, s.apiURL, s.tasks, s.Runner) 240 }) 241 242 svr.AddFunc(func(ctx context.Context) { 243 runner.StartWorkers(ctx, s.Runner, s.tasks) 244 }) 245 246 svr.Serve(ctx) 247 } 248 249 func (s *Server) bindHandlers(ctx context.Context) { 250 engine := s.Router 251 252 engine.GET("/", handlePing) 253 engine.GET("/version", handleVersion) 254 engine.GET("/stats", s.handleStats) 255 256 v1 := engine.Group("/v1") 257 v1.Use(s.middlewareWrapperFunc(ctx)) 258 { 259 v1.GET("/apps", s.handleAppList) 260 v1.POST("/apps", s.handleAppCreate) 261 262 v1.GET("/apps/:app", s.handleAppGet) 263 v1.PATCH("/apps/:app", s.handleAppUpdate) 264 v1.DELETE("/apps/:app", s.handleAppDelete) 265 266 v1.GET("/routes", s.handleRouteList) 267 268 apps := v1.Group("/apps/:app") 269 { 270 apps.GET("/routes", s.handleRouteList) 271 apps.POST("/routes", s.handleRouteCreate) 272 apps.GET("/routes/*route", s.handleRouteGet) 273 apps.PATCH("/routes/*route", s.handleRouteUpdate) 274 apps.DELETE("/routes/*route", s.handleRouteDelete) 275 } 276 } 277 278 engine.DELETE("/tasks", s.handleTaskRequest) 279 engine.GET("/tasks", s.handleTaskRequest) 280 engine.Any("/r/:app/*route", s.handleRunnerRequest) 281 282 // This final route is used for extensions, see Server.Add 283 engine.NoRoute(s.handleSpecial) 284 } 285 286 type appResponse struct { 287 Message string `json:"message"` 288 App *models.App `json:"app"` 289 } 290 291 type appsResponse struct { 292 Message string `json:"message"` 293 Apps models.Apps `json:"apps"` 294 } 295 296 type routeResponse struct { 297 Message string `json:"message"` 298 Route *models.Route `json:"route"` 299 } 300 301 type routesResponse struct { 302 Message string `json:"message"` 303 Routes models.Routes `json:"routes"` 304 } 305 306 type tasksResponse struct { 307 Message string `json:"message"` 308 Task models.Task `json:"tasksResponse"` 309 }