15
15
*******************************************************************************/
16
16
package org .tinystruct .system ;
17
17
18
- import com .sun .net .httpserver .HttpContext ;
19
18
import com .sun .net .httpserver .HttpExchange ;
20
19
import com .sun .net .httpserver .HttpHandler ;
21
20
import org .tinystruct .AbstractApplication ;
38
37
import static org .tinystruct .http .Constants .HTTP_REQUEST ;
39
38
import static org .tinystruct .http .Constants .HTTP_RESPONSE ;
40
39
import static org .tinystruct .http .Constants .HTTP_HOST ;
41
- import static org .tinystruct .Application .LANGUAGE ;
42
- import static org .tinystruct .Application .METHOD ;
43
40
44
41
public class HttpServer extends AbstractApplication implements Bootstrap {
45
42
private final Logger logger = Logger .getLogger (HttpServer .class .getName ());
@@ -123,11 +120,11 @@ public void start() throws ApplicationException {
123
120
if (serverThreads > 0 ) {
124
121
server .setExecutor (Executors .newFixedThreadPool (serverThreads ));
125
122
} else {
126
- server .setExecutor (null ); // Use default executor
123
+ server .setExecutor (Executors . newCachedThreadPool () ); // Use default executor
127
124
}
128
125
129
126
// Create context and set handler
130
- HttpContext context = server .createContext ("/" , new DefaultHttpHandler (getContext (), this .settings ));
127
+ server .createContext ("/" , new DefaultHttpHandler (getContext (), this .settings ));
131
128
132
129
// Configure context attributes similar to Tomcat setup
133
130
initServerDefaults ();
@@ -188,8 +185,8 @@ public void stop() {
188
185
189
186
@ Action (value = "error" , description = "Error page" )
190
187
public Object exceptionCaught () throws ApplicationException {
191
- Request request = (Request ) getContext ().getAttribute (HTTP_REQUEST );
192
- Response response = (Response ) getContext ().getAttribute (HTTP_RESPONSE );
188
+ Request <?, ?> request = (Request <?, ?> ) getContext ().getAttribute (HTTP_REQUEST );
189
+ Response <?, ?> response = (Response <?, ?> ) getContext ().getAttribute (HTTP_RESPONSE );
193
190
194
191
Reforward reforward = new Reforward (request , response );
195
192
this .setVariable ("from" , reforward .getFromURL ());
@@ -237,7 +234,6 @@ private DefaultHttpHandler(Context context, Settings settings) {
237
234
238
235
@ Override
239
236
public void handle (HttpExchange exchange ) throws IOException {
240
- boolean sseActive = false ;
241
237
try {
242
238
// Serve static files first (mirror Netty's HttpStaticFileHandler precedence)
243
239
if ("GET" .equalsIgnoreCase (exchange .getRequestMethod ())) {
@@ -256,7 +252,6 @@ public void handle(HttpExchange exchange) throws IOException {
256
252
257
253
// Process SSE first to ensure correct headers and long-lived connection
258
254
if (isSSE (exchange )) {
259
- sseActive = true ;
260
255
handleSSE (request , response , this .context );
261
256
return ;
262
257
}
@@ -267,31 +262,10 @@ public void handle(HttpExchange exchange) throws IOException {
267
262
logger .log (Level .SEVERE , "Error processing request" , e );
268
263
// Try to send error only if headers haven't been committed yet
269
264
try {
270
- Response resp = (Response ) this .context .getAttribute (HTTP_RESPONSE );
271
- if (resp instanceof ServerResponse ) {
272
- ServerResponse serverResponse = (ServerResponse ) resp ;
273
- if (!serverResponse .isCommitted ()) {
274
- sendErrorResponse (exchange , 500 , "Internal Server Error: " + e .getMessage ());
275
- }
276
- } else {
277
- sendErrorResponse (exchange , 500 , "Internal Server Error: " + e .getMessage ());
278
- }
265
+ sendErrorResponse (exchange , 500 , "Internal Server Error: " + e .getMessage ());
279
266
} catch (Exception ignored ) {
280
267
// If we can't send an error (headers/body already sent), just log.
281
268
}
282
- } finally {
283
- try {
284
- if (!sseActive ) {
285
- Response resp = (Response ) this .context .getAttribute (HTTP_RESPONSE );
286
- if (resp instanceof ServerResponse ) {
287
- ((ServerResponse ) resp ).close ();
288
- } else {
289
- exchange .close ();
290
- }
291
- }
292
- } catch (Exception ex ) {
293
- exchange .close ();
294
- }
295
269
}
296
270
}
297
271
@@ -309,6 +283,7 @@ private void handleSSE(ServerRequest request, ServerResponse response, Context c
309
283
response .addHeader (Header .CONTENT_TYPE .name (), "text/event-stream" );
310
284
response .addHeader (Header .CACHE_CONTROL .name (), "no-cache" );
311
285
response .addHeader (Header .CONNECTION .name (), "keep-alive" );
286
+ response .addHeader (Header .TRANSFER_ENCODING .name (), "chunked" );
312
287
response .addHeader ("X-Accel-Buffering" , "no" );
313
288
314
289
String query = request .getParameter ("q" );
@@ -322,22 +297,34 @@ private void handleSSE(ServerRequest request, ServerResponse response, Context c
322
297
Object call = ApplicationManager .call (query , context );
323
298
String sessionId = context .getId ();
324
299
SSEPushManager pushManager = getAppropriatePushManager (isMCP );
300
+ response .setStatus (ResponseStatus .OK );
301
+ // Ensure chunked streaming for SSE before any write
302
+ response .sendHeaders (-1 );
325
303
SSEClient client = pushManager .register (sessionId , response );
326
304
327
305
if (call instanceof org .tinystruct .data .component .Builder ) {
328
- if (client != null ) client .send ((org .tinystruct .data .component .Builder ) call );
329
- else pushManager .push (sessionId , (org .tinystruct .data .component .Builder ) call );
330
- } else if (call != null ) {
331
- // Send as data line
332
- String data = "data: " + String .valueOf (call ).replace ("\n " , "\n data: " ) + "\n \n " ;
333
- response .writeAndFlush (data .getBytes ("UTF-8" ));
334
- } else {
335
- // Initial comment to open stream and avoid proxy buffering
336
- response .writeAndFlush (": ok\n \n " .getBytes ("UTF-8" ));
306
+ pushManager .push (sessionId , (org .tinystruct .data .component .Builder ) call );
307
+ } else if (call instanceof String ) {
308
+ org .tinystruct .data .component .Builder builder = new org .tinystruct .data .component .Builder ();
309
+ builder .parse ((String ) call );
310
+ pushManager .push (sessionId , builder );
311
+ }
312
+
313
+ if (client != null ) {
314
+ try {
315
+ while (client .isActive ()) {
316
+ Thread .sleep (1000 );
317
+ }
318
+ } catch (InterruptedException e ) {
319
+ Thread .currentThread ().interrupt ();
320
+ throw new ApplicationException ("Stream interrupted: " + e .getMessage (), e );
321
+ } catch (Exception e ) {
322
+ throw new ApplicationException ("Error in stream: " + e .getMessage (), e );
323
+ } finally {
324
+ client .close ();
325
+ pushManager .remove (sessionId );
326
+ }
337
327
}
338
- } else {
339
- // No query, still open SSE stream
340
- response .writeAndFlush (": ok\n \n " .getBytes ("UTF-8" ));
341
328
}
342
329
}
343
330
@@ -525,12 +512,12 @@ private void processRequest(ServerRequest request, ServerResponse response) thro
525
512
} else {
526
513
handleDefaultPage (this .context , response );
527
514
}
528
-
529
515
} catch (Exception e ) {
530
516
logger .log (Level .SEVERE , "Error in request processing" , e );
531
517
response .setContentType ("text/plain; charset=UTF-8" );
532
518
response .setStatus (org .tinystruct .http .ResponseStatus .INTERNAL_SERVER_ERROR );
533
519
response .writeAndFlush ("500 - Internal Server Error" .getBytes ("UTF-8" ));
520
+ response .close ();
534
521
}
535
522
}
536
523
@@ -546,19 +533,22 @@ private void handleRequest(String query, Context context, ServerResponse respons
546
533
// Handle request
547
534
query = StringUtilities .htmlSpecialChars (query );
548
535
Object message = ApplicationManager .call (query , context );
536
+ byte [] bytes ;
549
537
if (message != null ) {
550
538
if (message instanceof byte []) {
551
- byte [] bytes = (byte []) message ;
552
- response .addHeader ("Content-Length" , String .valueOf (bytes .length ));
553
- response .writeAndFlush (bytes );
539
+ bytes = (byte []) message ;
554
540
} else {
555
541
response .setContentType ("text/html; charset=UTF-8" );
556
- response . writeAndFlush ( String .valueOf (message ).getBytes ("UTF-8" ) );
542
+ bytes = String .valueOf (message ).getBytes ("UTF-8" );
557
543
}
558
544
} else {
559
- response .setContentType ("text/plain ; charset=UTF-8" );
560
- response . writeAndFlush ( "No response retrieved!" .getBytes ("UTF-8" ) );
545
+ response .setContentType ("text/html ; charset=UTF-8" );
546
+ bytes = "No response retrieved!" .getBytes ("UTF-8" );
561
547
}
548
+
549
+ response .setStatus (ResponseStatus .OK );
550
+ response .writeAndFlush (bytes );
551
+ response .close ();
562
552
}
563
553
564
554
/**
@@ -568,10 +558,20 @@ private void handleRequest(String query, Context context, ServerResponse respons
568
558
* @param response The HTTP response object
569
559
* @throws IOException if an I/O error occurs
570
560
*/
571
- private void handleDefaultPage (Context context , ServerResponse response ) throws IOException , ApplicationException {
561
+ private void handleDefaultPage (Context context , ServerResponse response ) throws ApplicationException {
572
562
response .setContentType ("text/html; charset=UTF-8" );
573
563
Object result = ApplicationManager .call (settings .getOrDefault ("default.home.page" , "say/Praise the Lord." ), context );
574
- response .writeAndFlush (String .valueOf (result ).getBytes ("UTF-8" ));
564
+ if (!response .isClosed ()) {
565
+ try {
566
+ byte [] bytes = String .valueOf (result ).getBytes ("UTF-8" );
567
+ response .setStatus (ResponseStatus .OK );
568
+ response .writeAndFlush (bytes );
569
+ } catch (IOException e ) {
570
+ throw new ApplicationException (e );
571
+ } finally {
572
+ response .close ();
573
+ }
574
+ }
575
575
}
576
576
577
577
private void sendErrorResponse (HttpExchange exchange , int statusCode , String message ) {
@@ -582,11 +582,11 @@ private void sendErrorResponse(HttpExchange exchange, int statusCode, String mes
582
582
try (OutputStream os = exchange .getResponseBody ()) {
583
583
os .write (responseBytes );
584
584
}
585
+ exchange .close ();
585
586
} catch (IOException e ) {
586
587
logger .log (Level .SEVERE , "Failed to send error response" , e );
587
588
}
588
589
}
589
590
590
-
591
591
}
592
592
}
0 commit comments