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) {}