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