Implementation of C# XMLRPC.NET client and server over HTTPS

OlivierB picture OlivierB · Oct 26, 2011 · Viewed 17.2k times · Source

It's pretty hard to find information about XMLRPC.net library used with https.

The only documentation where an "https" URL can be set is here : http://xml-rpc.net/faq/xmlrpcnetfaq-2-5-0.html#2.3 but yet it does not explain exactly how can one setup correctly.

Experimenting on the base of samples provided in the downloads http://xmlrpcnet.googlecode.com/files/xml-rpc.net.2.5.0.zip I tried this :

Changes in the client.cs file of StateNameServer solution :

IStateName svr = (IStateName)Activator.GetObject(
typeof(IStateName), "https://localhost:5678/statename.rem");

What the server code looks like

    IDictionary props = new Hashtable();
    props["name"] = "MyHttpChannel";
    props["port"] = 5678;
    HttpChannel channel = new HttpChannel(
    props,
    null,
    new XmlRpcServerFormatterSinkProvider()
    );

    ChannelServices.RegisterChannel(channel, false);

    RemotingConfiguration.RegisterWellKnownServiceType(
    typeof(StateNameServer),
    "statename.rem",
    WellKnownObjectMode.Singleton);

The client obviously drops an exception when trying to contact the server using HTTPS because I don't know how to configure it. Could someone help in anyway please ? What kind of stuff should I look for ?

Thanks a lot !

Answer

OlivierB picture OlivierB · Oct 27, 2011

First, I would like to thank warmly Charles Cook for his help on this problem and for developing XMLRPC.NET.

Second, this sample is based on the XMLRPC.NET StateNameServer sample available for download here : http://xml-rpc.net/download.html

So here is the solution :

1. Generate or get a [self-signed] certificate (using makecert.exe for example)

2. Add this certificate to your server configuration and specify the port you want to use with your XMLRPC.NET server (in this case 5678) using httpcfg.exe or another tool like HttpSysConfig (Open Source)

3. Implement your XMLRPC.NET server using the following code :

using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;

using CookComputing.XmlRpc;

using System.Net;
using System.IO;

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

        Console.WriteLine("Press <ENTER> to shutdown");
        Console.ReadLine();
    }
}

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" };
}

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;
        }
    }
}

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;
}

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

  public string GetStateNames(StateStructRequest request)
  {
    if (request.state1 < 1 || request.state1 > m_stateNames.Length)
      throw new XmlRpcFaultException(1, "State number 1 invalid");
    if (request.state2 < 1 || request.state2 > m_stateNames.Length)
      throw new XmlRpcFaultException(1, "State number 1 invalid");
    if (request.state3 < 1 || request.state3 > m_stateNames.Length)
      throw new XmlRpcFaultException(1, "State number 1 invalid");
    string ret = m_stateNames[request.state1-1] + " "
      + m_stateNames[request.state2-1] + " " 
      + m_stateNames[request.state3-1];
    return ret;
  }

  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" };
}

4. Implement your XMLRPC.NET client using the following code (the code also creates a new X509 client certificate)

using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;

using CookComputing.XmlRpc;
using System.Net;
using System.Security.Cryptography.X509Certificates;

class _
{
    public class TrustAllCertificatePolicy : System.Net.ICertificatePolicy
    {
        public TrustAllCertificatePolicy() { }
        public bool CheckValidationResult(ServicePoint sp,
           X509Certificate cert,
           WebRequest req,
           int problem)
        {
            return true;
        }
    }
    static void Main(string[] args)
    {
        System.Net.ServicePointManager.CertificatePolicy = new TrustAllCertificatePolicy();
        IStateName proxy = XmlRpcProxyGen.Create<IStateName>();
        XmlRpcClientProtocol cp = (XmlRpcClientProtocol)proxy;
        cp.Url = "https://127.0.0.1:5678/";
        cp.ClientCertificates.Add(new System.Security.Cryptography.X509Certificates.X509Certificate(@"C:\path\to\your\certificate\file\my.cer"));
        cp.KeepAlive = false;
        //cp.Expect100Continue = false;
        //cp.NonStandard = XmlRpcNonStandard.All;

        string stateName = ((IStateName)cp).GetStateName(13);
    }
}

Of course, I don't give here the interface implementation for the ServerStateName but you'll find it in the sample files using the download link at the top.

Remark :

System.Net.ServicePointManager.CertificatePolicy = new TrustAllCertificatePolicy(); will allow the server implementation to accept the self-signed certificate you generated by yourself. I think this is not necessary with certificates issued by certification authorities.

If you find anything that could be improved and is incorrect it will be much appreciated.