Search Unity

Problem downloading an XML file in webgl

Discussion in 'Multiplayer' started by Jamsa78, Jun 18, 2020.

  1. Jamsa78

    Jamsa78

    Joined:
    Mar 29, 2017
    Posts:
    96
    Hi

    I hope I'm posting this in the right place...if not, please let me know. :)

    The overview:
    I'm making an editor that will run online and loads xml files from the same server.
    Each instance needs it's own XML file (like a save game).

    The details:
    I'm using an editor framework, and within that I need to load the XML file and show the content to the user.
    The framework doesn't have built in support for loading file automatically, nor from a server.
    So I found this script to download the file:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Networking;
    5.  
    6. public class SaveLoadOnline : MonoBehaviour
    7. {
    8.     public string LoadURL = "";
    9.     public string XMLDoc;
    10.  
    11.     private float progress;
    12.  
    13.     // Start is called before the first frame update
    14.     public void BeginLoad()
    15.     {
    16.         Debug.Log("SaveLoadOnline starting");
    17.         Debug.Log("LoadURL: " + LoadURL);
    18.         StartCoroutine(WWWRutine());
    19.     }
    20.  
    21.     private IEnumerator WWWRutine()
    22.     {
    23.         UnityWebRequest www = UnityWebRequest.Get(LoadURL);
    24.         var asyncOperation = www.SendWebRequest();
    25.      
    26.  
    27.         while (!www.isDone)
    28.         {
    29.             progress = asyncOperation.progress;
    30.             yield return null;
    31.         }
    32.  
    33.         progress = 1f;
    34.  
    35.         if (!string.IsNullOrEmpty(www.error))
    36.         {
    37.             Debug.Log(www.error);
    38.         }
    39.  
    40.         XMLDoc = www.downloadHandler.text;
    41.  
    42.         yield break;
    43.     }
    44.  
    45.     // Update is called once per frame
    46.     void Update()
    47.     {
    48.        
    49.     }

    And I start it using this code (in another class):
    Code (CSharp):
    1. var parameters = URLParameters.GetSearchParameters();
    2.             string MXID;
    3.             if (parameters.TryGetValue("MXID", out MXID))
    4.             {
    5.                 // use "MXID" here
    6.                 if (MXID.Length > 0)
    7.                 {
    8.                     Debug.Log("URLParameter: " + MXID);
    9.                     LoadFile = new SaveLoadOnline();
    10.                     LoadFile.LoadURL = "http://jamsatest.dk/GC3/user/getXML.php?MXID=" + MXID;
    11.                     LoadFile.BeginLoad();
    12.                     Debug.Log("After BeginLoad");
    13.                     var XMLDoc = LoadFile.XMLDoc;
    14.                     Debug.Log("XMLDoc: " + XMLDoc.ToString());
    15.                 }
    16.                 else
    17.                 {
    18.                     Debug.Log("Empty URLParameter!");
    19.                 }
    20.             }
    21.             else
    22.             {
    23.                 // no parameter with name "MXID" found
    24.                 Debug.Log("NO URLParameter!");
    25.             }
    But just after the "LoadURL" debug message, I get this error:
    NullReferenceException: Object reference not set to an instance of an object.

    I've tried googling it, but I can't seem to find a solution. :(

    But since I'm just a casual Unity-dev, I'm hoping you guys know exactly what I need to do! :D
     
  2. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,732
    The UnityWebRequest code looks ok-ish, can't spot a reason for it to fail, except maybe url being null.
    The only issue I do see in it is check for error: UWR does have properties for checking for errors, that are better then the error message that you check now. And you don't seem to have separate code path for success/error scenarios.
     
  3. Jamsa78

    Jamsa78

    Joined:
    Mar 29, 2017
    Posts:
    96
    Thanks for your reply. :)

    The url isn't empty, and the file (getXML.php) does exist. And the xml file is in the output.

    Hmm, could you point me to an example on how to do it right? :)
     
  4. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,732
    Just look at the code examples in UnityWebRequest API documentation.
    As for your real issue, I don't know where it happens, but it doesn't look to be in your code that uses UnityWebRequest. The second piece of code might be the reason, but that's too custom for your app for me to tell.
     
  5. Jamsa78

    Jamsa78

    Joined:
    Mar 29, 2017
    Posts:
    96
    Thanks, I'll have a look and see if it helps me. :)

    "too custom"? Lol, is that a polite way to say "ur code sucks"? :D
     
  6. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,732
    No it means "too specific for your app for me to tell anything about it".
     
  7. Jamsa78

    Jamsa78

    Joined:
    Mar 29, 2017
    Posts:
    96
    Fair enough. :)

    I guess this isn't SO. :D
     
  8. Jamsa78

    Jamsa78

    Joined:
    Mar 29, 2017
    Posts:
    96
    So, I've changed the code to this:
    Code (CSharp):
    1. public void BeginLoad()
    2.     {
    3.         Debug.Log("SaveLoadOnline starting");
    4.         Debug.Log("LoadURL: " + LoadURL);
    5.         StartCoroutine(WWWRutine(LoadURL));
    6.     }
    7.  
    8.     private IEnumerator WWWRutine(string LoadURL)
    9.     {
    10.         Debug.Log("WWWRutine starting");
    11.         UnityWebRequest webRequest = UnityWebRequest.Get(LoadURL);
    12.         Debug.Log("After Get");
    13.  
    14.         // Request and wait for the desired page.
    15.         yield return webRequest.SendWebRequest();
    16.         Debug.Log("After SendWebRequest");
    17.  
    18.         string[] pages = LoadURL.Split('/');
    19.         int page = pages.Length - 1;
    20.  
    21.         if (webRequest.isNetworkError)
    22.         {
    23.             Debug.Log(pages[page] + ": Error: " + webRequest.error);
    24.         }
    25.         else
    26.         {
    27.             Debug.Log(pages[page] + ":\nReceived: " + webRequest.downloadHandler.text);
    28.         }
    29.     }
    But I still get the same error, the same place. :(

    This entry doesn't even get shown: Debug.Log("WWWRutine starting");

    Debug.Log("LoadURL: " + LoadURL); output this:
    LoadURL: http://jamsatest.dk/GC3/user/getXML.php?MXID=9

    And if you go to the page with a browser, you get the xml file. So what's wrong?? o_O
     
  9. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,732
    Sounds like you have a NullReferenceException elsewhere in your script, that is thrown out of Unity method and disables the monobehaviour, so the coroutine never executes.
     
  10. Jamsa78

    Jamsa78

    Joined:
    Mar 29, 2017
    Posts:
    96
    Good suggestion! :)

    But, no dice. :(

    The error only goes away when I disable the coroutine. And even with the error, everything else works just fine.

    Soo, I move the code out, and tadaa! it works! Well, sort of work. :D

    First it complained about CORS, so I added these headers to my .php file:
    Code (CSharp):
    1. header('Access-Control-Allow-Credentials: true');
    2. header('Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS');
    3. header('Access-Control-Allow-Headers: Accept, Content-Type, X-Access-Token, X-Application-Name, X-Request-Sent-Time');
    4. header('Access-Control-Allow-Origin: *');
    The complete file now looks like this:
    Code (CSharp):
    1. <?php
    2. header('Access-Control-Allow-Credentials: true');
    3. header('Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS');
    4. header('Access-Control-Allow-Headers: Accept, Content-Type, X-Access-Token, X-Application-Name, X-Request-Sent-Time');
    5. header('Access-Control-Allow-Origin: *');
    6. header('Content-type: text/xml');
    7. header('Content-Disposition: attachment; filename="text9.xml"');
    8.  
    9. echo $xml_contents;
    10. exit();
    11. ?>
    But now it complains about ETag?? How do I get that to work?
     
  11. Jamsa78

    Jamsa78

    Joined:
    Mar 29, 2017
    Posts:
    96
    Any chance of a reply today? :)
     
  12. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,732
    You haven't explained what your latest issue is.
     
  13. Jamsa78

    Jamsa78

    Joined:
    Mar 29, 2017
    Posts:
    96
    Umm, the line last contains the problem:
    "But now it complains about ETag?? How do I get that to work?" :)
     
  14. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,732
    That's not the actual problem. What is the issue exactly?
     
  15. Jamsa78

    Jamsa78

    Joined:
    Mar 29, 2017
    Posts:
    96
    Well, ok. You're the expert - I just assumed that WAS the problem.. :D

    (Btw. any changes to .htaccess on the server doesn't change that error)

    The REAL problem is that I still don't get the xml file.
    Even though the url works fine when I try it in the browser.

    My current code:
    Code (CSharp):
    1. Debug.Log("SaveLoadOnline starting");
    2.         Debug.Log("LoadURL: " + LoadURL);
    3. UnityWebRequest request = new UnityWebRequest();
    4.         Debug.Log("After new");
    5.  
    6.         request = UnityWebRequest.Get(LoadURL);
    7.         Debug.Log("After Get");
    8.  
    9.         // yield return request.SendWebRequest();
    10.         request.SendWebRequest();
    11.         Debug.Log("After SendWebRequest");
    12.  
    13.         if (request.isNetworkError || request.isHttpError)
    14.         {
    15.             Debug.Log("Error " + request.error);
    16.         }
    17.         else
    18.         {
    19.             // Show results as text
    20.             Debug.Log("Success " + request.downloadHandler.text);
    21.             XMLDoc = request.downloadHandler.text;
    22.         }
    23.         Debug.Log("After Success");
    24.         return XMLDoc;
    All correct debug logs are shown, only request.downloadHandler.text is empty <- do I need to use something else here to get the file??
     
  16. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,732
    You don't need to create new UnityWebRequest, UnityWebRequest.Get does this for you, so your first one gets discarded.
    The code looks correct, so it is some setup issue. Since you mentioned ETag, one possible reason is that you successfully downloaded empty file in the past and it is cached by the browser and hasn't expired yet.
    I recommend to you use network monitoring tools to examine raw request & response. Firefox does have such tool in it's Web Developer tools, don't know about others.
     
  17. Jamsa78

    Jamsa78

    Joined:
    Mar 29, 2017
    Posts:
    96
    Ah, of cause. Old habits die hard. :p

    I've just checked with chrome and the browser does see the file and the content.

    The response headers look like this (if that's of any help?):
    Code (CSharp):
    1. Access-Control-Allow-Credentials: true
    2. Access-Control-Allow-Headers: Accept, Content-Type, X-Access-Token, X-Application-Name, X-Request-Sent-Time
    3. Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS
    4. Access-Control-Allow-Origin: *
    5. Cache-Control: max-age=420, private, must-revalidate
    6. Connection: Keep-Alive
    7. Content-Disposition: attachment; filename="text9.xml"
    8. Content-Type: text/html; charset=utf-8
    9. Date: Mon, 29 Jun 2020 10:38:24 GMT
    10. Keep-Alive: timeout=5, max=100
    11. Server: Apache
    12. Transfer-Encoding: chunked
    13. X-Powered-By: PHP/5.5.38
     
  18. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,732
    Try checking other properties on UnityWebRequest, such as downloadedBytes or accessing .data instead of .text on a download handler.
     
  19. Jamsa78

    Jamsa78

    Joined:
    Mar 29, 2017
    Posts:
    96
    Ok, so I tried this:
    Debug.Log("Success " + request.downloadedBytes);
    XMLDoc = request.downloadHandler.data.LongLength.ToString();
    Debug.Log("XMLDoc: " + XMLDoc);

    And got a big fat 0 on both.:(
     
  20. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,732
    What is the responseCode then?
     
  21. Jamsa78

    Jamsa78

    Joined:
    Mar 29, 2017
    Posts:
    96
    Code (CSharp):
    1. Request URL: http://jamsatest.dk/GC3/user/getXML.php?MXID=1
    2. Request Method: GET
    3. Status Code: 200 OK
    4. Remote Address: 185.21.40.94:80
    5. Referrer Policy: no-referrer-when-downgrade
     
  22. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,732
    I mean responseCode reported by UnityWebRequest.
     
  23. Jamsa78

    Jamsa78

    Joined:
    Mar 29, 2017
    Posts:
    96
    Ah, sneaky. :p

    Huh, wierd. This says 0:
    Debug.Log("responseCode " + request.responseCode);
     
  24. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,732
    Ha, just looked into your code: you have commented out the yield return, so you are sending request and not waiting for it to complete, hence you don't have anything :)
    You need to either yield return from coroutine or wait until isDone property becomes true. Note that you can't spin a loop waiting for that in WinGL, you have to check that property in Update.
     
  25. Jamsa78

    Jamsa78

    Joined:
    Mar 29, 2017
    Posts:
    96
    Arg. Then we're back at beginning.:(:(

    I tried putting this code in the Update function, but it doesn't fire.
    Code (CSharp):
    1. Debug.Log("responseCode " + request.responseCode.ToString());
    2.             Debug.Log("Success " + request.downloadedBytes);
    3.             XMLDoc = request.downloadHandler.data.LongLength.ToString();
    4.             Debug.Log("After Success");
    Sorry for being such a n00b, but how do I make Update() fire? Do I need Start()? If so, how do I get that to take a parameter(MXID) as input?
     
  26. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,732
    Coroutine is the simples way to use UnityWebRequest.
    And if using Update you have to check the isDone property on UnityWebRequest. Only query data when it reports true.
     
  27. Jamsa78

    Jamsa78

    Joined:
    Mar 29, 2017
    Posts:
    96
    ..and as I said, those conflicts with the rest of my code. :(


    I may be n00bish, but not THAT n00bish. :p

    Code (CSharp):
    1.  
    2. void Update()
    3.     {
    4.         if (request.isDone)
    5.         {
    6.             Debug.Log("responseCode " + request.responseCode.ToString());
    7.             Debug.Log("Success " + request.downloadedBytes);
    8.             XMLDoc = request.downloadHandler.data.LongLength.ToString();
    9.             Debug.Log("After Success");
    10.         }
    11.     }
     
  28. Jamsa78

    Jamsa78

    Joined:
    Mar 29, 2017
    Posts:
    96
    Ok, I've talked with the guy who has made the framework I'm using, and he says that it's not a problem with his framework.

    So, I REALLY need your help. :D

    My current code in the main thread:
    Code (CSharp):
    1. private void Start ()
    2.         {
    3.             string strMXID;
    4.             strMXID = "1";
    5.             Debug.Log("Before new SaveLoadOnline");
    6.             LoadFile = new SaveLoadOnline();
    7.             Debug.Log("after new SaveLoadOnline");
    8.             LoadFile.LoadURL = "http://jamsatest.dk/GC3/user/getXML.php?MXID=" + strMXID;
    9.             Debug.Log("after LoadURL");
    10.             LoadFile.BeginLoad();
    11.             Debug.Log("after BeginLoad");
    12.             string XMLDoc = LoadFile.XMLDoc;
    13.             Debug.Log("XMLDoc: " + XMLDoc);
    14.             NormalReInit();
    15.         }
    My save/load script:
    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEngine.Networking;
    6. using System;
    7. using MySql.Data.MySqlClient;
    8. using System.Data;
    9.  
    10. public class SaveLoadOnline : MonoBehaviour
    11. {
    12.     public string LoadURL = "";
    13.     public string XMLDoc;
    14.  
    15.     private float progress;
    16.     UnityWebRequest request;
    17.  
    18. public void BeginLoad()
    19.     {
    20.         Debug.Log("SaveLoadOnline starting");
    21.         Debug.Log("LoadURL: " + LoadURL);
    22.         StartCoroutine(WWWRutine(LoadURL));
    23.         Debug.Log("after StartCoroutine");
    24.        }
    25. public IEnumerator WWWRutine(string LoadURL)
    26.     {
    27.         Debug.Log("WWWRutine starting");
    28.         request = UnityWebRequest.Get(LoadURL);
    29.         Debug.Log("After Get");
    30.         yield return request.SendWebRequest();
    31.         Debug.Log("After SendWebRequest");
    32.         if (request.isNetworkError || request.isHttpError)
    33.         {
    34.             Debug.Log("Error " + request.error);
    35.         }
    36.         else
    37.         {
    38.             // Show results as text
    39.             Debug.Log("responseCode " + request.responseCode.ToString());
    40.             Debug.Log("Success " + request.downloadedBytes);
    41.             XMLDoc = request.downloadHandler.data.LongLength.ToString();
    42.         }
    43.         Debug.Log("After Success");
    44.     }
    45. }
    And the output I get:
    unity errors.png

    As you can see, the error stops execution just when it hits the StartCoroutine(WWWRutine(LoadURL)); line.
     
  29. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,732
    I believe this is your issue. You cannot create MonoBehaviour objects like that, you have to create them via AddComponent() on GameObject.
     
  30. Jamsa78

    Jamsa78

    Joined:
    Mar 29, 2017
    Posts:
    96
    Aha.

    So if I do this, it should work?

    SaveLoadOnline LoadFile = gameObject.AddComponent<SaveLoadOnline>() as SaveLoadOnline;
     
  31. Jamsa78

    Jamsa78

    Joined:
    Mar 29, 2017
    Posts:
    96
    Awesome! It worked! :D

    So now I just need to pass the file back to the main script/make the main script wait until the file is ready.

    I tried with this, but it doesn't stop or make it wait:

    Main script
    Code (CSharp):
    1. private void Start ()
    2.         {
    3.             string strMXID;
    4.             strMXID = "1";
    5.             Debug.Log("Before new SaveLoadOnline");
    6.             //LoadFile = new SaveLoadOnline();
    7.             SaveLoadOnline LoadFile = gameObject.AddComponent<SaveLoadOnline>() as SaveLoadOnline;
    8.             Debug.Log("after new SaveLoadOnline");
    9.             LoadFile.LoadURL = "http://jamsatest.dk/GC3/user/getXML.php?MXID=" + strMXID;
    10.             Debug.Log("after LoadURL");
    11.             LoadFile.BeginLoad();
    12.             Debug.Log("after BeginLoad");
    13.             NormalReInit();
    14.             Debug.Log("after NormalReInit");
    15.             byte[] XMLDoc = LoadFile.XMLDoc;
    16.             Debug.Log("RT XMLDoc: " + XMLDoc.ToString());
    17.             LoadFile.StopLoad();
    18.         }
    My save/load script:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Networking;
    5. using System;
    6. using MySql.Data.MySqlClient;
    7. using System.Data;
    8.  
    9. public class SaveLoadOnline : MonoBehaviour
    10. {
    11.     public string LoadURL = "";
    12.     public string XMLDocLength;
    13.     public byte[] XMLDoc;
    14.     public bool Stop;
    15.  
    16.     private float progress;
    17.     UnityWebRequest request;
    18.    
    19.  
    20.     public void BeginLoad()
    21.     {
    22.         Stop = false;
    23.         Debug.Log("SaveLoadOnline starting");
    24.         Debug.Log("LoadURL: " + LoadURL);
    25.         StartCoroutine(WWWRutine(LoadURL));
    26.         Debug.Log("after StartCoroutine");
    27.     }
    28.  
    29.     public void StopLoad()
    30.     {
    31.         Stop = true;
    32.     }
    33.  
    34.     public IEnumerator WWWRutine(string LoadURL)
    35.     {
    36.         Debug.Log("WWWRutine starting");
    37.         request = UnityWebRequest.Get(LoadURL);
    38.         Debug.Log("After Get");
    39.  
    40.         yield return request.SendWebRequest();
    41.  
    42.         Debug.Log("After SendWebRequest");
    43.  
    44.         if (request.isNetworkError || request.isHttpError)
    45.         {
    46.             Debug.Log("Error " + request.error);
    47.         }
    48.         else
    49.         {
    50.             // Show results as text
    51.             Debug.Log("responseCode " + request.responseCode.ToString());
    52.             Debug.Log("Success " + request.downloadedBytes);
    53.             XMLDocLength = request.downloadHandler.data.LongLength.ToString();
    54.         }
    55.         Debug.Log("After Success");
    56.  
    57.         if (Stop == true)
    58.         {
    59.             yield break;
    60.         }
    61.     }
    62.    
    63.     // Update is called once per frame
    64.     void Update()
    65.     {
    66.         if (request.isDone)
    67.         {
    68.             Debug.Log("responseCode " + request.responseCode.ToString());
    69.             Debug.Log("Success " + request.downloadedBytes);
    70.             XMLDoc = request.downloadHandler.data;
    71.             Debug.Log("After Success");
    72.         }
    73.     }
    74. }