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  }