Tools
URI Template
A common activity in clients is the process of creating URLs to be used in HTTP requests. In order to enable libraries to be built to make this process more standardized, RFC 6570 defined a template syntax for use in URL construction.
HapiKit contains a class called UriTemplate which is based on the class in Tavis.UriTemplates.
More details on how this class can be used are shown in the blog post Constructing URLs the easy way.
var url = new UriTemplate("http://example.org/{tenant}/customers")
.AddParameter("tenant", "acmé")
.Resolve();
RequestFactory
The most fundamentally different approach that Hapikit SDKs take is that they explicitly expose the ability to create HTTP request messages and process HTTP response messages in the native types of the platform being supported. Most API SDKs hide these steps behind a procedure call and due to this are forced to make many decisions on behalf of the client application. This severely limits the re-usability of the SDK.
The IRequestFactory interface is the C# implementation of the abstraction used to expose the ability to create a HTTP request. In C# the native type is a HttpRequestMessage class.
public interface IRequestFactory
{
string LinkRelation { get; }
HttpRequestMessage CreateRequest();
}
The LinkRelation property is a type identifier that is used to distinguish one HttpRequestMessage from another. The identifiers are either standardized or specific to the API being consumed.
The IRequestFactory interface is implemented by the Link class.
Link
In order to encapsulate the API semantics required to build HTTP requests, HapiKit SDKs create specialized link classes as factories for HTTP requests. A class named Link is used a base for the specialized Links.
namespace haveibeenpwnd.net.hapisdk
{
public class BreachedAccountLink : Link
{
public string Account { get; set; }
public string Domain { get; set; }
[LinkParameter("truncateResponse",Default=false)]
public bool TruncateResponse { get; set; }
public BreachedAccountLink()
{
Template = new UriTemplate("https://haveibeenpwned.com/api/v2/breachedaccount/{account}{?truncateResponse,domain}");
}
}
}
The Link class itself does not actually build the HTTP request, it holds a chain of RequestBuilders that do all the work.
RequestBuilder
RequestBuilder is an implementation of the chain of responsibility pattern for allowing link classes to compose the construction behaviour of request messages. Usually the construction of HTTP header values are independent from each other and frequently applicable to multiple link types. Encapsulating the behaviour in chainable RequestBuilder classes allows the code to be isolated and reused.
DefaultRequestBuilder is the default implementation of the factory class used by Link classes to create request messages. The DefaultRequestBuilder performs the following functions:
- Update the request message with the HTTP body assigned to the link, if there is one
- Update the request message with the HTTP method assigned to the link
- Reflect over the link class looking for LinkParameter attributes and attempt to resolve the UriTemplate in the Link class using those parameters.
The default requestbuilder can be replaced completely or new requestbuilder classes can be chained to together to compose the construction mechanism.
RequestBuilders can be used to build Authorization headers when authorization scheme parameters include signatures of the request body. They can be used to decide on whether a body should be chunked or compressed based on the size of the request body. They can add custom headers that are specific to a particular application or API.
Currently, the Link class only allows someone who is using a Link to add an additional RequestBuilder. There is no way to remove RequestBuilders that have been defined in the derived Link class. Having said that, there is nothing stopping an added RequestBuilder from undoing changes made by built in Requestbuilders.
There are three levels at which RequestBuilder behaviour can be added to Link classes:
- In the constructor of the derived link class
- RequestBuilders can be registered in the LinkFactory
- Client code can add RequestBuilders to the chain
The first option is used when the definition of the Link Relation Type defines a particular request behaviour. The second option is used when a particular client application always wants a particular behaviour whenever a the link type is discovered in a hypermedia document. The third option is when a particular instance of following a link needs to add a behaviour due to the current client state.
ResponseHandlers
Response handlers are based around the standard interface IResponseHandler
public interface IResponseHandler
{
Task<HttpResponseMessage> HandleResponseAsync(string linkRelation, HttpResponseMessage responseMessage);
}
The intent of this interface is to decouple the code that makes the HTTP request from the code that handles the response. The only context that can be used to process the response is type of link that was followed, the response message and the current state of the response handler. This de-coupling encourages clients to be more capable of dealing with changes that occur on the server as fewer assumptions are made about the response to a request.
There are several extension methods that enable consise use of a ResponseHandler to process the response to a request. For example, the following code uses the IRequestFactory interface on the link object to create a HttpRequestMessage and then the IResponseHandler interface on the clientState object to handle the HttpResponseMessage.
await client.FollowLinkAsync(link)
.ApplyRepresentationToAsync(clientState);
In this case we are still selecting a specific response handler to deal with the response from a specific request.
HttpResponseMachine
We can further de-couple the processing of responses to allow plugging in standard behaviours for many standard responses by using the HttpResponseMachine.
There are two variants of the HttpResponseMachine, a stateless one and a stateful one that is implemented as HttpResponseMachine
In the stateless version, you can add a response handler that will be executed based on the link relation type followed and certain elements of the response like HTTP Status Code, media type and profile.
For example, this code adds a handler for HTTP status code 200 when following a link type called “foolink” and the content type is application/json.
machine.AddResponseHandler(async (l, r) =>
{
var text = await r.Content.ReadAsStringAsync();
root = JToken.Parse(text);
return r;
}, HttpStatusCode.OK,linkRelation: "foolink", contentType: new MediaTypeHeaderValue("application/json"), profile: null);
Only the HTTP Status code value is mandatory for mapping a response handler. The other properties are optional. If you don’t need a link relation to provide context then that’s great because it means your response is fully self-descriptive. I have found there are scenarios where the response just doesn’t provide enough context to determine what action needs to be taken. For extremely generic media types like application/json you may need to invent some really specific link relation types that map 1:1 to resource templates. Profiles can be used layer application semantics on top of generic hypermedia types.
TODO: Currently the HttpResponseMachine does not provide any assistance in parsing media types and profiles, but the plan is to introduce a mechanism to register translation functions that will be able to do all the work to convert response bodies to application level objects. This should allow response handlers to be registered that accept application level objects as inputs and the HttpResponseMachine will take care of finding the right translators to do the work.