1package traffic
2
3import (
4 "bufio"
5 "context"
6 "io"
7 "net/http"
8 "testing"
9 "time"
10
11 "github.com/stretchr/testify/assert"
12 "github.com/stretchr/testify/require"
13
14 "github.com/bnema/gordon/internal/domain"
15)
16
17func TestNewEntryPointHTTPServerUsesConfiguredTimeouts(t *testing.T) {
18 server := newEntryPointHTTPServer(http.NotFoundHandler(), domain.HTTPOptions{
19 ReadHeaderTimeout: 3 * time.Second,
20 IdleTimeout: 7 * time.Second,
21 TransferIdleTimeout: 9 * time.Second,
22 })
23
24 assert.Equal(t, 3*time.Second, server.ReadHeaderTimeout)
25 assert.Equal(t, 7*time.Second, server.IdleTimeout)
26 assert.Zero(t, server.ReadTimeout, "whole-request read timeout must be replaced by the inactivity deadline")
27 assert.Zero(t, server.WriteTimeout, "whole-response write timeout must be replaced by the inactivity deadline")
28}
29
30func TestManagerHTTPOptionsDefaultsWithoutSnapshot(t *testing.T) {
31 assert.Equal(t, domain.DefaultHTTPOptions(), NewManager().httpOptions())
32}
33
34func TestEntryPointServersReceiveConfiguredHTTPOptions(t *testing.T) {
35 manager := NewManager()
36 graph := smartTCPGraph(t, freeTCPAddress(t), nil, nil)
37 graph.Options.HTTP = domain.HTTPOptions{ReadHeaderTimeout: 3 * time.Second, IdleTimeout: 7 * time.Second, TransferIdleTimeout: 9 * time.Second}
38 manager.SetSmartTCPHTTPServer("edge", http.NotFoundHandler(), nil)
39 manager.SetSmartTCPTLSServer("edge", http.NotFoundHandler(), testTLSConfig(t, "app.example.com"))
40 require.NoError(t, manager.Apply(context.Background(), &graph))
41 defer shutdownManager(t, manager)
42
43 runtime := tcpRuntimeForTest(t, manager, "edge")
44 require.NotNil(t, runtime)
45 runtime.mu.Lock()
46 plain, secure := runtime.smartHTTPServer, runtime.smartTLSServer
47 runtime.mu.Unlock()
48 for name, server := range map[string]*http.Server{"plain": plain, "tls": secure} {
49 require.NotNil(t, server, name)
50 assert.Equal(t, 3*time.Second, server.ReadHeaderTimeout, name)
51 assert.Equal(t, 7*time.Second, server.IdleTimeout, name)
52 assert.Zero(t, server.ReadTimeout, name)
53 assert.Zero(t, server.WriteTimeout, name)
54 }
55}
56
57func TestTLSMuxServerReceivesConfiguredHTTPOptions(t *testing.T) {
58 manager := NewManager()
59 graph := domain.TrafficGraph{
60 Options: domain.TrafficOptions{HTTP: domain.HTTPOptions{ReadHeaderTimeout: 4 * time.Second, IdleTimeout: 8 * time.Second, TransferIdleTimeout: time.Minute}},
61 EntryPoints: []domain.EntryPoint{{Name: "websecure", Address: freeTCPAddress(t), Protocol: domain.EntryPointProtocolTLSMux}},
62 }
63 manager.SetTLSHTTPServer("websecure", http.NotFoundHandler(), testTLSConfig(t, "app.example.com"))
64 require.NoError(t, manager.Apply(context.Background(), &graph))
65 defer shutdownManager(t, manager)
66
67 runtime := tcpRuntimeForTest(t, manager, "websecure")
68 require.NotNil(t, runtime)
69 runtime.mu.Lock()
70 server := runtime.tlsHTTPServer
71 runtime.mu.Unlock()
72 require.NotNil(t, server)
73 assert.Equal(t, 4*time.Second, server.ReadHeaderTimeout)
74 assert.Equal(t, 8*time.Second, server.IdleTimeout)
75 assert.Zero(t, server.ReadTimeout)
76 assert.Zero(t, server.WriteTimeout)
77}
78
79func TestSmartTCPStreamingResponseOutlivesTransferIdleTimeout(t *testing.T) {
80 const idle = 300 * time.Millisecond
81 manager := NewManager()
82 graph := smartTCPGraph(t, freeTCPAddress(t), nil, nil)
83 graph.Options.HTTP = domain.HTTPOptions{TransferIdleTimeout: idle}
84 manager.SetSmartTCPHTTPServer("edge", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
85 silent := r.URL.Path == "/silent"
86 for i := 0; i < 12; i++ {
87 if _, err := w.Write([]byte("x")); err != nil {
88 return
89 }
90 w.(http.Flusher).Flush()
91 if silent {
92 // Stay silent until the inactivity timeout cancels the request.
93 <-r.Context().Done()
94 return
95 }
96 select {
97 case <-r.Context().Done():
98 return
99 case <-time.After(50 * time.Millisecond):
100 }
101 }
102 }), nil)
103 require.NoError(t, manager.Apply(context.Background(), &graph))
104 defer shutdownManager(t, manager)
105
106 get := func(path string) (int, error) {
107 conn := dialTCP(t, graph.EntryPoints[0].Address)
108 defer conn.Close()
109 _, err := conn.Write([]byte("GET " + path + " HTTP/1.1\r\nHost: app.example.com\r\nConnection: close\r\n\r\n"))
110 require.NoError(t, err)
111 resp, err := http.ReadResponse(bufio.NewReader(conn), nil)
112 require.NoError(t, err)
113 defer resp.Body.Close()
114 body, err := io.ReadAll(resp.Body)
115 return len(body), err
116 }
117
118 n, err := get("/active")
119 require.NoError(t, err)
120 assert.Equal(t, 12, n, "active transfer longer than the idle timeout must complete")
121
122 n, err = get("/silent")
123 require.Error(t, err, "silent transfer must be cut")
124 assert.Less(t, n, 12)
125}