C# XMLRPC.NET客户端和服务器在HTTPS上的实现

7

很难找到关于使用XMLRPC.net库和https一起使用的信息。

唯一一个可以设置"https" URL的文档在这里:http://xml-rpc.net/faq/xmlrpcnetfaq-2-5-0.html#2.3,但它并没有解释如何正确设置。

通过在下载的示例 http://xmlrpcnet.googlecode.com/files/xml-rpc.net.2.5.0.zip 中提供的样本进行实验,我尝试了以下更改:

在StateNameServer solution的client.cs文件中的更改:

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

服务器端代码的样子

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

客户端在尝试使用HTTPS联系服务器时显然会抛出异常,因为我不知道如何进行配置。请问是否有人可以帮忙?我应该寻找哪些内容呢?
非常感谢!
3个回答

4

首先,我要衷心感谢Charles Cook在解决这个问题和开发XMLRPC.NET方面提供的帮助。

其次,本示例基于可在此处下载的XMLRPC.NET StateNameServer示例: http://xml-rpc.net/download.html

下面是解决方案:

1. 生成或获取[自签名]证书(例如使用makecert.exe)

2. 使用httpcfg.exe或其他类似工具(如HttpSysConfig(开源)),将此证书添加到服务器配置中,并指定您要使用的XMLRPC.NET服务器端口(在本例中为5678)

3. 使用以下代码实现您的XMLRPC.NET服务器:

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. 使用以下代码实现您的XMLRPC.NET客户端(该代码还创建了一个新的X509客户端证书)

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

当然,我在这里没有给出ServerStateName的接口实现,但是您可以在顶部的下载链接中找到示例文件。
备注:
使用System.Net.ServicePointManager.CertificatePolicy = new TrustAllCertificatePolicy(),将允许服务器实现接受您自己生成的自签名证书。我认为对于由认证机构发行的证书,这是不必要的。
如果您发现任何需要改进和不正确的内容,我们非常欢迎您的反馈。

经过测试,我可以确认该实现在两台不同的Windows机器上运行正常。 - OlivierB

2
使用XmlRpcProxyGen.Create创建客户端代理,指定https url(您的接口应派生自IXmlRpcProxy)。如果需要提供客户端证书,代理具有ClientCertificates属性,可以与System.Net.HttpWebRequest类上的相应属性以相同方式使用。
我认为Remoting不支持HTTPS。您可以像XML-RPC.NET FAQ中所述使用HttpListener,但是您需要配置证书,例如在此博客中所述。

谢谢Charles。我尝试使用XmlRpcProxyGen,但客户端在尝试发送数据时仍然会抛出异常。服务器难道也不需要某种证书吗? - OlivierB
添加了有关实现服务器的段落。 - Charles Cook
谢谢你的帮助,Charles。你提供的链接帮我找到了解决方案。我将证书添加到了服务器上(实际上是我的本地计算机)... 解决方案的其余部分在我的回复中。 - OlivierB

1

很棒的文章!对我帮助很大。 但是第一条和第二条让我花了一天时间去理解。所以这是我的经验:

  1. 为了生成自签名证书,我使用了 openssl 工具。只需按照链接中的说明操作即可。这个证书是我需要用于客户端应用程序。
  2. 对于服务器应用程序,我需要另一个证书。服务器代码使用 HttpListener 类,该类没有 Certificates 属性。没有办法将特定的证书应用于 HttpListener 类的实例。有另一种策略:
    • 在本地证书存储中创建新证书。 要做到这一点,在 cmd 中输入“mmc” -> 文件 -> 添加/删除插件 -> 证书 -> 添加 -> 计算机帐户 -> 本地计算机 -> 确定。转到 Personal -> 证书 -> 右键单击 -> 所有任务 -> 请求新证书。下一步 -> 下一步 -> 选择 Web 服务器 -> 单击蓝色链接 -> 基本上您只需要在此处填写 Common Name(放置所需的证书名称)。确定 -> 注册。现在您在本地存储中拥有您的证书。
    • 将创建的证书绑定到特定的 IP/端口对。 要做到这一点,请在 cmd 中运行以下字符串:netsh http add sslcert ipport=192.168.111.195:4022 certhash=c8973a86141a7a564a6f509d1ecfea326a1852a2 appid={0a582a74-fc2d-476c-9281-c73b2e4bfb26},其中“ipport”是将用于 SSL 连接的 IP/端口对;“certhash”是证书哈希值(打开您在上一步中创建的证书 -> 转到详细信息 -> 查找指纹);“appid”可以是任何内容。
如果您在HttpListener url中指定了“https”,这个类会自动查找绑定的证书。

很高兴它有帮助!:) 我会尝试看看您的笔记。 - OlivierB

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接