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).
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.
- 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:
- Creates an inner HttpServer instance, using the given configuration.
- Creates a WebServiceHost, using the generic DispatcherService as the service class.
- Adds an endpoint with the WebHttpRelayBinding binding and a TransportClientEndpointBehavior, which configures this endpoint with the credentials required when listening on the bus.
- 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.
Great series of posts! Thanks a lot for sharing this 🙂
is it practical to use this way? what about performance? connection from Azure to your server should not be fast enough I guess.
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.
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
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?
Joe,
Theoretically, the hosting option should not matter. Give me a couple of days to check what is happening.
Pedro
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
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?
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
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?
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.
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?
Now I got it:
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(“WRAP”, string.Format(“access_token=\”{0}\””, decodedToken));
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.
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)
And yes, this way the service bus can be your DMZ in the cloud.
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.
Pingback: Twiddler – Playing Catchup « IT Batman!
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 ?
Please create an issue in the repo.
Hello Pedro,
the repo is empty? 404?
There as an extra “.” on the URL. Sorry about that. I’ve already changed it.