| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System; |
| | | 5 | | using System.Threading; |
| | | 6 | | using System.Threading.Tasks; |
| | | 7 | | using CoreWCF.Configuration; |
| | | 8 | | using Microsoft.Extensions.Hosting; |
| | | 9 | | |
| | | 10 | | namespace CoreWCF.Channels.Framing |
| | | 11 | | { |
| | | 12 | | internal class FramingModeHandshakeMiddleware |
| | | 13 | | { |
| | | 14 | | private readonly HandshakeDelegate _next; |
| | | 15 | | private readonly IApplicationLifetime _appLifetime; |
| | | 16 | | |
| | 83 | 17 | | public FramingModeHandshakeMiddleware(HandshakeDelegate next, IApplicationLifetime appLifetime) |
| | | 18 | | { |
| | 83 | 19 | | _next = next; |
| | 83 | 20 | | _appLifetime = appLifetime; |
| | 83 | 21 | | } |
| | | 22 | | |
| | | 23 | | public async Task OnConnectedAsync(FramingConnection connection) |
| | | 24 | | { |
| | | 25 | | // If the remote client aborts or the connection, AspNetCore throws an exceptions which |
| | | 26 | | // bubbles all the way up to here and causes a critical level event to be logged. This is a normal thing |
| | | 27 | | // to happen so just swallowing the exception to suppress the critical log output. |
| | | 28 | | try |
| | | 29 | | { |
| | 82 | 30 | | await OnConnectedCoreAsync(connection); |
| | 76 | 31 | | } |
| | 0 | 32 | | catch (Microsoft.AspNetCore.Connections.ConnectionAbortedException) { } |
| | 0 | 33 | | catch (Microsoft.AspNetCore.Connections.ConnectionResetException) { } |
| | 0 | 34 | | catch (CoreWCF.Security.SecurityNegotiationException) { } // TODO: Work out where this needs to be caught |
| | 0 | 35 | | catch (System.IO.IOException) { } // TODO: Work out where this needs to be caught |
| | 2 | 36 | | catch (CommunicationException) { } // TODO: Work out how this is logged in WCF and replicate |
| | 77 | 37 | | } |
| | | 38 | | |
| | | 39 | | public async Task OnConnectedCoreAsync(FramingConnection connection) |
| | | 40 | | { |
| | 82 | 41 | | using (_appLifetime.ApplicationStopped.Register(() => |
| | 82 | 42 | | { |
| | 0 | 43 | | connection.RawTransport.Input.Complete(); |
| | 0 | 44 | | connection.RawTransport.Output.Complete(); |
| | 82 | 45 | | })) |
| | | 46 | | { |
| | 82 | 47 | | IConnectionReuseHandler reuseHandler = null; |
| | | 48 | | do |
| | | 49 | | { |
| | 118 | 50 | | System.IO.Pipelines.PipeReader inputPipe = connection.Input; |
| | 118 | 51 | | var modeDecoder = new ServerModeDecoder(connection.Logger); |
| | | 52 | | try |
| | | 53 | | { |
| | 118 | 54 | | if (!await modeDecoder.ReadModeAsync(inputPipe, connection.ChannelInitializationCancellationToke |
| | | 55 | | { |
| | 2 | 56 | | break; // Input pipe closed |
| | | 57 | | } |
| | 115 | 58 | | } |
| | 0 | 59 | | catch (CommunicationException e) |
| | | 60 | | { |
| | | 61 | | // see if we need to send back a framing fault |
| | 0 | 62 | | if (FramingEncodingString.TryGetFaultString(e, out string framingFault)) |
| | | 63 | | { |
| | 0 | 64 | | await connection.SendFaultAsync( |
| | 0 | 65 | | framingFault, |
| | 0 | 66 | | ConnectionOrientedTransportDefaults.MaxViaSize + ConnectionOrientedTransportDefaults.Max |
| | 0 | 67 | | connection.ChannelInitializationCancellationToken); |
| | | 68 | | } |
| | | 69 | | |
| | 0 | 70 | | return; // Completing the returned Task causes the connection to be closed if needed and cleans |
| | | 71 | | } |
| | 1 | 72 | | catch (OperationCanceledException oce) |
| | | 73 | | { |
| | | 74 | | // Need to Abort (RST) the connection as aspnetcore on .NET Framework doesn't correctly close th |
| | | 75 | | // In the case of connection establishment timeout, this is a change in behavior as WCF will clo |
| | 1 | 76 | | connection.Abort(oce); |
| | 1 | 77 | | throw; |
| | | 78 | | } |
| | | 79 | | |
| | 115 | 80 | | connection.FramingMode = modeDecoder.Mode; |
| | 115 | 81 | | await _next(connection); |
| | | 82 | | |
| | | 83 | | // Unwrap the connection. |
| | | 84 | | // TODO: Investigate calling Dispose on the wrapping stream to improve cleanup. nb: .NET Framework d |
| | 114 | 85 | | connection.Transport = connection.RawTransport; |
| | | 86 | | |
| | | 87 | | // connection.ServiceDispatcher is null until later middleware layers are executed. |
| | 114 | 88 | | if (connection.ServiceDispatcher == null) |
| | | 89 | | { |
| | 1 | 90 | | await connection.SendFaultAsync( |
| | 1 | 91 | | FramingEncodingString.EndpointNotFoundFault, |
| | 1 | 92 | | ConnectionOrientedTransportDefaults.MaxViaSize + ConnectionOrientedTransportDefaults.MaxCont |
| | 1 | 93 | | connection.ChannelInitializationCancellationToken); |
| | 0 | 94 | | return; |
| | | 95 | | } |
| | | 96 | | |
| | 113 | 97 | | if (reuseHandler == null) |
| | | 98 | | { |
| | 77 | 99 | | var listenOptions = connection.ConnectionFeatures.Get<NetFramingListenOptions>(); |
| | 77 | 100 | | reuseHandler = listenOptions.ConnectionReuseHandler; |
| | | 101 | | } |
| | 220 | 102 | | } while (await reuseHandler.ReuseConnectionAsync(connection, _appLifetime.ApplicationStopping)); |
| | | 103 | | |
| | | 104 | | // On .NET Framework, with the way that AspNetCore closes a connection, it sometimes doesn't send the |
| | | 105 | | // final bytes if those bytes haven't been sent yet. Delaying completeing the connection to compensate. |
| | 76 | 106 | | await Task.Delay(5); |
| | 76 | 107 | | } |
| | | 108 | | |
| | | 109 | | // AspNetCore 2.1 doesn't close the connection. 2.2+ does so these lines can eventually be removed. |
| | 76 | 110 | | connection.RawTransport.Input.Complete(); |
| | 76 | 111 | | connection.RawTransport.Output.Complete(); |
| | 76 | 112 | | } |
| | | 113 | | } |
| | | 114 | | } |