Автор:
Dimon aka Manowar (
aspnetman@aspnetmania.com)
Источник:
www.aspnetmania.com
Все наверное уже знают как получить содержимое URL с помошью методов GET или POST используя классы WebRequest/WebResponse. Но бывают моменты, когда этого мало, например, когда для доступа к так желаемым данным необходимо пройти предварительно аутентификацию.
Вот и я столкнулся с подобной задачей. Нужно было программно обработать порядка тысячи страниц и получить из них некоторые значения. Но для получения этих страниц необходимо было пройти аутентификацию.
Поиски решения данной задачи в инете ни к чему не привели и пришлось в итоге заняться этим вплотную самому. Некоторое время было потрачено на исследования и тестирование и результат не заставил себя долго ждать.
Итак для начала теоретические выкладки. Обычно аутентификация пользователя на сайте происходит следующим образом:
- Пользователь вводит свои данные и отсылает форму на сервер.
- Сервер проверяет есть ли пользователь с такими данными в базе и если все ок – возвращает в ответе какие-то куки (неважно какие, важно, что по ним потом проверяется, аутентифицирован ли пользователь).
- При запросе защищенной страницы сервер проверяет наличие нужных кук и если все ок – возвращает страницу.
Значит, судя по данному алгоритму все, что нужно добавить к стандартному методу получения страниц по URL – это добавить в него поддержку кук. Ну раз надо – значит надо :). Ниже представлен код, решающий данную задачу, с комментариями.
Для начала необходимо сходить на нужный сайт ручками и посмотреть в коде переменные, которые посылаются формой при логине. Допустим их 3 – login, pwd и action (всегда равный login). Теперь можно начинать программировать.
Вначале все выглядит как в известном примере получения данных по POST запросу – создается экземпляр класса WebRequest с нужным URL, устанавливаются свойства ContentType и Method, создается строка парамеров, которую необходимо будет отправить на данный URL и затем отсылается туда. Возвращаемый сервером результат принимается в экземпляр класса HttpWebResponse.
HttpWebResponse result = null;
HttpWebRequest req = (HttpWebRequest) HttpWebRequest.Create("(URL для авторизации)");
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
byte[] SomeBytes = null;
string FormParams = "Login=(имя пользователя)&Password=(пароль)&action=login";
SomeBytes = Encoding.UTF8.GetBytes(FormParams);
req.ContentLength = SomeBytes.Length;
Stream newStream = req.GetRequestStream();
newStream.Write(SomeBytes, 0, SomeBytes.Length);
newStream.Close();
result = (HttpWebResponse) req.GetResponse();
Теперь начинается самое интересное. Возвращаемые сервером куки передаются в заголовке ответа сервера с именем Set-Cookie. И теперь их необходимо получить из этого заголовка, создать на их основе правильный экземпляр класса CookieContainer и использовать его в дальнейших запросах.
Конечно для начала неплохо бы и проверить удачно ли прошла авторизация.
string[] cookieVal = null;
if(result.Headers["Set-Cookie"] != null)
cookieVal = result.Headers["Set-Cookie"].Split(new char[] {','});
Stream ReceiveStream = result.GetResponseStream();
Encoding encode = Encoding.GetEncoding("utf-8");
StreamReader sr = new StreamReader( ReceiveStream, encode );
string answer = sr.ReadToEnd();
sr.Close();
result.Close();
Будем считать что все в порядке и пара имя/пароль правильные (все таки в данной статье обсуждается несколько иная тема). Теперь необходимо создать правильный экземпляр CookieContainer.
Возвращаемая в заголовке «Set-Cookie» строка содержит в себе список кук, разделенных запятыми. Каждая же кука имеет следующий формат: «(имя куки)=(значение куки);path=(путь куки);domain=(домен куки);Expires=(дата, до которой кука хранится)». Основываясь на этих данных и создается контейнер с возвращенными куками.
CookieContainer cookie = new CookieContainer();
foreach(string cook in cookieVal)
{
string[] cookie1 = cook.Split(new char[] {';'});
if(cookie1.Length < 2)
continue;
cookie.Add(new Cookie(cookie1[0].Split(new char[] {'='})[0], cookie1[0].Split(new char[] {'='})[1],
cookie1[1].Split(new char[] {'='})[1], cookie1.Length > 2 ? cookie1[2].Split(new char[] {'='})[1] : ""));
}
Проверка на длину массива куки вставлена из-за того, что дата в возвращаемой в Set-Cookie строке представлена в полном формате и имеет символ «,» в своем составе. Соответственно может получиться так, что некоторые элементы массива cookieVal будут содержать часть даты. А так как нас не особо интересует Expires дата – мы просто отбрасываем данные строки.
Теперь есть все для получения защищенных страниц. Для присоединения CookieContainer к экземпляру класса WebRequest используется свойство CookieContainer данного класса. Ну а дальше все, как по теории:
HttpWebRequest req1 = (HttpWebRequest) HttpWebRequest.Create((URL защищенной страницы));
req1.UserAgent = "Mozilla/4.0+(compatible;+MSIE+5.01;+Windows+NT+5.0)";
//Вот оно - важное дополнение.
req1.CookieContainer = cookie;
req1.Method = "GET";
HttpWebResponse result1 = (HttpWebResponse) req1.GetResponse();
Stream ReceiveStream1 = result1.GetResponseStream();
StreamReader sr = new StreamReader( ReceiveStream1, encode );
string html = sr.ReadToEnd();
result1.Close();
Дело сделано :).
PS. Через 3 дня после моих мучений с данным алгоритмом администрация сервера, с которого я брал страницы, сделала их общедоступными :).