@@ -11,13 +11,16 @@ import (
11
11
"time"
12
12
13
13
cloudevents "github.com/cloudevents/sdk-go/v2"
14
+ cehttp "github.com/cloudevents/sdk-go/v2/protocol/http"
15
+
14
16
"github.com/google/uuid"
15
17
)
16
18
17
19
const (
18
20
DefaultInvokeSource = "/boson/fn"
19
21
DefaultInvokeType = "boson.fn"
20
22
DefaultInvokeContentType = "application/json"
23
+ DefaultInvokeRequestType = "POST"
21
24
DefaultInvokeData = `{"message":"Hello World"}`
22
25
DefaultInvokeFormat = "http"
23
26
)
@@ -30,6 +33,7 @@ type InvokeMessage struct {
30
33
Type string
31
34
ContentType string
32
35
Data []byte
36
+ RequestType string // HTTP Request GET/POST (defaults to POST)
33
37
Format string // optional override for function-defined message format
34
38
}
35
39
@@ -40,6 +44,7 @@ func NewInvokeMessage() InvokeMessage {
40
44
Source : DefaultInvokeSource ,
41
45
Type : DefaultInvokeType ,
42
46
ContentType : DefaultInvokeContentType ,
47
+ RequestType : DefaultInvokeRequestType ,
43
48
Data : []byte (DefaultInvokeData ),
44
49
// Format override not set by default: value from function being preferred.
45
50
}
@@ -63,6 +68,11 @@ func invoke(ctx context.Context, c *Client, f Function, target string, m InvokeM
63
68
// set. Once decided, codify in a test.
64
69
format := DefaultInvokeFormat
65
70
71
+ // RequestType is expected GET or POST
72
+ if m .RequestType == "" {
73
+ m .RequestType = DefaultInvokeRequestType
74
+ }
75
+
66
76
if verbose {
67
77
fmt .Printf ("Invoking '%v' function at %v\n " , f .Invoke , route )
68
78
}
@@ -79,19 +89,27 @@ func invoke(ctx context.Context, c *Client, f Function, target string, m InvokeM
79
89
}
80
90
}
81
91
92
+ if m .RequestType != "POST" && m .RequestType != "GET" {
93
+ err = fmt .Errorf ("http request type '%v' not supported, expected GET or POST" , m .RequestType )
94
+ return
95
+ }
96
+
82
97
switch format {
83
98
case "http" :
84
- return sendPost (ctx , route , m , c .transport , verbose )
99
+ return sendHttp (ctx , route , m , c .transport , verbose )
85
100
case "cloudevent" :
86
- // CouldEvents return a string which always includes a fairly verbose
87
- // summation of fields, so metadata is not applicable
88
- meta := make (map [string ][]string )
89
- body , err = sendEvent (ctx , route , m , c .transport , verbose )
90
- return meta , body , err
101
+ switch m .RequestType {
102
+ case "POST" :
103
+ body , err = sendEvent (ctx , route , m , c .transport , verbose )
104
+ case "GET" :
105
+ // Construct a special CloudEvents GET request.
106
+ // This will be used most likely only for very special cases
107
+ body , err = sendGetEvent (ctx , route , m , c .transport , verbose )
108
+ }
91
109
default :
92
110
err = fmt .Errorf ("format '%v' not supported" , format )
93
- return
94
111
}
112
+ return
95
113
}
96
114
97
115
// invocationRoute returns a route to the named target instance of a func:
@@ -176,30 +194,84 @@ func sendEvent(ctx context.Context, route string, m InvokeMessage, t http.RoundT
176
194
return
177
195
}
178
196
197
+ // sendGetEvent sends a GET event as a cloudEvent. Normally, this is not the
198
+ // way CEs are intended to be sent exactly.
199
+ // In CloudEvents specification it reads:
200
+ //
201
+ // "Events can be transferred with all standard or application-defined HTTP request
202
+ // methods that support payload body transfers"
203
+ //
204
+ // Since this is not the case for GET request, we need to specify custom protocol
205
+ // and use a slightly different client resulting in a slightly different
206
+ // function all together.
207
+ func sendGetEvent (ctx context.Context , route string , m InvokeMessage , t http.RoundTripper , verbose bool ) (resp string , err error ) {
208
+ if m .ID == "" {
209
+ // we're using a different Client function, we need to create an ID.
210
+ // ce.NewClientHTTP() sets ID if not present, ce.NewClient() doesn't
211
+ m .ID = uuid .NewString ()
212
+ }
213
+
214
+ // construct the event
215
+ event := cloudevents .NewEvent ()
216
+ event .SetID (m .ID )
217
+ event .SetSource (m .Source )
218
+ event .SetType (m .Type )
219
+ event .SetDataContentType (m .ContentType )
220
+
221
+ if verbose {
222
+ fmt .Println ("Constructing a GET request CloudEvent:" )
223
+ fmt .Printf ("Event: %+v\n " , event )
224
+ }
225
+ // create http protocol with GET method
226
+ protocol , err := cehttp .New (cehttp .WithRoundTripper (t ), cehttp .WithMethod ("GET" ))
227
+ if err != nil {
228
+ return
229
+ }
230
+
231
+ c , err := cloudevents .NewClient (protocol )
232
+ if err != nil {
233
+ return
234
+ }
235
+
236
+ if verbose {
237
+ fmt .Printf ("Sending event\n %v" , event )
238
+ }
239
+
240
+ evt , result := c .Request (cloudevents .ContextWithTarget (ctx , route ), event )
241
+ if cloudevents .IsUndelivered (result ) {
242
+ err = fmt .Errorf ("unable to invoke: %v" , result )
243
+ } else if evt != nil { // Check for nil in case no event is returned
244
+ resp = evt .String ()
245
+ }
246
+ return
247
+ }
248
+
179
249
// sendPost to the route populated with data in the invoke message.
180
- func sendPost (ctx context.Context , route string , m InvokeMessage , t http.RoundTripper , verbose bool ) (map [string ][]string , string , error ) {
250
+ func sendHttp (ctx context.Context , route string , m InvokeMessage , t http.RoundTripper , verbose bool ) (map [string ][]string , string , error ) {
181
251
client := http.Client {
182
252
Transport : t ,
183
253
Timeout : time .Minute ,
184
254
}
185
- values := url.Values {
186
- "ID" : {m .ID },
187
- "Source" : {m .Source },
188
- "Type" : {m .Type },
189
- "ContentType" : {m .ContentType },
190
- "Data" : {string (m .Data )},
191
- }
255
+
192
256
if verbose {
257
+ values := url.Values {
258
+ "ID" : {m .ID },
259
+ "Source" : {m .Source },
260
+ "Type" : {m .Type },
261
+ "ContentType" : {m .ContentType },
262
+ "Data" : {string (m .Data )},
263
+ }
193
264
fmt .Println ("Sending values" )
194
265
for k , v := range values {
195
266
fmt .Printf (" %v: %v\n " , k , v [0 ]) // NOTE len==1 value slices assumed
196
267
}
197
268
}
198
269
199
- req , err := http .NewRequestWithContext (ctx , "POST" , route , bytes .NewReader (m .Data ))
270
+ req , err := http .NewRequestWithContext (ctx , m . RequestType , route , bytes .NewReader (m .Data ))
200
271
if err != nil {
201
272
return nil , "" , fmt .Errorf ("failure to create request: %w" , err )
202
273
}
274
+
203
275
req .Header .Add ("Content-Type" , m .ContentType )
204
276
205
277
resp , err := client .Do (req )
0 commit comments