@@ -307,20 +307,33 @@ public void handle(HttpExchange exchange) throws IOException {
307307 processRequest (request , response , context );
308308 } catch (ApplicationException e ) {
309309 logger .log (Level .SEVERE , e .getMessage (), e );
310+ // If it's a closed stream exception, we shouldn't attempt to write an error back.
311+ if (e .getCause () instanceof IOException && e .getCause ().getClass ().getName ().contains ("StreamClosedException" )) {
312+ return ;
313+ }
314+
310315 int status = e .getStatus ();
311316 ResponseStatus responseStatus = ResponseStatus .valueOf (status );
312317 if (responseStatus == null )
313318 responseStatus = ResponseStatus .INTERNAL_SERVER_ERROR ;
314319
315320 try {
316- sendErrorResponse (exchange , responseStatus .code (), e .getMessage ());
321+ sendErrorResponse (exchange , responseStatus .code (), e .getMessage () != null ? e . getMessage () : "Error in request processing" );
317322 } catch (Exception ignored ) {
318323 }
319324 } catch (Exception e ) {
320325 logger .log (Level .SEVERE , e .getMessage (), e );
326+ // If it's a closed stream exception, we shouldn't attempt to write an error back.
327+ if (e instanceof IOException && e .getClass ().getName ().contains ("StreamClosedException" )) {
328+ return ;
329+ }
330+ if (e .getCause () instanceof IOException && e .getCause ().getClass ().getName ().contains ("StreamClosedException" )) {
331+ return ;
332+ }
333+
321334 // Try to send error only if headers haven't been committed yet
322335 try {
323- sendErrorResponse (exchange , 500 , "Internal Server Error: " + e .getMessage ());
336+ sendErrorResponse (exchange , 500 , "Internal Server Error" + ( e . getMessage () != null ? " : " + e .getMessage () : "" ));
324337 } catch (Exception ignored ) {
325338 // If we can't send an error (headers/body already sent), just log.
326339 }
@@ -354,34 +367,36 @@ private void handleSSE(ServerRequest request, ServerResponse response, Context c
354367 }
355368
356369 Object call = ApplicationManager .call (query , context );
357- String sessionId = context .getId ();
358- SSEPushManager pushManager = getAppropriatePushManager (isMCP );
359- response .setStatus (ResponseStatus .OK );
360- // Ensure chunked streaming for SSE before any write
361- response .sendHeaders (-1 );
362- SSEClient client = pushManager .register (sessionId , response );
363-
364- if (call instanceof org .tinystruct .data .component .Builder ) {
365- pushManager .push (sessionId , (org .tinystruct .data .component .Builder ) call );
366- } else if (call instanceof String ) {
367- org .tinystruct .data .component .Builder builder = new org .tinystruct .data .component .Builder ();
368- builder .parse ((String ) call );
369- pushManager .push (sessionId , builder );
370- }
370+ if (!response .isClosed ()) {
371+ String sessionId = context .getId ();
372+ SSEPushManager pushManager = getAppropriatePushManager (isMCP );
373+ response .setStatus (ResponseStatus .OK );
374+ // Ensure chunked streaming for SSE before any write
375+ response .sendHeaders (-1 );
376+ SSEClient client = pushManager .register (sessionId , response );
377+
378+ if (call instanceof org .tinystruct .data .component .Builder ) {
379+ pushManager .push (sessionId , (org .tinystruct .data .component .Builder ) call );
380+ } else if (call instanceof String ) {
381+ org .tinystruct .data .component .Builder builder = new org .tinystruct .data .component .Builder ();
382+ builder .parse ((String ) call );
383+ pushManager .push (sessionId , builder );
384+ }
371385
372- if (client != null ) {
373- try {
374- while (client .isActive ()) {
375- Thread .sleep (1000 );
386+ if (client != null ) {
387+ try {
388+ while (client .isActive ()) {
389+ Thread .sleep (1000 );
390+ }
391+ } catch (InterruptedException e ) {
392+ Thread .currentThread ().interrupt ();
393+ throw new ApplicationException ("Stream interrupted: " + e .getMessage (), e );
394+ } catch (Exception e ) {
395+ throw new ApplicationException ("Error in stream: " + e .getMessage (), e );
396+ } finally {
397+ client .close ();
398+ pushManager .remove (sessionId );
376399 }
377- } catch (InterruptedException e ) {
378- Thread .currentThread ().interrupt ();
379- throw new ApplicationException ("Stream interrupted: " + e .getMessage (), e );
380- } catch (Exception e ) {
381- throw new ApplicationException ("Error in stream: " + e .getMessage (), e );
382- } finally {
383- client .close ();
384- pushManager .remove (sessionId );
385400 }
386401 }
387402 }
@@ -571,19 +586,26 @@ private void processRequest(ServerRequest request, ServerResponse response, Cont
571586 }
572587 } catch (ApplicationException e ) {
573588 logger .log (Level .SEVERE , "Error in request processing" , e );
574- response .setContentType ("text/plain; charset=UTF-8" );
575- int status = e .getStatus ();
576- ResponseStatus responseStatus = ResponseStatus .valueOf (status );
577- if (responseStatus == null )
578- responseStatus = ResponseStatus .INTERNAL_SERVER_ERROR ;
589+ // If it's a closed stream exception, we shouldn't attempt to write an error back.
590+ if (e .getCause () instanceof IOException && e .getCause ().getClass ().getName ().contains ("StreamClosedException" )) {
591+ return ;
592+ }
579593
580- response .setStatus (responseStatus );
581- if (e .getMessage () != null ) {
582- response .writeAndFlush (e .getMessage ().getBytes (StandardCharsets .UTF_8 ));
583- } else {
584- response .writeAndFlush (new byte [0 ]);
594+ if (!response .isClosed ()) {
595+ try {
596+ response .setContentType ("text/plain; charset=UTF-8" );
597+ int status = e .getStatus ();
598+ ResponseStatus responseStatus = ResponseStatus .valueOf (status );
599+ if (responseStatus == null )
600+ responseStatus = ResponseStatus .INTERNAL_SERVER_ERROR ;
601+
602+ response .setStatus (responseStatus );
603+ response .writeAndFlush (e .getMessage () != null ? e .getMessage ().getBytes (StandardCharsets .UTF_8 ) : new byte [0 ]);
604+ response .close ();
605+ } catch (Exception ignored ) {
606+ // If we can't send an error (headers/body already sent or stream closed), just ignore.
607+ }
585608 }
586- response .close ();
587609 }
588610 }
589611
@@ -599,22 +621,25 @@ private void handleRequest(String query, Context context, ServerResponse respons
599621 // Handle request
600622 query = StringUtilities .htmlSpecialChars (query );
601623 Object message = ApplicationManager .call (query , context , mode );
602- byte [] bytes ;
603- if (message != null ) {
604- if (message instanceof byte []) {
605- bytes = (byte []) message ;
624+
625+ if (!response .isClosed ()) {
626+ byte [] bytes ;
627+ if (message != null ) {
628+ if (message instanceof byte []) {
629+ bytes = (byte []) message ;
630+ } else {
631+ response .setContentType ("text/html; charset=UTF-8" );
632+ bytes = String .valueOf (message ).getBytes (StandardCharsets .UTF_8 );
633+ }
606634 } else {
607635 response .setContentType ("text/html; charset=UTF-8" );
608- bytes = String . valueOf ( message ). getBytes ("UTF-8" );
636+ bytes = "No response retrieved!" . getBytes (StandardCharsets . UTF_8 );
609637 }
610- } else {
611- response .setContentType ("text/html; charset=UTF-8" );
612- bytes = "No response retrieved!" .getBytes ("UTF-8" );
613- }
614638
615- response .setStatus (ResponseStatus .OK );
616- response .writeAndFlush (bytes );
617- response .close ();
639+ response .setStatus (ResponseStatus .OK );
640+ response .writeAndFlush (bytes );
641+ response .close ();
642+ }
618643 }
619644
620645 /**
@@ -625,15 +650,15 @@ private void handleRequest(String query, Context context, ServerResponse respons
625650 * @throws IOException if an I/O error occurs
626651 */
627652 private void handleDefaultPage (Context context , ServerResponse response ) throws ApplicationException {
628- response .setContentType ("text/html; charset=UTF-8" );
629653 Object result = ApplicationManager .call (settings .getOrDefault ("default.home.page" , "say/Praise the Lord." ), context , Action .Mode .HTTP_GET );
630654 if (!response .isClosed ()) {
631655 try {
632- byte [] bytes = String .valueOf (result ).getBytes ("UTF-8" );
656+ response .setContentType ("text/html; charset=UTF-8" );
657+ byte [] bytes = String .valueOf (result ).getBytes (StandardCharsets .UTF_8 );
633658 response .setStatus (ResponseStatus .OK );
634659 response .writeAndFlush (bytes );
635- } catch (UnsupportedEncodingException e ) {
636- throw new ApplicationException (e );
660+ } catch (Exception e ) {
661+ throw new ApplicationException (e . getMessage (), e );
637662 } finally {
638663 response .close ();
639664 }
@@ -642,15 +667,19 @@ private void handleDefaultPage(Context context, ServerResponse response) throws
642667
643668 private void sendErrorResponse (HttpExchange exchange , int statusCode , String message ) {
644669 try {
645- byte [] responseBytes = message .getBytes ("UTF-8" );
670+ byte [] responseBytes = ( message != null ? message : "Unknown error" ) .getBytes (StandardCharsets . UTF_8 );
646671 exchange .getResponseHeaders ().set ("Content-Type" , "text/plain; charset=UTF-8" );
647672 exchange .sendResponseHeaders (statusCode , responseBytes .length );
648673 try (OutputStream os = exchange .getResponseBody ()) {
649674 os .write (responseBytes );
650675 os .flush ();
651676 }
652677 } catch (IOException e ) {
653- logger .log (Level .WARNING , "Client disconnected while sending error response" , e );
678+ if ("headers already sent" .equalsIgnoreCase (e .getMessage ())) {
679+ logger .log (Level .FINE , "Headers were already sent; cannot send error response again" , e );
680+ } else {
681+ logger .log (Level .WARNING , "Client disconnected while sending error response" , e );
682+ }
654683 } finally {
655684 try {
656685 exchange .close ();
0 commit comments