@@ -36,6 +36,25 @@ fn hasCapability(haystack: []const WindowCapability, needle: WindowCapability) b
3636 return false ;
3737}
3838
39+ fn writeAllStream (stream : std.net.Stream , bytes : []const u8 ) ! void {
40+ if (builtin .os .tag == .windows ) {
41+ var offset : usize = 0 ;
42+ while (offset < bytes .len ) {
43+ const n = std .posix .send (stream .handle , bytes [offset .. ], 0 ) catch | err | switch (err ) {
44+ error .WouldBlock = > {
45+ std .Thread .sleep (std .time .ns_per_ms );
46+ continue ;
47+ },
48+ else = > return err ,
49+ };
50+ if (n == 0 ) return error .Closed ;
51+ offset += n ;
52+ }
53+ return ;
54+ }
55+ try stream .writeAll (bytes );
56+ }
57+
3958test "window lifecycle" {
4059 var gpa = std .heap .GeneralPurposeAllocator (.{}){};
4160 defer _ = gpa .deinit ();
@@ -71,7 +90,8 @@ test "browser fallback serves window html over local http" {
7190 const stream = try std .net .tcpConnectToAddress (address );
7291 defer stream .close ();
7392
74- try stream .writeAll (
93+ try writeAllStream (
94+ stream ,
7595 "GET / HTTP/1.1\r \n " ++
7696 "Host: 127.0.0.1\r \n " ++
7797 "Connection: close\r \n " ++
@@ -150,7 +170,8 @@ test "websocket upgrade uses same http server port" {
150170 const stream = try std .net .tcpConnectToAddress (address );
151171 defer stream .close ();
152172
153- try stream .writeAll (
173+ try writeAllStream (
174+ stream ,
154175 "GET /webui/ws?client_id=test-client HTTP/1.1\r \n " ++
155176 "Host: 127.0.0.1\r \n " ++
156177 "Upgrade: websocket\r \n " ++
@@ -168,6 +189,139 @@ test "websocket upgrade uses same http server port" {
168189 try std .testing .expect (std .mem .indexOf (u8 , response , "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=" ) != null );
169190}
170191
192+ test "http server remains responsive after partial request disconnect bursts" {
193+ var gpa = std .heap .GeneralPurposeAllocator (.{}){};
194+ defer _ = gpa .deinit ();
195+
196+ var app = try App .init (gpa .allocator (), .{
197+ .launch_policy = .{ .first = .web_url , .second = null , .third = null },
198+ });
199+ defer app .deinit ();
200+
201+ var win = try app .newWindow (.{ .title = "PartialDisconnectBurst" });
202+ try win .showHtml ("<html><body>partial-disconnect-ok</body></html>" );
203+ try app .run ();
204+
205+ const address = try std .net .Address .parseIp4 ("127.0.0.1" , win .state ().server_port );
206+ var bursts : usize = 0 ;
207+ while (bursts < 64 ) : (bursts += 1 ) {
208+ const stream = try std .net .tcpConnectToAddress (address );
209+ try writeAllStream (stream , "GET / HTTP/1.1\r \n Host: 127.0.0.1\r \n " );
210+ stream .close ();
211+ }
212+
213+ var checks : usize = 0 ;
214+ while (checks < 16 ) : (checks += 1 ) {
215+ const response = try httpRoundTrip (gpa .allocator (), win .state ().server_port , "GET" , "/" , null );
216+ defer gpa .allocator ().free (response );
217+ try std .testing .expect (std .mem .indexOf (u8 , response , "HTTP/1.1 200 OK" ) != null );
218+ try std .testing .expect (std .mem .indexOf (u8 , response , "partial-disconnect-ok" ) != null );
219+ }
220+ }
221+
222+ test "websocket upgrade survives repeated connect-disconnect churn" {
223+ var gpa = std .heap .GeneralPurposeAllocator (.{}){};
224+ defer _ = gpa .deinit ();
225+
226+ var app = try App .init (gpa .allocator (), .{
227+ .launch_policy = .{ .first = .web_url , .second = null , .third = null },
228+ });
229+ defer app .deinit ();
230+
231+ var win = try app .newWindow (.{ .title = "WsChurn" });
232+ try win .showHtml ("<html><body>ws-churn-ok</body></html>" );
233+ try app .run ();
234+
235+ const address = try std .net .Address .parseIp4 ("127.0.0.1" , win .state ().server_port );
236+ var handshake_buf : [512 ]u8 = undefined ;
237+ var idx : usize = 0 ;
238+ while (idx < 48 ) : (idx += 1 ) {
239+ const stream = try std .net .tcpConnectToAddress (address );
240+ const request = try std .fmt .bufPrint (
241+ & handshake_buf ,
242+ "GET /webui/ws?client_id=churn-{d} HTTP/1.1\r \n " ++
243+ "Host: 127.0.0.1\r \n " ++
244+ "Upgrade: websocket\r \n " ++
245+ "Connection: Upgrade\r \n " ++
246+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r \n " ++
247+ "Sec-WebSocket-Version: 13\r \n " ++
248+ "\r \n " ,
249+ .{idx },
250+ );
251+ try writeAllStream (stream , request );
252+
253+ const response = try readHttpHeadersFromStream (gpa .allocator (), stream , 64 * 1024 );
254+ defer gpa .allocator ().free (response );
255+ try std .testing .expect (std .mem .indexOf (u8 , response , "HTTP/1.1 101 Switching Protocols" ) != null );
256+ stream .close ();
257+ }
258+
259+ const response = try httpRoundTrip (gpa .allocator (), win .state ().server_port , "GET" , "/" , null );
260+ defer gpa .allocator ().free (response );
261+ try std .testing .expect (std .mem .indexOf (u8 , response , "HTTP/1.1 200 OK" ) != null );
262+ try std .testing .expect (std .mem .indexOf (u8 , response , "ws-churn-ok" ) != null );
263+ }
264+
265+ test "mixed concurrent http routes remain stable under socket churn" {
266+ const Shared = struct { failed : std .atomic .Value (bool ) = std .atomic .Value (bool ).init (false ) };
267+ const Ctx = struct {
268+ allocator : std.mem.Allocator ,
269+ port : u16 ,
270+ start : usize ,
271+ shared : * Shared ,
272+ };
273+ const Worker = struct {
274+ fn run (ctx : * Ctx ) void {
275+ var i : usize = 0 ;
276+ while (i < 40 ) : (i += 1 ) {
277+ const route_idx = (ctx .start + i ) % 3 ;
278+ const path = switch (route_idx ) {
279+ 0 = > "/" ,
280+ 1 = > "/webui/window/control" ,
281+ else = > "/webui/window/style" ,
282+ };
283+ const response = httpRoundTrip (ctx .allocator , ctx .port , "GET" , path , null ) catch {
284+ ctx .shared .failed .store (true , .release );
285+ return ;
286+ };
287+ defer ctx .allocator .free (response );
288+
289+ if (std .mem .indexOf (u8 , response , "HTTP/1.1 200 OK" ) == null ) {
290+ ctx .shared .failed .store (true , .release );
291+ return ;
292+ }
293+ }
294+ }
295+ };
296+
297+ var gpa = std .heap .GeneralPurposeAllocator (.{}){};
298+ defer _ = gpa .deinit ();
299+
300+ var app = try App .init (gpa .allocator (), .{
301+ .launch_policy = .{ .first = .web_url , .second = null , .third = null },
302+ });
303+ defer app .deinit ();
304+
305+ var win = try app .newWindow (.{ .title = "MixedRouteStress" });
306+ try win .showHtml ("<html><body>mixed-route-stress-ok</body></html>" );
307+ try app .run ();
308+
309+ var shared = Shared {};
310+ var contexts : [8 ]Ctx = undefined ;
311+ var threads : [8 ]std.Thread = undefined ;
312+ for (& contexts , 0.. ) | * ctx , idx | {
313+ ctx .* = .{
314+ .allocator = gpa .allocator (),
315+ .port = win .state ().server_port ,
316+ .start = idx * 7 ,
317+ .shared = & shared ,
318+ };
319+ threads [idx ] = try std .Thread .spawn (.{}, Worker .run , .{ctx });
320+ }
321+ for (threads ) | thread | thread .join ();
322+ try std .testing .expect (! shared .failed .load (.acquire ));
323+ }
324+
171325test "tls enabled runtime redirects plaintext http and serves https content on same port" {
172326 var gpa = std .heap .GeneralPurposeAllocator (.{}){};
173327 defer _ = gpa .deinit ();
@@ -291,7 +445,8 @@ test "native_webview launch order keeps local runtime reachable" {
291445 const stream = try std .net .tcpConnectToAddress (address );
292446 defer stream .close ();
293447
294- try stream .writeAll (
448+ try writeAllStream (
449+ stream ,
295450 "GET / HTTP/1.1\r \n " ++
296451 "Host: 127.0.0.1\r \n " ++
297452 "Connection: close\r \n " ++
0 commit comments