@@ -22,20 +22,28 @@ package transport
2222
2323import (
2424 "io"
25- "io/ioutil"
2625 "net/http"
2726 "net/http/httptest"
2827 "net/url"
2928 "strconv"
3029 "testing"
3130 "time"
3231
32+ "golang.org/x/net/http2"
33+ "golang.org/x/net/http2/h2c"
34+
3335 "golang.org/x/net/context"
3436
3537 "github.com/stretchr/testify/assert"
3638 "github.com/stretchr/testify/require"
3739)
3840
41+ var (
42+ defaultIdleConnTimeout = 15 * time .Minute
43+ serverTimeout = 20 * time .Millisecond
44+ clientTimeout = 10 * time .Millisecond
45+ )
46+
3947func TestHTTPConstructor (t * testing.T ) {
4048 tests := []struct {
4149 opts HTTPOptions
@@ -71,73 +79,19 @@ func TestHTTPConstructor(t *testing.T) {
7179}
7280
7381func TestHTTPCall (t * testing.T ) {
74- timeoutCtx , _ := context .WithTimeout (context .Background (), 3 * time .Second )
75- immediateTimeout , _ := context .WithTimeout (context .Background (), time .Nanosecond )
76-
77- tests := []struct {
78- msg string
79- ctxOverride context.Context
80- hook string
81- method string
82- errMsg string
83- ttlMin time.Duration
84- ttlMax time.Duration
85- wantCode int
86- wantBody []byte // If nil, uses the request body
87- }{
88- {
89- msg : "ok" ,
90- ttlMin : time .Second ,
91- ttlMax : time .Second ,
92- wantCode : http .StatusOK ,
93- },
94- {
95- msg : "3 second timeout" ,
96- ctxOverride : timeoutCtx ,
97- ttlMin : 3 * time .Second - 100 * time .Millisecond ,
98- ttlMax : 3 * time .Second ,
99- wantCode : http .StatusOK ,
100- },
101- {
102- msg : "timed out" ,
103- ctxOverride : immediateTimeout ,
104- errMsg : context .DeadlineExceeded .Error (),
105- },
82+ tests := getCommonHttpTestCases ()
83+ tests = append (tests , []TestBody {
10684 {
10785 msg : "connection closed before data" ,
10886 hook : "kill_conn" ,
10987 errMsg : "EOF" ,
11088 },
111- {
112- msg : "bad request response" ,
113- hook : "bad_req" ,
114- errMsg : "non-success response code: 400, body: bad request" ,
115- },
11689 {
11790 msg : "connection closed after data" ,
11891 hook : "flush_and_kill" ,
11992 errMsg : "unexpected EOF" ,
12093 },
121- {
122- msg : "no content" ,
123- hook : "no_content" ,
124- wantCode : http .StatusNoContent ,
125- wantBody : []byte {},
126- },
127- {
128- msg : "default method to POST" ,
129- hook : "method" ,
130- wantCode : http .StatusOK ,
131- wantBody : []byte ("POST" ),
132- },
133- {
134- msg : "override method to GET" ,
135- method : "GET" ,
136- hook : "method" ,
137- wantCode : http .StatusOK ,
138- wantBody : []byte ("GET" ),
139- },
140- }
94+ }... )
14195
14296 lastReq := struct {
14397 url * url.URL
@@ -149,7 +103,7 @@ func TestHTTPCall(t *testing.T) {
149103 var err error
150104 lastReq .url = r .URL
151105 lastReq .headers = r .Header
152- lastReq .body , err = ioutil .ReadAll (r .Body )
106+ lastReq .body , err = io .ReadAll (r .Body )
153107 require .NoError (t , err , "Failed to read body from request" )
154108
155109 // Test hooks to change the request behaviour.
@@ -169,6 +123,9 @@ func TestHTTPCall(t *testing.T) {
169123 case "server_err" :
170124 w .WriteHeader (http .StatusInternalServerError )
171125 return
126+ case "timeout_hook" :
127+ time .Sleep (serverTimeout )
128+ return
172129 case "flush_and_kill" :
173130 io .WriteString (w , "some data" )
174131 flusher := w .(http.Flusher )
@@ -254,3 +211,185 @@ func TestHTTPCall(t *testing.T) {
254211 })
255212 }
256213}
214+
215+ func TestHTTP2Call (t * testing.T ) {
216+ tests := getCommonHttpTestCases ()
217+
218+ lastReq := struct {
219+ url * url.URL
220+ headers http.Header
221+ body []byte
222+ }{}
223+
224+ handler := http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
225+ var err error
226+ lastReq .url = r .URL
227+ lastReq .headers = r .Header
228+ lastReq .body , err = io .ReadAll (r .Body )
229+ require .NoError (t , err , "Failed to read body from request" )
230+
231+ // Test hooks to change the request behaviour.
232+ switch f := r .Header .Get ("hook" ); f {
233+ case "no_content" :
234+ w .Header ().Set ("Custom-Header" , "ok" )
235+ w .WriteHeader (http .StatusNoContent )
236+ return
237+ case "method" :
238+ w .Header ().Set ("Custom-Header" , "ok" )
239+ io .WriteString (w , r .Method )
240+ return
241+ case "bad_req" :
242+ w .WriteHeader (http .StatusBadRequest )
243+ io .WriteString (w , "bad request" )
244+ return
245+ case "server_err" :
246+ w .WriteHeader (http .StatusInternalServerError )
247+ return
248+ case "timeout_hook" :
249+ time .Sleep (serverTimeout )
250+ return
251+ }
252+
253+ w .Header ().Set ("Custom-Header" , "ok" )
254+ io .WriteString (w , "ok" )
255+ })
256+
257+ h2s := & http2.Server {
258+ IdleTimeout : defaultIdleConnTimeout ,
259+ }
260+
261+ svr := httptest .NewServer (h2c .NewHandler (handler , h2s ))
262+ defer svr .Close ()
263+
264+ for _ , tt := range tests {
265+ t .Run (tt .msg , func (t * testing.T ) {
266+ transport , err := NewHTTP (HTTPOptions {
267+ Method : tt .method ,
268+ URLs : []string {svr .URL + "/rpc" },
269+ SourceService : "source" ,
270+ TargetService : "target" ,
271+ ShardKey : "sk" ,
272+ RoutingKey : "rk" ,
273+ RoutingDelegate : "rd" ,
274+ Encoding : "raw" ,
275+ UseHTTP2 : true ,
276+ })
277+ require .NoError (t , err , "Failed to create HTTP transport" )
278+
279+ ctx := context .Background ()
280+ if tt .ctxOverride != nil {
281+ ctx = tt .ctxOverride
282+ }
283+
284+ r := & Request {Method : "method" , Body : []byte {1 , 2 , 3 }}
285+
286+ r .TransportHeaders = map [string ]string {"hook" : tt .hook }
287+ r .Headers = map [string ]string {"headerkey" : "headervalue" }
288+ got , err := transport .Call (ctx , r )
289+ if tt .errMsg != "" {
290+ if assert .Error (t , err , "Call should fail" ) {
291+ assert .Contains (t , err .Error (), tt .errMsg , "Unexpected error" )
292+ }
293+ return
294+ }
295+
296+ if ! assert .NoError (t , err , "Call shouldn't fail" ) {
297+ return
298+ }
299+
300+ wantBody := tt .wantBody
301+ if wantBody == nil {
302+ wantBody = []byte ("ok" )
303+ }
304+ if ! assert .Equal (t , wantBody , got .Body , "Response body mismatch" ) {
305+ return
306+ }
307+
308+ assert .Equal (t , "/rpc" , lastReq .url .Path , "Path mismatch" )
309+ assert .Equal (t , "target" , lastReq .headers .Get ("Rpc-Service" ), "Service header mismatch" )
310+ assert .Equal (t , "source" , lastReq .headers .Get ("Rpc-Caller" ), "Caller header mismatch" )
311+ assert .Equal (t , "sk" , lastReq .headers .Get ("Rpc-Shard-Key" ), "Shard key header mismatch" )
312+ assert .Equal (t , "rk" , lastReq .headers .Get ("Rpc-Routing-Key" ), "Routing key header mismatch" )
313+ assert .Equal (t , "rd" , lastReq .headers .Get ("Rpc-Routing-Delegate" ), "Routing delegate header mismatch" )
314+ assert .Equal (t , r .Method , lastReq .headers .Get ("Rpc-Procedure" ), "Method header mismatch" )
315+ assert .Equal (t , "raw" , lastReq .headers .Get ("Rpc-Encoding" ), "Encoding header mismatch" )
316+ assert .Equal (t , "headervalue" , lastReq .headers .Get ("Rpc-Header-Headerkey" ), "Application header is sent with prefix" )
317+
318+ if tt .ttlMin != 0 && tt .ttlMax != 0 {
319+ ttlMS , err := strconv .Atoi (lastReq .headers .Get ("Context-TTL-MS" ))
320+ if assert .NoError (t , err , "Failed to parse TTLms header: %v" , lastReq .headers .Get ("YARPC-TTLms" )) {
321+ gotTTL := time .Duration (ttlMS ) * time .Millisecond
322+ assert .True (t , gotTTL >= tt .ttlMin && gotTTL <= tt .ttlMax ,
323+ "Got TTL %v out of range [%v,%v]" , gotTTL , tt .ttlMin , tt .ttlMax )
324+ }
325+ }
326+
327+ assert .Equal (t , "ok" , got .Headers ["Custom-Header" ], "Header mismatch" )
328+ assert .Equal (t , tt .wantCode , got .TransportFields ["statusCode" ], "Status code mismatch" )
329+ assert .Equal (t , lastReq .body , r .Body , "Body mismatch" )
330+ })
331+ }
332+ }
333+
334+ type TestBody struct {
335+ msg string
336+ ctxOverride context.Context
337+ hook string
338+ method string
339+ errMsg string
340+ ttlMin time.Duration
341+ ttlMax time.Duration
342+ wantCode int
343+ wantBody []byte // If nil, uses the request body
344+ }
345+
346+ func getCommonHttpTestCases () []TestBody {
347+ timeoutCtx , _ := context .WithTimeout (context .Background (), 3 * time .Second )
348+ immediateTimeout , _ := context .WithTimeout (context .Background (), clientTimeout )
349+
350+ return []TestBody {
351+ {
352+ msg : "ok" ,
353+ ttlMin : time .Second ,
354+ ttlMax : time .Second ,
355+ wantCode : http .StatusOK ,
356+ },
357+ {
358+ msg : "3 second timeout" ,
359+ ctxOverride : timeoutCtx ,
360+ ttlMin : 3 * time .Second - 100 * time .Millisecond ,
361+ ttlMax : 3 * time .Second ,
362+ wantCode : http .StatusOK ,
363+ },
364+ {
365+ msg : "timed out" ,
366+ ctxOverride : immediateTimeout ,
367+ hook : "timeout_hook" ,
368+ errMsg : context .DeadlineExceeded .Error (),
369+ },
370+ {
371+ msg : "bad request response" ,
372+ hook : "bad_req" ,
373+ errMsg : "non-success response code: 400, body: bad request" ,
374+ },
375+ {
376+ msg : "no content" ,
377+ hook : "no_content" ,
378+ wantCode : http .StatusNoContent ,
379+ wantBody : []byte {},
380+ },
381+ {
382+ msg : "default method to POST" ,
383+ hook : "method" ,
384+ wantCode : http .StatusOK ,
385+ wantBody : []byte ("POST" ),
386+ },
387+ {
388+ msg : "override method to GET" ,
389+ method : "GET" ,
390+ hook : "method" ,
391+ wantCode : http .StatusOK ,
392+ wantBody : []byte ("GET" ),
393+ },
394+ }
395+ }
0 commit comments