Cook Computing

XML-RPC Server Using HttpListener

January 18, 2007 Written by Charles Cook

I've had a handful of requests from people who want to use the .Net System.Net.HttpListener class as the basis for an XML-RPC server implemented using XML-RPC.NET.

Acquiring the request synchronously via the HttpListener GetContext method, the top level code could look like this:


using System;
using System.IO;
using System.Net;
using CookComputing.XmlRpc;

class _
{
  public static void Main(string[] prefixes)
  {
    HttpListener listener = new HttpListener();
    listener.Prefixes.Add("http://127.0.0.1:11000/");
    listener.Start();
    while (true)
    {
      HttpListenerContext context = listener.GetContext();
      ListenerService svc = new StateNameService();
      svc.ProcessRequest(context);
    }
  }
}

ListenerService is an abstract base class we will define below and StateNameService is the derived class which contains the implementation of the methods required in the XML-RPC service:


public class StateNameService : ListenerService
{
  [XmlRpcMethod("examples.getStateName")]
  public string GetStateName(int stateNumber)
  {
    if (stateNumber < 1 || stateNumber > m_stateNames.Length)
      throw new XmlRpcFaultException(1, "Invalid state number");
    return m_stateNames[stateNumber - 1];
  }

  string[] m_stateNames
    = { "Alabama", "Alaska", "Arizona", "Arkansas",
        "California", "Colorado", "Connecticut", "Delaware", "Florida",
        "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", 
        "Kansas", "Kentucky", "Lousiana", "Maine", "Maryland", "Massachusetts",
        "Michigan", "Minnesota", "Mississipi", "Missouri", "Montana",
        "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", 
        "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma",
        "Oregon", "Pennsylviania", "Rhose Island", "South Carolina", 
        "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", "Virginia", 
        "Washington", "West Virginia", "Wisconsin", "Wyoming" };
}

The ListenerService class derives from the XmlRpcHttpServerProtocol class and so is similar in this respect to the XmlRpcService class which is used as the bass class for XML-RPC services which are hosted in ASP.NET.


public abstract class ListenerService : XmlRpcHttpServerProtocol
{
  public virtual void ProcessRequest(HttpListenerContext RequestContext)
  {
    try
    {
      IHttpRequest req = new ListenerRequest(RequestContext.Request);
      IHttpResponse resp = new ListenerResponse(RequestContext.Response);
      HandleHttpRequest(req, resp);
      RequestContext.Response.OutputStream.Close();
    }
    catch (Exception ex)
    {
      // "Internal server error"
      RequestContext.Response.StatusCode = 500;  
      RequestContext.Response.StatusDescription = ex.Message;
    }
  }
}

The remaining code defines the ListenerRequest and ListenerResponse classes. These essentially allow us to access the .Net HttpListenerRequest and HttpListenerResponse classes via the XML-RPC.NET IHttpRequest and IHttpResponse interfaces:


public class ListenerRequest : CookComputing.XmlRpc.IHttpRequest
{
  public ListenerRequest(HttpListenerRequest request)
  {
    this.request = request;
  }

  public Stream InputStream 
  { 
    get { return request.InputStream; } 
  }

  public string HttpMethod 
  { 
    get { return request.HttpMethod; } 
  }

  private HttpListenerRequest request;
}

public class ListenerResponse : CookComputing.XmlRpc.IHttpResponse
{
  public ListenerResponse(HttpListenerResponse response)
  {
    this.response = response;
  }

  string IHttpResponse.ContentType
  {
    get { return response.ContentType; }
    set { response.ContentType = value; }
  }

  TextWriter IHttpResponse.Output 
  {
    get { return new StreamWriter(response.OutputStream); } 
  }

  Stream IHttpResponse.OutputStream 
  {
    get { return response.OutputStream; } 
  }

  int IHttpResponse.StatusCode
  {
    get { return response.StatusCode; }
    set { response.StatusCode = value; }
  }

  string IHttpResponse.StatusDescription
  {
    get { return response.StatusDescription; }
    set { response.StatusDescription = value; }
  }

  private HttpListenerResponse response;
}

This sample server can then be accessed by a client implemented in the usual way:


using System;
using System.Collections.Generic;
using System.Text;
using CookComputing.XmlRpc;

[XmlRpcUrl("http://127.0.0.1:11000/index/")]
public interface IStateName : IXmlRpcProxy
{
  [XmlRpcMethod("examples.getStateName")]
  string GetStateName(int stateNumber);

}

class Program
{
  static void Main(string[] args)
  {
    IStateName proxy = XmlRpcProxyGen.Create<IStateName>();
      string ret = proxy.GetStateName(1);
  }
}

I'll illustrate how to use the asynchronous HttpListener methods BeginGetContext and EndGetContext in a future post. I'll also add the ListenerService, ListenerRequest, and ListenerResponse classes to the next release of XML-RPC.NET.