Unity3D XML(-RPC) and C#

前端 未结 1 743
情书的邮戳
情书的邮戳 2021-02-06 08:07

I\'m actually answering my own question here.

I must be the only person in the world who tried to do this but given that it has taken me about a week to work this out -

相关标签:
1条回答
  • 2021-02-06 09:09

    Build XML to send to our servers

    I couldn't find a standard function in Unity to do this without adding very large amounts of overhead. So I build the following procedure instead.

    public  string buildXMLRPCRequest(Hashtable FieldArray,string MethodName) 
    {
        string  ReturnString = "";
    
        ReturnString    +=         "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>" +
                            "\n" + "<simpleRPC version=\"0.9\">" +
                            "\n" + "<methodCall>" +
                            "\n" + "<methodName>" + MethodName + "</methodName>" +
                            "\n" + "<vector type=\"struct\">";
    
        ReturnString    +=  buildNode(FieldArray);
    
        ReturnString    +=  "\n</vector>" +
                            "\n</methodCall>" +
                            "\n</simpleRPC>";
        return  ReturnString;
    }
    
    public  string buildNode(Hashtable FieldArray) 
    {
        string  ReturnList = "";
    
        foreach (DictionaryEntry Item in FieldArray)    {
    
            string  TypeName    =   "int";
            string  NodeType    =   "scalar";
    
            Type myType =   Item.Value.GetType();
            string  fieldValue  =   "";
    
            if (myType == typeof(string) ) {
                TypeName    =   "string";
                fieldValue  =   Item.Value.ToString();
            }
    
            if (myType == typeof(Hashtable) ) {
                fieldValue  =   buildNode(Item.Value as Hashtable);
                NodeType    =   "vector";
                TypeName    =   "struct";
            }
    
            if (myType == typeof(int) ) {
                fieldValue  =   Item.Value.ToString();
                TypeName    = "int";
            }
    
            var ThisNode    =   "\n<" + NodeType + " type=\"" + TypeName + "\" id=\"" + Item.Key + "\">" + fieldValue + "</" + NodeType + ">";
            ReturnList +=   ThisNode;
        }
    
        return ReturnList;
    }
    

    The buildXMLRPCRequest is used to build XML. You hand it a HashTable with fields you want to encode which may include objects of the types: int, string or Hashtable. It will return a beautifully formated (Simple) XML-RPC string which is ready to go to our server.

    Send

    To send XML to our servers, you need to issue a POST request with the mime type set to text/xml. None of the standard C# methods can be used in Unity but using this with the output of the buildXMLRPCRequest logic works perfectly. What it does:

    Sending in Unity

    I used this code:

        private     void UnityPostXML(  int Staging,
                                            string WebServer,
                                            string MethodName,
                                            Hashtable   FieldArray)
        {
            string  WebServiceURL   =   "http://LIVESERVER/";
            if (Staging == 1) {
                WebServiceURL       =   "http://TESTSERVER";
            }
    
            // Encode the text to a UTF8 byte arrray
    
            string XMLRequest   =   buildXMLRPCRequest(FieldArray,MethodName);
    
            System.Text.Encoding enc = System.Text.Encoding.UTF8;
            byte[] myByteArray = enc.GetBytes(XMLRequest);
    
    
             // Get the Unity WWWForm object (a post version)
    
    
            var form = new WWWForm();
            var url = WebServiceURL;
    
            //  Add a custom header to the request.
            //  Change the content type to xml and set the character set
            var headers = form.headers;
            headers["Content-Type"]="text/xml;charset=UTF-8";
    
            // Post a request to an URL with our rawXMLData and custom headers
            var www = new WWW(WebServiceURL, myByteArray, headers);
    
            //  Start a co-routine which will wait until our servers comes back
    
            StartCoroutine(WaitForRequest(www));
    }
    
    IEnumerator WaitForRequest(WWW www)
    {
        yield return www;
    
        // check for errors
        if (www.error == null)
        {
            Debug.Log("WWW Ok!: " + www.text);
        } else {
            Debug.Log("WWW Error: "+ www.error);
        }    
    }
    
    • encode the XML to a ByteArray using UTF8
    • Create a new Unity WWWForm
    • Create a HashTable, store the current http headers (if any) and overwrite the content type to text/xml
    • Send that lot to the server
    • Set up a Coroutine which waits for the reply

    Sending without Unity

    I found that developing a library in C# (I use the standards version of MonoDevelop) is much simpler then using Unity for everything so the equivelant send logic in C# is below if wnat to do the same.

        private     string NormalXMLCall(int Staging,
                                             string WebServer,
                                             string MethodName,
                                             Hashtable Fields)
        {
            //  Figure out who to call
            string  WebServiceURL   =   "http://LIVSERVER";
            if (Staging == 1) {
                WebServiceURL       =   "http://TESTSERVER";
            }
    
            WebServiceURL           +=  WebServer;
    
            //  Build the request
    
            XmlRpcParser    parser  =   new XmlRpcParser();
            string XMLRequest       = parser.buildXMLRPCRequest(Fields,MethodName);
    
            //  Fire it off
    
            HttpWebRequest httpRequest =(HttpWebRequest)WebRequest.Create(WebServiceURL);
    
            httpRequest.Method = "POST";
    
            //Defining the type of the posted data as XML
            httpRequest.ContentType = "text/xml";
    
            // string data = xmlDoc.InnerXml;
            byte[] bytedata = Encoding.UTF8.GetBytes(XMLRequest);
    
            // Get the request stream.
            Stream requestStream = httpRequest.GetRequestStream();
    
            // Write the data to the request stream.
            requestStream.Write(bytedata, 0, bytedata.Length);
            requestStream.Close();
    
            //Get Response
            HttpWebResponse httpResponse = (HttpWebResponse)httpRequest.GetResponse();
    
            // Get the stream associated with the response.
            Stream receiveStream = httpResponse.GetResponseStream ();
    
            // Pipes the stream to a higher level stream reader with the required encoding format. 
            StreamReader readStream = new StreamReader (receiveStream, Encoding.UTF8);
    
            string  ReceivedData    =   readStream.ReadToEnd ();
            httpResponse.Close ();
            readStream.Close ();
    
            return  ReceivedData;
        }
    }
    

    Extract data from XML

    I wrote a simple parser. The constructor for the below findNode function should be given the raw XML data and the child node object you want to find. It will return the value of that node (as a string) if that node can be found on the highest level of the XML string or null if it can't find it. This parser is specific to "Simple XML-RPC" and needs a bit of work to decode encoded characters but that should be simple to add.

    public string findNode(string Xml,string SearchForTag) {
    
        int     NestCounter     =   0;
        bool    FoundTag        =   false;
        int     FoundTagLevel   =   0;
        string  ReturnValue     =   null;
    
        //  Break it down by "<"
        string  []  TagArray    =   Xml.Split('<');
    
        for (int i=0;i<TagArray.Length;i++) {
    
            if (i>175 && i<180) {
                int Hello=1;
            }
    
            string  ThisLine    =   "<" + TagArray[i];
            if (ThisLine.Length <= 1)                                           continue;
            if ((ThisLine.Length >= 2) && (ThisLine.Substring(0,2) == "<?"))    continue;
            if ((ThisLine.Length >= 3) && (ThisLine.Substring(0,3) == "<--"))   continue;
    
            //  It can be a vector or a scalar - vectors are full of scalars so we'll
    
            ThisLine                =   ThisLine.Replace("  "," ");
            ThisLine                =   ThisLine.Replace("</","</");
            string  []  FieldArray  =   ThisLine.Split(' ');
            bool    AddLineToResult =   FoundTag;
    
            //  Nest counter is the level we are operating on. We only check the first
            //  Level. When a vector is found we increase the NestCount and we won't
            //  search for the ID
    
            if (NestCounter <= 1) { //  Initial array we are looking on level 1
                for (int a=0;a<FieldArray.Length;a++) {
                    string  ThisTag =   FieldArray[a];
                    string  []  TagValue    =   ThisTag.Split("=\"".ToCharArray(),5);
    
                    //  Every TagValue is xx=yy pair... we want "ID=\"xxx\" 
    
                    if (TagValue.Length >= 3) {
                        string  TagName =   TagValue[2];
                        if (TagName == SearchForTag) {
                            FoundTag        =   true;
                            FoundTagLevel   =   NestCounter;
                            //  This could be a vector or Scalar so find the ">" in this string
                            //  and start adding from there
                            int TerminatePos    =   ThisLine.IndexOf(">");
                            if ((TerminatePos >= 0) && (TerminatePos < ThisLine.Length))  {
                                ReturnValue =   ThisLine.Substring(TerminatePos+1);
                            }
                            break;
                        }
                    }
                }
            }
    
            if (FieldArray.Length > 0) {
                string  ThisField   =   FieldArray[0].ToLower();
    
                /*
                 * If we are in the loop where we have found the tag,
                 * we haven't changed level and this is the end of a scalar it must
                 * mean that the tag was a scalar so we can safely leave now.
                 */
                if ((FoundTag) && (FoundTagLevel == NestCounter) && (ThisField == "</scalar>")) {
                    break;
                    // return ReturnValue;
                }
                //  If we end or leave a vector we change the NestCounter
                if (ThisField.IndexOf("<vector") >= 0) {
                    NestCounter++;
                }
                else if (ThisField.IndexOf("</vector>") >= 0) {
                    NestCounter--;
                }
            }
    
            //  If we have found our tag and the nest counte goes below the level 
            //  we where looking at - it's time to leave
    
            if (FoundTag) {
                if (NestCounter <= FoundTagLevel) {
                    break;
                    //return    ReturnValue;
                }
            }
    
            if (AddLineToResult) {
                ReturnValue +=  ThisLine;
            }
    
        }
    
        //  You may wanna do some url decoding here....
    
        return ReturnValue;
    }
    
    0 讨论(0)
提交回复
热议问题