github.com/Jeffail/benthos/v3@v3.65.0/lib/input/http_client_test.go (about)

     1  package input
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"mime/multipart"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"net/textproto"
    11  	"sync"
    12  	"sync/atomic"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/Jeffail/benthos/v3/lib/log"
    17  	"github.com/Jeffail/benthos/v3/lib/metrics"
    18  	"github.com/Jeffail/benthos/v3/lib/response"
    19  	"github.com/Jeffail/benthos/v3/lib/types"
    20  	"github.com/stretchr/testify/assert"
    21  	"github.com/stretchr/testify/require"
    22  )
    23  
    24  func TestHTTPClientGET(t *testing.T) {
    25  	inputs := []string{
    26  		"foo1",
    27  		"foo2",
    28  		"foo3",
    29  		"foo4",
    30  		"foo5",
    31  	}
    32  
    33  	var reqCount uint32
    34  	index := 0
    35  
    36  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    37  		if exp, act := "GET", r.Method; exp != act {
    38  			t.Errorf("Wrong method: %v != %v", act, exp)
    39  		}
    40  		atomic.AddUint32(&reqCount, 1)
    41  		w.Write([]byte(inputs[index%len(inputs)]))
    42  		index++
    43  	}))
    44  	defer ts.Close()
    45  
    46  	conf := NewConfig()
    47  	conf.HTTPClient.URL = ts.URL + "/testpost"
    48  	conf.HTTPClient.Retry = "1ms"
    49  
    50  	h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop())
    51  	if err != nil {
    52  		t.Error(err)
    53  		return
    54  	}
    55  
    56  	var tr types.Transaction
    57  	var open bool
    58  
    59  	for _, expPart := range inputs {
    60  		select {
    61  		case tr, open = <-h.TransactionChan():
    62  			if !open {
    63  				t.Fatal("Chan not open")
    64  			}
    65  			if exp, act := 1, tr.Payload.Len(); exp != act {
    66  				t.Fatalf("Wrong count of parts: %v != %v", act, exp)
    67  			}
    68  			if exp, act := expPart, string(tr.Payload.Get(0).Get()); exp != act {
    69  				t.Errorf("Wrong part: %v != %v", act, exp)
    70  			}
    71  		case <-time.After(time.Second):
    72  			t.Errorf("Action timed out")
    73  		}
    74  
    75  		select {
    76  		case tr.ResponseChan <- response.NewAck():
    77  		case <-time.After(time.Second):
    78  			t.Errorf("Action timed out")
    79  		}
    80  	}
    81  
    82  	h.CloseAsync()
    83  	if err := h.WaitForClose(time.Second); err != nil {
    84  		t.Error(err)
    85  	}
    86  
    87  	if exp, act := uint32(len(inputs)), atomic.LoadUint32(&reqCount); exp != act && exp+1 != act {
    88  		t.Errorf("Wrong count of HTTP attempts: %v != %v", act, exp)
    89  	}
    90  }
    91  
    92  func TestHTTPClientPagination(t *testing.T) {
    93  	var paths []string
    94  	var pathsLock sync.Mutex
    95  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    96  		fmt.Fprintf(w, "hello%v", len(paths))
    97  		pathsLock.Lock()
    98  		paths = append(paths, r.URL.Path)
    99  		pathsLock.Unlock()
   100  	}))
   101  	defer ts.Close()
   102  
   103  	conf := NewConfig()
   104  	conf.HTTPClient.URL = ts.URL + "/${!content()}"
   105  	conf.HTTPClient.Retry = "1ms"
   106  
   107  	h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop())
   108  	require.NoError(t, err)
   109  
   110  	var tr types.Transaction
   111  	var open bool
   112  
   113  	for i := 0; i < 10; i++ {
   114  		exp := fmt.Sprintf("hello%v", i)
   115  		select {
   116  		case tr, open = <-h.TransactionChan():
   117  			require.True(t, open)
   118  			require.Equal(t, 1, tr.Payload.Len())
   119  			assert.Equal(t, exp, string(tr.Payload.Get(0).Get()))
   120  		case <-time.After(time.Second):
   121  			t.Fatal("Action timed out")
   122  		}
   123  		select {
   124  		case tr.ResponseChan <- response.NewAck():
   125  		case <-time.After(time.Second):
   126  			t.Fatal("Action timed out")
   127  		}
   128  	}
   129  
   130  	h.CloseAsync()
   131  	assert.NoError(t, h.WaitForClose(time.Second))
   132  
   133  	pathsLock.Lock()
   134  	defer pathsLock.Unlock()
   135  	for i, url := range paths {
   136  		expURL := "/"
   137  		if i > 0 {
   138  			expURL = fmt.Sprintf("/hello%v", i-1)
   139  		}
   140  		assert.Equal(t, expURL, url)
   141  	}
   142  }
   143  
   144  func TestHTTPClientGETError(t *testing.T) {
   145  	t.Parallel()
   146  
   147  	requestChan := make(chan struct{})
   148  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   149  		http.Error(w, "nah", http.StatusBadGateway)
   150  		select {
   151  		case requestChan <- struct{}{}:
   152  		default:
   153  		}
   154  	}))
   155  	defer ts.Close()
   156  
   157  	conf := NewConfig()
   158  	conf.HTTPClient.URL = ts.URL + "/testpost"
   159  	conf.HTTPClient.Retry = "1ms"
   160  
   161  	h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop())
   162  	if err != nil {
   163  		t.Error(err)
   164  		return
   165  	}
   166  
   167  	for i := 0; i < 3; i++ {
   168  		select {
   169  		case <-requestChan:
   170  		case <-time.After(time.Second):
   171  			t.Error("Timed out")
   172  		}
   173  	}
   174  
   175  	h.CloseAsync()
   176  	if err := h.WaitForClose(time.Second); err != nil {
   177  		t.Error(err)
   178  	}
   179  }
   180  
   181  func TestHTTPClientGETNotExist(t *testing.T) {
   182  	t.Parallel()
   183  
   184  	conf := NewConfig()
   185  	conf.HTTPClient.URL = "jgljksdfhjgkldfjglkf"
   186  	conf.HTTPClient.Retry = "1ms"
   187  
   188  	h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop())
   189  	if err != nil {
   190  		t.Error(err)
   191  		return
   192  	}
   193  
   194  	<-time.After(time.Millisecond * 500)
   195  
   196  	h.CloseAsync()
   197  	if err := h.WaitForClose(time.Second); err != nil {
   198  		t.Error(err)
   199  	}
   200  }
   201  
   202  func TestHTTPClientGETStreamNotExist(t *testing.T) {
   203  	t.Parallel()
   204  
   205  	conf := NewConfig()
   206  	conf.HTTPClient.URL = "jgljksdfhjgkldfjglkf"
   207  	conf.HTTPClient.Retry = "1ms"
   208  	conf.HTTPClient.Stream.Enabled = true
   209  
   210  	h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop())
   211  	if err != nil {
   212  		t.Error(err)
   213  		return
   214  	}
   215  
   216  	<-time.After(time.Millisecond * 500)
   217  
   218  	h.CloseAsync()
   219  	if err := h.WaitForClose(time.Second * 5); err != nil {
   220  		t.Error(err)
   221  	}
   222  }
   223  
   224  func TestHTTPClientGETStreamError(t *testing.T) {
   225  	t.Parallel()
   226  
   227  	requestChan := make(chan struct{})
   228  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   229  		http.Error(w, "nah", http.StatusBadGateway)
   230  		select {
   231  		case requestChan <- struct{}{}:
   232  		default:
   233  		}
   234  	}))
   235  	defer ts.Close()
   236  
   237  	conf := NewConfig()
   238  	conf.HTTPClient.URL = ts.URL + "/testpost"
   239  	conf.HTTPClient.Retry = "1ms"
   240  	conf.HTTPClient.Stream.Enabled = true
   241  
   242  	h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop())
   243  	if err != nil {
   244  		t.Error(err)
   245  		return
   246  	}
   247  
   248  	select {
   249  	case <-requestChan:
   250  	case <-time.After(time.Second):
   251  		t.Error("Timed out")
   252  	}
   253  
   254  	h.CloseAsync()
   255  	if err := h.WaitForClose(time.Second * 2); err != nil {
   256  		t.Error(err)
   257  	}
   258  }
   259  
   260  func TestHTTPClientPOST(t *testing.T) {
   261  	var reqCount uint32
   262  	inputs := []string{
   263  		"foo1",
   264  		"foo2",
   265  		"foo3",
   266  		"foo4",
   267  		"foo5",
   268  	}
   269  
   270  	index := 0
   271  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   272  		if exp, act := "POST", r.Method; exp != act {
   273  			t.Errorf("Wrong method: %v != %v", act, exp)
   274  		}
   275  		defer r.Body.Close()
   276  
   277  		bodyBytes, err := io.ReadAll(r.Body)
   278  		if err != nil {
   279  			t.Error(err)
   280  		}
   281  
   282  		if exp, act := "foobar", string(bodyBytes); exp != act {
   283  			t.Errorf("Wrong post body: %v != %v", act, exp)
   284  		}
   285  
   286  		atomic.AddUint32(&reqCount, 1)
   287  		w.Write([]byte(inputs[index%len(inputs)]))
   288  		index++
   289  	}))
   290  	defer ts.Close()
   291  
   292  	conf := NewConfig()
   293  	conf.HTTPClient.URL = ts.URL + "/testpost"
   294  	conf.HTTPClient.Verb = "POST"
   295  	conf.HTTPClient.Payload = "foobar"
   296  	conf.HTTPClient.Retry = "1ms"
   297  
   298  	h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop())
   299  	if err != nil {
   300  		t.Error(err)
   301  		return
   302  	}
   303  
   304  	for _, expPart := range inputs {
   305  		var ts types.Transaction
   306  		var open bool
   307  
   308  		select {
   309  		case ts, open = <-h.TransactionChan():
   310  			if !open {
   311  				t.Fatal("Chan not open")
   312  			}
   313  			if exp, act := 1, ts.Payload.Len(); exp != act {
   314  				t.Fatalf("Wrong count of parts: %v != %v", act, exp)
   315  			}
   316  			if exp, act := expPart, string(ts.Payload.Get(0).Get()); exp != act {
   317  				t.Errorf("Wrong part: %v != %v", act, exp)
   318  			}
   319  		case <-time.After(time.Second):
   320  			t.Errorf("Action timed out")
   321  		}
   322  
   323  		select {
   324  		case ts.ResponseChan <- response.NewAck():
   325  		case <-time.After(time.Second):
   326  			t.Errorf("Action timed out")
   327  		}
   328  	}
   329  
   330  	h.CloseAsync()
   331  	if err := h.WaitForClose(time.Second); err != nil {
   332  		t.Error(err)
   333  	}
   334  
   335  	if exp, act := uint32(len(inputs)), atomic.LoadUint32(&reqCount); exp != act && exp+1 != act {
   336  		t.Errorf("Wrong count of HTTP attempts: %v != %v", act, exp)
   337  	}
   338  }
   339  
   340  func TestHTTPClientGETMultipart(t *testing.T) {
   341  	var reqCount uint32
   342  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   343  		if exp, act := "GET", r.Method; exp != act {
   344  			t.Errorf("Wrong method: %v != %v", act, exp)
   345  		}
   346  		atomic.AddUint32(&reqCount, 1)
   347  
   348  		body := &bytes.Buffer{}
   349  		writer := multipart.NewWriter(body)
   350  
   351  		parts := []string{
   352  			"hello", "http", "world",
   353  		}
   354  		for _, p := range parts {
   355  			var err error
   356  			var part io.Writer
   357  			if part, err = writer.CreatePart(textproto.MIMEHeader{
   358  				"Content-Type": []string{"application/octet-stream"},
   359  			}); err == nil {
   360  				_, err = io.Copy(part, bytes.NewReader([]byte(p)))
   361  			}
   362  			if err != nil {
   363  				t.Fatal(err)
   364  			}
   365  		}
   366  
   367  		writer.Close()
   368  		w.Header().Add("Content-Type", writer.FormDataContentType())
   369  		w.Write(body.Bytes())
   370  	}))
   371  	defer ts.Close()
   372  
   373  	conf := NewConfig()
   374  	conf.HTTPClient.URL = ts.URL + "/testpost"
   375  	conf.HTTPClient.Retry = "1ms"
   376  
   377  	h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop())
   378  	if err != nil {
   379  		t.Error(err)
   380  		return
   381  	}
   382  
   383  	var tr types.Transaction
   384  	var open bool
   385  
   386  	select {
   387  	case tr, open = <-h.TransactionChan():
   388  		if !open {
   389  			t.Fatal("Chan not open")
   390  		}
   391  		if exp, act := 3, tr.Payload.Len(); exp != act {
   392  			t.Fatalf("Wrong count of parts: %v != %v", act, exp)
   393  		}
   394  		if exp, act := "hello", string(tr.Payload.Get(0).Get()); exp != act {
   395  			t.Errorf("Wrong part: %v != %v", act, exp)
   396  		}
   397  		if exp, act := "http", string(tr.Payload.Get(1).Get()); exp != act {
   398  			t.Errorf("Wrong part: %v != %v", act, exp)
   399  		}
   400  		if exp, act := "world", string(tr.Payload.Get(2).Get()); exp != act {
   401  			t.Errorf("Wrong part: %v != %v", act, exp)
   402  		}
   403  	case <-time.After(time.Second):
   404  		t.Errorf("Action timed out")
   405  	}
   406  
   407  	select {
   408  	case tr.ResponseChan <- response.NewAck():
   409  	case <-time.After(time.Second):
   410  		t.Errorf("Action timed out")
   411  	}
   412  	h.CloseAsync()
   413  
   414  	if err := h.WaitForClose(time.Second); err != nil {
   415  		t.Error(err)
   416  	}
   417  
   418  	if exp, act := uint32(1), atomic.LoadUint32(&reqCount); exp != act && exp+1 != act {
   419  		t.Errorf("Wrong count of HTTP attempts: %v != %v", act, exp)
   420  	}
   421  }
   422  
   423  func TestHTTPClientGETMultipartLoop(t *testing.T) {
   424  	tests := [][]string{
   425  		{
   426  			"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
   427  			"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
   428  			"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
   429  		},
   430  		{
   431  			"Tristique et egestas quis ipsum suspendisse ultrices. Quis enim lobortis scelerisque fermentum dui faucibus.",
   432  		},
   433  		{
   434  			"Lorem donec massa sapien faucibus et molestie ac. Lectus proin nibh nisl condimentum id venenatis a.",
   435  			"Ultricies mi eget mauris pharetra et ultrices neque ornare aenean.",
   436  		},
   437  		{
   438  			"Amet tellus cras adipiscing enim. Non pulvinar neque laoreet suspendisse interdum consectetur. Venenatis cras sed felis eget velit aliquet sagittis.",
   439  			"Ac feugiat sed lectus vestibulum mattis ullamcorper velit. Phasellus vestibulum lorem sed risus ultricies tristique nulla aliquet.",
   440  			"Odio ut sem nulla pharetra diam sit. Neque vitae tempus quam pellentesque nec nam aliquam sem.",
   441  			"Scelerisque eu ultrices vitae auctor eu augue. Ut eu sem integer vitae justo eget. Purus in massa tempor nec feugiat nisl pretium fusce id.",
   442  		},
   443  	}
   444  
   445  	var reqMut sync.Mutex
   446  
   447  	var index int
   448  	tserve := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   449  		reqMut.Lock()
   450  		defer reqMut.Unlock()
   451  
   452  		if exp, act := "GET", r.Method; exp != act {
   453  			t.Errorf("Wrong method: %v != %v", act, exp)
   454  		}
   455  
   456  		body := &bytes.Buffer{}
   457  		writer := multipart.NewWriter(body)
   458  
   459  		parts := tests[index%len(tests)]
   460  		for _, p := range parts {
   461  			var err error
   462  			var part io.Writer
   463  			if part, err = writer.CreatePart(textproto.MIMEHeader{
   464  				"Content-Type": []string{"application/octet-stream"},
   465  			}); err == nil {
   466  				_, err = io.Copy(part, bytes.NewReader([]byte(p)))
   467  			}
   468  			if err != nil {
   469  				t.Fatal(err)
   470  			}
   471  		}
   472  		index++
   473  
   474  		writer.Close()
   475  		w.Header().Add("Content-Type", writer.FormDataContentType())
   476  		w.Write(body.Bytes())
   477  	}))
   478  	defer tserve.Close()
   479  
   480  	conf := NewConfig()
   481  	conf.HTTPClient.URL = tserve.URL + "/testpost"
   482  	conf.HTTPClient.Retry = "1ms"
   483  
   484  	h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop())
   485  	if err != nil {
   486  		t.Error(err)
   487  		return
   488  	}
   489  
   490  	reqMut.Lock()
   491  	for _, test := range tests {
   492  		var ts types.Transaction
   493  		var open bool
   494  
   495  		reqMut.Unlock()
   496  		select {
   497  		case ts, open = <-h.TransactionChan():
   498  			if !open {
   499  				t.Fatal("Chan not open")
   500  			}
   501  			if exp, act := len(test), ts.Payload.Len(); exp != act {
   502  				t.Fatalf("Wrong count of parts: %v != %v", act, exp)
   503  			}
   504  			for i, part := range test {
   505  				if exp, act := part, string(ts.Payload.Get(i).Get()); exp != act {
   506  					t.Errorf("Wrong part: %v != %v", act, exp)
   507  				}
   508  			}
   509  		case <-time.After(time.Second):
   510  			t.Errorf("Action timed out")
   511  		}
   512  
   513  		reqMut.Lock()
   514  		select {
   515  		case ts.ResponseChan <- response.NewAck():
   516  		case <-time.After(time.Second):
   517  			t.Errorf("Action timed out")
   518  		}
   519  	}
   520  
   521  	h.CloseAsync()
   522  	reqMut.Unlock()
   523  
   524  	select {
   525  	case <-h.TransactionChan():
   526  	case <-time.After(time.Second):
   527  		t.Errorf("Action timed out")
   528  	}
   529  
   530  	if err := h.WaitForClose(time.Second); err != nil {
   531  		t.Error(err)
   532  	}
   533  }
   534  
   535  func TestHTTPClientStreamGETMultipartLoop(t *testing.T) {
   536  	tests := [][]string{
   537  		{
   538  			"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
   539  			"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
   540  			"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
   541  		},
   542  		{
   543  			"Tristique et egestas quis ipsum suspendisse ultrices. Quis enim lobortis scelerisque fermentum dui faucibus.",
   544  		},
   545  		{
   546  			"Lorem donec massa sapien faucibus et molestie ac. Lectus proin nibh nisl condimentum id venenatis a.",
   547  			"Ultricies mi eget mauris pharetra et ultrices neque ornare aenean.",
   548  		},
   549  		{
   550  			"Amet tellus cras adipiscing enim. Non pulvinar neque laoreet suspendisse interdum consectetur. Venenatis cras sed felis eget velit aliquet sagittis.",
   551  			"Ac feugiat sed lectus vestibulum mattis ullamcorper velit. Phasellus vestibulum lorem sed risus ultricies tristique nulla aliquet.",
   552  			"Odio ut sem nulla pharetra diam sit. Neque vitae tempus quam pellentesque nec nam aliquam sem.",
   553  			"Scelerisque eu ultrices vitae auctor eu augue. Ut eu sem integer vitae justo eget. Purus in massa tempor nec feugiat nisl pretium fusce id.",
   554  		},
   555  	}
   556  
   557  	tserve := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   558  		if exp, act := "GET", r.Method; exp != act {
   559  			t.Errorf("Wrong method: %v != %v", act, exp)
   560  		}
   561  
   562  		body := &bytes.Buffer{}
   563  
   564  		for _, test := range tests {
   565  			for _, part := range test {
   566  				body.WriteString(part)
   567  				body.WriteByte('\n')
   568  			}
   569  			body.WriteByte('\n')
   570  		}
   571  		body.WriteString("A msg that we won't read\nsecond part\n\n")
   572  
   573  		w.Header().Add("Content-Type", "application/octet-stream")
   574  		w.Write(body.Bytes())
   575  	}))
   576  	defer tserve.Close()
   577  
   578  	conf := NewConfig()
   579  	conf.HTTPClient.URL = tserve.URL + "/testpost"
   580  	conf.HTTPClient.Retry = "1ms"
   581  	conf.HTTPClient.Stream.Enabled = true
   582  	conf.HTTPClient.Stream.Multipart = true
   583  
   584  	h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop())
   585  	if err != nil {
   586  		t.Error(err)
   587  		return
   588  	}
   589  
   590  	for _, test := range tests {
   591  		var ts types.Transaction
   592  		var open bool
   593  
   594  		select {
   595  		case ts, open = <-h.TransactionChan():
   596  			if !open {
   597  				t.Fatal("Chan not open")
   598  			}
   599  			if exp, act := len(test), ts.Payload.Len(); exp != act {
   600  				t.Fatalf("Wrong count of parts: %v != %v", act, exp)
   601  			}
   602  			for i, part := range test {
   603  				if exp, act := part, string(ts.Payload.Get(i).Get()); exp != act {
   604  					t.Errorf("Wrong part: %v != %v", act, exp)
   605  				}
   606  			}
   607  		case <-time.After(time.Second):
   608  			t.Errorf("Action timed out")
   609  		}
   610  
   611  		select {
   612  		case ts.ResponseChan <- response.NewAck():
   613  		case <-time.After(time.Second):
   614  			t.Errorf("Action timed out")
   615  		}
   616  	}
   617  
   618  	h.CloseAsync()
   619  	if err := h.WaitForClose(time.Second); err != nil {
   620  		t.Error(err)
   621  	}
   622  }
   623  
   624  func TestHTTPClientStreamGETMultiRecover(t *testing.T) {
   625  	msgs := [][]string{
   626  		{"foo", "bar"},
   627  		{"foo", "baz"},
   628  	}
   629  
   630  	tserve := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   631  		if exp, act := "GET", r.Method; exp != act {
   632  			t.Errorf("Wrong method: %v != %v", act, exp)
   633  		}
   634  
   635  		body := &bytes.Buffer{}
   636  		for _, msg := range msgs {
   637  			for _, part := range msg {
   638  				body.WriteString(part)
   639  				body.WriteByte('\n')
   640  			}
   641  			body.WriteByte('\n')
   642  		}
   643  
   644  		w.Header().Add("Content-Type", "application/octet-stream")
   645  		w.Write(body.Bytes())
   646  	}))
   647  	defer tserve.Close()
   648  
   649  	conf := NewConfig()
   650  	conf.HTTPClient.URL = tserve.URL + "/testpost"
   651  	conf.HTTPClient.Retry = "1ms"
   652  	conf.HTTPClient.Stream.Enabled = true
   653  	conf.HTTPClient.Stream.Multipart = true
   654  
   655  	h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop())
   656  	if err != nil {
   657  		t.Error(err)
   658  		return
   659  	}
   660  
   661  	for i := 0; i < 10; i++ {
   662  		for _, testMsg := range msgs {
   663  			var ts types.Transaction
   664  			var open bool
   665  			select {
   666  			case ts, open = <-h.TransactionChan():
   667  				if !open {
   668  					t.Fatal("Chan not open")
   669  				}
   670  				if exp, act := len(testMsg), ts.Payload.Len(); exp != act {
   671  					t.Fatalf("Wrong count of parts: %v != %v", act, exp)
   672  				}
   673  				for j, part := range testMsg {
   674  					if exp, act := part, string(ts.Payload.Get(j).Get()); exp != act {
   675  						t.Errorf("Wrong part: %v != %v", act, exp)
   676  					}
   677  				}
   678  			case <-time.After(time.Second):
   679  				t.Errorf("Action timed out")
   680  			}
   681  
   682  			select {
   683  			case ts.ResponseChan <- response.NewAck():
   684  			case <-time.After(time.Second):
   685  				t.Errorf("Action timed out")
   686  			}
   687  		}
   688  	}
   689  
   690  	h.CloseAsync()
   691  	if err := h.WaitForClose(time.Second); err != nil {
   692  		t.Error(err)
   693  	}
   694  }
   695  
   696  func TestHTTPClientStreamGETRecover(t *testing.T) {
   697  	msgs := []string{"foo", "bar"}
   698  
   699  	tserve := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   700  		if exp, act := "GET", r.Method; exp != act {
   701  			t.Errorf("Wrong method: %v != %v", act, exp)
   702  		}
   703  
   704  		body := &bytes.Buffer{}
   705  		for _, msg := range msgs {
   706  			body.WriteString(msg)
   707  			body.WriteByte('\n')
   708  		}
   709  
   710  		w.Header().Add("Content-Type", "application/octet-stream")
   711  		w.Write(body.Bytes())
   712  	}))
   713  	defer tserve.Close()
   714  
   715  	conf := NewConfig()
   716  	conf.HTTPClient.URL = tserve.URL + "/testpost"
   717  	conf.HTTPClient.Retry = "1ms"
   718  	conf.HTTPClient.Stream.Enabled = true
   719  	conf.HTTPClient.Stream.Multipart = false
   720  
   721  	h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop())
   722  	if err != nil {
   723  		t.Error(err)
   724  		return
   725  	}
   726  
   727  	for i := 0; i < 10; i++ {
   728  		for _, testMsg := range msgs {
   729  			var ts types.Transaction
   730  			var open bool
   731  			select {
   732  			case ts, open = <-h.TransactionChan():
   733  				if !open {
   734  					t.Fatal("Chan not open")
   735  				}
   736  				if exp, act := 1, ts.Payload.Len(); exp != act {
   737  					t.Fatalf("Wrong count of parts: %v != %v", act, exp)
   738  				}
   739  				if exp, act := testMsg, string(ts.Payload.Get(0).Get()); exp != act {
   740  					t.Errorf("Wrong part: %v != %v", act, exp)
   741  				}
   742  			case <-time.After(time.Second):
   743  				t.Errorf("Action timed out")
   744  			}
   745  
   746  			select {
   747  			case ts.ResponseChan <- response.NewAck():
   748  			case <-time.After(time.Second):
   749  				t.Errorf("Action timed out")
   750  			}
   751  		}
   752  	}
   753  
   754  	h.CloseAsync()
   755  	if err := h.WaitForClose(time.Second); err != nil {
   756  		t.Error(err)
   757  	}
   758  }
   759  
   760  func TestHTTPClientStreamGETTokenization(t *testing.T) {
   761  	msgs := []string{`{"token":"foo"}`, `{"token":"bar"}`}
   762  
   763  	var tokensLock sync.Mutex
   764  	updateTokens := true
   765  	tokens := []string{}
   766  
   767  	tserve := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   768  		assert.Equal(t, "GET", r.Method)
   769  
   770  		tokensLock.Lock()
   771  		if updateTokens {
   772  			tokens = append(tokens, r.URL.Query().Get("token"))
   773  		}
   774  		tokensLock.Unlock()
   775  
   776  		body := &bytes.Buffer{}
   777  		for _, msg := range msgs {
   778  			body.WriteString(msg)
   779  			body.WriteByte('\n')
   780  		}
   781  
   782  		w.Header().Add("Content-Type", "application/octet-stream")
   783  		w.Write(body.Bytes())
   784  	}))
   785  	defer tserve.Close()
   786  
   787  	conf := NewConfig()
   788  	conf.HTTPClient.URL = tserve.URL + `/testpost?token=${!json("token")}`
   789  	conf.HTTPClient.Retry = "1ms"
   790  	conf.HTTPClient.Stream.Enabled = true
   791  	conf.HTTPClient.Stream.Multipart = false
   792  
   793  	h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop())
   794  	require.NoError(t, err)
   795  
   796  	for i := 0; i < 10; i++ {
   797  		if i == 9 {
   798  			tokensLock.Lock()
   799  			updateTokens = false
   800  			tokensLock.Unlock()
   801  		}
   802  
   803  		for _, testMsg := range msgs {
   804  			var ts types.Transaction
   805  			var open bool
   806  			select {
   807  			case ts, open = <-h.TransactionChan():
   808  				require.True(t, open)
   809  				require.Equal(t, 1, ts.Payload.Len())
   810  				assert.Equal(t, testMsg, string(ts.Payload.Get(0).Get()))
   811  			case <-time.After(time.Second):
   812  				t.Errorf("Action timed out")
   813  			}
   814  
   815  			select {
   816  			case ts.ResponseChan <- response.NewAck():
   817  			case <-time.After(time.Second):
   818  				t.Errorf("Action timed out")
   819  			}
   820  		}
   821  	}
   822  
   823  	tokensLock.Lock()
   824  	assert.Equal(t, []string{
   825  		"null", "bar", "bar", "bar", "bar", "bar", "bar", "bar", "bar",
   826  	}, tokens)
   827  	tokensLock.Unlock()
   828  
   829  	h.CloseAsync()
   830  	require.NoError(t, h.WaitForClose(time.Second))
   831  }
   832  
   833  func BenchmarkHTTPClientGETMultipart(b *testing.B) {
   834  	parts := []string{
   835  		"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
   836  		"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
   837  		"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
   838  	}
   839  
   840  	body := &bytes.Buffer{}
   841  	writer := multipart.NewWriter(body)
   842  	for _, p := range parts {
   843  		var err error
   844  		var part io.Writer
   845  		if part, err = writer.CreatePart(textproto.MIMEHeader{
   846  			"Content-Type": []string{"application/octet-stream"},
   847  		}); err == nil {
   848  			_, err = io.Copy(part, bytes.NewReader([]byte(p)))
   849  		}
   850  		if err != nil {
   851  			b.Fatal(err)
   852  		}
   853  	}
   854  	writer.Close()
   855  	header := writer.FormDataContentType()
   856  
   857  	tserve := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   858  		if exp, act := "GET", r.Method; exp != act {
   859  			b.Errorf("Wrong method: %v != %v", act, exp)
   860  		}
   861  
   862  		w.Header().Add("Content-Type", header)
   863  		w.Write(body.Bytes())
   864  	}))
   865  	defer tserve.Close()
   866  
   867  	conf := NewConfig()
   868  	conf.HTTPClient.URL = tserve.URL + "/testpost"
   869  	conf.HTTPClient.Retry = "1ms"
   870  
   871  	h, err := NewHTTPClient(conf, nil, log.Noop(), metrics.Noop())
   872  	if err != nil {
   873  		b.Error(err)
   874  		return
   875  	}
   876  
   877  	b.ReportAllocs()
   878  	b.ResetTimer()
   879  
   880  	for n := 0; n < b.N; n++ {
   881  		ts, open := <-h.TransactionChan()
   882  		if !open {
   883  			b.Fatal("Chan not open")
   884  		}
   885  		if exp, act := 3, ts.Payload.Len(); exp != act {
   886  			b.Fatalf("Wrong count of parts: %v != %v", act, exp)
   887  		}
   888  		for i, part := range parts {
   889  			if exp, act := part, string(ts.Payload.Get(i).Get()); exp != act {
   890  				b.Errorf("Wrong part: %v != %v", act, exp)
   891  			}
   892  		}
   893  		ts.ResponseChan <- response.NewAck()
   894  	}
   895  
   896  	b.StopTimer()
   897  
   898  	h.CloseAsync()
   899  	if err := h.WaitForClose(time.Second); err != nil {
   900  		b.Error(err)
   901  	}
   902  }