github.com/prebid/prebid-server/v2@v2.18.0/stored_requests/config/config.go (about) 1 package config 2 3 import ( 4 "context" 5 "net/http" 6 "time" 7 8 "github.com/prebid/prebid-server/v2/metrics" 9 10 "github.com/golang/glog" 11 "github.com/julienschmidt/httprouter" 12 "github.com/prebid/prebid-server/v2/config" 13 "github.com/prebid/prebid-server/v2/stored_requests" 14 "github.com/prebid/prebid-server/v2/stored_requests/backends/db_fetcher" 15 "github.com/prebid/prebid-server/v2/stored_requests/backends/db_provider" 16 "github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher" 17 "github.com/prebid/prebid-server/v2/stored_requests/backends/file_fetcher" 18 "github.com/prebid/prebid-server/v2/stored_requests/backends/http_fetcher" 19 "github.com/prebid/prebid-server/v2/stored_requests/caches/memory" 20 "github.com/prebid/prebid-server/v2/stored_requests/caches/nil_cache" 21 "github.com/prebid/prebid-server/v2/stored_requests/events" 22 apiEvents "github.com/prebid/prebid-server/v2/stored_requests/events/api" 23 databaseEvents "github.com/prebid/prebid-server/v2/stored_requests/events/database" 24 httpEvents "github.com/prebid/prebid-server/v2/stored_requests/events/http" 25 "github.com/prebid/prebid-server/v2/util/task" 26 ) 27 28 // CreateStoredRequests returns three things: 29 // 30 // 1. A Fetcher which can be used to get Stored Requests 31 // 2. A function which should be called on shutdown for graceful cleanups. 32 // 33 // If any errors occur, the program will exit with an error message. 34 // It probably means you have a bad config or networking issue. 35 // 36 // As a side-effect, it will add some endpoints to the router if the config calls for it. 37 // In the future we should look for ways to simplify this so that it's not doing two things. 38 func CreateStoredRequests(cfg *config.StoredRequests, metricsEngine metrics.MetricsEngine, client *http.Client, router *httprouter.Router, provider db_provider.DbProvider) (fetcher stored_requests.AllFetcher, shutdown func()) { 39 // Create database connection if given options for one 40 if cfg.Database.ConnectionInfo.Database != "" { 41 if provider == nil { 42 glog.Infof("Connecting to Database for Stored %s. Driver=%s, DB=%s, host=%s, port=%d, user=%s", 43 cfg.DataType(), 44 cfg.Database.ConnectionInfo.Driver, 45 cfg.Database.ConnectionInfo.Database, 46 cfg.Database.ConnectionInfo.Host, 47 cfg.Database.ConnectionInfo.Port, 48 cfg.Database.ConnectionInfo.Username) 49 provider = db_provider.NewDbProvider(cfg.DataType(), cfg.Database.ConnectionInfo) 50 } 51 52 // Error out if config is trying to use multiple database connections for different stored requests (not supported yet) 53 if provider.Config() != cfg.Database.ConnectionInfo { 54 glog.Fatal("Multiple database connection settings found in config, only a single database connection is currently supported.") 55 } 56 } 57 58 eventProducers := newEventProducers(cfg, client, provider, metricsEngine, router) 59 fetcher = newFetcher(cfg, client, provider) 60 61 var shutdown1 func() 62 63 if cfg.InMemoryCache.Type != "" { 64 cache := newCache(cfg) 65 fetcher = stored_requests.WithCache(fetcher, cache, metricsEngine) 66 shutdown1 = addListeners(cache, eventProducers) 67 } 68 69 shutdown = func() { 70 if shutdown1 != nil { 71 shutdown1() 72 } 73 74 if provider == nil { 75 return 76 } 77 78 if err := provider.Close(); err != nil { 79 glog.Errorf("Error closing DB connection: %v", err) 80 } 81 } 82 83 return 84 } 85 86 // NewStoredRequests returns: 87 // 88 // 1. A function which should be called on shutdown for graceful cleanups. 89 // 2. A Fetcher which can be used to get Stored Requests for /openrtb2/auction 90 // 3. A Fetcher which can be used to get Stored Requests for /openrtb2/amp 91 // 4. A Fetcher which can be used to get Account data 92 // 5. A Fetcher which can be used to get Category Mapping data 93 // 6. A Fetcher which can be used to get Stored Requests for /openrtb2/video 94 // 95 // If any errors occur, the program will exit with an error message. 96 // It probably means you have a bad config or networking issue. 97 // 98 // As a side-effect, it will add some endpoints to the router if the config calls for it. 99 // In the future we should look for ways to simplify this so that it's not doing two things. 100 func NewStoredRequests(cfg *config.Configuration, metricsEngine metrics.MetricsEngine, client *http.Client, router *httprouter.Router) (shutdown func(), 101 fetcher stored_requests.Fetcher, 102 ampFetcher stored_requests.Fetcher, 103 accountsFetcher stored_requests.AccountFetcher, 104 categoriesFetcher stored_requests.CategoryFetcher, 105 videoFetcher stored_requests.Fetcher, 106 storedRespFetcher stored_requests.Fetcher) { 107 108 var provider db_provider.DbProvider 109 110 fetcher1, shutdown1 := CreateStoredRequests(&cfg.StoredRequests, metricsEngine, client, router, provider) 111 fetcher2, shutdown2 := CreateStoredRequests(&cfg.StoredRequestsAMP, metricsEngine, client, router, provider) 112 fetcher3, shutdown3 := CreateStoredRequests(&cfg.CategoryMapping, metricsEngine, client, router, provider) 113 fetcher4, shutdown4 := CreateStoredRequests(&cfg.StoredVideo, metricsEngine, client, router, provider) 114 fetcher5, shutdown5 := CreateStoredRequests(&cfg.Accounts, metricsEngine, client, router, provider) 115 fetcher6, shutdown6 := CreateStoredRequests(&cfg.StoredResponses, metricsEngine, client, router, provider) 116 117 fetcher = fetcher1.(stored_requests.Fetcher) 118 ampFetcher = fetcher2.(stored_requests.Fetcher) 119 categoriesFetcher = fetcher3.(stored_requests.CategoryFetcher) 120 videoFetcher = fetcher4.(stored_requests.Fetcher) 121 accountsFetcher = fetcher5.(stored_requests.AccountFetcher) 122 storedRespFetcher = fetcher6.(stored_requests.Fetcher) 123 124 shutdown = func() { 125 shutdown1() 126 shutdown2() 127 shutdown3() 128 shutdown4() 129 shutdown5() 130 shutdown6() 131 } 132 133 return 134 } 135 136 func addListeners(cache stored_requests.Cache, eventProducers []events.EventProducer) (shutdown func()) { 137 listeners := make([]*events.EventListener, 0, len(eventProducers)) 138 139 for _, ep := range eventProducers { 140 listener := events.SimpleEventListener() 141 go listener.Listen(cache, ep) 142 listeners = append(listeners, listener) 143 } 144 145 return func() { 146 for _, l := range listeners { 147 l.Stop() 148 } 149 } 150 } 151 152 func newFetcher(cfg *config.StoredRequests, client *http.Client, provider db_provider.DbProvider) (fetcher stored_requests.AllFetcher) { 153 idList := make(stored_requests.MultiFetcher, 0, 3) 154 155 if cfg.Files.Enabled { 156 fFetcher := newFilesystem(cfg.DataType(), cfg.Files.Path) 157 idList = append(idList, fFetcher) 158 } 159 if cfg.Database.FetcherQueries.QueryTemplate != "" { 160 glog.Infof("Loading Stored %s data via Database.\nQuery: %s", cfg.DataType(), cfg.Database.FetcherQueries.QueryTemplate) 161 idList = append(idList, db_fetcher.NewFetcher(provider, 162 cfg.Database.FetcherQueries.QueryTemplate, cfg.Database.FetcherQueries.QueryTemplate)) 163 } else if cfg.Database.CacheInitialization.Query != "" && cfg.Database.PollUpdates.Query != "" { 164 //in this case data will be loaded to cache via poll for updates event 165 idList = append(idList, empty_fetcher.EmptyFetcher{}) 166 } 167 if cfg.HTTP.Endpoint != "" { 168 glog.Infof("Loading Stored %s data via HTTP. endpoint=%s", cfg.DataType(), cfg.HTTP.Endpoint) 169 idList = append(idList, http_fetcher.NewFetcher(client, cfg.HTTP.Endpoint)) 170 } 171 172 fetcher = consolidate(cfg.DataType(), idList) 173 return 174 } 175 176 func newCache(cfg *config.StoredRequests) stored_requests.Cache { 177 cache := stored_requests.Cache{ 178 Requests: &nil_cache.NilCache{}, 179 Imps: &nil_cache.NilCache{}, 180 Responses: &nil_cache.NilCache{}, 181 Accounts: &nil_cache.NilCache{}, 182 } 183 switch { 184 case cfg.InMemoryCache.Type == "none": 185 glog.Warningf("No %s cache configured. The %s Fetcher backend will be used for all data requests", cfg.DataType(), cfg.DataType()) 186 case cfg.DataType() == config.AccountDataType: 187 cache.Accounts = memory.NewCache(cfg.InMemoryCache.Size, cfg.InMemoryCache.TTL, "Accounts") 188 default: 189 cache.Requests = memory.NewCache(cfg.InMemoryCache.RequestCacheSize, cfg.InMemoryCache.TTL, "Requests") 190 cache.Imps = memory.NewCache(cfg.InMemoryCache.ImpCacheSize, cfg.InMemoryCache.TTL, "Imps") 191 cache.Responses = memory.NewCache(cfg.InMemoryCache.RespCacheSize, cfg.InMemoryCache.TTL, "Responses") 192 } 193 return cache 194 } 195 196 func newEventProducers(cfg *config.StoredRequests, client *http.Client, provider db_provider.DbProvider, metricsEngine metrics.MetricsEngine, router *httprouter.Router) (eventProducers []events.EventProducer) { 197 if cfg.CacheEvents.Enabled { 198 eventProducers = append(eventProducers, newEventsAPI(router, cfg.CacheEvents.Endpoint)) 199 } 200 if cfg.HTTPEvents.RefreshRate != 0 && cfg.HTTPEvents.Endpoint != "" { 201 eventProducers = append(eventProducers, newHttpEvents(client, cfg.HTTPEvents.TimeoutDuration(), cfg.HTTPEvents.RefreshRateDuration(), cfg.HTTPEvents.Endpoint)) 202 } 203 if cfg.Database.CacheInitialization.Query != "" { 204 dbEventCfg := databaseEvents.DatabaseEventProducerConfig{ 205 Provider: provider, 206 RequestType: cfg.DataType(), 207 CacheInitQuery: cfg.Database.CacheInitialization.Query, 208 CacheInitTimeout: time.Duration(cfg.Database.CacheInitialization.Timeout) * time.Millisecond, 209 CacheUpdateQuery: cfg.Database.PollUpdates.Query, 210 CacheUpdateTimeout: time.Duration(cfg.Database.PollUpdates.Timeout) * time.Millisecond, 211 MetricsEngine: metricsEngine, 212 } 213 dbEventProducer := databaseEvents.NewDatabaseEventProducer(dbEventCfg) 214 fetchInterval := time.Duration(cfg.Database.PollUpdates.RefreshRate) * time.Second 215 dbEventTickerTask := task.NewTickerTask(fetchInterval, dbEventProducer) 216 dbEventTickerTask.Start() 217 eventProducers = append(eventProducers, dbEventProducer) 218 } 219 return 220 } 221 222 func newEventsAPI(router *httprouter.Router, endpoint string) events.EventProducer { 223 producer, handler := apiEvents.NewEventsAPI() 224 router.POST(endpoint, handler) 225 router.DELETE(endpoint, handler) 226 return producer 227 } 228 229 func newHttpEvents(client *http.Client, timeout time.Duration, refreshRate time.Duration, endpoint string) events.EventProducer { 230 ctxProducer := func() (ctx context.Context, canceller func()) { 231 return context.WithTimeout(context.Background(), timeout) 232 } 233 return httpEvents.NewHTTPEvents(client, endpoint, ctxProducer, refreshRate) 234 } 235 236 func newFilesystem(dataType config.DataType, configPath string) stored_requests.AllFetcher { 237 glog.Infof("Loading Stored %s data from filesystem at path %s", dataType, configPath) 238 fetcher, err := file_fetcher.NewFileFetcher(configPath) 239 if err != nil { 240 glog.Fatalf("Failed to create a %s FileFetcher: %v", dataType, err) 241 } 242 return fetcher 243 } 244 245 // consolidate returns a single Fetcher from an array of fetchers of any size. 246 func consolidate(dataType config.DataType, fetchers []stored_requests.AllFetcher) stored_requests.AllFetcher { 247 if len(fetchers) == 0 { 248 switch dataType { 249 case config.RequestDataType: 250 glog.Warning("No Stored Request support configured. request.imp[i].ext.prebid.storedrequest will be ignored. If you need this, check your app config") 251 default: 252 glog.Warningf("No Stored %s support configured. If you need this, check your app config", dataType) 253 } 254 return empty_fetcher.EmptyFetcher{} 255 } else if len(fetchers) == 1 { 256 return fetchers[0] 257 } else { 258 return stored_requests.MultiFetcher(fetchers) 259 } 260 }