Overview
The Trackback technical specification describes a communication framework that allows web content authors to notify external web sites that they have authored content that refers to that sites content. Through the process of sending pings, web sites can be explicitly notified that some portion of the content they are hosting has been referred to by an external source. This peer-to-peer notification is typically used by web logs to send and receive this kind of notification to their peers.
In the Trackback system, the URL that receives Trackback pings is the Trackback Ping URL. To send a Trackback ping, the client makes a standard HTTP request to the server, and receives a response in a simple XML format. When implementing the Trackback specification, developers must then provide both a means of sending a properly formatted Trackback ping request, and a server hosted endpoint that Trackback pings can be sent to. Within the Argotic Syndication Framework, both the transmission and reception of Trackback information has been implemented in such a way that developers must perform a minimum amount of work in order to enable Trackback support within their applications.
Sending a Trackback Ping
When sending a Trackback ping, the client should provide the character encoding of the content being sent, along with one required and three optional parameters. The expected parameters are:
- url - Required parameter. The permalink for the entry, should point as closely as possible to the actual entry on the HTML page, as it will be used when linking to the entry in question.
- title - Optional parameter. The title of the entry.
- blog_name - Optional parameter. The name of the weblog to which the entry was posted.
- excerpt - Optional parameter. An excerpt of the entry.
All of the parameters provided must be in the specified character encoding. There are no length restrictions on the above parameters inherent in the Trackback protocol, but server implementations are free to crop or ignore any of the above fields. In the event of a successful ping, the server must return a response. In the event of an unsuccessful ping, the server must return an response.
The sending of Trackback ping requests in Argotic is handled by the TrackbackClient class, found in the Argotic.Core.Net namespace. The framework uses a message-response model, where sending of a Trackback ping involves defining the Trackback Ping URL the ping will be sent to and a TrackbackMessage that describes the required and optional parameters that compose a Trackback ping request. Trackback pings can be sent either synchronously or asynchronously, and will result in a TrackbackResponse that describes the result of the Trackback ping being sent.
Below is an example of how to send a synchronous Trackback ping using the Argotic framework:
using System;
using System.Net;
using System.Text;
using System.Web;
using Argotic.Core.Net;
//------------------------------------------------------------
// Local members
//------------------------------------------------------------
TrackbackClient client = null;
TrackbackMessage message = null;
TrackbackResponse response = null;
Uri host = new Uri("http://www.example.com/trackback/5");
Uri permalink = new Uri("http://www.bar.com");
//------------------------------------------------------------
// Initialize trackback client using specified host
//------------------------------------------------------------
client = new TrackbackClient(host);
client.UseDefaultCredentials = true;
client.Timeout = TimeSpan.FromSeconds(15);
//------------------------------------------------------------
// Build the Trackback ping that will be sent by the client
//------------------------------------------------------------
message = new TrackbackMessage(permalink);
message.Encoding = Encoding.UTF8;
message.Title = "Trackback Simple Example";
message.BlogName = "Foo";
message.Excerpt = "My Excerpt.";
//------------------------------------------------------------
// Send the Trackback ping synchronously and get response
//------------------------------------------------------------
response = client.Send(message);
//------------------------------------------------------------
// Verify there was a valid response to the Trackback ping
//------------------------------------------------------------
if (response != null)
{
//------------------------------------------------------------
// Determine if response indicated an error
//------------------------------------------------------------
if (response.HasError)
{
//------------------------------------------------------------
// Code to process the error message would go here
//------------------------------------------------------------
string errorMessage = response.Message;
}
}
As can be seen above, the Argotic framework classes abstract most of the work that is necessary when constructing and sending a Trackback ping. The TrackbackClient class allows a user to specify the Trackback Ping URL that the ping will be sent to, along with a timeout period to wait for a response and any security credentials that might be required. The TrackbackMessage class acts as a container for the Trackback ping parameters and character encoding of the content being sent. The response to a Trackback ping request is returned as a TrackbackResponse class instance that contains properties indicating if the ping was successful and any error message that was provided in the event of a ping failure.
Auto-Discovery of Trackback Ping URLs
Trackback clients need a method of determining the Trackback Ping URL for a particular URL or weblog entry. The Trackback specification specifies the use of embedded RDF elements within an HTML/XHTML formatted web pages that represents the meta-data about an entry, allowing clients to auto-discover the Trackback Ping URL. Pages can contain multiple instances of these embedded RDF elements. This meta-data provides a unique identifier, title, and Trackback Ping URL for an entry.
Below is an example RDF element that would be used to enable auto-discovery of a Trackback ping handler:
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"
>
<rdf:Description
rdf:about="http://www.foo.com/archive.html#foo"
dc:identifier="http://www.foo.com/archive.html#foo"
dc:title="Foo Bar"
trackback:ping="http://www.foo.com/tb.cgi/5"
/>
</rdf:RDF>
The dc:identifier and dc:title elements conform to the Dublin Core Metadata Element Set and the trackback:ping element conforms to the TrackBack Module for RSS 1.0/2.0 specification. When attempting to auto-discover the location to send Trackback pings to for a given URL, clients are expected to perform the following steps:
- Retrieve the contents of the page at the given URL.
- Parse the page content for one or more embedded RDF elements. If multiple RDF elements are found, the client should choose the element whose dc:identifier attribute value matches the given URL.
- Extract the Trackback Ping URL from the RDF element's trackback:ping attribute value.
The Argotic framework supports the embedding of Trackback auto-discovery RDF elements in ASP.NET pages through the use of the TrackbackDiscoveryControl web control. This web control provides a means of specifying the necessary Trackback meta-data by either declaratively or programmatically setting the Endpoint, Permalink, and Title properties of the web control. The Argotic framework also provides a 'no-touch' model, where the auto-discovery web control will configure itself using information contained in the Web.config file of an ASP.NET application.
Below is a brief example of how to utilize the TrackbackDiscoveryControl:
<%@ Register Assembly="Argotic.Core" Namespace="Argotic.Core.Web" TagPrefix="argotic" %>
<argotic:TrackbackDiscoveryControl
ID="TrackbackDiscoveryControl"
runat="server"
Endpoint="http://www.foo.com/tb.cgi/5"
Permalink="http://www.foo.com/archive.html#foo"
Title="Foo Bar"
></argotic:TrackbackDiscoveryControl>
In the example above, Trackback auto-discovery is enabled by registering the Argotic framework assembly that contains the web control, and then placing a Trackback auto-discovery web control within the ASP.NET content with the Endpoint, Permalink, and Title properties being set declaratively. If the Web.config file provides the appropriate configuration settings, these three properties do not need to be set declaratively unless overriding the default Trackback information is desired.
Receiving a Trackback Ping
Trackback uses a REST model, where requests are made through standard HTTP calls. To send a ping, the client sends an HTTP POST request to the Trackback Ping URL. The client must send a Content-Type HTTP header, with the content type set to application/x-www-form-urlencoded. The client should include the character encoding of the content being sent (title, excerpt, and weblog name) in the charset attribute of the Content-Type header. The Trackback content parameters are sent as a x-www-form-urlencoded encoded string as part of the HTTP POST.
The Argotic framework provides most of the plumbing needed to receive a Trackback ping request through the TrackbackHttpHandler class. This IHttpHandler derived class extracts the character encoding and parameters of the content being sent, as well as performs basic validation of the Trackback ping request to ensure the request conforms with the current Trackback specification requirements. The handler constructs a TrackbackMessage from the HTTP context and provides protected virtual methods that allow developers to implement their own custom processing of Trackback ping notifications. The Argotic framework provides a lightweight listener model that allows developers to easily subscribe to any valid Trackback ping requests that are received as well as a IHttpHandlerFactory derived class that allows the reuse of the TrackbackHttpHandler.
By default, the TrackbackHttpHandler class's PingReferencesTarget method returns an enumeration value that indicates an indeterminate result. Developers implementing Trackback support within their ASP.NET application may choose to override this method to validate if the source of the Trackback ping request does actually contain a reference to the content being pinged.
Below is an example of how to implement a custom TrackbackHttpHandler for use in an ASP.NET application:
using System;
using System.Web;
using Argotic.Core.Net;
using Argotic.Core.Web;
namespace ArgoticWeb
{
/// <summary>
/// Synchronously processes HTTP Web requests of trackback ping notifications.
/// </summary>
public class CustomTrackbackHttpHandler : TrackbackHttpHandler
{
//============================================================
// EVENT HANDLERS
//============================================================
#region OnPingReceived(TrackbackPingReceivedEventArgs e)
/// <summary>
/// Raises the <see cref="TrackbackHttpHandler.PingReceived"/> event.
/// </summary>
/// <param name="e">A <see cref="TrackbackPingReceivedEventArgs"/> that contains the event data.</param>
/// <remarks>
/// <para>This method overrides the base <b>OnPingReceived</b> method implementation in order to demonstrate how you can easily implement custom processing for received trackback pings.</para>
/// <para>
/// After a trackback ping request is received and validated, the base HTTP handler will call the <b>OnPingReceived</b> method just prior to responding to the trackback ping.
/// In addition to the framework's loosely-coupled listener model, you may choose to override the <b>OnPingReceived</b> method and use the <see cref="TrackbackPingReceivedEventArgs"/>
/// that were provided as a parameter to implement custom processing or logging of the trackback ping that was sent.
/// </para>
/// </remarks>
protected override void OnPingReceived(TrackbackPingReceivedEventArgs e)
{
//------------------------------------------------------------
// Local members
//------------------------------------------------------------
TrackbackMessage message = null;
DateTime pingReceivedOn = DateTime.MinValue;
TrackbackPingReferencesContent referencesContent = TrackbackPingReferencesContent.None;
string clientHostAddress = String.Empty;
string clientHostName = String.Empty;
object userToken = null;
//------------------------------------------------------------
// Attempt to handle the event
//------------------------------------------------------------
try
{
//------------------------------------------------------------
// Verify event arguments provided are valid
//------------------------------------------------------------
if(e != null)
{
//------------------------------------------------------------
// Extract information from the event arguments
//------------------------------------------------------------
message = e.Message;
pingReceivedOn = e.ReceivedOn;
referencesContent = e.ReferencesContent;
clientHostAddress = e.RemoteClientHostAddress;
clientHostName = e.RemoteClientHostName;
userToken = e.UserState;
// Your code would go here to process the trackback ping that was received
}
//------------------------------------------------------------
// Pass event to the base event handler
//------------------------------------------------------------
base.OnPingReceived(e);
}
catch
{
//------------------------------------------------------------
// Rethrow exception
//------------------------------------------------------------
throw;
}
}
#endregion
//============================================================
// PROTECTED OVERRIDDEN METHODS
//============================================================
#region PingReferencesTarget(Uri pingSource, HttpRequest request)
/// <summary>
/// Returns a value indicating if a trackback ping references the content it is pinging.
/// </summary>
/// <param name="pingSource">A <see cref="Uri"/> that represents the permalink for the web log entry the trackback service is being notified about.</param>
/// <param name="request">A <see cref="HttpRequest"/> that provides request information that can be used to determine if a trackback ping references the target content.</param>
/// <returns>
/// <see cref="TrackbackPingReferencesContent.Yes">Yes</see> if the <paramref name="pingSource"/> content contains at least one reference to the content being pinged,
/// <see cref="TrackbackPingReferencesContent.Yes">No</see> if the <paramref name="pingSource"/> content <b>does not</b> contain a reference to the content being pinged;
/// otherwise should return <see cref="TrackbackPingReferencesContent.Indeterminate">Indeterminate</see> if unable to determine or the <paramref name="pingSource"/>
/// is inaccessible.
/// </returns>
/// <remarks>
/// <para>This method overrides the base <b>TrackbackPingReferencesContent</b> method implementation, which always returns <see cref="TrackbackPingReferencesContent.Indeterminate"/>.</para>
/// <para>
/// For the purposes of this demonstration, this method returns <see cref="TrackbackPingReferencesContent.Yes"/> if the trackback request provides an <i>id</i> request parameter,
/// otherwise this method returns <see cref="TrackbackPingReferencesContent.No"/>. In a real world implementation, your custom trackback HTTP handler would extract whatever
/// information was relavent from the supplied <see cref="HttpRequest"/> to determine if the <paramref name="pingSource"/> referenced the target.
/// </para>
/// <para>
/// When overridding this method, you may want to use the <see cref="TrackbackHttpHandler.GetTrackbackMessage(HttpRequest)"/> to get the <see cref="TrackbackMessage"/>
/// that represents the trackback ping information that was sent.
/// </para>
/// </remarks>
/// <exception cref="ArgumentNullException">The <paramref name="pingSource"/> is a null reference (Nothing in Visual Basic).</exception>
/// <exception cref="ArgumentNullException">The <paramref name="request"/> is a null reference (Nothing in Visual Basic).</exception>
protected override TrackbackPingReferencesContent PingReferencesTarget(Uri pingSource, HttpRequest request)
{
//------------------------------------------------------------
// Local members
//------------------------------------------------------------
TrackbackPingReferencesContent pingReferencesTarget = TrackbackPingReferencesContent.None;
TrackbackMessage message = null;
//------------------------------------------------------------
// Attempt to determine if source references target
//------------------------------------------------------------
try
{
//------------------------------------------------------------
// Validate parameters
//------------------------------------------------------------
if (pingSource == null)
{
throw new ArgumentNullException("pingSource");
}
if (request == null)
{
throw new ArgumentNullException("request");
}
//------------------------------------------------------------
// Extract the trackback ping message from the request
//------------------------------------------------------------
message = TrackbackHttpHandler.GetTrackbackMessage(request);
//------------------------------------------------------------
// Arbitrary determination for demonstration purposes only
//------------------------------------------------------------
string idParameter = request.Params["id"];
if (!String.IsNullOrEmpty(idParameter))
{
pingReferencesTarget = TrackbackPingReferencesContent.Yes;
}
else
{
pingReferencesTarget = TrackbackPingReferencesContent.No;
}
}
catch (ArgumentNullException)
{
//------------------------------------------------------------
// Rethrow argument null exception
//------------------------------------------------------------
throw;
}
catch
{
//------------------------------------------------------------
// Rethrow exception
//------------------------------------------------------------
throw;
}
//------------------------------------------------------------
// Return result
//------------------------------------------------------------
return pingReferencesTarget;
}
#endregion
}
}
Below is an example of how to implement the custom companion TrackbackHttpHandlerFactory for use in an ASP.NET application:
using System;
using System.Collections;
using System.Web;
using Argotic.Core.Web;
namespace ArgoticWeb
{
/// <summary>
/// HTTP handler factory used to create new <see cref="CustomTrackbackHttpHandler"/> handler objects.
/// </summary>
/// <seealso cref="CustomTrackbackHttpHandler"/>
/// <remarks>
/// <para>
/// By default this custom trackback handler factory will return a default instance of the <see cref="CustomTrackbackHttpHandler"/> class.
/// </para>
/// </remarks>
public class CustomTrackbackHttpHandlerFactory : TrackbackHttpHandlerFactory
{
//============================================================
// CONSTRUCTORS
//============================================================
#region CustomTrackbackHttpHandlerFactory()
/// <summary>
/// Creates a new instance of the <see cref="CustomTrackbackHttpHandlerFactory"/> class.
/// </summary>
public CustomTrackbackHttpHandlerFactory() : base()
{
//------------------------------------------------------------
// Attempt to handle class initialization
//------------------------------------------------------------
try
{
//------------------------------------------------------------
// Initialization handled by base class
//------------------------------------------------------------
}
catch
{
//------------------------------------------------------------
// Rethrow exception
//------------------------------------------------------------
throw;
}
}
#endregion
#region CustomTrackbackHttpHandlerFactory(int poolSize)
/// <summary>
/// Creates a new instance of the <see cref="CustomTrackbackHttpHandlerFactory"/> class using the supplied pool size.
/// </summary>
/// <param name="poolSize">
/// The maximum number of resuable handlers that can be pooled by the factory.
/// A value of 0 or less indicates an unlimted number of handlers will be pooled by the factory
/// </param>
public CustomTrackbackHttpHandlerFactory(int poolSize) : base(poolSize)
{
//------------------------------------------------------------
// Attempt to handle class initialization
//------------------------------------------------------------
try
{
//------------------------------------------------------------
// Initialization handled by base class
//------------------------------------------------------------
}
catch
{
//------------------------------------------------------------
// Rethrow exception
//------------------------------------------------------------
throw;
}
}
#endregion
//============================================================
// PROTECTED OVERRIDDEN METHODS
//============================================================
#region GetFactoryHandler(HttpContext context, string requestType, Uri url, string pathTranslated)
/// <summary>
/// Returns an instance of the <see cref="CustomTrackbackHttpHandler"/> class served by this factory.
/// </summary>
/// <param name="context">
/// An instance of the <see cref="HttpContext"/> class that provides references to
/// intrinsic server objects (for example, <b>Request</b>, <b>Response</b>, <b>Session</b>, and <b>Server</b>) used to service HTTP requests.
/// </param>
/// <param name="requestType">The HTTP data transfer method (<b>GET</b> or <b>POST</b>) that the client uses.</param>
/// <param name="url">The <see cref="HttpRequest.RawUrl">RawUrl</see> of the requested resource.</param>
/// <param name="pathTranslated">The <see cref="HttpRequest.PhysicalApplicationPath">PhysicalApplicationPath</see> to the requested resource.</param>
/// <returns>A new <see cref="CustomTrackbackHttpHandler"/> object that processes the request.</returns>
/// <exception cref="ArgumentNullException">The <paramref name="context"/> is a null reference (Nothing in Visual Basic).</exception>
/// <exception cref="ArgumentNullException">The <paramref name="requestType"/> is a null reference (Nothing in Visual Basic).</exception>
/// <exception cref="ArgumentNullException">The <paramref name="requestType"/> is an empty string.</exception>
/// <exception cref="ArgumentNullException">The <paramref name="url"/> is a null reference (Nothing in Visual Basic).</exception>
/// <exception cref="HttpException">The factory was unable to create a <see cref="CustomTrackbackHttpHandler"/> for the request.</exception>
[System.Security.Permissions.ReflectionPermission(System.Security.Permissions.SecurityAction.Demand, MemberAccess = true)]
protected override TrackbackHttpHandler GetFactoryHandler(HttpContext context, string requestType, Uri url, string pathTranslated)
{
//------------------------------------------------------------
// Local members
//------------------------------------------------------------
CustomTrackbackHttpHandler handler = null;
//------------------------------------------------------------
// Attempt to get factory handler
//------------------------------------------------------------
try
{
//------------------------------------------------------------
// Validate parameters
//------------------------------------------------------------
if (context == null)
{
throw new ArgumentNullException("context");
}
if (String.IsNullOrEmpty(requestType))
{
throw new ArgumentNullException("requestType");
}
if (url == null)
{
throw new ArgumentNullException("url");
}
//------------------------------------------------------------
// Attempt to get reusable handler from the factory handler pool
//------------------------------------------------------------
IHttpHandler reusableHandler = TrackbackHttpHandlerFactory.GetHandler();
//------------------------------------------------------------
// Verify a reusable handler was retrieved from the factory pool
//------------------------------------------------------------
if (reusableHandler != null)
{
//------------------------------------------------------------
// Cast reusable handler to the expected type
//------------------------------------------------------------
handler = reusableHandler as CustomTrackbackHttpHandler;
//------------------------------------------------------------
// Verify a reusable handler is of expected type
//------------------------------------------------------------
if (handler != null)
{
//------------------------------------------------------------
// Return the reusable handler retrieved from the factory pool
//------------------------------------------------------------
return handler;
}
}
//------------------------------------------------------------
// Attempt to create handler
//------------------------------------------------------------
try
{
//------------------------------------------------------------
// Create handler via activator by type
//------------------------------------------------------------
handler = (CustomTrackbackHttpHandler)Activator.CreateInstance(typeof(CustomTrackbackHttpHandler));
}
catch (Exception exception)
{
//------------------------------------------------------------
// Rethrow exception as HttpException with param information
//------------------------------------------------------------
throw new HttpException(String.Format(null, "The CustomPingbackHttpHandlerFactory was unable to create a handler for requestType='{0}', url='{1}', pathTranslated='{2}'.", requestType, url, pathTranslated), exception);
}
}
catch (ArgumentNullException)
{
//------------------------------------------------------------
// Rethrow argument null exception
//------------------------------------------------------------
throw;
}
catch (ArgumentException)
{
//------------------------------------------------------------
// Rethrow argument exception
//------------------------------------------------------------
throw;
}
catch (HttpException)
{
//------------------------------------------------------------
// Rethrow HTTP exception
//------------------------------------------------------------
throw;
}
catch
{
//------------------------------------------------------------
// Rethrow exception
//------------------------------------------------------------
throw;
}
//------------------------------------------------------------
// Return result
//------------------------------------------------------------
return handler;
}
#endregion
#region ReleaseFactoryHandler(IHttpHandler handler)
/// <summary>
/// Enables this factory to reuse an existing <see cref="CustomTrackbackHttpHandler"/> instance.
/// </summary>
/// <param name="handler">The IHttpHandler object to reuse.</param>
protected override void ReleaseFactoryHandler(IHttpHandler handler)
{
//------------------------------------------------------------
// Attempt to release factory handler
//------------------------------------------------------------
try
{
//------------------------------------------------------------
// Cast handler to expected type
//------------------------------------------------------------
CustomTrackbackHttpHandler pingbackHandler = handler as CustomTrackbackHttpHandler;
//------------------------------------------------------------
// Verify handler was of expected type
//------------------------------------------------------------
if (pingbackHandler != null)
{
//------------------------------------------------------------
// Add handler to the factory handler pool if it is reusable
//------------------------------------------------------------
TrackbackHttpHandlerFactory.SetHandler(pingbackHandler);
}
}
catch
{
//------------------------------------------------------------
// Rethrow exception
//------------------------------------------------------------
throw;
}
}
#endregion
}
}
Below is an example Web.config file that ASP.NET applications would use to configure Trackback support:
<?xml version="1.0"?>
<configuration>
<!--
*******************************************************************************************************
WEB APPLICATION CONFIGURATION SECTIONS
*******************************************************************************************************
-->
<configSections>
<!-- Define Argotic Trackback Handler Section -->
<section name="argotic.trackbackHandler" type="Argotic.Core.Configuration.TrackbackHttpHandlerConfigurationSection, Argotic.Core"/>
<!-- Define Argotic Network Clients Section -->
<section name="argotic.net" type="Argotic.Core.Configuration.SyndicationNetworkConfigurationSection, Argotic.Core"/>
</configSections>
<!--
*******************************************************************************************************
ARGOTIC SYNDICATION FRAMEWORK CUSTOM CONFIGURATION SECTIONS
*******************************************************************************************************
-->
<!-- Configure Argotic Trackback Handler -->
<argotic.trackbackHandler useGlobalLock="true" title="Trackback Notification Handler">
<listeners>
<add name="XmlTrackbackListener" type="Argotic.Core.Web.XmlTrackbackListener, Argotic.Core" path="~/App_Data/Syndication/Trackback.xml"/>
</listeners>
</argotic.trackbackHandler>
<!-- Argotic Network Configuration -->
<argotic.net>
<!-- Configure Argotic Trackback Client -->
<trackback timeout="0:0:30" defaultCredentials="true" />
</argotic.net>
<!--
*******************************************************************************************************
SYSTEM.WEB CONFIGURATION
*******************************************************************************************************
-->
<system.web>
<!-- Web Application HTTP Handlers (IIS 6 or ASP.NET Dev Server) -->
<httpHandlers>
<!-- Add Customized Argotic Trackback HTTP Handler Factory -->
<add verb="*" path="trackback.axd" type="ArgoticWeb.CustomTrackbackHttpHandlerFactory, App_Code" validate="false" />
</httpHandlers>
</system.web>
</configuration>
Conclusion:
As you can see, the Argotic syndication framework provides a solid implementation of the Trackback peer-to-peer notification specification that makes the sending and receiving of Trackback ping requests as painless as possible. If you have any questions or ideas, please feel free to post a comment. Happy coding!