Search code examples
c#azuresslazureservicebusazure-servicebusrelay

Azure Service Bus Relay and http protocol


I have to set up an Azure service which must be accessible for other platforms (android, iOS). That's why I try to set it up using http or https protocol, rather than sb (Service Bus) protocol (ref: Service Bus Bindings, last paragraph).

Unfortunately the service throws an exception while initializing:

"HTTP could not register URL http://+:80/ServiceBusDefaultNamespace/. Your process does not have access rights to this namespace (see http://go.microsoft.com/fwlink/?LinkId=70353 for details)."

The service initialization code in WorkerRole is:

  private void InitailizeService()
  {
     Trace.WriteLine("Initializing service");
     try
     {
        var serviceAddress = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ServiceAddress");
        var protocol = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.Protocol");
        string keyName = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ServiceKeyName");
        string sharedAccessKey = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ServiceSharedAccessKey");

        Uri uri = new Uri(protocol + "://" + serviceAddress + "/ServiceBusDefaultNamespace");

        ServiceBusEnvironment.SystemConnectivity.Mode = ConnectivityMode.Http;
        _host = new ServiceHost(typeof(WorkerRoleService), uri);

        TokenProvider tp = null;
        if (!String.IsNullOrEmpty(keyName))
        {
           tp = TokenProvider.CreateSharedAccessSignatureTokenProvider(keyName, sharedAccessKey);
        }
        var sharedSecretServiceBusCredential = new TransportClientEndpointBehavior(tp);

        ContractDescription contractDescription = ContractDescription.GetContract(typeof(IInstalSoftCloudService), typeof(WorkerRoleService));

        ServiceEndpoint serviceEndPoint = new ServiceEndpoint(contractDescription);
        serviceEndPoint.Address = new EndpointAddress(uri);

        Binding binding;
        switch (protocol)
        {
           case "sb":
              binding = new NetTcpRelayBinding { TransferMode = TransferMode.Streamed, MaxReceivedMessageSize = 1048576000, MaxBufferSize = 10485760, MaxConnections = 200 };
              break;
           case "http":
           case "https":
              binding = new WebHttpRelayBinding { TransferMode = TransferMode.Streamed, MaxReceivedMessageSize = 1048576000, MaxBufferSize = 10485760 };
              break;
           default:
              throw new NotSupportedException("Protocol not supported: " + protocol);
        }
        serviceEndPoint.Binding = binding;
        serviceEndPoint.Behaviors.Add(sharedSecretServiceBusCredential);

        _host.Description.Endpoints.Add(serviceEndPoint);

        _host.Open();

        Trace.WriteLine("Service initialization completed");
     }
     catch (Exception e)
     {
        Trace.WriteLine("Service initialization failed.\r\n" + e.Message);

        throw; 
     }
  }

The settings in ServiceConfiguration.Cloud.cscfg are:

  <Setting name="Microsoft.ServiceBus.ServiceAddress" value="<my namespace here>.servicebus.windows.net" />
  <Setting name="Microsoft.ServiceBus.ServiceKeyName" value="RootManageSharedAccessKey" />
  <Setting name="Microsoft.ServiceBus.ServiceSharedAccessKey" value="<my key here>" />
  <Setting name="Microsoft.ServiceBus.Protocol" value="http" />

The above code works fine when protocol in settings is changed to "sb".


Solution

  • After several hours of struggle I eventually made it working with https protocol. The crucial change was made in service host creation line:

    _host = new ServiceHost(typeof(WorkerRoleService));
    

    instead of:

    _host = new ServiceHost(typeof(WorkerRoleService), uri);
    

    I also changed the security token from SAS to ACS. It required though recreating my service bus with use of Azure CLI, because Azure portal doesn't allow for enabling ACS to a previously created service bus. For some more details see this post: How can I create a windows service ACS by powershell? (read all the comments because the correct command for subscription selection is Select-AzureSubscription).

    My final code is:

      private void InitailizeService()
      {
         try
         {
            var serviceAddress = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ServiceAddress");
            var serviceNamespace = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ServiceNamespace");
            var protocol = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.Protocol");
            string issuerName = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ServiceIssuerName");
            string issuerSecret = CloudConfigurationManager.GetSetting("Microsoft.ServiceBus.ServiceIssuerSecret");
    
            Uri uri = ServiceBusEnvironment.CreateServiceUri(protocol, serviceAddress, serviceNamespace);
    
            ServiceBusEnvironment.SystemConnectivity.Mode = ConnectivityMode.Http;
            _host = new ServiceHost(typeof(WorkerRoleService));
    
            TokenProvider tp = null;
            if (!String.IsNullOrEmpty(issuerName))
            {
               tp = TokenProvider.CreateSharedSecretTokenProvider(issuerName, issuerSecret);
            }
            var sharedSecretServiceBusCredential = new TransportClientEndpointBehavior(tp);
    
            Binding binding;
            switch (protocol)
            {
               case "sb":
                  binding = new NetTcpRelayBinding { TransferMode = TransferMode.Streamed, MaxReceivedMessageSize = 1048576000, MaxBufferSize = 10485760, MaxConnections = 200 };
                  break;
               case "http":
                  binding = new BasicHttpBinding { TransferMode = TransferMode.Streamed, MaxReceivedMessageSize = 1048576000, MaxBufferSize = 10485760 };
                  break;
               case "https":
                  var wsbinding = new WS2007HttpRelayBinding { MaxReceivedMessageSize = 1048576000 };
                  wsbinding.Security.Mode = EndToEndSecurityMode.Transport;
                  wsbinding.Security.RelayClientAuthenticationType = RelayClientAuthenticationType.None;
                  binding = wsbinding;
                  break;
               default:
                  throw new NotSupportedException("Protocol not supported: " + protocol);
            }
            var serviceEndPoint = _host.AddServiceEndpoint(typeof(IInstalSoftCloudService), binding, uri);
            serviceEndPoint.Behaviors.Add(sharedSecretServiceBusCredential);
    
            // Lines below are for MEX and publishing of the service
            EnableMetadataExchange(uri, sharedSecretServiceBusCredential, binding);
            ServiceRegistrySettings serviceRegistrySettings = new ServiceRegistrySettings(DiscoveryType.Public) { DisplayName = "InstalSystemMobileEngine" };
            foreach (ServiceEndpoint subscriberEndpoint in _host.Description.Endpoints)
            {
               subscriberEndpoint.Behaviors.Add(serviceRegistrySettings);
            }
    
            _host.Open();
    
            Trace.WriteLine("Service initialization completed");
         }
         catch (Exception e)
         {
            Trace.WriteLine("Service initialization failed.\r\n" + e.Message);
    
            throw; 
         }
      }
    
      private void EnableMetadataExchange(Uri aBaseUri, TransportClientEndpointBehavior aBehavior, Binding aBinding, bool aEnableHttpGet = true)
      {
         if (_host.State == CommunicationState.Opened)
            throw new InvalidOperationException("Host already opened");
         var metadataBehavior = _host.Description.Behaviors.Find<ServiceMetadataBehavior>();
         if (metadataBehavior == null)
         {
            metadataBehavior = new ServiceMetadataBehavior();
            _host.Description.Behaviors.Add(metadataBehavior);
            Trace.WriteLine("_host.Description.Behaviors.Add(metadataBehavior)");
         }
         var mexEndpoint = _host.AddServiceEndpoint(typeof(IMetadataExchange), aBinding, new Uri(aBaseUri, "mex"));
         mexEndpoint.Behaviors.Add(aBehavior);
      }
    

    Configuration for the above code:

      <Setting name="Microsoft.ServiceBus.ServiceAddress" value="<service bus address - without .servicebus.windows.net>" />
      <Setting name="Microsoft.ServiceBus.ServiceNamespace" value="ServiceBusDefaultNamespace/" />
      <Setting name="Microsoft.ServiceBus.ServiceIssuerName" value="<issuer name>" />
      <Setting name="Microsoft.ServiceBus.ServiceIssuerSecret" value="<issuer secret>" />
      <Setting name="Microsoft.ServiceBus.Protocol" value="https" />
    

    Now we'll have to try to connect to this service from an Android application - I hope it goes fine. The connection from a test WCF application works correctly and what's very important: allows for Azure scaling (several worker role instances attached to the Service Bus).

    The above code doesn't work with http protocol. I left it here because it works on Azure Emulator (with switching the service bus to local Service Bus for Windows).

    Hope the above helps someone ...