@@ -55,10 +55,10 @@ public async Task SendAsync(ReadOnlyMemory<byte> data)
5555 }
5656 }
5757
58- public async Task < ( byte [ ] Data , int Length , ConnectionState State ) > ReadResponseAsync ( )
58+ public async Task < ( byte [ ] Data , int Length , ConnectionState State , bool DrainCaughtData ) > ReadResponseAsync ( )
5959 {
6060 if ( _socket is null )
61- return ( [ ] , 0 , ConnectionState . Error ) ;
61+ return ( [ ] , 0 , ConnectionState . Error , false ) ;
6262
6363 var buffer = new byte [ 65536 ] ;
6464 var totalRead = 0 ;
@@ -67,6 +67,7 @@ public async Task SendAsync(ReadOnlyMemory<byte> data)
6767
6868 try
6969 {
70+ // Phase 1: Read until we have the complete headers (\r\n\r\n)
7071 while ( totalRead < buffer . Length )
7172 {
7273 var read = await _socket . ReceiveAsync (
@@ -75,29 +76,60 @@ public async Task SendAsync(ReadOnlyMemory<byte> data)
7576 cts . Token ) ;
7677
7778 if ( read == 0 )
78- return ( buffer , totalRead , ConnectionState . ClosedByServer ) ;
79+ return ( buffer , totalRead , ConnectionState . ClosedByServer , false ) ;
7980
8081 totalRead += read ;
8182
82- // Check if we've received the end of headers
83- if ( ContainsHeaderTerminator ( buffer . AsSpan ( 0 , totalRead ) ) )
83+ if ( FindHeaderTerminator ( buffer . AsSpan ( 0 , totalRead ) ) >= 0 )
8484 break ;
8585 }
8686
87- return ( buffer , totalRead , ConnectionState . Open ) ;
87+ // Phase 2: Wait briefly for the body to arrive, then drain
88+ await Task . Delay ( 100 , cts . Token ) ;
89+ var beforeDrain = totalRead ;
90+ totalRead = await DrainAvailable ( buffer , totalRead , cts . Token ) ;
91+ var drainCaughtData = totalRead > beforeDrain ;
92+
93+ return ( buffer , totalRead , ConnectionState . Open , drainCaughtData ) ;
8894 }
8995 catch ( OperationCanceledException )
9096 {
91- return ( buffer , totalRead , ConnectionState . TimedOut ) ;
97+ return ( buffer , totalRead , ConnectionState . TimedOut , false ) ;
9298 }
9399 catch ( SocketException )
94100 {
95- return ( buffer , totalRead , ConnectionState . ClosedByServer ) ;
101+ return ( buffer , totalRead , ConnectionState . ClosedByServer , false ) ;
96102 }
97103 catch
98104 {
99- return ( buffer , totalRead , ConnectionState . Error ) ;
105+ return ( buffer , totalRead , ConnectionState . Error , false ) ;
106+ }
107+ }
108+
109+ /// <summary>
110+ /// Non-blocking drain: reads whatever bytes are already in the socket buffer
111+ /// without waiting for more data to arrive.
112+ /// </summary>
113+ private async Task < int > DrainAvailable ( byte [ ] buffer , int totalRead , CancellationToken ct )
114+ {
115+ if ( _socket is null ) return totalRead ;
116+
117+ while ( totalRead < buffer . Length )
118+ {
119+ // Poll with zero timeout — returns true only if data is ready right now
120+ if ( ! _socket . Poll ( 0 , SelectMode . SelectRead ) )
121+ break ;
122+
123+ var read = await _socket . ReceiveAsync (
124+ buffer . AsMemory ( totalRead ) ,
125+ SocketFlags . None ,
126+ ct ) ;
127+
128+ if ( read == 0 ) break ; // peer closed
129+ totalRead += read ;
100130 }
131+
132+ return totalRead ;
101133 }
102134
103135 public ConnectionState CheckConnectionState ( )
@@ -123,11 +155,10 @@ public ConnectionState CheckConnectionState()
123155 }
124156 }
125157
126- private static bool ContainsHeaderTerminator ( ReadOnlySpan < byte > data )
158+ private static int FindHeaderTerminator ( ReadOnlySpan < byte > data )
127159 {
128- // Look for \r\n\r\n
129160 ReadOnlySpan < byte > terminator = [ 0x0D , 0x0A , 0x0D , 0x0A ] ;
130- return data . IndexOf ( terminator ) >= 0 ;
161+ return data . IndexOf ( terminator ) ;
131162 }
132163
133164 public async ValueTask DisposeAsync ( )
0 commit comments