github.com/Jeffail/benthos/v3@v3.65.0/lib/processor/http_test.go (about) 1 package processor 2 3 import ( 4 "fmt" 5 "io" 6 "net/http" 7 "net/http/httptest" 8 "strings" 9 "sync" 10 "sync/atomic" 11 "testing" 12 "time" 13 14 "github.com/Jeffail/benthos/v3/lib/log" 15 "github.com/Jeffail/benthos/v3/lib/message" 16 "github.com/Jeffail/benthos/v3/lib/metrics" 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/require" 19 ) 20 21 func TestHTTPClientRetries(t *testing.T) { 22 var reqCount uint32 23 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 24 atomic.AddUint32(&reqCount, 1) 25 http.Error(w, "test error", http.StatusForbidden) 26 })) 27 defer ts.Close() 28 29 conf := NewConfig() 30 conf.HTTP.Config.URL = ts.URL + "/testpost" 31 conf.HTTP.Config.Retry = "1ms" 32 conf.HTTP.Config.NumRetries = 3 33 34 h, err := NewHTTP(conf, nil, log.Noop(), metrics.Noop()) 35 if err != nil { 36 t.Fatal(err) 37 } 38 39 msgs, res := h.ProcessMessage(message.New([][]byte{[]byte("test")})) 40 if res != nil { 41 t.Fatal(res.Error()) 42 } 43 if len(msgs) != 1 { 44 t.Fatal("Wrong count of error messages") 45 } 46 if msgs[0].Len() != 1 { 47 t.Fatal("Wrong count of error message parts") 48 } 49 if exp, act := "test", string(msgs[0].Get(0).Get()); exp != act { 50 t.Errorf("Wrong message contents: %v != %v", act, exp) 51 } 52 if !HasFailed(msgs[0].Get(0)) { 53 t.Error("Failed message part not flagged") 54 } 55 if exp, act := "403", msgs[0].Get(0).Metadata().Get("http_status_code"); exp != act { 56 t.Errorf("Wrong response code metadata: %v != %v", act, exp) 57 } 58 59 if exp, act := uint32(4), atomic.LoadUint32(&reqCount); exp != act { 60 t.Errorf("Wrong count of HTTP attempts: %v != %v", exp, act) 61 } 62 } 63 64 func TestHTTPClientBasic(t *testing.T) { 65 i := 0 66 expPayloads := []string{"foo", "bar", "baz"} 67 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 68 reqBytes, err := io.ReadAll(r.Body) 69 if err != nil { 70 t.Fatal(err) 71 } 72 if exp, act := expPayloads[i], string(reqBytes); exp != act { 73 t.Errorf("Wrong payload value: %v != %v", act, exp) 74 } 75 i++ 76 w.Header().Add("foobar", "baz") 77 w.WriteHeader(http.StatusCreated) 78 w.Write([]byte("foobar")) 79 })) 80 defer ts.Close() 81 82 conf := NewConfig() 83 conf.HTTP.Config.URL = ts.URL + "/testpost" 84 85 h, err := NewHTTP(conf, nil, log.Noop(), metrics.Noop()) 86 if err != nil { 87 t.Fatal(err) 88 } 89 90 msgs, res := h.ProcessMessage(message.New([][]byte{[]byte("foo")})) 91 if res != nil { 92 t.Error(res.Error()) 93 } else if expC, actC := 1, msgs[0].Len(); actC != expC { 94 t.Errorf("Wrong result count: %v != %v", actC, expC) 95 } else if exp, act := "foobar", string(message.GetAllBytes(msgs[0])[0]); act != exp { 96 t.Errorf("Wrong result: %v != %v", act, exp) 97 } else if exp, act := "201", msgs[0].Get(0).Metadata().Get("http_status_code"); exp != act { 98 t.Errorf("Wrong response code metadata: %v != %v", act, exp) 99 } else if exp, act := "", msgs[0].Get(0).Metadata().Get("foobar"); exp != act { 100 t.Errorf("Wrong metadata value: %v != %v", act, exp) 101 } 102 103 msgs, res = h.ProcessMessage(message.New([][]byte{[]byte("bar")})) 104 if res != nil { 105 t.Error(res.Error()) 106 } else if expC, actC := 1, msgs[0].Len(); actC != expC { 107 t.Errorf("Wrong result count: %v != %v", actC, expC) 108 } else if exp, act := "foobar", string(message.GetAllBytes(msgs[0])[0]); act != exp { 109 t.Errorf("Wrong result: %v != %v", act, exp) 110 } else if exp, act := "201", msgs[0].Get(0).Metadata().Get("http_status_code"); exp != act { 111 t.Errorf("Wrong response code metadata: %v != %v", act, exp) 112 } else if exp, act := "", msgs[0].Get(0).Metadata().Get("foobar"); exp != act { 113 t.Errorf("Wrong metadata value: %v != %v", act, exp) 114 } 115 116 // Check metadata persists. 117 msg := message.New([][]byte{[]byte("baz")}) 118 msg.Get(0).Metadata().Set("foo", "bar") 119 msgs, res = h.ProcessMessage(msg) 120 if res != nil { 121 t.Error(res.Error()) 122 } else if expC, actC := 1, msgs[0].Len(); actC != expC { 123 t.Errorf("Wrong result count: %v != %v", actC, expC) 124 } else if exp, act := "foobar", string(message.GetAllBytes(msgs[0])[0]); act != exp { 125 t.Errorf("Wrong result: %v != %v", act, exp) 126 } else if exp, act := "bar", msgs[0].Get(0).Metadata().Get("foo"); exp != act { 127 t.Errorf("Metadata not preserved: %v != %v", act, exp) 128 } else if exp, act := "201", msgs[0].Get(0).Metadata().Get("http_status_code"); exp != act { 129 t.Errorf("Wrong response code metadata: %v != %v", act, exp) 130 } else if exp, act := "", msgs[0].Get(0).Metadata().Get("foobar"); exp != act { 131 t.Errorf("Wrong metadata value: %v != %v", act, exp) 132 } 133 } 134 135 func TestHTTPClientEmptyResponse(t *testing.T) { 136 i := 0 137 expPayloads := []string{"foo", "bar", "baz"} 138 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 139 reqBytes, err := io.ReadAll(r.Body) 140 if err != nil { 141 t.Fatal(err) 142 } 143 if exp, act := expPayloads[i], string(reqBytes); exp != act { 144 t.Errorf("Wrong payload value: %v != %v", act, exp) 145 } 146 i++ 147 w.WriteHeader(http.StatusOK) 148 })) 149 defer ts.Close() 150 151 conf := NewConfig() 152 conf.HTTP.Config.URL = ts.URL + "/testpost" 153 154 h, err := NewHTTP(conf, nil, log.Noop(), metrics.Noop()) 155 if err != nil { 156 t.Fatal(err) 157 } 158 159 msgs, res := h.ProcessMessage(message.New([][]byte{[]byte("foo")})) 160 if res != nil { 161 t.Error(res.Error()) 162 } else if expC, actC := 1, msgs[0].Len(); actC != expC { 163 t.Errorf("Wrong result count: %v != %v", actC, expC) 164 } else if exp, act := "", string(message.GetAllBytes(msgs[0])[0]); act != exp { 165 t.Errorf("Wrong result: %v != %v", act, exp) 166 } else if exp, act := "200", msgs[0].Get(0).Metadata().Get("http_status_code"); exp != act { 167 t.Errorf("Wrong response code metadata: %v != %v", act, exp) 168 } 169 170 msgs, res = h.ProcessMessage(message.New([][]byte{[]byte("bar")})) 171 if res != nil { 172 t.Error(res.Error()) 173 } else if expC, actC := 1, msgs[0].Len(); actC != expC { 174 t.Errorf("Wrong result count: %v != %v", actC, expC) 175 } else if exp, act := "", string(message.GetAllBytes(msgs[0])[0]); act != exp { 176 t.Errorf("Wrong result: %v != %v", act, exp) 177 } else if exp, act := "200", msgs[0].Get(0).Metadata().Get("http_status_code"); exp != act { 178 t.Errorf("Wrong response code metadata: %v != %v", act, exp) 179 } 180 181 // Check metadata persists. 182 msg := message.New([][]byte{[]byte("baz")}) 183 msg.Get(0).Metadata().Set("foo", "bar") 184 msgs, res = h.ProcessMessage(msg) 185 if res != nil { 186 t.Error(res.Error()) 187 } else if expC, actC := 1, msgs[0].Len(); actC != expC { 188 t.Errorf("Wrong result count: %v != %v", actC, expC) 189 } else if exp, act := "", string(message.GetAllBytes(msgs[0])[0]); act != exp { 190 t.Errorf("Wrong result: %v != %v", act, exp) 191 } else if exp, act := "200", msgs[0].Get(0).Metadata().Get("http_status_code"); exp != act { 192 t.Errorf("Wrong response code metadata: %v != %v", act, exp) 193 } 194 } 195 196 func TestHTTPClientEmpty404Response(t *testing.T) { 197 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 198 w.WriteHeader(http.StatusNotFound) 199 })) 200 defer ts.Close() 201 202 conf := NewConfig() 203 conf.HTTP.Config.URL = ts.URL + "/testpost" 204 205 h, err := NewHTTP(conf, nil, log.Noop(), metrics.Noop()) 206 if err != nil { 207 t.Fatal(err) 208 } 209 210 msgs, res := h.ProcessMessage(message.New([][]byte{[]byte("foo")})) 211 if res != nil { 212 t.Error(res.Error()) 213 } else if expC, actC := 1, msgs[0].Len(); actC != expC { 214 t.Errorf("Wrong result count: %v != %v", actC, expC) 215 } else if exp, act := "foo", string(message.GetAllBytes(msgs[0])[0]); act != exp { 216 t.Errorf("Wrong result: %v != %v", act, exp) 217 } else if exp, act := "404", msgs[0].Get(0).Metadata().Get("http_status_code"); exp != act { 218 t.Errorf("Wrong response code metadata: %v != %v", act, exp) 219 } else if !HasFailed(msgs[0].Get(0)) { 220 t.Error("Expected error flag") 221 } 222 } 223 224 func TestHTTPClientBasicWithMetadata(t *testing.T) { 225 i := 0 226 expPayloads := []string{"foo", "bar", "baz"} 227 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 228 reqBytes, err := io.ReadAll(r.Body) 229 if err != nil { 230 t.Fatal(err) 231 } 232 if exp, act := expPayloads[i], string(reqBytes); exp != act { 233 t.Errorf("Wrong payload value: %v != %v", act, exp) 234 } 235 i++ 236 w.Header().Add("foobar", "baz") 237 w.WriteHeader(http.StatusCreated) 238 w.Write([]byte("foobar")) 239 })) 240 defer ts.Close() 241 242 conf := NewConfig() 243 conf.HTTP.Config.URL = ts.URL + "/testpost" 244 conf.HTTP.Config.CopyResponseHeaders = true 245 246 h, err := NewHTTP(conf, nil, log.Noop(), metrics.Noop()) 247 if err != nil { 248 t.Fatal(err) 249 } 250 251 msgs, res := h.ProcessMessage(message.New([][]byte{[]byte("foo")})) 252 if res != nil { 253 t.Error(res.Error()) 254 } else if expC, actC := 1, msgs[0].Len(); actC != expC { 255 t.Errorf("Wrong result count: %v != %v", actC, expC) 256 } else if exp, act := "foobar", string(message.GetAllBytes(msgs[0])[0]); act != exp { 257 t.Errorf("Wrong result: %v != %v", act, exp) 258 } else if exp, act := "201", msgs[0].Get(0).Metadata().Get("http_status_code"); exp != act { 259 t.Errorf("Wrong response code metadata: %v != %v", act, exp) 260 } else if exp, act := "baz", msgs[0].Get(0).Metadata().Get("foobar"); exp != act { 261 t.Errorf("Wrong metadata value: %v != %v", act, exp) 262 } 263 } 264 265 func TestHTTPClientParallel(t *testing.T) { 266 wg := sync.WaitGroup{} 267 wg.Add(5) 268 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 269 wg.Done() 270 wg.Wait() 271 w.WriteHeader(http.StatusCreated) 272 w.Write([]byte("foobar")) 273 })) 274 defer ts.Close() 275 276 conf := NewConfig() 277 conf.HTTP.Config.URL = ts.URL + "/testpost" 278 conf.HTTP.Parallel = true 279 280 h, err := NewHTTP(conf, nil, log.Noop(), metrics.Noop()) 281 if err != nil { 282 t.Fatal(err) 283 } 284 285 inputMsg := message.New([][]byte{ 286 []byte("foo"), 287 []byte("bar"), 288 []byte("baz"), 289 []byte("qux"), 290 []byte("quz"), 291 }) 292 inputMsg.Get(0).Metadata().Set("foo", "bar") 293 msgs, res := h.ProcessMessage(inputMsg) 294 if res != nil { 295 t.Error(res.Error()) 296 } else if expC, actC := 5, msgs[0].Len(); actC != expC { 297 t.Errorf("Wrong result count: %v != %v", actC, expC) 298 } else if exp, act := "foobar", string(message.GetAllBytes(msgs[0])[0]); act != exp { 299 t.Errorf("Wrong result: %v != %v", act, exp) 300 } else if exp, act := "bar", msgs[0].Get(0).Metadata().Get("foo"); exp != act { 301 t.Errorf("Metadata not preserved: %v != %v", act, exp) 302 } else if exp, act := "201", msgs[0].Get(0).Metadata().Get("http_status_code"); exp != act { 303 t.Errorf("Wrong response code metadata: %v != %v", act, exp) 304 } 305 } 306 307 func TestHTTPClientParallelError(t *testing.T) { 308 wg := sync.WaitGroup{} 309 wg.Add(5) 310 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 311 wg.Done() 312 wg.Wait() 313 reqBytes, err := io.ReadAll(r.Body) 314 if err != nil { 315 t.Fatal(err) 316 } 317 if string(reqBytes) == "baz" { 318 http.Error(w, "test error", http.StatusForbidden) 319 return 320 } 321 w.Write([]byte("foobar")) 322 })) 323 defer ts.Close() 324 325 conf := NewConfig() 326 conf.HTTP.Config.URL = ts.URL + "/testpost" 327 conf.HTTP.Parallel = true 328 conf.HTTP.Config.NumRetries = 0 329 330 h, err := NewHTTP(conf, nil, log.Noop(), metrics.Noop()) 331 if err != nil { 332 t.Fatal(err) 333 } 334 335 msgs, res := h.ProcessMessage(message.New([][]byte{ 336 []byte("foo"), 337 []byte("bar"), 338 []byte("baz"), 339 []byte("qux"), 340 []byte("quz"), 341 })) 342 if res != nil { 343 t.Error(res.Error()) 344 } 345 if expC, actC := 5, msgs[0].Len(); actC != expC { 346 t.Fatalf("Wrong result count: %v != %v", actC, expC) 347 } 348 if exp, act := "baz", string(msgs[0].Get(2).Get()); act != exp { 349 t.Errorf("Wrong result: %v != %v", act, exp) 350 } 351 if !HasFailed(msgs[0].Get(2)) { 352 t.Error("Expected failed flag") 353 } 354 if exp, act := "403", msgs[0].Get(2).Metadata().Get("http_status_code"); exp != act { 355 t.Errorf("Wrong response code metadata: %v != %v", act, exp) 356 } 357 for _, i := range []int{0, 1, 3, 4} { 358 if exp, act := "foobar", string(msgs[0].Get(i).Get()); act != exp { 359 t.Errorf("Wrong result: %v != %v", act, exp) 360 } 361 if HasFailed(msgs[0].Get(i)) { 362 t.Error("Did not expect failed flag") 363 } 364 if exp, act := "200", msgs[0].Get(i).Metadata().Get("http_status_code"); exp != act { 365 t.Errorf("Wrong response code metadata: %v != %v", act, exp) 366 } 367 } 368 } 369 370 func TestHTTPClientParallelCapped(t *testing.T) { 371 var reqs int64 372 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 373 if req := atomic.AddInt64(&reqs, 1); req > 5 { 374 t.Errorf("Beyond parallelism cap: %v", req) 375 } 376 <-time.After(time.Millisecond * 10) 377 w.Write([]byte("foobar")) 378 atomic.AddInt64(&reqs, -1) 379 })) 380 defer ts.Close() 381 382 conf := NewConfig() 383 conf.HTTP.Config.URL = ts.URL + "/testpost" 384 conf.HTTP.Parallel = true 385 conf.HTTP.MaxParallel = 5 386 387 h, err := NewHTTP(conf, nil, log.Noop(), metrics.Noop()) 388 if err != nil { 389 t.Fatal(err) 390 } 391 392 msgs, res := h.ProcessMessage(message.New([][]byte{ 393 []byte("foo"), 394 []byte("bar"), 395 []byte("baz"), 396 []byte("qux"), 397 []byte("quz"), 398 []byte("foo2"), 399 []byte("bar2"), 400 []byte("baz2"), 401 []byte("qux2"), 402 []byte("quz2"), 403 })) 404 if res != nil { 405 t.Error(res.Error()) 406 } else if expC, actC := 10, msgs[0].Len(); actC != expC { 407 t.Errorf("Wrong result count: %v != %v", actC, expC) 408 } else if exp, act := "foobar", string(message.GetAllBytes(msgs[0])[0]); act != exp { 409 t.Errorf("Wrong result: %v != %v", act, exp) 410 } 411 } 412 413 func TestHTTPClientFailLogURL(t *testing.T) { 414 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 415 if strings.HasSuffix(r.URL.Path, "notfound") { 416 w.WriteHeader(http.StatusNotFound) 417 } 418 })) 419 defer ts.Close() 420 421 tests := []struct { 422 name string 423 url string 424 wantError bool 425 }{ 426 { 427 name: "200 OK", 428 url: ts.URL, 429 wantError: false, 430 }, 431 { 432 name: "404 Not Found", 433 url: ts.URL + "/notfound", 434 wantError: true, 435 }, 436 { 437 name: "no such host", 438 url: "http://test.invalid", 439 wantError: true, 440 }, 441 } 442 443 for _, tt := range tests { 444 t.Run(tt.name, func(t *testing.T) { 445 conf := NewConfig() 446 conf.HTTP.Config.NumRetries = 0 447 conf.HTTP.Config.URL = tt.url 448 449 logMock := &mockLog{} 450 h, err := NewHTTP(conf, nil, logMock, metrics.Noop()) 451 if err != nil { 452 t.Fatal(err) 453 } 454 455 _, res := h.ProcessMessage(message.New([][]byte{[]byte("foo")})) 456 if res != nil { 457 t.Error(res.Error()) 458 } 459 460 if !tt.wantError { 461 assert.Empty(t, logMock.errors) 462 return 463 } 464 465 require.Len(t, logMock.errors, 1) 466 467 got := logMock.errors[0] 468 if !strings.HasPrefix(got, fmt.Sprintf("HTTP request failed: %s", tt.url)) { 469 t.Errorf("Expected to find %q in logs: %sq", tt.url, got) 470 } 471 }) 472 } 473 474 }