johnhattan

RPCmin_XML (minimal XML-RPC client) in C#

Oct 27th, 2017
314
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 13.58 KB | None | 0 0
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Xml;
  5. using System.IO;
  6. using System.Text;
  7. using System.Net;
  8.  
  9. using UnityEngine;
  10.  
  11. public class RPCmin_XML
  12. {
  13.     public class RPCFault
  14.     { // a simple struct with a code and a string. All errors will be given to you as one of these
  15.         public int FaultCode = 400; // if there's a better "generic fault", let me know
  16.         public string FaultString;
  17.  
  18.         public RPCFault(string faultString = "") { FaultString = faultString; }
  19.     };
  20.  
  21.     public string URL;
  22.  
  23.     public RPCmin_XML(string _URL = null)
  24.     { // hang on to the URL. If we make several calls, it'll likely be to the same URL
  25.         URL = _URL;
  26.     }
  27.  
  28.     // the following calls are Unity-specific. If you're using generic C#, use the blocking calls or roll your own threaded version
  29.     public static MonoBehaviour MBHook { protected get; set; } = null; // coroutines require a monobehavior, so we'll hook into an existing one. Set this to a monobehavior (preferably a singleton) for the coroutine
  30.  
  31.     public bool Call(MonoBehaviour mBHook, string method, object[] parms = null, Action<object> fnSuccess = null, Action<RPCFault> fnFault = null)
  32.     {
  33.         MBHook = mBHook;
  34.         return Call(method, parms, fnSuccess, fnFault);
  35.     }
  36.  
  37.     public bool Call(string method, object[] parms = null, Action<object> fnSuccess = null, Action<RPCFault> fnFault = null)
  38.     { // non-blocking call that returns immediately and returns success or fault as actions. Returns true if call was sent
  39.         bool RetVal = false;
  40.         if ((URL != null) && (MBHook != null))
  41.         { // build the request
  42.             var Form = new WWWForm();
  43.             var Headers = Form.headers;
  44.             Headers["Content-Type"] = "text/xml;charset=UTF-8";
  45.  
  46.             string Request = EncodeXMLRPCRequest(method, parms);
  47. //          Debug.Log(Request);
  48.             var wwwRequest = new WWW(URL, Encoding.UTF8.GetBytes(Request), Headers);
  49.  
  50.             MBHook.StartCoroutine(WaitForRequest(wwwRequest, fnSuccess, fnFault)); //  Start a coroutine which will wait until our servers comes back
  51.             RetVal = true;
  52.         }
  53.         return RetVal;
  54.     }
  55.  
  56.     static public bool RPCXMLCall(string _URL, MonoBehaviour mBHook, string method, object[] parms = null, Action<object> fnSuccess = null, Action<RPCFault> fnFault = null)
  57.     { // if you're just making one call, do this. It'll make the call, return from whence it came, and then disappear
  58.         return new RPCmin_XML(_URL).Call(mBHook, method, parms, fnSuccess, fnFault);
  59.     }
  60.  
  61.     static public bool RPCXMLCall(string _URL, string method, object[] parms = null, Action<object> fnSuccess = null, Action<RPCFault> fnFault = null)
  62.     { // if you're just making one call, do this. It'll make the call, return from whence it came, and then disappear
  63.         return new RPCmin_XML(_URL).Call(method, parms, fnSuccess, fnFault);
  64.     }
  65.  
  66.     protected IEnumerator WaitForRequest(WWW wwwResult, Action<object> fnSuccess, Action<RPCFault> fnFault)
  67.     { // coroutine that yields until a result appears. Might want to add a timeout here eventually.
  68.         yield return wwwResult;
  69.  
  70.         if (String.IsNullOrEmpty(wwwResult.error))
  71.         { // parse the XML and see if there's a fault
  72.             string HTMLResult = Encoding.UTF8.GetString(wwwResult.bytes);
  73.             object Ob = DecodeXMLRPCResult(HTMLResult);
  74.             if (Ob == null)
  75.                 fnFault.Invoke(new RPCFault("Could not parse " + HTMLResult)); // not something parse-able
  76.             else if (Ob.GetType().ToString().Contains("RPCFault"))
  77.                 fnFault.Invoke((RPCFault)Ob); // RPC didn't run, most likely passed it bogus info
  78.             else
  79.                 fnSuccess.Invoke(Ob); // returned a non-fault object, which is good
  80.         }
  81.         else
  82.         { // we got some error text from the http server
  83.             RPCFault Fault = new RPCFault(wwwResult.error);
  84.  
  85.             if ((wwwResult.responseHeaders != null) && wwwResult.responseHeaders.ContainsKey("STATUS"))
  86.             { // getting the numeric fault code isn't easy
  87.                 string[] StatusStrs = wwwResult.responseHeaders["STATUS"].Split(' ');
  88.                 int StatusInt;
  89.                 if ((StatusStrs.Length >= 3) && int.TryParse(StatusStrs[1], out StatusInt))
  90.                     Fault.FaultCode = StatusInt; // the second value was a number, so assumme it's the fault code
  91.             }
  92.             fnFault.Invoke(Fault);
  93.         }
  94.     }
  95.     // End of the Unity-specific stuff
  96.  
  97.     public static RPCFault Fault(object Ob)
  98.     { // pass the result of the Call() below here, it will return a RPCFault if it's a fault, or null if not
  99.         return Ob.GetType().Equals(typeof(RPCFault)) ? (RPCFault)Ob : null;
  100.     }
  101.  
  102.     public object Call(string method, object[] parms = null)
  103.     { // returns an object that you can cast to the results you expect, null if there is no URL, or a RPCmin_XML.RPCFault if there is a fault
  104.         object RetVal = null;
  105.         if (URL != null)
  106.         { // build the request
  107.             HttpWebRequest HTTPRequest = (HttpWebRequest)WebRequest.Create(URL);
  108.             HTTPRequest.Method = "POST";
  109.             HTTPRequest.ContentType = "text/xml";
  110.  
  111.             // Add the request stream.
  112.             Stream RequestStream = HTTPRequest.GetRequestStream();
  113.             string Request = EncodeXMLRPCRequest(method, parms);
  114. //          Debug.Log(Request);
  115.             byte[] Bytes = Encoding.UTF8.GetBytes(Request);
  116.             RequestStream.Write(Bytes, 0, Bytes.Length);
  117.             RequestStream.Close();
  118.  
  119.             HttpWebResponse HTTPResponse = (HttpWebResponse)HTTPRequest.GetResponse(); // Now send it off
  120.  
  121.             // And we're (hopefully) back. Get the response as a stream with the required encoding format.
  122.             StreamReader ReadStream = new StreamReader(HTTPResponse.GetResponseStream(), Encoding.UTF8);
  123.             RetVal = DecodeXMLRPCResult(ReadStream.ReadToEnd()); // now parse the received XML back into an object
  124.             ReadStream.Close();
  125.  
  126.             HTTPResponse.Close();
  127.         }
  128.         return RetVal;
  129.     }
  130.  
  131.     // End of the public interface
  132.  
  133.     protected const string XMLRPC_STRING = "string"; // string
  134.     protected const string XMLRPC_INT = "int"; // int
  135.     protected const string XMLRPC_I4 = "i4"; // int
  136.     protected const string XMLRPC_DOUBLE = "double"; // double, float
  137.     protected const string XMLRPC_BOOLEAN = "boolean"; // bool
  138.     protected const string XMLRPC_ARRAY = "array"; // Array
  139.     protected const string XMLRPC_STRUCT = "struct"; // Hashtable
  140.     protected const string XMLRPC_DATE = "dateTime.iso8601"; // DateTimeOffset (preferred) or DateTime
  141.     protected const string XMLRPC_BASE64 = "base64"; // byte[]
  142.  
  143.     protected string XMLRPCTypeOf(object obValue)
  144.     { // converts a C# type to its equivalent in XMLRPC
  145.         Type ObType = obValue.GetType();
  146.  
  147.         if (ObType.Equals(typeof(int)) || ObType.Equals(typeof(uint)) || ObType.Equals(typeof(short)) || ObType.Equals(typeof(ushort)))
  148.             return XMLRPC_INT; // you can send all kinds of ints, but only an standard 32-bit signed int comes back
  149.         else if (ObType.Equals(typeof(double)) || ObType.Equals(typeof(float)))
  150.             return XMLRPC_DOUBLE; // you can send floats or doubles, but only a double comes back
  151.         else if (ObType.Equals(typeof(bool)))
  152.             return XMLRPC_BOOLEAN;
  153.         else if (ObType.Equals(typeof(Hashtable)))
  154.             return XMLRPC_STRUCT;
  155.         else if (ObType.Equals(typeof(DateTime)) || ObType.Equals(typeof(DateTimeOffset)))
  156.             return XMLRPC_DATE; // you can pass a DateTime or a DateTimeOffset. A DateTimeOffset comes back
  157.         else if (ObType.Equals(typeof(byte[])))
  158.             return XMLRPC_BASE64; // It converts to Base64 and back, so you'll get a byte[] back
  159.         else if (ObType.IsArray)
  160.             return XMLRPC_ARRAY;
  161.         else // everything else is a string, per the spec
  162.             return XMLRPC_STRING;
  163.     }
  164.  
  165.     protected string EncodeXMLRPCValue(object obValue)
  166.     { // figures out what an object is, converts it to a string, and returns it bracketed with <value><type>
  167.         String RetVal = null;
  168.         string TypeStr = XMLRPCTypeOf(obValue);
  169.  
  170.         switch (TypeStr)
  171.         {
  172.             case XMLRPC_INT:
  173.                 RetVal = ((int)obValue).ToString();
  174.             break;
  175.  
  176.             case XMLRPC_DOUBLE:
  177.                 RetVal = ((double)obValue).ToString();
  178.             break;
  179.  
  180.             case XMLRPC_BOOLEAN:
  181.                 RetVal = (((bool)obValue) ? "1" : "0");
  182.             break;
  183.  
  184.             case XMLRPC_ARRAY:
  185.             {
  186.                 StringBuilder S = new StringBuilder();
  187.                 foreach (object Ob in (Array)obValue)
  188.                 {
  189.                     string Node = EncodeXMLRPCValue(Ob);
  190.                     if (Node != null)
  191.                         S.AppendLine(Node);
  192.                 }
  193.  
  194.                 if (S.Length > 0) // we encoded something
  195.                     RetVal = "<data>" + S.ToString() + "</data>";
  196.             }
  197.             break;
  198.  
  199.             case XMLRPC_STRUCT:
  200.             {
  201.                 StringBuilder S = new StringBuilder();
  202.                 foreach (string Key in ((Hashtable)obValue).Keys)
  203.                 {
  204.                     string Node = EncodeXMLRPCValue(((Hashtable)obValue)[Key]);
  205.                     if (Node != null)
  206.                     { // this was a good value, so save the key and value
  207.                         S.AppendLine("<member>");
  208.                         S.AppendLine("<name>" + Key + "</name>");
  209.                         S.AppendLine(Node);
  210.                         S.AppendLine("</member>");
  211.                     }
  212.                 }
  213.  
  214.                 if (S.Length > 0) // we encoded something
  215.                     RetVal = S.ToString();
  216.             }
  217.             break;
  218.  
  219.             case XMLRPC_DATE:
  220.             {
  221.                 DateTimeOffset D;
  222.                 if (obValue.GetType().ToString() == "System.DateTime")
  223.                     D = new DateTimeOffset((DateTime)obValue);
  224.                 else
  225.                     D = (DateTimeOffset)obValue;
  226.  
  227.                 RetVal = D.ToString("o");
  228.             }
  229.             break;
  230.  
  231.             case XMLRPC_BASE64:
  232.                 RetVal = Convert.ToBase64String((byte[])obValue);
  233.             break;
  234.  
  235.             default: // assume string
  236.                 TypeStr = XMLRPC_STRING;
  237.                 RetVal = ((string)obValue).ToString();
  238.             break;
  239.         }
  240.  
  241.         return String.IsNullOrEmpty(RetVal) ? null : "<value><" + TypeStr + ">" + RetVal.ToString() + "</" + TypeStr + "></value>";
  242.     }
  243.  
  244.     protected string EncodeXMLRPCRequest(string method, object[] parms)
  245.     { // returns an XMLRPC-formatted object to send to a server
  246.         StringBuilder XMLRPC = new StringBuilder();
  247.         XMLRPC.AppendLine("<?xml version = \"1.0\" ?>");
  248.         XMLRPC.AppendLine("<methodCall>");
  249.         XMLRPC.AppendLine("<methodName>" + method + "</methodName>");
  250.  
  251.         if (parms != null)
  252.         {
  253.             StringBuilder S = new StringBuilder();
  254.  
  255.             foreach (object Item in parms)
  256.             {
  257.                 string ObEncoded = EncodeXMLRPCValue(Item);
  258.                 if (ObEncoded != null)
  259.                     S.AppendLine("<param>" + ObEncoded + "</param>");
  260.             }
  261.  
  262.             if (S.Length > 0)
  263.                 XMLRPC.AppendLine("<params>" + S.ToString() + "</params>");
  264.         }
  265.  
  266.         XMLRPC.AppendLine("</methodCall>");
  267.         return XMLRPC.ToString();
  268.     }
  269.  
  270.     protected static readonly string[] Format8601 = new string[] { "s", "u", "o" }; // permissive date parsing, as we don't know what the server thinks is standard
  271.  
  272.     protected object DecodeXMLRPCValue(XmlNode Node)
  273.     { // converts a single XML <type> string to an object
  274.         object RetVal = null;
  275.  
  276.         XmlNode ValueNode = Node.FirstChild;
  277.         switch (ValueNode.LocalName)
  278.         {
  279.             case XMLRPC_INT:
  280.             case XMLRPC_I4:
  281.             case XMLRPC_BOOLEAN:
  282.             {
  283.                 int Val = 0;
  284.                 int.TryParse(ValueNode.InnerText, out Val);
  285.  
  286.                 if (ValueNode.LocalName == XMLRPC_BOOLEAN)
  287.                     RetVal = (Val == 0) ? false : true;
  288.                 else
  289.                     RetVal = Val;
  290.             }
  291.             break;
  292.  
  293.             case XMLRPC_STRING:
  294.                 RetVal = ValueNode.InnerText;
  295.             break;
  296.  
  297.             case XMLRPC_DOUBLE:
  298.             {
  299.                 double Val = 0.0;
  300.                 double.TryParse(ValueNode.InnerText, out Val);
  301.                 RetVal = Val;
  302.             }
  303.             break;
  304.  
  305.             case XMLRPC_ARRAY:
  306.             {
  307.                 List<object> ObArr = new List<object>();
  308.  
  309.                 foreach (XmlNode ArrayNode in ValueNode.FirstChild) // there's a single <data> member, so get around it
  310.                 {
  311.                     object ObArrayElement = DecodeXMLRPCValue(ArrayNode);
  312.                     if (ObArrayElement != null)
  313.                         ObArr.Add(ObArrayElement);
  314.                 }
  315.  
  316.                 if (ObArr.Count > 0)
  317.                     RetVal = ObArr.ToArray(); // there's an array with items, so return it
  318.             }
  319.             break;
  320.  
  321.             case XMLRPC_STRUCT:
  322.             {
  323.                 Hashtable H = new Hashtable();
  324.  
  325.                 foreach (XmlNode StructNode in ValueNode) // loop through the <member> members
  326.                 {
  327.                     string Key = null;
  328.                     object Value = null;
  329.  
  330.                     XmlNode StructNameNode = StructNode.SelectSingleNode("name");
  331.                     if (StructNameNode != null)
  332.                         Key = StructNameNode.InnerText;
  333.  
  334.                     XmlNode StructValueNode = StructNode.SelectSingleNode("value");
  335.                     if (StructValueNode != null)
  336.                         Value = DecodeXMLRPCValue(StructValueNode);
  337.  
  338.                     if ((Key != null) && (Value != null))
  339.                         H[Key] = Value;
  340.                 }
  341.  
  342.                 if (H.Count > 0) // there was something in the struct
  343.                     RetVal = H;
  344.             }
  345.             break;
  346.  
  347.             case XMLRPC_DATE:
  348.                 DateTimeOffset Result;
  349.                 if (DateTimeOffset.TryParseExact(ValueNode.InnerText, Format8601, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.AllowWhiteSpaces | System.Globalization.DateTimeStyles.AdjustToUniversal, out Result))
  350.                     RetVal = Result;
  351.             break;
  352.  
  353.             case XMLRPC_BASE64:
  354.                 RetVal = Convert.FromBase64String(ValueNode.InnerText);
  355.             break;
  356.         }
  357.  
  358.         return RetVal;
  359.     }
  360.  
  361.     protected object DecodeXMLRPCResult(string XMLResultString)
  362.     {
  363. //      Debug.Log(XMLResultString);
  364.         object RetVal = null;
  365.         XmlDocument XML = new XmlDocument();
  366.         XML.LoadXml(XMLResultString);
  367.         XmlNode XMLMethodResponse = XML.SelectSingleNode("/methodResponse");
  368.         if (XMLMethodResponse != null)
  369.         { // we have a response
  370.             XmlNode XMLFault = XMLMethodResponse.SelectSingleNode("fault");
  371.             if (XMLFault != null)
  372.             { // we have a <fault> tag, so create a fault
  373.                 RetVal = new RPCFault();
  374.  
  375.                 foreach (XmlNode XMLFaultMember in XMLFault.SelectNodes("value/struct/member"))
  376.                 { // get the value
  377.                     object XMLFaultMemberValue = DecodeXMLRPCValue(XMLFaultMember.SelectSingleNode("value"));
  378.                     switch (XMLRPCTypeOf(XMLFaultMemberValue))
  379.                     { // only concerned about string and int values, as those are the code and the error
  380.                         case XMLRPC_STRING:
  381.                             ((RPCFault)RetVal).FaultString = (string)XMLFaultMemberValue;
  382.                         break;
  383.  
  384.                         case XMLRPC_INT:
  385.                             ((RPCFault)RetVal).FaultCode = (int)XMLFaultMemberValue;
  386.                         break;
  387.                     }
  388.                 }
  389.             }
  390.             else // no fault, so parse the value
  391.                 RetVal = DecodeXMLRPCValue(XMLMethodResponse.SelectNodes("params/param/value").Item(0)); // return only has one param and value, so assume the first
  392.         }
  393.         return RetVal;
  394.     }
  395. }
Advertisement
Add Comment
Please, Sign In to add comment