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  }