1
- package main
1
+ package streamprocessingoffload
2
2
3
3
import (
4
+ "context"
5
+ "fmt"
4
6
"log"
5
- "strings"
6
7
7
- "github.com/negasus/haproxy-spoe-go/action"
8
+ "github.com/DataDog/dd-trace-go/contrib/envoyproxy/go-control-plane/v2/message_processor"
9
+ "github.com/DataDog/dd-trace-go/v2/instrumentation"
10
+
8
11
"github.com/negasus/haproxy-spoe-go/message"
9
12
"github.com/negasus/haproxy-spoe-go/request"
10
13
)
11
14
15
+ var instr * instrumentation.Instrumentation
16
+
17
+ func init () {
18
+ instr = instrumentation .Load (instrumentation .PackageHAProxyStreamProcessingOffload )
19
+ }
20
+
21
+ type HAProxySPOA struct {
22
+ mp message_processor.MessageProcessor
23
+ }
24
+
25
+ type AppsecHAProxyConfig struct {
26
+ Context context.Context
27
+ BlockingUnavailable bool
28
+ BodyParsingSizeLimit int
29
+ }
30
+
31
+ func NewHAProxySPOA (config AppsecHAProxyConfig ) * HAProxySPOA {
32
+ mp := message_processor .NewMessageProcessor (message_processor.MessageProcessorConfig {
33
+ BlockingUnavailable : config .BlockingUnavailable ,
34
+ BodyParsingSizeLimit : config .BodyParsingSizeLimit ,
35
+ }, instr )
36
+
37
+ handler := & HAProxySPOA {
38
+ mp : mp ,
39
+ }
40
+
41
+ initRequestStateCache ()
42
+
43
+ return handler
44
+ }
45
+
12
46
// Handler processes SPOE requests from HAProxy
13
- func Handler (req * request.Request ) {
47
+ func ( s * HAProxySPOA ) Handler (req * request.Request ) {
14
48
log .Printf ("handle request EngineID: '%s', StreamID: '%d', FrameID: '%d' with %d messages" ,
15
49
req .EngineID , req .StreamID , req .FrameID , req .Messages .Len ())
16
50
51
+ mp := message_processor .NewMessageProcessor (
52
+ message_processor.MessageProcessorConfig {
53
+ BlockingUnavailable : false ,
54
+ BodyParsingSizeLimit : 1024 ,
55
+ },
56
+ instr ,
57
+ )
58
+
17
59
// Process each message
18
60
for i := 0 ; i < req .Messages .Len (); i ++ {
19
61
msg , err := req .Messages .GetByIndex (i )
@@ -22,123 +64,90 @@ func Handler(req *request.Request) {
22
64
continue
23
65
}
24
66
25
- //log.Printf("Processing message: '%s'", msg.Name)
26
-
27
- switch msg .Name {
28
- case "http-request-headers-msg" :
29
- handleRequestHeadersMessage (req , msg )
30
- case "http-request-body-msg" :
31
- handleRequestBodyMessage (req , msg )
32
- case "http-response-headers-msg" :
33
- handleResponseHeadersMessage (req , msg )
34
- case "http-response-body-msg" :
35
- handleResponseBodyMessage (req , msg )
36
- default :
37
- log .Printf ("Unknown message type: %s" , msg .Name )
67
+ ctx := context .Background ()
68
+ mpAction , reqState , err := processMessage (mp , ctx , req , msg )
69
+ if err != nil {
70
+ log .Printf ("Error processing message %s: %v" , msg .Name , err )
71
+ return
38
72
}
39
- }
40
- }
41
73
42
- func handleRequestHeadersMessage (req * request.Request , msg * message.Message ) {
43
- // Extract headers and analyze them
44
- method := getStringValue (msg , "method" )
45
- path := getStringValue (msg , "path" )
46
- headers := getStringValue (msg , "headers" )
47
-
48
- log .Printf ("Headers - Method: %s, Path: %s" , method , path )
49
-
50
- isJSON := isJSONContentType (headers )
51
-
52
- log .Printf ("Content-Type analysis - Is JSON: %t" , isJSON )
53
-
54
- // Always mark headers as processed
55
- setVariable (req , "headers_processed" , "true" )
56
-
57
- req .Actions .SetVar (action .ScopeTransaction , "span_id" , 1234 )
58
- }
59
-
60
- func handleRequestBodyMessage (req * request.Request , msg * message.Message ) {
61
- // This should only be called for JSON content
62
- body := getBytesArrayValue (msg , "body" )
63
-
64
- log .Printf ("Processing JSON Request body - Size: %d bytes" , len (body ))
74
+ err = s .handleAction (mpAction , req , & reqState )
75
+ if err != nil {
76
+ log .Printf ("Error handling action for message %s: %v" , msg .Name , err )
77
+ return
78
+ }
79
+ }
65
80
}
66
81
67
- func handleResponseHeadersMessage (req * request.Request , msg * message.Message ) {
68
- status := getIntValue (msg , "status" )
69
- headers := getStringValue (msg , "headers" )
70
-
71
- log .Printf ("Response Headers - Status: %d" , status )
72
- log .Printf ("Response Headers content: %s" , headers )
73
-
74
- isJSON := isJSONContentType (headers )
75
- log .Printf ("Response Content-Type analysis - Is JSON: %t" , isJSON )
76
- }
82
+ func processMessage (mp message_processor.MessageProcessor , ctx context.Context , req * request.Request , msg * message.Message ) (message_processor.Action , message_processor.RequestState , error ) {
83
+ log .Printf ("Handling message: %s" , msg .Name )
84
+
85
+ switch msg .Name {
86
+ case "http-request-headers-msg" :
87
+ var (
88
+ mpAction message_processor.Action
89
+ err error
90
+ currentRequest message_processor.RequestState
91
+ )
92
+ currentRequest , mpAction , err = mp .OnRequestHeaders (ctx , & requestHeadersHAProxy {req : req , msg : msg })
93
+ return mpAction , currentRequest , err
94
+ case "http-request-body-msg" :
95
+ currentRequest , err := getCurrentRequest (msg )
96
+ if err != nil {
97
+ return message_processor.Action {}, message_processor.RequestState {}, err
98
+ }
77
99
78
- func handleResponseBodyMessage (req * request.Request , msg * message.Message ) {
79
- body := getBytesArrayValue (msg , "body" )
100
+ var action message_processor.Action
101
+ action , err = mp .OnRequestBody (& requestBodyHAProxy {msg : msg }, currentRequest )
102
+ return action , currentRequest , err
103
+ case "http-response-headers-msg" :
104
+ currentRequest , err := getCurrentRequest (msg )
105
+ if err != nil {
106
+ return message_processor.Action {}, message_processor.RequestState {}, err
107
+ }
80
108
81
- log .Printf ("Processing JSON Response body - Size: %d bytes" , len (body ))
82
- }
109
+ var action message_processor.Action
110
+ action , err = mp .OnResponseHeaders (& responseHeadersHAProxy {msg : msg }, currentRequest )
111
+ return action , currentRequest , err
112
+ case "http-response-body-msg" :
113
+ currentRequest , err := getCurrentRequest (msg )
114
+ if err != nil {
115
+ return message_processor.Action {}, message_processor.RequestState {}, err
116
+ }
83
117
84
- // Helper function to set SPOE variables
85
- func setVariable (req * request.Request , name , value string ) {
86
- //log.Printf("Setting variable %s = %s", name, value)
87
-
88
- // Use the Actions interface to set variables
89
- if req .Actions != nil {
90
- // Create a set-var action using the library's action interface
91
- // The exact API may vary, but this is the typical pattern
92
- req .Actions .SetVar (action .ScopeTransaction , name , value )
93
- } else {
94
- log .Printf ("WARNING: req.Actions is nil, cannot set variable %s" , name )
118
+ var action message_processor.Action
119
+ action , err = mp .OnResponseBody (& responseBodyHAProxy {msg : msg }, currentRequest )
120
+ return action , currentRequest , err
121
+ default :
122
+ return message_processor.Action {}, message_processor.RequestState {}, fmt .Errorf ("unknown message type: %s" , msg .Name )
95
123
}
96
124
}
97
125
98
- // Helper function to check if Content-Type indicates JSON
99
- func isJSONContentType (headers string ) bool {
100
- // Parse headers and look for Content-Type
101
- lines := strings .Split (headers , "\n " )
102
- for _ , line := range lines {
103
- if strings .HasPrefix (strings .ToLower (line ), "content-type:" ) {
104
- contentType := strings .ToLower (strings .TrimSpace (line [13 :]))
105
- return strings .Contains (contentType , "application/json" ) ||
106
- strings .Contains (contentType , "text/json" ) ||
107
- strings .HasSuffix (contentType , "+json" )
126
+ func (s * HAProxySPOA ) handleAction (action message_processor.Action , req * request.Request , reqState * message_processor.RequestState ) error {
127
+ switch action .Type {
128
+ case message_processor .ActionTypeContinue :
129
+ if action .Response == nil {
130
+ return nil
108
131
}
109
- }
110
- return false
111
- }
112
132
113
- // Helper functions to extract values from SPOE messages
114
- func getStringValue (msg * message.Message , key string ) string {
115
- if val , exists := msg .KV .Get (key ); exists {
116
- if str , ok := val .(string ); ok {
117
- return str
133
+ if data := action .Response .(* message_processor.HeadersResponseData ); data != nil {
134
+ // Set the headers in the request
135
+ // setHeadersResponseData(data)
136
+ return nil
118
137
}
119
- }
120
- return ""
121
- }
122
138
123
- func getBytesArrayValue (msg * message.Message , key string ) []byte {
124
- if val , exists := msg .KV .Get (key ); exists {
125
- if bytes , ok := val .([]byte ); ok {
126
- return bytes
127
- }
139
+ // Could happen if a new response type with data is implemented, and we forget to handle it here.
140
+ // However, at the moment, we only have HeadersResponseData as a response type for ActionTypeContinue
141
+ return fmt .Errorf ("unknown action data type: %T for ActionTypeContinue" , action .Response )
142
+ case message_processor .ActionTypeBlock :
143
+ data := action .Response .(* message_processor.BlockResponseData )
144
+ setBlockResponseData (data , req )
145
+ _ = deleteCurrentRequest (reqState .Span .Context ().SpanID ())
146
+ return nil
147
+ case message_processor .ActionTypeFinish :
148
+ // Remove the current request from the cache
149
+ _ = deleteCurrentRequest (reqState .Span .Context ().SpanID ())
128
150
}
129
- return nil
130
- }
131
151
132
- func getIntValue (msg * message.Message , key string ) int {
133
- if val , exists := msg .KV .Get (key ); exists {
134
- switch v := val .(type ) {
135
- case int :
136
- return v
137
- case int64 :
138
- return int (v )
139
- case uint64 :
140
- return int (v )
141
- }
142
- }
143
- return 0
152
+ return fmt .Errorf ("unknown action type: %T" , action .Type )
144
153
}
0 commit comments