| | | 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.Collections.Generic; |
| | | 6 | | using System.Linq; |
| | | 7 | | using System.Runtime.InteropServices; |
| | | 8 | | using System.Threading; |
| | | 9 | | using System.Threading.Tasks; |
| | | 10 | | using CoreWCF.Configuration; |
| | | 11 | | using Microsoft.AspNetCore.Hosting.Server; |
| | | 12 | | using Microsoft.AspNetCore.Hosting.Server.Features; |
| | | 13 | | using Microsoft.AspNetCore.Server.Kestrel.Core; |
| | | 14 | | using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; |
| | | 15 | | using Microsoft.Extensions.DependencyInjection; |
| | | 16 | | using Microsoft.Extensions.Hosting; |
| | | 17 | | using Microsoft.Extensions.Logging; |
| | | 18 | | using Microsoft.Extensions.Options; |
| | | 19 | | |
| | | 20 | | namespace CoreWCF.Channels |
| | | 21 | | { |
| | | 22 | | internal class NetTcpHostedService : IHostedService, IAsyncDisposable, IDisposable |
| | | 23 | | { |
| | 158 | 24 | | private static bool s_isNetFramework => RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework", Str |
| | | 25 | | |
| | | 26 | | private readonly NetTcpOptions _serverOptions; |
| | | 27 | | private readonly IServiceBuilder _serviceBuilder; |
| | | 28 | | private readonly ILogger<NetTcpHostedService> _logger; |
| | | 29 | | private readonly IServiceProvider _serviceProvider; |
| | | 30 | | private KestrelServer _kestrel; |
| | | 31 | | private CancellationTokenRegistration _applicationStartedRegistration; |
| | | 32 | | private bool _started; |
| | | 33 | | |
| | 80 | 34 | | public NetTcpHostedService(IOptions<NetTcpOptions> options, IServer server, IServiceBuilder serviceBuilder, ILog |
| | | 35 | | { |
| | | 36 | | // We request the IServer in the constructor to trigger the constructor of the implementation. If the implem |
| | | 37 | | // is KestrelServer it will run NetTcpFramingOptionsSetup.Configure, which we use to detect the server is Ke |
| | | 38 | | // We can't just examine the type as we wrap it so we can throw any startup exceptions using WrappingIServer |
| | | 39 | | // The IServer will have already been started on asp.net core 2.1, but later versions start it after IHosted |
| | | 40 | | _ = server; |
| | 80 | 41 | | _serverOptions = options.Value ?? new NetTcpOptions(); |
| | 80 | 42 | | _serviceBuilder = serviceBuilder; |
| | 80 | 43 | | _logger = logger; |
| | 80 | 44 | | _serviceProvider = serviceProvider; |
| | 80 | 45 | | IApplicationLifetime appLifetime = _serviceProvider.GetRequiredService<IApplicationLifetime>(); |
| | | 46 | | // asp.net core 2.1 executes the ApplicationStarted registered callback before it starts hosted services |
| | 80 | 47 | | if (!s_isNetFramework) |
| | | 48 | | { |
| | 80 | 49 | | _applicationStartedRegistration = appLifetime.ApplicationStarted.Register(UpdateServerAddressesFeature); |
| | | 50 | | } |
| | 80 | 51 | | Init(); |
| | 80 | 52 | | } |
| | | 53 | | |
| | 318 | 54 | | public bool KestrelAlreadyInUse { get; private set; } |
| | | 55 | | |
| | | 56 | | private void Init() |
| | | 57 | | { |
| | | 58 | | // Check if Kestrel is already being used and if it is, it was already configured so there's nothing to do. |
| | 322 | 59 | | var netTcpFramingOptionsSetup = _serviceProvider.GetServices<IConfigureOptions<KestrelServerOptions>>().Sing |
| | 80 | 60 | | if (netTcpFramingOptionsSetup != null && netTcpFramingOptionsSetup.ConfigureCalled) |
| | | 61 | | { |
| | 80 | 62 | | KestrelAlreadyInUse = true; |
| | 80 | 63 | | return; |
| | | 64 | | } |
| | 0 | 65 | | } |
| | | 66 | | |
| | | 67 | | public async Task StartAsync(CancellationToken cancellationToken) |
| | | 68 | | { |
| | 80 | 69 | | _started = true; |
| | 160 | 70 | | if (KestrelAlreadyInUse) return; |
| | | 71 | | |
| | 0 | 72 | | var transportFactory = _serviceProvider.GetRequiredService<SocketTransportFactory>(); |
| | 0 | 73 | | _kestrel = ActivatorUtilities.CreateInstance(_serviceProvider, typeof(KestrelServer), transportFactory) as K |
| | | 74 | | |
| | 0 | 75 | | await _kestrel.StartAsync<int>(null, cancellationToken); |
| | | 76 | | |
| | | 77 | | // As we don't register the ApplicationStarted callback when running on .NET Framework, and we know that the |
| | | 78 | | // has already started before us, we can fixup the server addresses now. |
| | 0 | 79 | | if (s_isNetFramework) |
| | | 80 | | { |
| | 0 | 81 | | UpdateServerAddressesFeature(); |
| | | 82 | | // Need to update base addresses as ServiceBuilder was opened by wrapping WrappingIServer |
| | 0 | 83 | | var framingOptionsSetup = _serviceProvider.GetRequiredService<NetTcpFramingOptionsSetup>(); |
| | 0 | 84 | | framingOptionsSetup.UpdateServiceBuilderBaseAddresses(); |
| | 0 | 85 | | var serviceBuilder = _serviceProvider.GetRequiredService<IServiceBuilder>() as ICommunicationObject; |
| | 0 | 86 | | if (serviceBuilder.State == CommunicationState.Opened) |
| | | 87 | | { |
| | | 88 | | // Need to resolve the UriPrefixTable as the service builder was already opened |
| | | 89 | | // before we could hook up NetMessageFramingConnectionHandler.OnServiceBuilderOpened |
| | | 90 | | // to be called when the service builder is opened. As UriPrefixTable is internal |
| | | 91 | | // to NetFramingBase, it was also registered as its implemented interface type which |
| | | 92 | | // allows us to resolve it here. |
| | 0 | 93 | | _ = _serviceProvider.GetRequiredService<IEnumerable<KeyValuePair<BaseUriWithWildcard, HandshakeDeleg |
| | | 94 | | } |
| | | 95 | | } |
| | 80 | 96 | | } |
| | | 97 | | |
| | | 98 | | public Task StopAsync(CancellationToken cancellationToken) |
| | | 99 | | { |
| | 234 | 100 | | if (!_started) return Task.CompletedTask; |
| | 78 | 101 | | _started = false; |
| | 78 | 102 | | if (!s_isNetFramework) |
| | | 103 | | { |
| | 78 | 104 | | _applicationStartedRegistration.Dispose(); |
| | | 105 | | } |
| | | 106 | | |
| | 156 | 107 | | if (KestrelAlreadyInUse) return Task.CompletedTask; |
| | 0 | 108 | | return _kestrel.StopAsync(cancellationToken); |
| | | 109 | | } |
| | | 110 | | |
| | | 111 | | public void Dispose() |
| | | 112 | | { |
| | | 113 | | // No need to check if StopAsync has been called as it is a no-op after the first call |
| | 0 | 114 | | StopAsync(default).GetAwaiter().GetResult(); |
| | 0 | 115 | | } |
| | | 116 | | |
| | | 117 | | public ValueTask DisposeAsync() |
| | | 118 | | { |
| | | 119 | | // No need to check if StopAsync has been called as it is a no-op after the first call |
| | 78 | 120 | | return new ValueTask(StopAsync(default)); |
| | | 121 | | } |
| | | 122 | | |
| | | 123 | | private void UpdateServerAddressesFeature() |
| | | 124 | | { |
| | | 125 | | // This method needs to be called from appLifetime.ApplicationStarted otherwise an IServer might try to list |
| | | 126 | | // the address that Kestrel is listening on. It's not needed if Kestrel is the IServer implementation for th |
| | 160 | 127 | | if (KestrelAlreadyInUse) return; |
| | 0 | 128 | | var kestrelServerAddresses = _kestrel.Features.Get<IServerAddressesFeature>(); |
| | 0 | 129 | | var serverAddressesFeature = _serviceProvider.GetRequiredService<IServer>().Features.Get<IServerAddressesFea |
| | 0 | 130 | | foreach (var address in kestrelServerAddresses.Addresses) |
| | | 131 | | { |
| | 0 | 132 | | serverAddressesFeature.Addresses.Add(address); |
| | | 133 | | } |
| | 0 | 134 | | } |
| | | 135 | | } |
| | | 136 | | } |