- Home /
UnityWebRequest.Put fails on iOS
I am trying to upload a file to a vanilla Apache Webserver (v2.4.18) using UnityWebRequest and its Put(uri, bodyData)-Method. The server uses HTTPS and an authentication via username and password. Here's the code I use:
string url = "https://" + username + ":" + password + "@" + serverUrl + "/" + filename;
byte[] myData = Encoding.UTF8.GetBytes(csvString);
UnityWebRequest www = UnityWebRequest.Put(url, myData);
yield return www.Send();
// and some debugging errors etc. after that
In the editor and on Android this works just fine and the file is uploaded correctly, but on iOS it just doesn't work. What's weird is that both in the editor and on iOS the yield seems to never finish, so I don't get to the debugging code on either platform. When i put the debugging into the Update-Method www.isError is false, but the response stays empty.
A colleague reported that he got NSURLConnection error code -1002, which is a NSURLErrorUnsupportedURL error. Because of this I tried to percent escape the password and filename, because they include characters like : and =, but to no avail. I also tried setting NSAppTransportSecurity -> NSAllowsArbitraryLoads to true in the Info.plist, but this doesn't help either.
Do I need to configure the app or the server somehow? Why does this work in the editor but not on iOS?
EDIT: The file upload also works on Android, so it's most certainly an iOS exclusive issue.
Answer by Bunny83 · Oct 15, 2017 at 01:38 PM
URL encoding of basic authentication information has been deprecated a long time ago for security reasons. You have to use the proper basic authentitation header. Every browser which still supports the URL encoding of authentication information actually strips that information from the URL and creates an authentication header with the information.
The WWW class documentation page actually has now an example how to create the basic authentication header
You can use a helper class like this:
using UnityEngine;
using UnityEngine.Networking;
public static class WWWHeaders
{
public static string CreateAuthorization(string aUserName, string aPassword)
{
return "Basic " + System.Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(aUserName + ":" + aPassword));
}
public static Hashtable AddAuthorizationHeader(this Hashtable aHeaders, string aUserName, string aPassword)
{
aHeaders.Add("Authorization", CreateAuthorization(aUserName, aPassword));
return aHeaders;
}
public static UnityWebRequest AddAuthorizationHeader(this UnityWebRequest aRequest, string aUserName, string aPassword)
{
aRequest.SetRequestHeader("Authorization", CreateAuthorization(aUserName, aPassword));
return aRequest;
}
}
With this extension class you can simply do:
string url = "https://" + serverUrl + "/" + filename;
byte[] myData = Encoding.UTF8.GetBytes(csvString);
UnityWebRequest www = UnityWebRequest.Put(url, myData);
www.AddAuthorizationHeader(username, password);
yield return www.Send();
Still not working on iOS using this code, but it does work in the editor as well as on Android and is certainly a step up from the code I was using previously. Still, the problem remains and seems iOS specific.
I'm not really an iOS developer. However if you still get an "NSURLErrorUnsupportedURL" it's clearly should be something wrong with your URL.
Again HTTP(S) does generally not support authentication information before the domain part. If a certain client does support it, it usually transforms it into an authorization header.
According to Apple this error is "usually" returned when the URL uses an unsupported scheme. However it would be strange if it doesn't support HTTPS. I actually read the opposite that iOS more ore less dropped the HTTP support and enforces HTTPS now.
I got it to work. The reason it did not work before was also the reason for the endless yield of the www.Send(); I made a mistake in the callchain of the coroutine. Not exactly sure what was wrong, because I was just calling the coroutine through another coroutine which was started but did not yield the result, but hey, it works now. Thanks a lot for your answer and the help!
Answer by Smith-Hou · Dec 29, 2017 at 10:45 AM
Hello, I also met, your similar problem, code: -1002, using UnityWebRequest, I don't quite understand what you mean "the reason for the endless yield of the www.Send();"
You can paste it, you solve the code,
I did not really understand what was wrong either, but changing the callchain of the coroutine solved the issue for me. I will paste the final code I used and hope it helps (it is pretty much the same as in the accepted answer):
The call happens inside an IEnumerator Start() method:
yield return StartCoroutine(Web$$anonymous$$anager.Instance.PutRequestCoroutineHeaders());
And this is the method:
public IEnumerator PutRequestCoroutineHeaders()
{
string csv = "someString";
string filename = "someFilename.csv";
string url = "theServerUrl" + filename;
byte[] myData = Encoding.UTF8.GetBytes(csv);
UnityWebRequest www = UnityWebRequest.Put(url, myData);
www.AddAuthorizationHeader("username", "password");
yield return www.Send();
Debug.Log("url " + url + " sent successfully");
}
Thank you very much for your answer, yield return StartCoroutine (Web$$anonymous$$anager. Instance. PutRequestCoroutineHeaders ()), which is an IEnumerator return value, call this method, also want to use StartCoroutine (), is used in this way, you look at my code.I don't know where there's a mistake. This is updated, listen to the protocol request to send, open the co-program, and after you send it, remove the list of URL。
public void Update()
{
if ((m_requestList.Count > 0) /* && this.intervalSwitch*/)
{
this.SendRequest(m_requestList[0]); //request
m_requestList.RemoveAt(0);
}
}
This code is the processing after I start the request and send. Please check it for me. Thank you
private void SendRequest(string url)
{
StartCoroutine(SendFormUrl(url, this.formDic));
ClearFormDict();
}
private IEnumerator SendFormUrl(string url, Dictionary<string, List<string>> dic, HFBaseHttpRequest request =null)
{
url = Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(url));
Debug.Log("send the url:" + url);
UnityEngine.Networking.UnityWebRequest WebRequest = UnityEngine.Networking.UnityWebRequest.Get(url);
WebRequest.SetRequestHeader("kl-go-token", token);
WebRequest.SetRequestHeader("kl-go-device", device);
WebRequest.SetRequestHeader("kl-go-version", version);
yield return WebRequest.Send();
Debug.Log( " ==result==code==== :WebRequest.Send()==" + WebRequest.responseCode);
}
”AddAuthorizationHeader“ Does this method have to be used?
The URL is fine, I'm printing the URL in xcode, it's normal to copy it to the browser,
I'm really sorry, but I'm not an expert at this stuff, but I'll try to help. The "AddAuthorizationHeader" is just for authorization stuff and I believe you don't need that, do you? (username, password). Does the Debug.Log after the Send() get called? If so, the request is sent successfully and you don't have the problem I had with the endless yield. Do you need the request headers? Can you try sending the request without the headers and see what comes out? A simple GET-Request should probably work without any headers, but again, I'm no expert. For simple GET-Requests I use the following, maybe it works for you...
public IEnumerator DownloadAndThenApply(string url, System.Action<WWW> action, System.Action<WWW> actionError)
{
var w = new WWW(url);
var t = Time.time;
yield return w;
//Debug.Log("Download Time of: " + url + " was : " + (Time.time - t));
if (string.IsNullOrEmpty(w.error))
{
action(w);
}
else
{
Debug.LogError("Download Error: " + w.error);
actionError(w);
}
w.Dispose();
}
Your answer
Follow this Question
Related Questions
UnityWebRequest.error return "cannot connect to destination host" on iOS13.5.1 0 Answers
WebRequest Post empty on iOS 1 Answer
Why do iOS web requests stop working after a few hours? 0 Answers
About Implemented host name resolution with IPv6 in Unity 4.6.9 release-notes. 0 Answers
The name 'Joystick' does not denote a valid type ('not found') 2 Answers