Cookie的基本操作在移动客户端的开发中是一件非常基本的事情,也是必须要掌握的一件事情。它是什么呢,是一个保存你登陆状态的临时对象。有了他,你就可以不用再次登陆。这一次的需求,大概就是一个模拟网页登录,然后利用获得的cookie进行之后的操作。

从最开始说起吧。首先对于cookie的操作,在runtime的开发模式中,大部分都是采用了过滤器filter的方式,在这个需求中,我一开始也是才用这样的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
HttpClient _httpClient = new HttpClient();

//一定要注意filter的位置,在getcookie的时候,应该放在请求的后面。在setcookie的时候,应该放在请求前面。
//我今天就在这个地方犯了错,导致后面出问题的时候,解决问题花了很长时间
get cookie:
HttpResponseMessage _response = await httpclient.GetAsync(AddressUrl);
HttpBaseProtocolFilter filter = new HttpBaseProtocolFilter();
HttpCookieCollection cookieCollection = filter.CookieManager.GetCookies(imageUri);

set cookie:
HttpCookie _cookie = new HttpCookie("", "", "");
HttpBaseProtocolFilter filter = new HttpBaseProtocolFilter();
bool replaced = filter.CookieManager.SetCookie(_cookie , false);
HttpResponseMessage response = await httpclient.SendRequestAsync(request);

上面的代码大家应该也很清楚意思,在这里也就不过多解释。接下来就给大家介绍之后遇到的三个主要问题。

验证码与cookie

在利用chrome观察要处理的页面的时候发现,我们在第一次访问这个页面的时候,会有一个名为“ASP.NET_SessionId”的cookie出现。可是我对于其来源并不了解,在一开始的时候,错误的以为是访问主页的时候产生的这个cookie。也没想到是请求验证码的时候获取的这个cookie。一开始的时候试了好几次,前后得有半个小时的时间,总是不成功。才想到会不会是验证码这个地方获取的cookie。

在获取验证码路径的时候,通过查看原网页的后台,发现其在验证码的参数中加入了一个时间戳。用JS的话是这样实现的:

1
var timeDate = new Date().getTime();

但是在C#中,并没有直接的方法生成时间戳。不过还好有iWIFI之前的经验,可以直接写出来转换的代码:

1
2
3
4
5
public static string GetTimeStamp()
{

TimeSpan ts = DateTime.Now - DateTime.Parse("1970-1-1");;
return Convert.ToInt64(ts.TotalSeconds).ToString();
}

之后又遇上了新的问题,之前给checkCode的这个Image的源设置网络路径,在后台往往通过下面的方式设置图片源:

1
2
3
Uri imageUri = new Uri("...");
Windows.UI.Xaml.Media.ImageSource _imgSource = new BitmapImage(imageUri);
checkCodeImageShow.Source = _imgSource;

这样一个简单的代码,自动实现了图像的下载,但是这样做的影响也十分清楚,那就是并不能在这个过程中保存cookie。因此我用httpclient将这个验证码图片的流下载下来之后,通过RandomAccessStream将其设置为验证码图片显示的流。代码为:

1
2
3
4
5
6
7
8
9
10
//可以直接使用httpclient.getBufferAsync,不过我为了在断点中观察,使用了HttpResponseMessage
//get buffer
HttpResponseMessage _response = await httpclient.GetAsync(imageUri);
IBuffer _buffer = await _response.Content.ReadAsBufferAsync();

//set image
BitmapImage bi = new BitmapImage();
await bi.SetSourceAsync(_buffer.AsStream().AsRandomAccessStream());
Windows.UI.Xaml.Media.ImageSource _imgSource = bi;
checkCodeImageShow.Source = _imgSource;

这样,只要在请求之后加上过滤器,并将第一次请求验证码时候获得的cookie存储下来,在实现login接口的时候带上,就可以成功了。到现在为止还是没有遇上什么大问题。

真机与模拟器的差别

其实微软的模拟器做的很好了,在我看来比android的好用,和iOS的差不多,不过按照showing with coding的原则来看,VS的设计器要好用很多。

那这次遇上的问题是什么呢,其实也就是下面的这个cookie设置不上的问题,模拟器上设置的好好的cookie,而且有效能用,在真机上就失效了,不知道是什么原因,难道是因为后台写的问题 = = 也不知道 所以在这里卡了好久好久,也烦死我了。然后用下面的方式来解决掉这个问题。

cookie设置不上

所以之前遇到了这个问题就慢慢解决,用filter取到cookie之后,下一次请求强行带上这个cookie的每个字段,虽然比较麻烦一点,但是这样做在真机上的确是有效果的。

那么是怎样带的呢?我来从开始描述一下这个过程。

首先我来到这个网站:http://card.sdu.edu.cn/ , 利用审查元素查看这个网站在第一次访问时带有的cookie:

首次访问的cookie

在登陆之后:

登陆之后的cookie

我们可以观察到主要是由名为 NET_SessionId 、 iPlanetDirectoryPro 、 pgv_pvi 这三个cookie控制的。验证码主要通过NET_SessionId ,而登陆状态主要通过iPlanetDirectoryPro。那我们就明白这个机制了,在一开始的时候就定义好这两个cookie,然后第一次获取验证码的时候取得NET_SessionId ,带着这个cookie提交验证码登陆,获得iPlanetDirectoryPro的value。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//Initialize cookie
private HttpCookie _loadCookie
private HttpCookie _loginCookie;
_loadCookie = new HttpCookie("ASP.NET_SessionId", "card.sdu.edu.cn", "/");
_loginCookie = new HttpCookie("iPlanetDirectoryPro", ".sdu.edu.cn", "/");

//Get cookie
TimeSpan ts = DateTime.Now - DateTime.Parse("1970-1-1");
long tt = Convert.ToInt64(ts.TotalMilliseconds);
Uri imageUri = new Uri(("http://card.sdu.edu.cn/Account/GetCheckCodeImg/Flag=" + tt.ToString()));
HttpResponseMessage _response = await httpClient.GetAsync(imageUri);
Windows.Storage.Streams.IBuffer _buffer = await _response.Content.ReadAsBufferAsync();

HttpBaseProtocolFilter filter = new HttpBaseProtocolFilter();
HttpCookieCollection cookieCollection = filter.CookieManager.GetCookies(imageUri);
foreach (HttpCookie _cookie in cookieCollection)
{
_loadCookie.Value = _cookie.Value;
_loadCookie.Secure = _cookie.Secure;
_loadCookie.Expires = _cookie.Expires;
_loadCookie.HttpOnly = _cookie.HttpOnly;
}

//Set cookie
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, new Uri(posturl));
request.Headers.Cookie.Add(new Windows.Web.Http.Headers.HttpCookiePairHeaderValue("Name", _loadCookie.Name));
request.Headers.Cookie.Add(new Windows.Web.Http.Headers.HttpCookiePairHeaderValue("Value", _loadCookie.Value));
request.Headers.Cookie.Add(new Windows.Web.Http.Headers.HttpCookiePairHeaderValue("Domain", _loadCookie.Domain));
request.Headers.Cookie.Add(new Windows.Web.Http.Headers.HttpCookiePairHeaderValue("Path", _loadCookie.Path));
request.Headers.Cookie.Add(new Windows.Web.Http.Headers.HttpCookiePairHeaderValue("Expires", _loadCookie.Expires.ToString()));
request.Headers.Cookie.Add(new Windows.Web.Http.Headers.HttpCookiePairHeaderValue("Secure", _loadCookie.Secure.ToString()));
request.Headers.Cookie.Add(new Windows.Web.Http.Headers.HttpCookiePairHeaderValue("HttpOnly", _loadCookie.HttpOnly.ToString()));

再之后就可以这两个cookie进行余额查询,充值转账等基本功能了。

不过在充值转账的过程中会有一个小问题,这个我们下一篇文章再谈。

推荐方式

在慢慢熟悉HttpClient这个类之后,到现在也对于cookie的管理有了更好的处理方式。

  • 首先声明了一个HttpHelper类,在这个里面,我们用单例的模式声明了一个HttpClient变量,并定义了doPost(), doGet(), downImg(), uploadImg()等方法。
  • 然后我们在App.xaml.cs中静态声明了HttpHelper这样一个类,之后的网络访问,我们都通过App.httpHelper.XXX方法来进行。
  • 这样做的原因是HttpClient会自动管理cookie,只要我们不清除这个变量,那么这个cookie就会一直存在于变量当中。

总之,多熟悉一些cookie的知识及相关api,总会有一天会用上。