github.com/prebid/prebid-server/v2@v2.18.0/hooks/hookexecution/executor.go (about)

     1  package hookexecution
     2  
     3  import (
     4  	"context"
     5  	"net/http"
     6  	"sync"
     7  
     8  	"github.com/prebid/openrtb/v20/openrtb2"
     9  	"github.com/prebid/prebid-server/v2/adapters"
    10  	"github.com/prebid/prebid-server/v2/config"
    11  	"github.com/prebid/prebid-server/v2/exchange/entities"
    12  	"github.com/prebid/prebid-server/v2/hooks"
    13  	"github.com/prebid/prebid-server/v2/hooks/hookstage"
    14  	"github.com/prebid/prebid-server/v2/metrics"
    15  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    16  	"github.com/prebid/prebid-server/v2/privacy"
    17  )
    18  
    19  const (
    20  	EndpointAuction = "/openrtb2/auction"
    21  	EndpointAmp     = "/openrtb2/amp"
    22  )
    23  
    24  // An entity specifies the type of object that was processed during the execution of the stage.
    25  type entity string
    26  
    27  const (
    28  	entityHttpRequest              entity = "http-request"
    29  	entityAuctionRequest           entity = "auction-request"
    30  	entityAuctionResponse          entity = "auction_response"
    31  	entityAllProcessedBidResponses entity = "all_processed_bid_responses"
    32  )
    33  
    34  type StageExecutor interface {
    35  	ExecuteEntrypointStage(req *http.Request, body []byte) ([]byte, *RejectError)
    36  	ExecuteRawAuctionStage(body []byte) ([]byte, *RejectError)
    37  	ExecuteProcessedAuctionStage(req *openrtb_ext.RequestWrapper) error
    38  	ExecuteBidderRequestStage(req *openrtb_ext.RequestWrapper, bidder string) *RejectError
    39  	ExecuteRawBidderResponseStage(response *adapters.BidderResponse, bidder string) *RejectError
    40  	ExecuteAllProcessedBidResponsesStage(adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
    41  	ExecuteAuctionResponseStage(response *openrtb2.BidResponse)
    42  }
    43  
    44  type HookStageExecutor interface {
    45  	StageExecutor
    46  	SetAccount(account *config.Account)
    47  	SetActivityControl(activityControl privacy.ActivityControl)
    48  	GetOutcomes() []StageOutcome
    49  }
    50  
    51  type hookExecutor struct {
    52  	account         *config.Account
    53  	accountID       string
    54  	endpoint        string
    55  	planBuilder     hooks.ExecutionPlanBuilder
    56  	stageOutcomes   []StageOutcome
    57  	moduleContexts  *moduleContexts
    58  	metricEngine    metrics.MetricsEngine
    59  	activityControl privacy.ActivityControl
    60  	// Mutex needed for BidderRequest and RawBidderResponse Stages as they are run in several goroutines
    61  	sync.Mutex
    62  }
    63  
    64  func NewHookExecutor(builder hooks.ExecutionPlanBuilder, endpoint string, me metrics.MetricsEngine) *hookExecutor {
    65  	return &hookExecutor{
    66  		endpoint:       endpoint,
    67  		planBuilder:    builder,
    68  		stageOutcomes:  []StageOutcome{},
    69  		moduleContexts: &moduleContexts{ctxs: make(map[string]hookstage.ModuleContext)},
    70  		metricEngine:   me,
    71  	}
    72  }
    73  
    74  func (e *hookExecutor) SetAccount(account *config.Account) {
    75  	if account == nil {
    76  		return
    77  	}
    78  
    79  	e.account = account
    80  	e.accountID = account.ID
    81  }
    82  
    83  func (e *hookExecutor) SetActivityControl(activityControl privacy.ActivityControl) {
    84  	e.activityControl = activityControl
    85  }
    86  
    87  func (e *hookExecutor) GetOutcomes() []StageOutcome {
    88  	return e.stageOutcomes
    89  }
    90  
    91  func (e *hookExecutor) ExecuteEntrypointStage(req *http.Request, body []byte) ([]byte, *RejectError) {
    92  	plan := e.planBuilder.PlanForEntrypointStage(e.endpoint)
    93  	if len(plan) == 0 {
    94  		return body, nil
    95  	}
    96  
    97  	handler := func(
    98  		ctx context.Context,
    99  		moduleCtx hookstage.ModuleInvocationContext,
   100  		hook hookstage.Entrypoint,
   101  		payload hookstage.EntrypointPayload,
   102  	) (hookstage.HookResult[hookstage.EntrypointPayload], error) {
   103  		return hook.HandleEntrypointHook(ctx, moduleCtx, payload)
   104  	}
   105  
   106  	stageName := hooks.StageEntrypoint.String()
   107  	executionCtx := e.newContext(stageName)
   108  	payload := hookstage.EntrypointPayload{Request: req, Body: body}
   109  
   110  	outcome, payload, contexts, rejectErr := executeStage(executionCtx, plan, payload, handler, e.metricEngine)
   111  	outcome.Entity = entityHttpRequest
   112  	outcome.Stage = stageName
   113  
   114  	e.saveModuleContexts(contexts)
   115  	e.pushStageOutcome(outcome)
   116  
   117  	return payload.Body, rejectErr
   118  }
   119  
   120  func (e *hookExecutor) ExecuteRawAuctionStage(requestBody []byte) ([]byte, *RejectError) {
   121  	plan := e.planBuilder.PlanForRawAuctionStage(e.endpoint, e.account)
   122  	if len(plan) == 0 {
   123  		return requestBody, nil
   124  	}
   125  
   126  	handler := func(
   127  		ctx context.Context,
   128  		moduleCtx hookstage.ModuleInvocationContext,
   129  		hook hookstage.RawAuctionRequest,
   130  		payload hookstage.RawAuctionRequestPayload,
   131  	) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) {
   132  		return hook.HandleRawAuctionHook(ctx, moduleCtx, payload)
   133  	}
   134  
   135  	stageName := hooks.StageRawAuctionRequest.String()
   136  	executionCtx := e.newContext(stageName)
   137  	payload := hookstage.RawAuctionRequestPayload(requestBody)
   138  
   139  	outcome, payload, contexts, reject := executeStage(executionCtx, plan, payload, handler, e.metricEngine)
   140  	outcome.Entity = entityAuctionRequest
   141  	outcome.Stage = stageName
   142  
   143  	e.saveModuleContexts(contexts)
   144  	e.pushStageOutcome(outcome)
   145  
   146  	return payload, reject
   147  }
   148  
   149  func (e *hookExecutor) ExecuteProcessedAuctionStage(request *openrtb_ext.RequestWrapper) error {
   150  	plan := e.planBuilder.PlanForProcessedAuctionStage(e.endpoint, e.account)
   151  	if len(plan) == 0 {
   152  		return nil
   153  	}
   154  
   155  	if err := request.RebuildRequest(); err != nil {
   156  		return err
   157  	}
   158  
   159  	handler := func(
   160  		ctx context.Context,
   161  		moduleCtx hookstage.ModuleInvocationContext,
   162  		hook hookstage.ProcessedAuctionRequest,
   163  		payload hookstage.ProcessedAuctionRequestPayload,
   164  	) (hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload], error) {
   165  		return hook.HandleProcessedAuctionHook(ctx, moduleCtx, payload)
   166  	}
   167  
   168  	stageName := hooks.StageProcessedAuctionRequest.String()
   169  	executionCtx := e.newContext(stageName)
   170  	payload := hookstage.ProcessedAuctionRequestPayload{Request: request}
   171  
   172  	outcome, _, contexts, reject := executeStage(executionCtx, plan, payload, handler, e.metricEngine)
   173  	outcome.Entity = entityAuctionRequest
   174  	outcome.Stage = stageName
   175  
   176  	e.saveModuleContexts(contexts)
   177  	e.pushStageOutcome(outcome)
   178  
   179  	// remove type information if there is no rejection
   180  	if reject == nil {
   181  		return nil
   182  	}
   183  
   184  	return reject
   185  }
   186  
   187  func (e *hookExecutor) ExecuteBidderRequestStage(req *openrtb_ext.RequestWrapper, bidder string) *RejectError {
   188  	plan := e.planBuilder.PlanForBidderRequestStage(e.endpoint, e.account)
   189  	if len(plan) == 0 {
   190  		return nil
   191  	}
   192  
   193  	handler := func(
   194  		ctx context.Context,
   195  		moduleCtx hookstage.ModuleInvocationContext,
   196  		hook hookstage.BidderRequest,
   197  		payload hookstage.BidderRequestPayload,
   198  	) (hookstage.HookResult[hookstage.BidderRequestPayload], error) {
   199  		return hook.HandleBidderRequestHook(ctx, moduleCtx, payload)
   200  	}
   201  
   202  	stageName := hooks.StageBidderRequest.String()
   203  	executionCtx := e.newContext(stageName)
   204  	payload := hookstage.BidderRequestPayload{Request: req, Bidder: bidder}
   205  	outcome, payload, contexts, reject := executeStage(executionCtx, plan, payload, handler, e.metricEngine)
   206  	outcome.Entity = entity(bidder)
   207  	outcome.Stage = stageName
   208  
   209  	e.saveModuleContexts(contexts)
   210  	e.pushStageOutcome(outcome)
   211  
   212  	return reject
   213  }
   214  
   215  func (e *hookExecutor) ExecuteRawBidderResponseStage(response *adapters.BidderResponse, bidder string) *RejectError {
   216  	plan := e.planBuilder.PlanForRawBidderResponseStage(e.endpoint, e.account)
   217  	if len(plan) == 0 {
   218  		return nil
   219  	}
   220  
   221  	handler := func(
   222  		ctx context.Context,
   223  		moduleCtx hookstage.ModuleInvocationContext,
   224  		hook hookstage.RawBidderResponse,
   225  		payload hookstage.RawBidderResponsePayload,
   226  	) (hookstage.HookResult[hookstage.RawBidderResponsePayload], error) {
   227  		return hook.HandleRawBidderResponseHook(ctx, moduleCtx, payload)
   228  	}
   229  
   230  	stageName := hooks.StageRawBidderResponse.String()
   231  	executionCtx := e.newContext(stageName)
   232  	payload := hookstage.RawBidderResponsePayload{Bids: response.Bids, Bidder: bidder}
   233  
   234  	outcome, payload, contexts, reject := executeStage(executionCtx, plan, payload, handler, e.metricEngine)
   235  	response.Bids = payload.Bids
   236  	outcome.Entity = entity(bidder)
   237  	outcome.Stage = stageName
   238  
   239  	e.saveModuleContexts(contexts)
   240  	e.pushStageOutcome(outcome)
   241  
   242  	return reject
   243  }
   244  
   245  func (e *hookExecutor) ExecuteAllProcessedBidResponsesStage(adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) {
   246  	plan := e.planBuilder.PlanForAllProcessedBidResponsesStage(e.endpoint, e.account)
   247  	if len(plan) == 0 {
   248  		return
   249  	}
   250  
   251  	handler := func(
   252  		ctx context.Context,
   253  		moduleCtx hookstage.ModuleInvocationContext,
   254  		hook hookstage.AllProcessedBidResponses,
   255  		payload hookstage.AllProcessedBidResponsesPayload,
   256  	) (hookstage.HookResult[hookstage.AllProcessedBidResponsesPayload], error) {
   257  		return hook.HandleAllProcessedBidResponsesHook(ctx, moduleCtx, payload)
   258  	}
   259  
   260  	stageName := hooks.StageAllProcessedBidResponses.String()
   261  	executionCtx := e.newContext(stageName)
   262  	payload := hookstage.AllProcessedBidResponsesPayload{Responses: adapterBids}
   263  	outcome, _, contexts, _ := executeStage(executionCtx, plan, payload, handler, e.metricEngine)
   264  	outcome.Entity = entityAllProcessedBidResponses
   265  	outcome.Stage = stageName
   266  
   267  	e.saveModuleContexts(contexts)
   268  	e.pushStageOutcome(outcome)
   269  }
   270  
   271  func (e *hookExecutor) ExecuteAuctionResponseStage(response *openrtb2.BidResponse) {
   272  	plan := e.planBuilder.PlanForAuctionResponseStage(e.endpoint, e.account)
   273  	if len(plan) == 0 {
   274  		return
   275  	}
   276  
   277  	handler := func(
   278  		ctx context.Context,
   279  		moduleCtx hookstage.ModuleInvocationContext,
   280  		hook hookstage.AuctionResponse,
   281  		payload hookstage.AuctionResponsePayload,
   282  	) (hookstage.HookResult[hookstage.AuctionResponsePayload], error) {
   283  		return hook.HandleAuctionResponseHook(ctx, moduleCtx, payload)
   284  	}
   285  
   286  	stageName := hooks.StageAuctionResponse.String()
   287  	executionCtx := e.newContext(stageName)
   288  	payload := hookstage.AuctionResponsePayload{BidResponse: response}
   289  
   290  	outcome, _, contexts, _ := executeStage(executionCtx, plan, payload, handler, e.metricEngine)
   291  	outcome.Entity = entityAuctionResponse
   292  	outcome.Stage = stageName
   293  
   294  	e.saveModuleContexts(contexts)
   295  	e.pushStageOutcome(outcome)
   296  }
   297  
   298  func (e *hookExecutor) newContext(stage string) executionContext {
   299  	return executionContext{
   300  		account:         e.account,
   301  		accountID:       e.accountID,
   302  		endpoint:        e.endpoint,
   303  		moduleContexts:  e.moduleContexts,
   304  		stage:           stage,
   305  		activityControl: e.activityControl,
   306  	}
   307  }
   308  
   309  func (e *hookExecutor) saveModuleContexts(ctxs stageModuleContext) {
   310  	for _, moduleCtxs := range ctxs.groupCtx {
   311  		for moduleName, moduleCtx := range moduleCtxs {
   312  			e.moduleContexts.put(moduleName, moduleCtx)
   313  		}
   314  	}
   315  }
   316  
   317  func (e *hookExecutor) pushStageOutcome(outcome StageOutcome) {
   318  	e.Lock()
   319  	defer e.Unlock()
   320  	e.stageOutcomes = append(e.stageOutcomes, outcome)
   321  }
   322  
   323  type EmptyHookExecutor struct{}
   324  
   325  func (executor EmptyHookExecutor) SetAccount(_ *config.Account) {}
   326  
   327  func (executor EmptyHookExecutor) SetActivityControl(_ privacy.ActivityControl) {}
   328  
   329  func (executor EmptyHookExecutor) GetOutcomes() []StageOutcome {
   330  	return []StageOutcome{}
   331  }
   332  
   333  func (executor EmptyHookExecutor) ExecuteEntrypointStage(_ *http.Request, body []byte) ([]byte, *RejectError) {
   334  	return body, nil
   335  }
   336  
   337  func (executor EmptyHookExecutor) ExecuteRawAuctionStage(body []byte) ([]byte, *RejectError) {
   338  	return body, nil
   339  }
   340  
   341  func (executor EmptyHookExecutor) ExecuteProcessedAuctionStage(_ *openrtb_ext.RequestWrapper) error {
   342  	return nil
   343  }
   344  
   345  func (executor EmptyHookExecutor) ExecuteBidderRequestStage(_ *openrtb_ext.RequestWrapper, bidder string) *RejectError {
   346  	return nil
   347  }
   348  
   349  func (executor EmptyHookExecutor) ExecuteRawBidderResponseStage(_ *adapters.BidderResponse, _ string) *RejectError {
   350  	return nil
   351  }
   352  
   353  func (executor EmptyHookExecutor) ExecuteAllProcessedBidResponsesStage(_ map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) {
   354  }
   355  
   356  func (executor EmptyHookExecutor) ExecuteAuctionResponseStage(_ *openrtb2.BidResponse) {}