外部客户端无法访问 Azure Service Fabric 上的 WCF 通信侦听器
External Clients unable to access WCF Communication Listener on Azure Service Fabric
我正在尝试使用 WCF 通信侦听器将 Azure Web 角色 运行 WCF 迁移到 Azure Service Fabric 中的无状态服务。一切都在我的本地服务集群中工作。发布到 Azure 后,集群中的其他服务可以访问无状态 WCF 服务,但外部(互联网)客户端(包括我的开发机器)无法连接,出现暂时性网络错误。
我验证了资源组中的负载平衡器具有 rules/probes 端口 80 和 8080,并且已经使用 TCP 和 HTTP 进行了测试。我还尝试在 WCF 客户端上设置分区解析器以指向服务集群上的 "client connection endpoint"(默认情况下,它在服务集群内工作)。
此时,我不确定我是否有配置问题,或者外部(互联网)客户端是否有可能连接到无状态服务 运行 WCF 通信侦听器。
这是我的配置:
WCF 通信侦听器
private Func<StatelessServiceContext, ICommunicationListener> CreateListener()
{
return delegate (StatelessServiceContext context)
{
var host = new WcfCommunicationListener<IHello>(
wcfServiceObject: this,
serviceContext: context,
endpointResourceName: "ServiceEndpoint",
listenerBinding: CreateDefaultHttpBinding()
);
return host;
};
}
WCF 绑定
public static Binding CreateDefaultHttpBinding()
{
var binding = new WSHttpBinding(SecurityMode.None)
{
CloseTimeout = new TimeSpan(00, 05, 00),
OpenTimeout = new TimeSpan(00, 05, 00),
ReceiveTimeout = new TimeSpan(00, 05, 00),
SendTimeout = new TimeSpan(00, 05, 00),
MaxReceivedMessageSize = int.MaxValue,
};
var quota = new XmlDictionaryReaderQuotas
{
MaxArrayLength = int.MaxValue,
MaxDepth = int.MaxValue
};
binding.ReaderQuotas = quota;
return binding;
}
ServiceManifest.xml(我还使用了各种端口的默认 TCP 绑定)
<Endpoints>
<Endpoint Name="ServiceEndpoint" Protocol="http" Port="8080" />
</Endpoints>
WCF 控制台应用程序
var address = new Uri("fabric:/ServiceFabricWcf.Azure/ServiceFabricWcf");
var client = GetClient(address, CreateDefaultHttpBinding());
try
{
var results = client.InvokeWithRetry(x => x.Channel.Hello());
System.WriteLine($"Results from WCF Service: '{results}'");
Console.ReadKey();
}
catch (Exception e)
{
System.Console.WriteLine("Exception calling WCF Service: '{e}'");
}
WCF 客户端
public static WcfServiceFabricCommunicationClient<IHello> GetClient(Uri address, Binding binding)
{
//ServicePartitionResolver.GetDefault(); Works with other services in cluster
var partitionResolver = new ServicePartitionResolver("<clientConnectionEndpointOfServiceCluster>:8080");
var wcfClientFactory = new WcfCommunicationClientFactory<IHello>(binding, null, partitionResolver);
var sfclient = new WcfServiceFabricCommunicationClient<IHello>(wcfClientFactory, address, ServicePartitionKey.Singleton);
return sfclient;
}
WCF 客户端工厂
public class WcfServiceFabricCommunicationClient<T> : ServicePartitionClient<WcfCommunicationClient<T>> where T : class
{
public WcfServiceFabricCommunicationClient(ICommunicationClientFactory<WcfCommunicationClient<T>> communicationClientFactory,
Uri serviceUri,
ServicePartitionKey partitionKey = null,
TargetReplicaSelector targetReplicaSelector = TargetReplicaSelector.Default,
string listenerName = null,
OperationRetrySettings retrySettings = null
)
: base(communicationClientFactory, serviceUri, partitionKey, targetReplicaSelector, listenerName, retrySettings)
{
}
}
下面是一种适用于具有 WebHttpBinding
的 WCF 服务的方法:https://github.com/loekd/ServiceFabric.WcfCalc
尝试更改您的代码,使其不使用 endpointResourceName
,而是使用包含显式 URL 的 address
。 URL 应该是集群的 public 名称,例如 mycluster.region.cloudapp.azure.com.
编辑:url 应该使用节点名称,这样更容易。
string host = context.NodeContext.IPAddressOrFQDN;
var endpointConfig = context.CodePackageActivationContext.GetEndpoint
("CalculatorEndpoint");
int port = endpointConfig.Port;
string scheme = endpointConfig.Protocol.ToString();
string uri = string.Format(CultureInfo.InvariantCulture,
"{0}://{1}:{2}/", scheme, host, port);
这是我根据 LoekD 的回答更新的代码。
服务变更:
要使服务可用于 Internet 客户端,您必须向 WCFCommunicationListener 添加 "Address" 属性 以告知服务要侦听的端点 (http://mycluster.region.azure.com or http://localhost)
客户端更改: 使用普通的 WCF 客户端,没有任何 WCFCommunicationListener 引用。仅在服务结构内部使用 WCFCommunicationListener 客户端(我的原始代码在这种情况下工作正常)。
WCF 服务器侦听器
return delegate (StatelessServiceContext context)
{
string host = HostFromConfig(context);
if (string.IsNullOrWhiteSpace(host))
{
host = context.NodeContext.IPAddressOrFQDN;
}
var endpointConfig = context.CodePackageActivationContext.GetEndpoint("ServiceEndpoint");
int port = endpointConfig.Port;
string scheme = endpointConfig.Protocol.ToString();
//http://mycluster.region.cloudapp.azure.com or http://localhost
string uri = string.Format(CultureInfo.InvariantCulture, "{0}://{1}:{2}", scheme, host, port);
var listener = new WcfCommunicationListener<IHello>(
wcfServiceObject: this,
serviceContext: context,
listenerBinding: CreateDefaultHttpBinding(),
address: new EndpointAddress(uri)
);
return listener;
};
WCF 客户端应用程序
static void Main(string[] args)
{
System.Console.WriteLine("\nPress any key to start the wcf client.");
System.Console.ReadKey();
System.Console.WriteLine("*************Calling Hello Service*************");
try
{
var binding = CreateDefaultHttpBinding();
var address = new EndpointAddress("http://cluster.region.cloudapp.azure.com/"); //new EndpointAddress("http://localhost");
var results = WcfWebClient<IHello>.InvokeRestMethod(x => x.Hello(),binding, address );
System.Console.WriteLine($"*************Results from Hello Service: '{results}'*************");
Console.ReadKey();
}
catch (Exception e)
{
System.Console.WriteLine($"*************Exception calling Hello Service: '{e}'*************");
}
}
WCF 绑定
public static Binding CreateDefaultHttpBinding()
{
var binding = new WSHttpBinding(SecurityMode.None)
{
CloseTimeout = new TimeSpan(00, 05, 00),
OpenTimeout = new TimeSpan(00, 05, 00),
ReceiveTimeout = new TimeSpan(00, 05, 00),
SendTimeout = new TimeSpan(00, 05, 00),
MaxReceivedMessageSize = int.MaxValue,
};
var quota = new XmlDictionaryReaderQuotas
{
MaxArrayLength = int.MaxValue,
MaxDepth = int.MaxValue
};
binding.ReaderQuotas = quota;
return binding;
}
示例 External/Internet WCF 客户端:
public abstract class WcfWebClient<T> where T : class
{
public static TResult InvokeRestMethod<TResult>(Func<T, TResult> method, Binding binding, EndpointAddress address)
{
var myChannelFactory = new ChannelFactory<T>(binding, address);
var wcfClient = myChannelFactory.CreateChannel();
try
{
var result = method(wcfClient);
((IClientChannel)wcfClient).Close();
return result;
}
catch (TimeoutException e)
{
Trace.TraceError("WCF Client Timeout Exception" + e.Message);
// Handle the timeout exception.
((IClientChannel)wcfClient).Abort();
throw;
}
catch (CommunicationException e)
{
Trace.TraceError("WCF Client Communication Exception" + e.Message);
// Handle the communication exception.
((IClientChannel)wcfClient).Abort();
throw;
}
}
}
您还可以通过将以下属性放在服务实现上来尝试为 WCF 服务启用任何地址绑定模式:
[ServiceBehavior(AddressFilterMode=AddressFilterMode.Any)]
我正在尝试使用 WCF 通信侦听器将 Azure Web 角色 运行 WCF 迁移到 Azure Service Fabric 中的无状态服务。一切都在我的本地服务集群中工作。发布到 Azure 后,集群中的其他服务可以访问无状态 WCF 服务,但外部(互联网)客户端(包括我的开发机器)无法连接,出现暂时性网络错误。
我验证了资源组中的负载平衡器具有 rules/probes 端口 80 和 8080,并且已经使用 TCP 和 HTTP 进行了测试。我还尝试在 WCF 客户端上设置分区解析器以指向服务集群上的 "client connection endpoint"(默认情况下,它在服务集群内工作)。
此时,我不确定我是否有配置问题,或者外部(互联网)客户端是否有可能连接到无状态服务 运行 WCF 通信侦听器。
这是我的配置:
WCF 通信侦听器
private Func<StatelessServiceContext, ICommunicationListener> CreateListener()
{
return delegate (StatelessServiceContext context)
{
var host = new WcfCommunicationListener<IHello>(
wcfServiceObject: this,
serviceContext: context,
endpointResourceName: "ServiceEndpoint",
listenerBinding: CreateDefaultHttpBinding()
);
return host;
};
}
WCF 绑定
public static Binding CreateDefaultHttpBinding()
{
var binding = new WSHttpBinding(SecurityMode.None)
{
CloseTimeout = new TimeSpan(00, 05, 00),
OpenTimeout = new TimeSpan(00, 05, 00),
ReceiveTimeout = new TimeSpan(00, 05, 00),
SendTimeout = new TimeSpan(00, 05, 00),
MaxReceivedMessageSize = int.MaxValue,
};
var quota = new XmlDictionaryReaderQuotas
{
MaxArrayLength = int.MaxValue,
MaxDepth = int.MaxValue
};
binding.ReaderQuotas = quota;
return binding;
}
ServiceManifest.xml(我还使用了各种端口的默认 TCP 绑定)
<Endpoints>
<Endpoint Name="ServiceEndpoint" Protocol="http" Port="8080" />
</Endpoints>
WCF 控制台应用程序
var address = new Uri("fabric:/ServiceFabricWcf.Azure/ServiceFabricWcf");
var client = GetClient(address, CreateDefaultHttpBinding());
try
{
var results = client.InvokeWithRetry(x => x.Channel.Hello());
System.WriteLine($"Results from WCF Service: '{results}'");
Console.ReadKey();
}
catch (Exception e)
{
System.Console.WriteLine("Exception calling WCF Service: '{e}'");
}
WCF 客户端
public static WcfServiceFabricCommunicationClient<IHello> GetClient(Uri address, Binding binding)
{
//ServicePartitionResolver.GetDefault(); Works with other services in cluster
var partitionResolver = new ServicePartitionResolver("<clientConnectionEndpointOfServiceCluster>:8080");
var wcfClientFactory = new WcfCommunicationClientFactory<IHello>(binding, null, partitionResolver);
var sfclient = new WcfServiceFabricCommunicationClient<IHello>(wcfClientFactory, address, ServicePartitionKey.Singleton);
return sfclient;
}
WCF 客户端工厂
public class WcfServiceFabricCommunicationClient<T> : ServicePartitionClient<WcfCommunicationClient<T>> where T : class
{
public WcfServiceFabricCommunicationClient(ICommunicationClientFactory<WcfCommunicationClient<T>> communicationClientFactory,
Uri serviceUri,
ServicePartitionKey partitionKey = null,
TargetReplicaSelector targetReplicaSelector = TargetReplicaSelector.Default,
string listenerName = null,
OperationRetrySettings retrySettings = null
)
: base(communicationClientFactory, serviceUri, partitionKey, targetReplicaSelector, listenerName, retrySettings)
{
}
}
下面是一种适用于具有 WebHttpBinding
的 WCF 服务的方法:https://github.com/loekd/ServiceFabric.WcfCalc
尝试更改您的代码,使其不使用 endpointResourceName
,而是使用包含显式 URL 的 address
。 URL 应该是集群的 public 名称,例如 mycluster.region.cloudapp.azure.com.
编辑:url 应该使用节点名称,这样更容易。
string host = context.NodeContext.IPAddressOrFQDN;
var endpointConfig = context.CodePackageActivationContext.GetEndpoint
("CalculatorEndpoint");
int port = endpointConfig.Port;
string scheme = endpointConfig.Protocol.ToString();
string uri = string.Format(CultureInfo.InvariantCulture,
"{0}://{1}:{2}/", scheme, host, port);
这是我根据 LoekD 的回答更新的代码。
服务变更: 要使服务可用于 Internet 客户端,您必须向 WCFCommunicationListener 添加 "Address" 属性 以告知服务要侦听的端点 (http://mycluster.region.azure.com or http://localhost)
客户端更改: 使用普通的 WCF 客户端,没有任何 WCFCommunicationListener 引用。仅在服务结构内部使用 WCFCommunicationListener 客户端(我的原始代码在这种情况下工作正常)。
WCF 服务器侦听器
return delegate (StatelessServiceContext context)
{
string host = HostFromConfig(context);
if (string.IsNullOrWhiteSpace(host))
{
host = context.NodeContext.IPAddressOrFQDN;
}
var endpointConfig = context.CodePackageActivationContext.GetEndpoint("ServiceEndpoint");
int port = endpointConfig.Port;
string scheme = endpointConfig.Protocol.ToString();
//http://mycluster.region.cloudapp.azure.com or http://localhost
string uri = string.Format(CultureInfo.InvariantCulture, "{0}://{1}:{2}", scheme, host, port);
var listener = new WcfCommunicationListener<IHello>(
wcfServiceObject: this,
serviceContext: context,
listenerBinding: CreateDefaultHttpBinding(),
address: new EndpointAddress(uri)
);
return listener;
};
WCF 客户端应用程序
static void Main(string[] args)
{
System.Console.WriteLine("\nPress any key to start the wcf client.");
System.Console.ReadKey();
System.Console.WriteLine("*************Calling Hello Service*************");
try
{
var binding = CreateDefaultHttpBinding();
var address = new EndpointAddress("http://cluster.region.cloudapp.azure.com/"); //new EndpointAddress("http://localhost");
var results = WcfWebClient<IHello>.InvokeRestMethod(x => x.Hello(),binding, address );
System.Console.WriteLine($"*************Results from Hello Service: '{results}'*************");
Console.ReadKey();
}
catch (Exception e)
{
System.Console.WriteLine($"*************Exception calling Hello Service: '{e}'*************");
}
}
WCF 绑定
public static Binding CreateDefaultHttpBinding()
{
var binding = new WSHttpBinding(SecurityMode.None)
{
CloseTimeout = new TimeSpan(00, 05, 00),
OpenTimeout = new TimeSpan(00, 05, 00),
ReceiveTimeout = new TimeSpan(00, 05, 00),
SendTimeout = new TimeSpan(00, 05, 00),
MaxReceivedMessageSize = int.MaxValue,
};
var quota = new XmlDictionaryReaderQuotas
{
MaxArrayLength = int.MaxValue,
MaxDepth = int.MaxValue
};
binding.ReaderQuotas = quota;
return binding;
}
示例 External/Internet WCF 客户端:
public abstract class WcfWebClient<T> where T : class
{
public static TResult InvokeRestMethod<TResult>(Func<T, TResult> method, Binding binding, EndpointAddress address)
{
var myChannelFactory = new ChannelFactory<T>(binding, address);
var wcfClient = myChannelFactory.CreateChannel();
try
{
var result = method(wcfClient);
((IClientChannel)wcfClient).Close();
return result;
}
catch (TimeoutException e)
{
Trace.TraceError("WCF Client Timeout Exception" + e.Message);
// Handle the timeout exception.
((IClientChannel)wcfClient).Abort();
throw;
}
catch (CommunicationException e)
{
Trace.TraceError("WCF Client Communication Exception" + e.Message);
// Handle the communication exception.
((IClientChannel)wcfClient).Abort();
throw;
}
}
}
您还可以通过将以下属性放在服务实现上来尝试为 WCF 服务启用任何地址绑定模式:
[ServiceBehavior(AddressFilterMode=AddressFilterMode.Any)]