ASP.NET Web API: Creating an Host using Azure Service Bus

In the last posts, I’ve presented the new ASP.NET Web API processing architecture and described three different hosting capabilities, supported “out of the box”: web hosting, in-memory hosting and self-hosting.

In this post, I will describe the development of a custom host using the Azure Service Bus relaying capabilities. This new host enables the exposure of a Web API on the public cloud, while running on a private machine (e.g. my laptop), that is, a machine without inbound connectivity (e.g. private addresses, firewall, NAT).

ServiceBusRelay

This host design is inspired in the self-host architecture, namely the usage of WCF and its integration with the service bus and WCF. Is composed by the following main components, shown in the following diagram.

 

 

ServiceBusHosting

 

  • The HttpServiceBusConfiguration class derives from HttpConfiguration and adds a couple of properties specific to the this scenario, such as the bus authentication credentials (IssuerName and IssuerSecret).
  • The HttpServiceBusServer is initialized with a HttpServiceBusConfiguration and internally performs the following:
  • When a request message is received, the WCF runtime delivers it to the DispatcherService, containing generic asynchronous operations to handle GET requests (BeginGet and EndGet methods) and other HTTP methods (BeginInvoke and EndInvoke).
    [ServiceContract]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
    internal class DispatcherService
    {
        private readonly HttpServer _server;
        private readonly HttpServiceBusConfiguration _config;

        public DispatcherService(HttpServer server, HttpServiceBusConfiguration config)
        {
            _server = server;
            _config = config;
        }

        [WebGet(UriTemplate = "*")]
        [OperationContract(AsyncPattern = true)]
        public IAsyncResult BeginGet(AsyncCallback callback, object state)
        {
            var context = WebOperationContext.Current;
            return DispatchToHttpServer(context.IncomingRequest, null, 
				context.OutgoingResponse, _config.BufferRequestContent, callback, state);
        }

        public Message EndGet(IAsyncResult ar)
        {
            var t = ar as Task;
            var stream = t.Result;
            return StreamMessageHelper.CreateMessage(MessageVersion.None, "GETRESPONSE", stream ?? new MemoryStream());
        }

        [WebInvoke(UriTemplate = "*", Method = "*")]
        [OperationContract(AsyncPattern = true)]
        public IAsyncResult BeginInvoke(Stream s, AsyncCallback callback, object state)
        {
            var context = WebOperationContext.Current;
            return DispatchToHttpServer(context.IncomingRequest, s, 
				context.OutgoingResponse, _config.BufferRequestContent, callback, state);
        }

        public Message EndInvoke(IAsyncResult ar)
        {
            var t = ar as Task;
            var stream = t.Result;
            return StreamMessageHelper.CreateMessage(MessageVersion.None, "GETRESPONSE", stream ?? new MemoryStream());
        }
		...
    }

 

  • These generic operations convert the WCF requests, represented by the older IncomingWebRequestContext class, into instances of the new HttpRequestMessage class. Then, they pushe these messages into the HttpServer pipeline. When the server finally returns the responses’ HttpResponseMessage instances, the generic operations convert them back into WCF messages.

The code, still in alpha/”works in my machine” status, is available from https://github.com/pmhsfelix/WebApi.Explorations.ServiceBusRelayHost.

Feedback is appreciated.

Advertisements

22 thoughts on “ASP.NET Web API: Creating an Host using Azure Service Bus

  1. davidw

    is it practical to use this way? what about performance? connection from Azure to your server should not be fast enough I guess.

    Reply
    1. pedrofelix Post author

      You should only use it if you need the relaying capabilities of Service Bus. E.g. the server is on-premises and does not have public address and connectivity, for instance a home server.

      Reply
  2. vrStijn

    Is there a way to do the same in web-hosting mode? Hence making use of IIS/AppFabric Server?
    I know there is a problem when using classical asp.net pipeline to register to the service bus.
    This bridge you make between Service Bus and ASP.NET through WCF sounds like a solution …

    http://blogs.msdn.com/b/avkashchauhan/archive/2011/11/17/wcf-rest-http-application-connecting-azure-service-bus-using-webhttprelaybinding-causes-aspnetcompatibilityenabled-error.aspx

    Reply
  3. Joe

    Good work, thank you! I have 2 issues:

    1) to get the response when using direct connection I could use

    var value = resp.Content.ReadAsAsync().Result;

    but now (when going through Azure Service Bus) I have to write

    var value = resp.Content.ReadAsStringAsync().Result;

    2) I could use following Controller when using direct connection:

    public class TestController : ApiController
    {
    public string GetData(int id)
    {
    return new PatientService().GetData(id);
    }
    }

    now (with Azure Service Bus in between) I have to write this:

    public class TestController : ApiController
    {
    public HttpResponseMessage GetData(int id)
    {
    return new HttpResponseMessage
    {
    Content = new StringContent(new PatientService().GetData(id))
    };
    }
    }

    Otherwise the connection gets closed.

    Why the difference?

    Reply
    1. pedrofelix Post author

      Joe,

      Theoretically, the hosting option should not matter. Give me a couple of days to check what is happening.

      Pedro

      Reply
    2. pedrofelix Post author

      Joe,

      I’ ve just pushed a new commit to the github repo. It solves a problem when POSTing XML or JSON content. Please check if this solves your problem. Feel free to create an issue at github if the problem persists or there is another error. I’ve also added two tests illustrating these scenarions.

      Pedro

      Reply
  4. Joe

    Hi Pedro,
    the problem persists. Looking at your code – why do you have to write

    public class ScreenController : ApiController
    {
    public HttpResponseMessage Get()
    {
    var content = new StreamContent(ScreenCapturer.GetEncodedByteStream());
    content.Headers.ContentType = new MediaTypeHeaderValue(“image/jpeg”);
    return new HttpResponseMessage()
    {
    Content = content
    };
    }
    }

    instead of

    public class ScreenController : ApiController
    {
    public Stream Get()
    {
    return ScreenCapturer.GetEncodedByteStream();
    }
    }

    as one can do without going though Azure Service Bus?

    Reply
    1. pedrofelix Post author

      I’m explicitly returning a HttpResponseMessage because I want to control the Content-Type header in the response (set it to “image/jpeg”). Could you create an issue at github and be a little more explicit about the problem. In the meanwhile, I will check the behavior when returning a Stream.

      Thanks

      Reply
      1. Joe

        Ok, I have created an issue. One more thing I can’t get into: on the server side you are securing the connection via a shared secret – but on the client side you just create a new HttpClient with the ServiceBus address WITHOUT providing a shared secret – but it still works. What is going wrong there?

  5. pedrofelix Post author

    1) I’ve already see the issue. I will look at it this weekend.
    2) I’ve configured the service bus relay to allow anonymous clients: RelayClientAuthenticationType.None. I will expose this property via the configuration so that it can be changed.

    Reply
    1. Joe

      Ok, I have changed the RelayClientAuthenticationType – how do I tell HttpClient then to use the shared key? I already aquired a token via https://MyNamespace-sb.accesscontrol.windows.net/WRAPv0.9/ and set it using client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(“WRAP”, decodedToken) – but it always returns 401 (Unauthorized). Do you have any idea how this works?

      Reply
      1. Joe

        Now I got it:

        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(“WRAP”, string.Format(“access_token=\”{0}\””, decodedToken));

  6. davidsavagejr

    This is really interesting. How much of a performance hit do you take by going over SB relay? Its almost as if you could use this as a way to prevent attackers from reverse look-up on the “home base” of your site.

    Reply
    1. pedrofelix Post author

      I’ve never measure it. I’ve used it more as a proof-of-concept and in situations where the hosting server does not have inbound connectivity (e.g. home automation)

      Reply
      1. davidsavagejr

        That’s a good scenario. The only other thought I’ve had is that you could accomplish similar functionality in a round-about way with SignalR but you lose the “DMZ” effect from the equation.

  7. Pingback: Twiddler – Playing Catchup « IT Batman!

  8. Amber

    Hi pedrofelix, Its really helpful solution. I have tried to perform load test on this solution and found that after 50 concurrent users the SB relay is broken and we started getting “504-Gateway Timeout ” error . Do you know the reason for it? Do we need to increase the timeout at some where ?

    Reply
    1. pedrofelix Post author

      There as an extra “.” on the URL. Sorry about that. I’ve already changed it.

      Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s