Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Service-Fabric bind to multiple endpoints

Is it possible to bind a service fabric app to listen on multiple ports?

Basically I'm trying to have a public facing service which listens on http:80 and https:443, and redirects any http requests to https.

I created a new ASP.net Core service, it works fine individually. I.e. with SSL 443 or just non-SSL 80, but when I add both ServiceInstanceListeners it just fails!

Service Fabric Explorer says the following error after timing out several times:

Unhealthy event: SourceId='System.RA', Property='ReplicaOpenStatus', HealthState='Warning', ConsiderWarningAsError=false.
Replica had multiple failures in API call: IStatelessServiceInstance.Open(); Error = System.Fabric.FabricElementAlreadyExistsException (-2146233088)
Unique Name must be specified for each listener when multiple communication listeners are used
   at Microsoft.ServiceFabric.Services.Communication.ServiceEndpointCollection.AddEndpointCallerHoldsLock(String listenerName, String endpointAddress)
   at Microsoft.ServiceFabric.Services.Communication.ServiceEndpointCollection.AddEndpoint(String listenerName, String endpointAddress)
   at Microsoft.ServiceFabric.Services.Runtime.StatelessServiceInstanceAdapter.d__13.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.ServiceFabric.Services.Runtime.StatelessServiceInstanceAdapter.d__0.MoveNext()

Which is strange because both listeners have different names -- so it would seem. IS there somewhere I should be setting the listener name that I have missed?

I'm using the Asp.net Core template for this. My Stateless Service code is as follows:

internal sealed class Web : StatelessService
{
    public Web(StatelessServiceContext context)
        : base(context)
    { }

    /// <summary>
    /// Optional override to create listeners (like tcp, http) for this service instance.
    /// </summary>
    /// <returns>The collection of listeners.</returns>
    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
    {
        return new ServiceInstanceListener[]
        {
            new ServiceInstanceListener(serviceContext =>
                new WebListenerCommunicationListener(serviceContext, "ServiceEndpointHttps", url =>
                {
                    ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting WebListener on {url}");

                    return new WebHostBuilder()
                                .UseWebListener()
                                .ConfigureServices(
                                    services => services
                                        .AddSingleton<StatelessServiceContext>(serviceContext))
                                .UseContentRoot(Directory.GetCurrentDirectory())
                                .UseStartup<Startup>()
                                .UseUrls(url)
                                .Build();
                })),


            new ServiceInstanceListener(serviceContext =>
                new WebListenerCommunicationListener(serviceContext, "ServiceEndpointHttp", url =>
                {
                    ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting WebListener on {url}");

                    return new WebHostBuilder()
                                .UseWebListener()
                                .ConfigureServices(
                                    services => services
                                        .AddSingleton<StatelessServiceContext>(serviceContext))
                                .UseContentRoot(Directory.GetCurrentDirectory())
                                .UseStartup<Startup>()
                                .UseUrls(url)
                                .Build();
                }))
        };
    }
}
like image 795
Mardoxx Avatar asked Feb 06 '17 16:02

Mardoxx


2 Answers

I needed to set the name on ServiceInstanceListener which has the constructor

public ServiceInstanceListener(Func<StatelessServiceContext, ICommunicationListener> createCommunicationListener, string name = "");

I didn't realize it had extra params :)

like image 55
Mardoxx Avatar answered Nov 15 '22 19:11

Mardoxx


You can automate doing all of that by using the code below and also log the required information wherever is needed.

var currentEndpoint = "";
try
{
  IList<ServiceInstanceListener> listeners = new List<ServiceInstanceListener>();
  var endpoints = FabricRuntime.GetActivationContext().GetEndpoints();

  foreach (var endpoint in endpoints)
  {
    currentEndpoint = endpoint.Name;
    logger.LogInformation("Website trying to LISTEN : " + currentEndpoint);

    var webListner = new ServiceInstanceListener(serviceContext =>
       new WebListenerCommunicationListener(serviceContext, endpoint.Name, (url, listener) =>
       {
         url = endpoint.Protocol + "://+:" + endpoint.Port;
         logger.LogInformation("Website Listening : " + currentEndpoint);
         return new WebHostBuilder().UseWebListener()       .UseContentRoot(Directory.GetCurrentDirectory())
              .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
              .UseStartup<Startup>()
              .UseUrls(url)
              .Build();
       }), endpoint.Name.ToString());
    listeners.Add(webListner);
  }
  return listeners;
}
catch (Exception ex)
{
  logger.LogError("Exception occured while listening endpoint: " + currentEndpoint, ex);
  throw;
}
like image 29
Vijay Chakravarthy Avatar answered Nov 15 '22 18:11

Vijay Chakravarthy