I have a basic class that makes GET and POST requests using HttpWebRequest
/HttpWebResponse
.
I use my class to login to an API and then request data. In a Windows 8 "Metro" application, it works exactly as expected. On a Windows Phone 8 application, the login appears to succeed, but in the subsequent request for data, no cookies are sent and the server responds as if the client is not logged in.
Here is the class, this exact same code is used in the Windows 8 app and the Windows Phone app:
class Class1
{
CookieContainer cookieJar = new CookieContainer();
CookieCollection responseCookies = new CookieCollection();
public async Task<string> httpRequest(HttpWebRequest request)
{
string received;
using (var response = (HttpWebResponse)(await Task<WebResponse>.Factory.FromAsync(request.BeginGetResponse, request.EndGetResponse, null)))
{
using (var responseStream = response.GetResponseStream())
{
using (var sr = new StreamReader(responseStream))
{
cookieJar = request.CookieContainer;
responseCookies = response.Cookies;
received = await sr.ReadToEndAsync();
}
}
}
return received;
}
public async Task<string> get(string path)
{
var request = WebRequest.Create(new Uri(path)) as HttpWebRequest;
request.CookieContainer = cookieJar;
return await httpRequest(request);
}
public async Task<string> post(string path, string postdata)
{
var request = WebRequest.Create(new Uri(path)) as HttpWebRequest;
request.Method = "POST";
request.CookieContainer = cookieJar;
byte[] data = Encoding.UTF8.GetBytes(postdata);
using (var requestStream = await Task<Stream>.Factory.FromAsync(request.BeginGetRequestStream, request.EndGetRequestStream, null))
{
await requestStream.WriteAsync(data, 0, data.Length);
}
return await httpRequest(request);
}
}
And the code to initiate the requests:
var n = new Class1();
await n.post("https://mydomain.com/api/login/", "username=myusername&password=mypassword");
await n.get("https://mydomain.com/reader/feeds/");
The curious thing is that if I prefix the domain name with "www." it works in both the Windows Phone 8 app and the Windows 8 Metro app.
I think it has something to do with how the domain is handled. The cookie's domain is ".mydomain.com", and without the prefix it must think the cookie's do not belong to that domain. After some searching I found a report of someone noticing a similar problem.
What I do not understand is why this is treated differently in the Windows 8 app than the Windows Phone app, so that line-for-line identical code works on one platform but fails on another.
I have done some more digging into this.
The server code I used for this is in PHP:
<?php
if ($_REQUEST["what"] == "set")
{
setcookie("TestCookie",$_REQUEST["username"] . " " . $_REQUEST["password"], time()+3600*24, "/", "subd.mydomain.com");
}
if ($_GET["what"] == "get")
{
var_dump($_COOKIE);
Client code in C#:
var n = new ClassLibrary1.Class1();
await n.get("http://subd.mydomain.com/?what=set&username=foo&password=bar");
await n.get("http://subd.mydomain.com/?what=get");
Here is an example of a cookie response from the server
Set-Cookie: ARRAffinity=295612ca; Path=/;Domain=subd.mydomain.com
Set-Cookie: TestCookie=foo+bar; expires=Fri, 04-Jan-2013 17:19:25 GMT; path=/; domain=subd.mydomain.com
In the Windows 8 Store/Metro app, this is the result:
array(2) {
["ARRAffinity"]=>
string(8) "295612ca"
["TestCookie"]=>
string(7) "foo bar"
}
In the Windows Phone app, this is the result:
array(0){
}
The Windows Phone does not see any cookies when they are set this way.
I change how TestCookie
is set, the result from the server now looks like this:
Set-Cookie: ARRAffinity=295612ca;Path=/;Domain=subd.mydomain.com
Set-Cookie: TestCookie=foo+bar; expires=Fri, 04-Jan-2013 17:29:59 GMT; path=/
TestCookie
now does not explicitly set a domain, ARRAffinity is unchanged.
The Windows 8 Store/Metro app now returns this:
array(2) {
["TestCookie"]=>
string(7) "foo bar"
["ARRAffinity"]=>
string(8) "295612ca"
}
The Windows Phone 8 app, returns this:
array(1) {
["TestCookie"]=>
string(7) "foo bar"
}
The ARRAffinity
cookie is not sent because it explicitly declares a domain.
If I assign some breakpoints and check the CookieContainer
of the request, I have two entries in the m_domainTable
+ [0] {[.subd.mydomain.com, System.Net.PathList]} System.Collections.Generic.KeyValuePair<string,System.Net.PathList>
+ [1] {[subd.mydomain.com, System.Net.PathList]} System.Collections.Generic.KeyValuePair<string,System.Net.PathList>
The cookie that isn't sent, is in the .subd.mydomain.com
container. This is the same on both Windows 8 and Windows Phone 8.
However, if the cookie declares itself like this:
Set-Cookie: TestCookie=foo+bar; expires=Fri, 04-Jan-2013 17:19:25 GMT; path=/; domain=.mydomain.com
It is correctly sent on Windows Phone 8.
In my original case, the server declares the cookie the same way regardless of if it is accessed via mydomain.com or www.mydomain.com; as ".mydomain.com", but Windows Phone 8 doesn't seem to think a cookie for ".mydomain.com" should be sent to "mydomain.com". This is problematic, as even if the server puts "subd.mydomain.com" as the domain, it is treated as having a preceding dot, and then doesn't work through no fault of its own. It seems it has to not send domain info with the cookie to have it treated correctly.