Get redirection URL from UnityWebRequest

I am issuing a UnityWebRequest with a URL that has redirection involved. The redirection is being logged as a warning by the compiler. Is there any way of accessing that redirection URL?

I’ve tried the method of using ResponseHeaders[“Location”]. That does not work as the Response Headers don’t have such a field.

Can someone help me log that warning URL? I don’t want to write a dump of the console into a file and parse it from there. Is there a cleaner approach?

TIA

Unfortunately no. The WWW class as well as the UnityWebRequest class follow a redirection response transparently. So they don’t provide any information about that redirect. The UnityWebRequest has a redirectLimit, however it doesn’t provide informations of the redirection but only generates an error if the redirection amount exceeds that limit.

I never was in need of getting the redirection URL manually. However if the URL is dumped as warning you could intercept it by using a log-callback.

A cleaner approach would be to use a more low-level API. The .NET / Mono WebRequest class does allow to disable the auto-redirect. That way you get the actual response and you have to follow the new URL manually.

I also tried with UnityWebRequest and WWW to find the Location header in the responses, and I didn’t find a proper way to do so.

So, I ended using directly TcpClient, and it kinda works. Remark: it looks like my implementation is not very efficient/stable, as you’ll notice that the request take an usual time and sometimes fail. I’ll investigate it later.

If you simply want to reach an http server, with a GET request, it is pretty straighforward.

If you want to POST a form, you have to build the request body.

And if you want to use an https connection, it becomes a bit tricky :

  1. you have to use an SslStream
  2. as certificates don’t seem to be properly loaded, you have to handle their manipulation manually
  3. I still haven’t found how to do it securely, so for the moment, I completely disable the certificate check. Please keep in mind that it is not safe at all.

So, first, for the simple case (an http GET request, whose we want to find the redirection) :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net.Sockets;
using System.IO;

public class TCPTest : MonoBehaviour {
	public string redirection = null;
	public string result = null;
	public bool connected = false;

	// Use this for initialization
	void Start () {
		fetchGetHttpRedirection ();
	}
	
	// Update is called once per frame
	void Update () {
		if (!this.connected) {
			if (this.redirection != null) {
				Debug.Log ("Redirection:

" + this.redirection);
this.redirection = null;
} else if(this.result != null) {
Debug.Log ("Answer:
" + this.result);
this.result = null;
}
}
}

	void fetchGetHttpRedirection(){
		StartCoroutine (GetTCPClientRedirect ("example.com","/"));

	}

	IEnumerator GetTCPClientRedirect(string host, string query){		
		TcpClient client = new TcpClient(host, 80);
		NetworkStream networkStream = client.GetStream();
		StreamReader reader = new StreamReader(networkStream);
		StreamWriter writer = new  StreamWriter(networkStream);
		// Basic GET request: 
		// GET /foo.html HTTP/1.1

Host: example.com

		writer.WriteLine("GET "+ query + " HTTP/1.1");
		writer.WriteLine("Host: " + host);
		writer.WriteLine("");
		writer.Flush ();
		this.redirection = null; // variable where we'll store the redirection
		string lastLine = null;
		this.result = "";
		this.connected = true;
		while (true) {
			if (!client.Connected) {
				break;
			}

			if (networkStream.CanRead) {
				string line = reader.ReadLine ();
				if (line != null) {
					string redirectionHeader = "Location: ";
					if(line.StartsWith(redirectionHeader)){
						this.redirection = line.Substring (redirectionHeader.Length, line.Length - redirectionHeader.Length);
					}
					if (this.result != "") {
						this.result = this. result + "

";
}

					if (lastLine == "" && line == "0") {
						break;
					} else {
						lastLine = line;
						this.result = this.result + line;
					}
				} else {
					break;
				}
			}
			// To avoid blocking the thread
			yield return null;
		}
		// Disconnection
		networkStream.Close();
		client.Close();
		this.connected = false;
		yield return null;
	}

}

Then, an example with both a POST and the (unsecure) https handling :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Net.Sockets;
using System.IO;
using UnityEngine.Networking;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Security.Authentication;

public class TCPPostSSLTest:MonoBehaviour
{
	SampleAuthentificationRequest request = null;

	void Start () {
		string host = "example.com";
		string authorizationQuery = "/foo/login.html";
		request = new SampleAuthentificationRequest (host, authorizationQuery);
		StartCoroutine(request.Launch ("<login>", "<password>"));
	}

	// Update is called once per frame
	void Update () {
		if (!this.request.connected) {
			if (this.request.redirection != null) {
				Debug.Log ("Redirection:

" + this.request.redirection);
this.request.redirection = null;
} else if(this.request.result != null) {
Debug.Log ("Answer:
" + this.request.result);
this.request.result = null;
}
}
}

	public class SampleAuthentificationRequest
	{
		public string result;
		public string redirection = null;
		public bool connected = false;
		string host;
		string authorizationQuery;
		public SampleAuthentificationRequest(string host, string authorizationQuery){
			this.host = host;
			this.authorizationQuery = authorizationQuery;
			
		}

		public static bool CertificateValidationCallback(
			object sender,
			X509Certificate certificate,
			X509Chain chain,
			SslPolicyErrors sslPolicyErrors)
		{
			Debug.Log ("[Warning] Using unsafe certification: always accepting server certificats !");
			return true;
		}

		public IEnumerator Launch(string username, string password) {
			string body = "username=" +  WWW.EscapeURL(username) + "&password=" +  WWW.EscapeURL(password) + "&login=1";
			int contentLength = System.Text.Encoding.UTF8.GetBytes(body).Length;

			TcpClient client = new TcpClient(this.host, 443);
			NetworkStream networkStream = client.GetStream();
			SslStream sslStream = new SslStream(networkStream
				,false
				,new RemoteCertificateValidationCallback(CertificateValidationCallback)
			);
			// Debug.Log("Authenticating...");
			sslStream.AuthenticateAsClient (host);
			// Debug.Log("Authent done");
			while (!sslStream.IsAuthenticated) {
				// Debug.Log("Not yet authenticated...");
				yield return null;
			}
			this.connected = true;
			StreamReader reader = new StreamReader(sslStream);
			StreamWriter writer = new  StreamWriter(sslStream);

			string requestData = "POST " + this.authorizationQuery + " HTTP/1.1

";
requestData = requestData + "Host: " + this.host + "
";
requestData = requestData + "User-Agent: UniNoco
";
requestData = requestData + "Accept: /
";
requestData = requestData + "Content-Length: " + contentLength + "
";
requestData = requestData + "Content-Type: application/x-www-form-urlencoded
";
requestData = requestData + "
";
requestData = requestData + body + "
";
requestData = requestData + "
";
requestData = requestData + "
";
writer.Write (requestData);
writer.Flush ();
// Debug.Log (“Request:”);
Debug.Log(requestData);
result = “”;
string lastLine = null;
while (true) {
if (!client.Connected) {
// Debug.Log (“Disconnected”);
break;
}

				if (sslStream.CanRead) {
					string line = reader.ReadLine ();
					if (line != null) {
						string redirectionHeader = "Location: ";
						if(line.StartsWith(redirectionHeader)){
							this.redirection = line.Substring (redirectionHeader.Length, line.Length - redirectionHeader.Length);
						}
						if (result != "") {
							result = result + "

";
}

						if (lastLine == "" && line == "0") {
							break;
						}
						lastLine = line;

						// Debug.Log ("Received: >" + line + "<");
						result = result + line;
					} else {
						break;
					}
				}
				// Debug.Log ("No data ...");
				yield return null;

			}

			// Debug.Log("Closing...");
			sslStream.Close();
			client.Close();
			// Debug.Log("Closed.");
			this.connected = false;
			yield return null;
		}
	}
}