@@ -569,6 +569,14 @@ def _on_complete(self, error_code: int) -> None:
569569 else :
570570 self ._completion_future .set_exception (awscrt .exceptions .from_code (error_code ))
571571
572+ def _on_h2_remote_end_stream (self ) -> None :
573+ """Called when remote peer sends END_STREAM (HTTP/2 only).
574+
575+ This callback is only invoked for HTTP/2 connections. HTTP/1.x streams
576+ will never receive this callback. Base implementation does nothing.
577+ """
578+ pass
579+
572580 def update_window (self , increment_size : int ) -> None :
573581 """
574582 Update the stream's flow control window.
@@ -613,14 +621,57 @@ def activate(self) -> None:
613621
614622
615623class Http2ClientStream (HttpClientStreamBase ):
624+ __slots__ = ('_remote_end_stream_future' ,)
625+
616626 def __init__ (self ,
617627 connection : HttpClientConnection ,
618628 request : 'HttpRequest' ,
619629 on_response : Optional [Callable [..., None ]] = None ,
620630 on_body : Optional [Callable [..., None ]] = None ,
621631 manual_write : bool = False ) -> None :
632+ self ._remote_end_stream_future = Future ()
622633 self ._init_common (connection , request , on_response , on_body , manual_write )
623634
635+ @property
636+ def remote_end_stream_future (self ) -> "concurrent.futures.Future" :
637+ """
638+ concurrent.futures.Future: Future that completes when the remote peer has finished
639+ sending (HTTP/2 only). This occurs when the server sends an END_STREAM flag.
640+
641+ The future will contain a result of None on success, or an exception if the stream
642+ encounters an error before END_STREAM is received (e.g., RST_STREAM).
643+
644+ This is different from `completion_future` which completes when both the
645+ client and server have finished (bidirectional stream closure).
646+
647+ Note: This future only applies to HTTP/2 connections. It will complete when the
648+ server sends END_STREAM, which may occur before the client finishes sending.
649+ In case of stream completed without END_STREAM received, this future will complete
650+ with exception.
651+ """
652+ return self ._remote_end_stream_future
653+
654+ def _on_h2_remote_end_stream (self ) -> None :
655+ """Internal callback when remote peer sends END_STREAM (HTTP/2 only)."""
656+ if not self ._remote_end_stream_future .done ():
657+ self ._remote_end_stream_future .set_result (None )
658+
659+ def _on_complete (self , error_code : int ) -> None :
660+ # done with HttpRequest, drop reference
661+ self ._request = None # type: ignore
662+
663+ # Ensure remote_completion_future is always resolved
664+ if not self ._remote_end_stream_future .done ():
665+ # Stream completed successfully but END_STREAM was never received,
666+ # complete `remote_completion_future` with exception.
667+ self ._remote_end_stream_future .set_exception (
668+ RuntimeError ("Stream completed without receiving remote END_STREAM" ))
669+
670+ if error_code == 0 :
671+ self ._completion_future .set_result (self ._response_status_code )
672+ else :
673+ self ._completion_future .set_exception (awscrt .exceptions .from_code (error_code ))
674+
624675 def activate (self ) -> None :
625676 """Begin sending the request.
626677
0 commit comments