1+ package proxy
2+
3+ import (
4+ "bufio"
5+ "context"
6+ "crypto/tls"
7+ "fmt"
8+ "net"
9+ "net/http"
10+ "net/url"
11+ "testing"
12+ "time"
13+
14+ "github.com/codefionn/msgtausch/msgtausch-srv/config"
15+ )
16+
17+ // TestConnectionCloseHandling tests that the proxy handles "Connection: close" responses quickly
18+ func TestConnectionCloseHandling (t * testing.T ) {
19+ // Create a mock HTTPS server that sends Connection: close
20+ ln , err := net .Listen ("tcp" , "127.0.0.1:0" )
21+ if err != nil {
22+ t .Fatalf ("Failed to create listener for mock server: %v" , err )
23+ }
24+ defer ln .Close ()
25+
26+ serverAddr := ln .Addr ().String ()
27+
28+ // JavaScript content similar to the problematic wgv.de response
29+ jsContent := `/* Dynamically generated content! DON'T COPY IT TO YOUR SERVERS! */
30+ (function(){var w=window,d=document,t="script";console.log("test")})();`
31+
32+ // Start mock server that sends Connection: close
33+ go func () {
34+ for {
35+ conn , err := ln .Accept ()
36+ if err != nil {
37+ return // Listener closed
38+ }
39+
40+ go func (conn net.Conn ) {
41+ defer conn .Close ()
42+
43+ // Read the HTTP request
44+ reader := bufio .NewReader (conn )
45+ _ , err := http .ReadRequest (reader )
46+ if err != nil {
47+ return
48+ }
49+
50+ // Send response with Connection: close (like wgv.de does)
51+ response := fmt .Sprintf ("HTTP/1.1 200 OK\r \n " +
52+ "Content-Type: text/javascript\r \n " +
53+ "Connection: close\r \n " +
54+ "Content-Length: %d\r \n " +
55+ "Cache-Control: private, no-cache, no-store, must-revalidate\r \n " +
56+ "\r \n " +
57+ "%s" , len (jsContent ), jsContent )
58+
59+ conn .Write ([]byte (response ))
60+ // Immediately close the connection after sending (like Connection: close should)
61+ }(conn )
62+ }
63+ }()
64+
65+ // Create proxy configuration
66+ cfg := & config.Config {
67+ Servers : []config.ServerConfig {
68+ {
69+ Type : config .ProxyTypeStandard ,
70+ ListenAddress : "127.0.0.1:0" ,
71+ Enabled : true ,
72+ },
73+ },
74+ TimeoutSeconds : 30 ,
75+ MaxConcurrentConnections : 100 ,
76+ Classifiers : make (map [string ]config.Classifier ),
77+ }
78+
79+ proxy := NewProxy (cfg )
80+
81+ // Start proxy server
82+ proxyListener , err := net .Listen ("tcp" , cfg .Servers [0 ].ListenAddress )
83+ if err != nil {
84+ t .Fatalf ("Failed to create proxy listener: %v" , err )
85+ }
86+ proxyAddr := proxyListener .Addr ().String ()
87+
88+ go func () {
89+ if err := proxy .StartWithListener (proxyListener ); err != http .ErrServerClosed && err != nil {
90+ t .Errorf ("Proxy server error: %v" , err )
91+ }
92+ }()
93+ defer proxy .Stop ()
94+
95+ // Wait for proxy to start
96+ time .Sleep (100 * time .Millisecond )
97+
98+ // Test the Connection: close handling
99+ t .Run ("Connection close handling" , func (t * testing.T ) {
100+ // Create HTTP client that uses our proxy
101+ client := & http.Client {
102+ Transport : & http.Transport {
103+ Proxy : http .ProxyURL (& url.URL {Host : proxyAddr }),
104+ TLSClientConfig : & tls.Config {
105+ InsecureSkipVerify : true , // For testing
106+ },
107+ },
108+ Timeout : 10 * time .Second , // Should complete much faster
109+ }
110+
111+ // Measure the time it takes to complete the request
112+ start := time .Now ()
113+
114+ // Make request to our mock server through the proxy
115+ targetURL := fmt .Sprintf ("http://%s/test.js" , serverAddr )
116+ req , err := http .NewRequestWithContext (context .Background (), "GET" , targetURL , nil )
117+ if err != nil {
118+ t .Fatalf ("Failed to create request: %v" , err )
119+ }
120+
121+ resp , err := client .Do (req )
122+ if err != nil {
123+ t .Fatalf ("Request through proxy failed: %v" , err )
124+ }
125+ defer resp .Body .Close ()
126+
127+ duration := time .Since (start )
128+
129+ // Verify response
130+ if resp .StatusCode != http .StatusOK {
131+ t .Errorf ("Expected status code %d, got %d" , http .StatusOK , resp .StatusCode )
132+ }
133+
134+ // Read response body
135+ body := make ([]byte , len (jsContent ))
136+ n , err := resp .Body .Read (body )
137+ if err != nil && err .Error () != "EOF" {
138+ t .Fatalf ("Failed to read response body: %v" , err )
139+ }
140+
141+ if string (body [:n ]) != jsContent {
142+ t .Errorf ("Response body mismatch.\n Expected: %s\n Got: %s" , jsContent , string (body [:n ]))
143+ }
144+
145+ // Verify that the connection closed quickly (should be under 2 seconds)
146+ // Before the fix, this would take 30+ seconds due to timeout
147+ if duration > 2 * time .Second {
148+ t .Errorf ("Connection close handling too slow: %v (should be < 2s)" , duration )
149+ }
150+
151+ t .Logf ("Connection close handling completed in: %v" , duration )
152+ })
153+ }
154+
155+ // TestConnectionCloseHTTPS tests Connection: close handling over HTTPS tunnel
156+ func TestConnectionCloseHTTPS (t * testing.T ) {
157+ // Create a TLS server that sends Connection: close
158+ tlsServer , certPool := setupTLSServer (t )
159+ defer tlsServer .Close ()
160+
161+ // Replace the default handler with one that sends Connection: close
162+ tlsServer .Config .Handler = http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
163+ w .Header ().Set ("Connection" , "close" )
164+ w .Header ().Set ("Content-Type" , "text/javascript" )
165+ w .Header ().Set ("Cache-Control" , "private, no-cache, no-store, must-revalidate" )
166+
167+ jsContent := `/* Test JS content with Connection: close */
168+ (function(){console.log("connection close test")})();`
169+ w .Write ([]byte (jsContent ))
170+ })
171+
172+ // Create proxy configuration
173+ cfg := & config.Config {
174+ Servers : []config.ServerConfig {
175+ {
176+ Type : config .ProxyTypeStandard ,
177+ ListenAddress : "127.0.0.1:0" ,
178+ Enabled : true ,
179+ },
180+ },
181+ TimeoutSeconds : 30 ,
182+ MaxConcurrentConnections : 100 ,
183+ Classifiers : make (map [string ]config.Classifier ),
184+ }
185+
186+ proxy := NewProxy (cfg )
187+
188+ // Start proxy server
189+ proxyListener , err := net .Listen ("tcp" , cfg .Servers [0 ].ListenAddress )
190+ if err != nil {
191+ t .Fatalf ("Failed to create proxy listener: %v" , err )
192+ }
193+ proxyAddr := proxyListener .Addr ().String ()
194+
195+ go func () {
196+ if err := proxy .StartWithListener (proxyListener ); err != http .ErrServerClosed && err != nil {
197+ t .Errorf ("Proxy server error: %v" , err )
198+ }
199+ }()
200+ defer proxy .Stop ()
201+
202+ // Wait for proxy to start
203+ time .Sleep (100 * time .Millisecond )
204+
205+ t .Run ("HTTPS Connection close handling" , func (t * testing.T ) {
206+ // Create HTTP client that uses our proxy
207+ client := & http.Client {
208+ Transport : & http.Transport {
209+ Proxy : http .ProxyURL (& url.URL {Host : proxyAddr }),
210+ TLSClientConfig : & tls.Config {
211+ RootCAs : certPool ,
212+ },
213+ },
214+ Timeout : 10 * time .Second ,
215+ }
216+
217+ // Measure the time it takes to complete the request
218+ start := time .Now ()
219+
220+ resp , err := client .Get (tlsServer .URL + "/test.js" )
221+ if err != nil {
222+ t .Fatalf ("HTTPS request through proxy failed: %v" , err )
223+ }
224+ defer resp .Body .Close ()
225+
226+ duration := time .Since (start )
227+
228+ // Verify response
229+ if resp .StatusCode != http .StatusOK {
230+ t .Errorf ("Expected status code %d, got %d" , http .StatusOK , resp .StatusCode )
231+ }
232+
233+ // Note: Connection: close header might not be preserved in HTTP/2
234+ // but the connection handling should still be fast
235+ t .Logf ("Connection header: %v" , resp .Header .Get ("Connection" ))
236+
237+ // Verify that the connection closed quickly
238+ if duration > 2 * time .Second {
239+ t .Errorf ("HTTPS connection close handling too slow: %v (should be < 2s)" , duration )
240+ }
241+
242+ t .Logf ("HTTPS connection close handling completed in: %v" , duration )
243+ })
244+ }
0 commit comments