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