Rest Agent uses hypermedia

As I alluded to in my first post, REST clients really should be driven by hypermedia.  So how can our RestAgent class access this hypermedia and its links.  Ideally, given a single root URI, I would like to be able to do:

var agent = new RestAgent(new HttpClient()); 
agent.NavigateTo(new Uri(“http://api.stackoverflow.com/”)); 
var questionsLink = agent.CurrentLinks[“Questions”]
agent.NavigateTo(questionsLink);
var questions = agent.CurrentContent.ReadAsJson(); 

Before I talk about the magic that is going on under the covers here, let me take a little jab at my friend StackOverflow.  The above code would not work, because when the team created the api, they chose not to put a root representation on the API. Making the request to the root of the api actually redirects to an html page with API documentation.  I’ve tried scraping the urls out of that document, but it is proving to be painful.

The Link Class

The CurrentLinks property makes a dictionary of links available based on the links contained in the current content and potentially links that were included in the header.  You will notice that the second call to NavigateTo passes a “Link”, not a URL.  So what’s the difference?  Here is a basic implementation of the Link class:

public class Link {

  public string Name{ get; set;}
  public LinkRelation Relation{ get; set;}
  public Uri Url { get; set;}
  public HttpMethod Method { get; set;}
  public HttpContent Content {get; set;}

  public Dictionary<Parameter> QueryParameters {get; set;}
  public Dictionary<Parameter> PathParameters {get; set;}

}

The Link class holds plenty of additional information that is needed to allow the NavigateTo method to transfer to a new state.  The Link objects are created from information contained in retrieved hypermedia content.  If the HttpMethod is a PUT or a POST then the Content property can be used to hold the entity that will be sent to the origin server. 

The LinkRelation property is critical concept.  The Link Relation is one of the two points of coupling between the client and the server.  The other being the media-type.  From an architectural point of view the Link Relation describes the relationship between two resources.  For example, if you have a representation A that contains a link to resource B with the relation “edit”, this can be interpreted as B allows you to “edit” A.  IANA keeps a registry of well known link relations here http://www.iana.org/assignments/link-relations/link-relations.txt.  The WHATWG group are also maintaining a list here http://wiki.whatwg.org/wiki/RelExtensions.

From an implementation perspective, the Link Relation can be used to tell the client mechanical information about how to perform the HTTP request.  For example, what HTTP method to use, what content needs to be posted back to the server, if any. It also can be used to tell the client what query string parameters are valid.  Arguably you can introduce any kind of arbitrary significance to a link relation as long as you are prepared to put up with the client/server coupling.

I have been experimenting with implementing LinkRelation as an actual class instead of just an identifer.  Many different link relations have similar sets of behaviour and by creating a base class with some standard attributes it is possible to represent specific link relations as derived classes and still have plenty of generic mechanisms that process links without scattering the code base with fragile switch statements.  Hopefully as we go into more detail I will show some examples that take advantage of this LinkRelation base class.

So far I have only used very simple URI templates and so my needs have been satisfied with just a collection of query parameter values and path parameter values.  The current draft of the URI template specification introduces many more variants that I could be supported by the Link class.

The Name property is the black sheep of the Link class.  I’m not convinced that we really need this property.  However, I include it to support the scenario where there are multiple links within a hypermedia document that has the same LinkRelation.  How a client is supposed to differentiate between those different links without introducing out of band coupling is not always clear to me.  If the current representation is to be rendered to the user as a set of links then the user should be capable of deciding which they wish to follow.  However, in a scripted (ie. machine 2 machine) scenario, it becomes highly suspect if the script starts referring to link names.  Depending on how precise the media type, is, the link names could be pre-specified in the media-type.  Or code-download could be used to activate links with particular names.  The final solution that I have seen is to drop the name property completely and use very specific link relation values.  I’m not a big fan of this approach because I think it leads to the same explosion problems as using extremely specific media-types.  Highly specific media-types and link relations destroy the possibility of serendipitous reuse.

Hypermedia Content

So now, you have an idea of how I handle links, let’s look at how those links get created in the first place.  As I mentioned before, the CurrentLinks dictionary is populated from the CurrentContent.  The question of course is how can the generic RestAgent know how to pull links out of content that could be any media type.  The solution is a plug in model that allows handlers to be registered in a media type handler registry. 

Behind the scenes I use MEF to load up the media type handlers, but I stuck an interface in front, mainly because I’m not very experienced with MEF and I wasn’t exactly sure how to setup unit tests using MEF.  With the interface I can easily create a fake registry.

public interface IHandlerRegistry {
        IMediaTypeHandler GetHandler(ContentType contentType);
    }

Media type handlers I will cover in more detail at a later date.  All we care about for this series of articles, is that they can be used to do two things:  1) they can convert the stream of bytes coming from the http response into a class that implements IHypermediaContent and 2) they can create and run a controller with RunController(HttpResponseMessage response) method that will “do” whatever that media type is supposed to do.  I realize that sounds very vague, but vague is good when we don’t want to introduce coupling :-).

public interface IMediaTypeHandler {
    IHypermediaContent GetContent(HttpContent content);
    IMediaTypeController RunController(HttpResponseMessage message);
}

For this article I’m going to ignore RunController and we will get to human driven clients in an upcoming article.  That leaves us with GetContent that returns an IHypermediaContent interface.  That interface looks like this:

public interface IHypermediaContent {
       IDictionary<string, Link> Links { get; }
   } 

Not exactly complex to implement!  Consider what it would take to write a handler that processes, HTML, XHTML, Atom Feeds, HAL, or even one that attempts to scrape links out of Json or generic XML based on some link convention. 

Magical Links

Pulling this all back together, when you navigate the agent to a particular location on the web, the agent will identify the media type of the returned response, determine a handler for that media type, allow the handler to process the bytes and extra the links and store them in a dictionary available via the CurrentLinks property of the agent.

Here is a completely hypothetical use of the RestAgent to crawl safe links on a site:

var agent = new RestAgent(new HttpClient(), new Uri(“http://mythical.com/hypermediaService”));
ExploreLinks(agent);

public void ExploreLinks(RestAgent agent) {
var links = CopyLinks(agent.CurrentLinks);
foreach(Link link in links) {
    if (Link.Method == HttpMethod.GET && Link.LinkRelation.RequiresParameters == false) {
        agent.NavigateTo(link);
        Explore(agent);
    }
}
}

To keep things simple, I only traverse links whose LinkRelation specifies a GET is the appropriate verb and only links that do not require parameters. 

I realize that this is still not a very useful example, but enough of the pieces are in place so that in the next article I will talk more about how to get the agent to actually do something useful.

No Comments

Add a Comment

comments powered by Disqus