Silverlight HTTP Networking Stack – Part 1 (Site of Origin Communication)

This is going to be a two parter.  :)  Today, we’ll dig into the core networking capabilities of the HTTP stack for site of origin communication.  Next time, we’ll go deep into understanding our HTTP cross domain support.

update: this is now a 3 part series

So, You Want To Phone Home…

In Silverlight 2, by default, you are only able to talk back to your site of origin server.  This is in line with the general browser sandbox, and is designed to prevent cross site forgery.

To determine if a HTTP request is going back to site of origin, we look at the deployment URI of the XAP and the request URI.  Comparing these two URIs, 3 things must match:

  1. Same domain. http://foo.com  is different than http://bar.foo.com or http://www.foo.com
  2. Same protocol. http://foo.com is different than https://foo.com
  3. Same port. http://foo.com is different than http://foo.com:8080

If all three things match, the request will be allowed to go out into the world.  If they don’t, by default, we’ll disallow the request and throw an exception.  (Cross domain policies will be covered in part 2 of this post series.)

What Can You Say to Your Site of Origin Server?

You can talk to your site of origin server with the below capabilities:

  • Verb support:  GET & POST
  • Request header support:  Most standard & custom headers
  • Status codes:  200 (OK) or 404 (NotFound) only

Also, all the requests you send will have the “right thing happen” to them from a cookies and authentication standpoint.  This is a result of us leveraging the hosting browser to make the HTTP request.

HTTP requests themselves are:

  • Asynchronous only
  • Can only be initiated on the UI thread*

* We’re investigating to see if we can relax this latter restriction.

Why Those Capabilities?

The above capabilities are the result of:

  • Requirements for basic web services
  • Restrictions in the underlying browser plug-in networking APIs

To implement our Silverlight HTTP stack, we use the browser plug-in’s networking APIs.  The HTTP capabilities we expose are therefore bound by the common set of capabilities exposed by the browsers that Silverlight supports.

Note:  There are other ways we could have implemented the networking stack.  For instance, we could have proxied all calls through the browser’s XmlHttpRequest object.  However, this would have imposed a site of origin restriction on the requests, and we wanted to enable cross domain communication to existing web services.  Similarly, we could have gone directly to the operating system’s networking APIs, but then we would have lost the cookies and authentication integration with the browser.  In the future, exploring multiple of these stacks to expose more capabilities is definitely a possibility, and is something we would like feedback on.

What APIs Should I Use?

There are two APIs in Silverlight for HTTP communication.  The first is WebClient and the second is HttpWebRequest.

WebClient

WebClient is a great API with a simple, events-based paradigm.  You can use it to easily download a string or a Stream.

WebClient, behind the scenes, does a GET request and then gives you back the result in the form you wanted.  It also automatically resolves relative URIs against the deployment URI of the XAP.

Sample: Downloading a string using WebClient

   1: private void DownloadString()
   2: {
   3:     WebClient webClient = new WebClient();
   4:  
   5:     // Hook up events
   6:     webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(webClient_DownloadProgressChanged);
   7:     webClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(webClient_DownloadStringCompleted);
   8:  
   9:     // Initiate download
  10:     webClient.DownloadStringAsync(new Uri("myfeed.xml", UriKind.Relative));
  11: }
  12:  
  13: void webClient_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
  14: {
  15:     // Update progress UI
  16:     progressTextBox.Text = e.ProgressPercentage;
  17: }
  18:  
  19: void webClient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
  20: {
  21:     if (e.Error == null)
  22:     {
  23:         // Clear progress UI and show downloaded string
  24:         progressTextBox.Text = "";
  25:         feedTextBox.Text = e.Result;
  26:     }
  27: }
  28:  

HttpWebRequest & HttpWebResponse

HttpWebRequest and HttpWebResponse are the standard .NET HTTP apis.  They are much more powerful than WebClient but also more complex to use. You should use them when you want to set headers or issues a POST request.

Sample:  Sending POST request using HttpWebRequest

   1: private void SendPostRequest()
   2: {
   3:     HttpWebRequest request = WebRequest.Create(new Uri("http://foo.com/upload")) as HttpWebRequest;
   4:     request.Method = "POST";
   5:  
   6:     //  Set ContentType through property 
   7:     request.ContentType = "application/xml";
   8:  
   9:     //  Set other headers through Headers property
  10:     request.Headers["x-custom-header"] = "value";
  11:  
  12:     //  Initiate getting request stream
  13:     IAsyncResult asyncResult =
  14:     request.BeginGetRequestStream(new AsyncCallback(RequestStreamCallback), request);
  15: }
  16:  
  17: private void RequestStreamCallback(IAsyncResult ar)
  18: {
  19:     HttpWebRequest request = ar.AsyncState as HttpWebRequest;
  20:  
  21:     // populate request stream
  22:     request.ContentType = "text/xml";
  23:     Stream requestStream = request.EndGetRequestStream(ar);
  24:     StreamWriter streamWriter = new StreamWriter(requestStream, Encoding.UTF8);
  25:     streamWriter.Write("<?xml version="1.0"?>"
  26:                        + "<entry xmlns="http://www.w3.org/2005/Atom">"
  27:                        + "<author>"
  28:                        + "<name>Elizabeth Bennet</name>"
  29:                        + "<email>liz@gmail.com</email>"
  30:                        + "</author>"
  31:                        + "<title type="text">Entry 1</title>"
  32:                        + "<content type="text">This is my entry</content>"
  33:                        + "</entry>");
  34:     streamWriter.Close();
  35:  
  36:     // Make async call for response
  37:     request.BeginGetResponse(new AsyncCallback(ResponseCallback), request);
  38: }
  39:  
  40: private void ResponseCallback(IAsyncResult ar)
  41: {
  42:     HttpWebRequest request = ar.AsyncState as HttpWebRequest;
  43:     WebResponse response = request.EndGetResponse(ar);
  44:  
  45:     // use response.  
  46: }

Feedback – We Love It

Do the capabilities of our HTTP stack satisfy your needs?  What do you think of the API set?  We’d love to hear your thoughts.

Stay Tuned for Cross Domain Support in Part 2….

10 Responses to “Silverlight HTTP Networking Stack – Part 1 (Site of Origin Communication)”


  1. 1 Alexey Lavnikov

    Why only GET & POST?

    According to REST principles, there should be at least PUT & DELETE methods…

  2. 2 Laurent Bugnion

    Great article (as usual) in what I hope will be a long series to come :)
    I played with the WebClient (http://www.galasoft.ch/mydotnet/articles/article-2008032301.html) and it works well. I welcome that you replaced the Downloader with that object. The Downloader felt a little like a hack. Good move there.

    I also think that supporting the whole set of verbs (also HEAD) would be a good move. What is the reason to leave them out?

    Looking forward to the next post about cross-domain.

    Greetings,
    Laurent

  3. 3 Ole

    I really feel that the ability to do synchronous communication is essential.

    The code base of large applications get way too complicated when using asynchronous communication. Just look at your (very small) post example above. With synchronous communications, it would have been a one-liner. In large applications with hundreds (if not thousands) of web service calls that has to be called in specific sequences, this is not maintainable.

    The burden it places on the programmer with regards to state handling is simply too great for many applications.

    And of course, it should be posible to do synchronous communications in background threads to avoid blocking the UI.

    At the very least, I think you should implement a standard way of calling out through the XmlHttpRequest object. As it stands, everybody has to create their own wrappers for this. I know that the XmlHttpRequest is limited in what it will do, but for many (most?) applications it will be enough.

    Also, you need to support all the HTTP verbs, return the exact status codes from the server, and allow for access to all ports on the server (if the server allows it).

    Thanks!

  4. 4 Andrew Davey

    I second the missing PUT and DELETE verbs problem. WTF?!
    Also, why restrict the supported status code either - double WTF?!

    I see Silverlight as finally enabling the creation of an awesome RESTful client - why cripple it?

    I hope there is still time to fix these before 2.0 RTMs.

  5. 5 Andrew Davey

    OK, so I understand you have browser APIs to contend with…
    In which case PLEASE can we have some way to switch back to using XmlHttpRequeset under the covers? I need PUT and DELETE. I need access to a full HTTP spec implementation.

    Sure, for cross-site access limit me to GET/POST, if that’s what you need to do. But please do not limit the 80% use case (for me) which is talking to my own server!

    I can’t think of anything worse than having to stick with JavaScript, just for XHR!!

  6. 6 Karen

    First, thank you everyone who has both read and commented so far!

    As Andrew pointed out, the verb, status code and other HTTP capability limitations bound by the fact that the browser plugin networking APIs do not support things beyond what we’ve exposed. We wanted to use the browser stack (in order to get browser-enabled caching, auth, cookies, etc). And we also wanted to enable cross domain support, which meant not using XmlHttpRequest. This left us the browser plugin networking stack.

    We are definitely evaluating what we can do here in the short term and long term, and your feedback is especially helpful in this area.

    Also note that you are, if you really need to, able to access the XmlHttpRequest object via the HTML Bridge feature in managed code. After I finish this series, I’ll try to publish a full sample on how to do this.

    As to supporting asynchronous only requests, this is also a result of browser limitations. The browser plugin networking API only exposes async requests on the UI thread. Enabling a Silverlight synchronous request would result in a deadlock: we would block on the synchronous request and then never get the asynchronous callback from the browser. Moreover, though, the idea of a blocking call on the UI thread (which would affect the entire browser UI) seems like something we don’t want to encourage as a platform. We are looking at expanding what you can do with the WebClient class so as to make certain types of calls easier and reduce the scenarios that someone would need to use the HttpWebRequest class directly.

    Anyways, thanks again for the read and the comments - keep them coming!

    Thanks,
    Karen

  7. 7 Mike Amundsen

    Limited HTTP verbs and minimal HTTP Status codes makes Silverlight a non-starter for anyone building scalable rich clients that support intermediaries like caching proxies.

  8. 8 Karen

    As a call out, there are existing ways that web services choose to deal with limitations with limited HTTP verbs and status codes… Examples include:

    - Tunneling verbs through post (”POST Tunneling”).
    Many firewalls today only allow GET and POST verbs through.
    Some web services look at the X-HTTP-Method-Override header
    on posts. If X-HTTP-Method-Override is “DELETE”, the server
    treats that request like a DELETE.

    - Returning errors in the response stream.
    Since returning status codes is a limitation for any plugin
    using the browser’s networking APIs, some web services choose
    to always return OK and then serialize an error message in
    the response stream.

    We definitely understand that these limitations are less than ideal, but hopefully after reading this post, you have a better understanding of why certain tradeoffs were made in the SL2 timeframe.

  9. 9 Rick Strahl

    I’ve been playing around with HttpWebRequest and doing POST operations with doubly async gets tricky. Relatively easy for single files, but if you want to capture information on POST status and sizes there’s nothing that provides this (unlike the full .NET stack which does based on feeding the stream). The workarounds for this are horrendous (Wilco’s Upload component is a good example of the complexity involved). While a specialty scenario I too feel that HTTP operation - even if it’s a more complex scenario - should be easier because it’s the lifeblood of a thin client application. One of the key scenarios will be to send data back and forth and status information is a key scenario for this IMHO.

    There’s socket support now, so why couldn’t there be a native HTTP stack that bypassed XHR and all the limitations that implies completely? Wouldn’t it be much easier to spin up a new thread and on that thread synchronously deal with the post operation where you could capture progress events? From a programming model that’s much easier. Alternately if there was someway to implement the API in such a way that progress events can fire as the upload is in progress that too would help greatly…

  10. 10 Stuart Carnie

    Karen,

    The GET / POST methods are okay, since we can use the x-http-method-override pattern.

    I am concerned about the limited support for HTTP response codes. How would we handle a 401 to implement security?

    A real world example of something I am working on is to consume RESTful services in a SilverLight client via a server, which is secured using digest authentication. It would seem awkward to navigate to the web server and be prompted with the browser’s default credentials dialog prior to even downloading the SilverLight application. How would we do this in a cross-domain scenario?

    I also noticed that the WebClient class does not even have a Credentials (ICredentials) property like the full framework.

    Cheers,

    Stuart

Leave a Reply