11using System ;
2+ using System . IO ;
23using System . IO . Pipelines ;
34using System . Net . WebSockets ;
45using System . Threading ;
@@ -15,23 +16,28 @@ class SimpleWebSocketPipe : IWebSocketPipe
1516 // Wait 250 ms before giving up on a Close, same as SignalR WebSocketHandler
1617 static readonly TimeSpan closeTimeout = TimeSpan . FromMilliseconds ( 250 ) ;
1718
19+ readonly CancellationTokenSource disposeCancellation = new CancellationTokenSource ( ) ;
1820 readonly Pipe inputPipe ;
19- readonly Pipe outputPipe ;
21+ readonly PipeWriter outputWriter ;
2022
2123 readonly WebSocket webSocket ;
2224 readonly WebSocketPipeOptions options ;
2325
2426 bool completed ;
2527
2628 public SimpleWebSocketPipe ( WebSocket webSocket , WebSocketPipeOptions options )
27- => ( this . webSocket , this . options , inputPipe , outputPipe )
28- = ( webSocket , options , new Pipe ( options . InputPipeOptions ) , new Pipe ( options . OutputPipeOptions ) ) ;
29+ {
30+ this . webSocket = webSocket ;
31+ this . options = options ;
32+ inputPipe = new Pipe ( options . InputPipeOptions ) ;
33+ outputWriter = PipeWriter . Create ( new WebSocketStream ( webSocket ) ) ;
34+ }
2935
3036 bool IsClient => webSocket is ClientWebSocket ;
3137
3238 public PipeReader Input => inputPipe . Reader ;
3339
34- public PipeWriter Output => outputPipe . Writer ;
40+ public PipeWriter Output => outputWriter ;
3541
3642 public WebSocketCloseStatus ? CloseStatus => webSocket . CloseStatus ;
3743
@@ -41,23 +47,16 @@ public SimpleWebSocketPipe(WebSocket webSocket, WebSocketPipeOptions options)
4147
4248 public string ? SubProtocol => webSocket . SubProtocol ;
4349
44- public async ValueTask RunAsync ( CancellationToken cancellation = default )
50+ public Task RunAsync ( CancellationToken cancellation = default )
4551 {
4652 if ( webSocket . State != WebSocketState . Open )
4753 throw new InvalidOperationException ( $ "WebSocket must be opened. State was { webSocket . State } ") ;
4854
49- var writing = FillInputAsync ( cancellation ) ;
50- var reading = SendOutputAsync ( cancellation ) ;
51-
52- // NOTE: when both are completed, the CompleteAsync will be called automatically
53- // by both writing and reading, so we ensure CloseWhenCompleted is performed.
54-
55- // TODO: replace with ValueTask.WhenAll if/when it ships.
56- // See https://github.com/dotnet/runtime/issues/23625
57- await Task . WhenAll ( reading . AsTask ( ) , writing . AsTask ( ) ) ;
55+ var combined = CancellationTokenSource . CreateLinkedTokenSource ( cancellation , disposeCancellation . Token ) ;
56+ return ReadInputAsync ( combined . Token ) ;
5857 }
5958
60- public async ValueTask CompleteAsync ( WebSocketCloseStatus ? closeStatus = null , string ? closeStatusDescription = null )
59+ public async Task CompleteAsync ( WebSocketCloseStatus ? closeStatus = null , string ? closeStatusDescription = null )
6160 {
6261 if ( completed )
6362 return ;
@@ -68,14 +67,11 @@ public async ValueTask CompleteAsync(WebSocketCloseStatus? closeStatus = null, s
6867 await inputPipe . Writer . CompleteAsync ( ) ;
6968 await inputPipe . Reader . CompleteAsync ( ) ;
7069
71- await outputPipe . Writer . CompleteAsync ( ) ;
72- await outputPipe . Reader . CompleteAsync ( ) ;
73-
7470 if ( options . CloseWhenCompleted || closeStatus != null )
7571 await CloseAsync ( closeStatus ?? WebSocketCloseStatus . NormalClosure , closeStatusDescription ?? "" ) ;
7672 }
7773
78- async ValueTask CloseAsync ( WebSocketCloseStatus closeStatus , string closeStatusDescription )
74+ async Task CloseAsync ( WebSocketCloseStatus closeStatus , string closeStatusDescription )
7975 {
8076 var state = State ;
8177 if ( state == WebSocketState . Closed || state == WebSocketState . CloseSent || state == WebSocketState . Aborted )
@@ -90,7 +86,7 @@ async ValueTask CloseAsync(WebSocketCloseStatus closeStatus, string closeStatusD
9086 await Task . WhenAny ( closeTask , Task . Delay ( closeTimeout ) ) ;
9187 }
9288
93- async ValueTask FillInputAsync ( CancellationToken cancellation )
89+ async Task ReadInputAsync ( CancellationToken cancellation )
9490 {
9591 while ( webSocket . State == WebSocketState . Open && ! cancellation . IsCancellationRequested )
9692 {
@@ -129,56 +125,31 @@ ex is WebSocketException ||
129125 await CompleteAsync ( webSocket . CloseStatus , webSocket . CloseStatusDescription ) ;
130126 }
131127
132- async ValueTask SendOutputAsync ( CancellationToken cancellation )
128+ public void Dispose ( )
133129 {
134- while ( webSocket . State == WebSocketState . Open && ! cancellation . IsCancellationRequested )
135- {
136- try
137- {
138- var result = await outputPipe . Reader . ReadAsync ( cancellation ) ;
139- if ( result . IsCompleted || result . IsCanceled )
140- break ;
141-
142- if ( result . Buffer . IsSingleSegment )
143- {
144- await webSocket . SendAsync ( result . Buffer . First , WebSocketMessageType . Binary , true , cancellation ) ;
145- }
146- else
147- {
148- var enumerator = result . Buffer . GetEnumerator ( ) ;
149- if ( enumerator . MoveNext ( ) )
150- {
151- // NOTE: we don't use the cancellation here because we don't want to send
152- // partial messages from an already completely read buffer.
153- while ( true )
154- {
155- var current = enumerator . Current ;
156- if ( default ( ReadOnlyMemory < byte > ) . Equals ( current ) )
157- break ;
158-
159- // Peek next to see if we should send an end of message
160- if ( enumerator . MoveNext ( ) )
161- await webSocket . SendAsync ( current , WebSocketMessageType . Binary , false , cancellation ) ;
162- else
163- await webSocket . SendAsync ( current , WebSocketMessageType . Binary , true , cancellation ) ;
164- }
165- }
166- }
167-
168- outputPipe . Reader . AdvanceTo ( result . Buffer . End ) ;
169-
170- }
171- catch ( Exception ex ) when ( ex is OperationCanceledException ||
172- ex is WebSocketException ||
173- ex is InvalidOperationException )
174- {
175- break ;
176- }
177- }
178-
179- // Preserve the close status since it might be triggered by a received Close message containing the status and description.
180- await CompleteAsync ( webSocket . CloseStatus , webSocket . CloseStatusDescription ) ;
130+ disposeCancellation . Cancel ( ) ;
131+ webSocket . Dispose ( ) ;
181132 }
182133
183- public void Dispose ( ) => webSocket . Dispose ( ) ;
134+ class WebSocketStream : Stream
135+ {
136+ readonly WebSocket webSocket ;
137+
138+ public WebSocketStream ( WebSocket webSocket ) => this . webSocket = webSocket ;
139+
140+ public override ValueTask WriteAsync ( ReadOnlyMemory < byte > buffer , CancellationToken cancellationToken = default )
141+ => webSocket . SendAsync ( buffer , WebSocketMessageType . Binary , true , cancellationToken ) ;
142+
143+ public override Task FlushAsync ( CancellationToken cancellationToken ) => Task . CompletedTask ;
144+ public override bool CanRead => throw new NotImplementedException ( ) ;
145+ public override bool CanSeek => throw new NotImplementedException ( ) ;
146+ public override bool CanWrite => throw new NotImplementedException ( ) ;
147+ public override long Length => throw new NotImplementedException ( ) ;
148+ public override long Position { get => throw new NotImplementedException ( ) ; set => throw new NotImplementedException ( ) ; }
149+ public override void Flush ( ) => throw new NotImplementedException ( ) ;
150+ public override int Read ( byte [ ] buffer , int offset , int count ) => throw new NotImplementedException ( ) ;
151+ public override long Seek ( long offset , SeekOrigin origin ) => throw new NotImplementedException ( ) ;
152+ public override void SetLength ( long value ) => throw new NotImplementedException ( ) ;
153+ public override void Write ( byte [ ] buffer , int offset , int count ) => throw new NotImplementedException ( ) ;
154+ }
184155}
0 commit comments