@@ -38,19 +38,20 @@ type ChannelChainKeySet struct {
3838
3939// ChannelBinding holds the runtime state for a registered channel.
4040type ChannelBinding struct {
41- APIID string
42- Name string
43- Mode string // "websub" or "protocol-mediation"
44- Context string
45- Version string
46- Vhost string
47- SubscribeChainKey string
48- InboundChainKey string
49- OutboundChainKey string
50- BrokerDriverTopic string
51- Ordering string // "ordered" or "unordered"
52- Channels map [string ]string // channel-name → Kafka topic (WebSubApi only)
53- ChannelChainKeys map [string ]ChannelChainKeySet // channel-name → per-channel chain keys
41+ APIID string
42+ Name string
43+ Mode string // "websub" or "protocol-mediation"
44+ Context string
45+ Version string
46+ Vhost string
47+ SubscribeChainKey string
48+ InboundChainKey string
49+ OutboundChainKey string
50+ BrokerDriverTopic string
51+ Ordering string // "ordered" or "unordered"
52+ Channels map [string ]string // channel-name → Kafka topic (WebSubApi only)
53+ KafkaTopicToChannel map [string ]string // Kafka topic → channel-name (reverse of Channels, cached)
54+ ChannelChainKeys map [string ]ChannelChainKeySet // channel-name → per-channel chain keys
5455}
5556
5657// Hub is the central message router. It holds the policy engine reference and
@@ -70,9 +71,16 @@ func NewHub(eng *engine.Engine) *Hub {
7071}
7172
7273// RegisterBinding adds a channel binding to the hub.
74+ // It automatically builds the KafkaTopicToChannel reverse map from b.Channels.
7375func (h * Hub ) RegisterBinding (b ChannelBinding ) {
7476 h .mu .Lock ()
7577 defer h .mu .Unlock ()
78+ if len (b .Channels ) > 0 {
79+ b .KafkaTopicToChannel = make (map [string ]string , len (b .Channels ))
80+ for channelName , topic := range b .Channels {
81+ b .KafkaTopicToChannel [topic ] = channelName
82+ }
83+ }
7684 h .bindings [b .Name ] = & b
7785}
7886
@@ -138,7 +146,7 @@ func (h *Hub) ProcessSubscribe(ctx context.Context, bindingName string, msg *con
138146 }
139147 if result .ShortCircuited {
140148 logShortCircuit ("Subscribe request short-circuited by hub policy" , bindingName , binding .SubscribeChainKey , result .ImmediateResponse )
141- return nil , true , nil
149+ return immediateResponseToMessage ( result . ImmediateResponse ) , true , nil
142150 }
143151 if err := ApplyRequestHeaderResult (result , msg ); err != nil {
144152 return nil , false , fmt .Errorf ("failed to apply subscribe header result: %w" , err )
@@ -158,7 +166,7 @@ func (h *Hub) ProcessSubscribe(ctx context.Context, bindingName string, msg *con
158166 }
159167 if result .ShortCircuited {
160168 logShortCircuit ("Subscribe request short-circuited by channel policy" , bindingName , keys .SubscribeChainKey , result .ImmediateResponse )
161- return nil , true , nil
169+ return immediateResponseToMessage ( result . ImmediateResponse ) , true , nil
162170 }
163171 if err := ApplyRequestHeaderResult (result , msg ); err != nil {
164172 return nil , false , fmt .Errorf ("failed to apply subscribe channel header result: %w" , err )
@@ -190,7 +198,7 @@ func (h *Hub) ProcessInbound(ctx context.Context, bindingName string, msg *conne
190198 }
191199 if result .ShortCircuited {
192200 logShortCircuit ("Inbound message short-circuited by hub policy" , bindingName , binding .InboundChainKey , result .ImmediateResponse )
193- return nil , true , nil
201+ return immediateResponseToMessage ( result . ImmediateResponse ) , true , nil
194202 }
195203 if err := ApplyRequestHeaderResult (result , msg ); err != nil {
196204 return nil , false , fmt .Errorf ("failed to apply inbound header result: %w" , err )
@@ -202,7 +210,7 @@ func (h *Hub) ProcessInbound(ctx context.Context, bindingName string, msg *conne
202210 return nil , false , fmt .Errorf ("inbound body policy execution failed: %w" , err )
203211 }
204212 if bodyResult .ShortCircuited {
205- return nil , true , nil
213+ return immediateResponseToMessage ( bodyResult . ImmediateResponse ) , true , nil
206214 }
207215 if err := ApplyRequestBodyResult (bodyResult , msg ); err != nil {
208216 return nil , false , fmt .Errorf ("failed to apply inbound body result: %w" , err )
@@ -223,7 +231,7 @@ func (h *Hub) ProcessInbound(ctx context.Context, bindingName string, msg *conne
223231 }
224232 if result .ShortCircuited {
225233 logShortCircuit ("Inbound message short-circuited by channel policy" , bindingName , keys .InboundChainKey , result .ImmediateResponse )
226- return nil , true , nil
234+ return immediateResponseToMessage ( result . ImmediateResponse ) , true , nil
227235 }
228236 if err := ApplyRequestHeaderResult (result , msg ); err != nil {
229237 return nil , false , fmt .Errorf ("failed to apply inbound channel header result: %w" , err )
@@ -235,7 +243,7 @@ func (h *Hub) ProcessInbound(ctx context.Context, bindingName string, msg *conne
235243 return nil , false , fmt .Errorf ("inbound channel body policy execution failed: %w" , err )
236244 }
237245 if bodyResult .ShortCircuited {
238- return nil , true , nil
246+ return immediateResponseToMessage ( bodyResult . ImmediateResponse ) , true , nil
239247 }
240248 if err := ApplyRequestBodyResult (bodyResult , msg ); err != nil {
241249 return nil , false , fmt .Errorf ("failed to apply inbound channel body result: %w" , err )
@@ -292,7 +300,7 @@ func (h *Hub) ProcessOutbound(ctx context.Context, bindingName string, msg *conn
292300 // Apply channel-level outbound chain if present.
293301 // msg.Topic here is the Kafka topic; resolve back to channel name.
294302 if msg .Topic != "" && len (binding .ChannelChainKeys ) > 0 {
295- channelName := resolveChannelName (binding .Channels , msg .Topic )
303+ channelName := resolveChannelName (binding .KafkaTopicToChannel , binding . Channels , msg .Topic )
296304 if channelName != "" {
297305 if keys , ok := binding .ChannelChainKeys [channelName ]; ok && keys .OutboundChainKey != "" {
298306 chain := h .engine .GetChain (keys .OutboundChainKey )
@@ -330,9 +338,13 @@ func (h *Hub) ProcessOutbound(ctx context.Context, bindingName string, msg *conn
330338 return msg , false , nil
331339}
332340
333- // resolveChannelName reverse-maps a Kafka topic name to the channel name
334- // using the binding's channel-name → Kafka-topic map.
335- func resolveChannelName (channels map [string ]string , kafkaTopic string ) string {
341+ // resolveChannelName reverse-maps a Kafka topic name to the channel name.
342+ // It first checks the pre-built kafkaTopicToChannel reverse map for an O(1) lookup,
343+ // and falls back to an O(n) scan over channels only if the cached map is absent.
344+ func resolveChannelName (kafkaTopicToChannel map [string ]string , channels map [string ]string , kafkaTopic string ) string {
345+ if len (kafkaTopicToChannel ) > 0 {
346+ return kafkaTopicToChannel [kafkaTopic ]
347+ }
336348 for channelName , topic := range channels {
337349 if topic == kafkaTopic {
338350 return channelName
@@ -341,6 +353,28 @@ func resolveChannelName(channels map[string]string, kafkaTopic string) string {
341353 return ""
342354}
343355
356+ // immediateResponseToMessage encodes an ImmediateResponseResult from the policy engine
357+ // into a connectors.Message so callers can write the policy-provided HTTP response.
358+ // The status code is stored in Metadata["status_code"] (int), body in Value,
359+ // and response headers (single-value) in Headers ([]string slice per key).
360+ // Returns nil when resp is nil.
361+ func immediateResponseToMessage (resp * engine.ImmediateResponseResult ) * connectors.Message {
362+ if resp == nil {
363+ return nil
364+ }
365+ headers := make (map [string ][]string , len (resp .Headers ))
366+ for k , v := range resp .Headers {
367+ headers [k ] = []string {v }
368+ }
369+ return & connectors.Message {
370+ Value : resp .Body ,
371+ Headers : headers ,
372+ Metadata : map [string ]interface {}{
373+ "status_code" : resp .StatusCode ,
374+ },
375+ }
376+ }
377+
344378// logShortCircuit keeps Info logs to metadata only; ImmediateResponse.Body is
345379// user-visible content and must not contain sensitive information.
346380func logShortCircuit (message , bindingName , chainKey string , resp * engine.ImmediateResponseResult ) {
0 commit comments