[Вступление] В последнее время к нам на форум и в систему Эксперт поступает большое количество вопросов на тему взаимодействия с другими сайтами. Большие трудности у авторов таких вопросов вызывают проблемы программного заполнения и отправки Web-форм, причем многие из авторов идут, по их мнению, легким путем и пытаются использовать компонент WebBrowser, или всячески манипулировать «живыми» браузерами, и многие даже пытаться это делать при разработке Web-приложений! Помимо того, что подобный подход попросту не является правильным, так он еще и «сложный». В данной статье я расскажу о том, как можно программно заполнить и отправить Web-форму, а также получить результат с использованием довольно простых классов - HttpWebRequest и HttpWebResponse. [---] Инструменты Для полноценной работы нам понадобится локальный прокси-сервер для анализа трафика, в данной статье я буду использовать Fiddler. Все примеры будут приведены на языках Visual Basic и C#, под Framework .NET 2.x. В статье я буду все делать на примере консольного приложения, однако приведенные примеры кода можно легко использовать и в стандартных Windows-приложениях или в Web-проектах, или в любых других проектах .NET, без какой-либо особой модернизации. HttpWebRequest и HttpWebResponse. Классы HttpWebRequest и HttpWebResponse принадлежат к пространству имен System.Net. Класс HttpWebRequest позволяет отправлять HTTP-запросы, а класс HttpWebResponse позволяет обрабатывать полученные ответы. Перед началом работы, нам потребуется импортировать пространство имен System.Net в проект, а также дополнительно пространство имен System.Text и System.IO: VB .NET Код: Imports System.Net Imports System.Text Imports System.IO C# Код: using System.Net; using System.Text; using System.IO; Имитация работы браузера Возможно, вам может показаться, что имитация работы браузера это что-то сверхъестественное и не выполнимое, но на самом деле это не так, более того, в .NET это делается довольно легко, при этом, даже не надо знать основ HTTP протокола, хотя конечно подобные знания никому не повредят. Собственно сделать простейший HTTP запрос можно всего двумя-тремя строчками кода: VB .NET Код: Dim myHttpWebRequest As HttpWebRequest = HttpWebRequest.Create("http://kbyte.ru") Dim myHttpWebResponse As HttpWebResponse = myHttpWebRequest.GetResponse() C# Код: HttpWebRequest myHttpWebRequest = (HttpWebRequest)HttpWebRequest.Create("http://kbyte.ru"); HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse(); В данном примере, в первой строке создается экземпляр класса HttpWebRequest для работы с сервером http://kbyte.ru. Во второй строке сдается экземпляр класса HttpWebResponse, который получает результат выполнения GET-запроса к серверу http://kbyte.ru. Метод GET – это значение по умолчанию. Для того чтобы вывести полученный результат в консоль, достаточно добавить еще несколько строчек кода: VB .NET Код: Dim myStreamReader As New StreamReader(myHttpWebResponse.GetResponseStream, Encoding.GetEncoding(1251)) Console.WriteLine(myStreamReader.ReadToEnd()) Console.ReadKey() C# Код: StreamReader myStreamReader = new StreamReader(myHttpWebResponse.GetResponseStream(), Encoding.GetEncoding(1251)); Console.WriteLine(myStreamReader.ReadToEnd()); Console.ReadKey(); В первой строке создается экземпляр StreamReader, который принимает данные из HttpWebResponse, при этом указывается кодировка Windows-1251, если ее не указать, получится «абракадабра». Во второй строке полученные данные выводятся в консоль. В третьей строке консоль ждет нажатия любой клавиши, чтобы работа программы не завершилась. Для того чтобы посмотреть, какой запрос сформировал этот код, запустите локальный прокси-сервер, напомню, что я использую Fiddler, который по умолчанию прослушивает 8888 порт. Чтобы наш экземпляр класса HttpWebRequest использовал прокси-сервер, необходимо указать параметры прокси-сервера в свойстве Proxy: VB .NET Код: myHttpWebRequest.Proxy = New WebProxy("127.0.0.1", 8888) C# Код: myHttpWebRequest.Proxy = new WebProxy("127.0.0.1", 8888); В данном случае 127.0.0.1 – это локальный адрес компьютера, а 8888 порт, который прослушивает прокси-сервер. VB .NET Код: Dim myHttpWebRequest As HttpWebRequest = HttpWebRequest.Create("http://kbyte.ru") myHttpWebRequest.Proxy = New WebProxy("127.0.0.1", 8888) Dim myHttpWebResponse As HttpWebResponse = myHttpWebRequest.GetResponse() C# Код: HttpWebRequest myHttpWebRequest = (HttpWebRequest)HttpWebRequest.Create("http://kbyte.ru"); myHttpWebRequest.Proxy = new WebProxy("127.0.0.1", 8888); HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse(); После выполнения данного кода, в отчете прокси-сревера, вы должны увидеть что-то типа этого: Код: GET / HTTP/1.1 Host: kbyte.ru Proxy-Connection: Keep-Alive Теперь, попробуйте подключить ваш браузер к локальному прокси-серверу (см. параметры подключения браузера к сети) и перейдете по ссылке http://kbyte.ru, и вы увидите немного другие заголовки: Код: GET / HTTP/1.1 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */* Accept-Language: ru User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; MyIE2; .NET CLR 1.1.4322; .NET CLR 2.0.50727) Host: kbyte.ru Proxy-Connection: Keep-Alive Cookie: Kbyte.Ru=XXX&Theme=XXX; ASP.NET_SessionId=XXX Как видите, браузер передает гораздо больше заголовков серверу, главным образом это информация о себе (User-Agent), куки (Cookies), предпочитаемый язык (Accept-Language) и тип данных (Accept). Все эти заголовки также можно создать программно через коллекцию Headers, либо одноименные свойства объекта HttWebRequest. Конечно, объем кода придется увеличить, но сложнее он от этого точно не станет: VB .NET Код: Dim myHttpWebRequest As HttpWebRequest = HttpWebRequest.Create("http://kbyte.ru") myHttpWebRequest.Proxy = New WebProxy("127.0.0.1", 8888) myHttpWebRequest.UserAgent = "Mozila/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; MyIE2;" myHttpWebRequest.Accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*" myHttpWebRequest.Headers.Add("Accept-Language", "ru") Dim myHttpWebResponse As HttpWebResponse = myHttpWebRequest.GetResponse() C# Код: HttpWebRequest myHttpWebRequest = (HttpWebRequest)HttpWebRequest.Create("http://kbyte.ru"); myHttpWebRequest.Proxy = new WebProxy("127.0.0.1", 8888); myHttpWebRequest.UserAgent = "Mozila/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; MyIE2;"; myHttpWebRequest.Accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*"; myHttpWebRequest.Headers.Add("Accept-Language", "ru"); HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse(); Если теперь посмотреть на отчет проскси-сервера, то там будут видны все заголовки, которые мы передали. Таким образом, сервер будет «думать», что на сайт пришел пользовать с браузером Internet Explorer, для которого предпочитаемый язык – русский (ru). В данном примере я не стал передавать куки (Cookies), поскольку для начала их надо бы получить, но об этом речь пойдет в следующей части статьи. Подопытный Яндекс. Авторизация на сайте. Анализ В приведенных ранее примерах мы использовали GET-запросы, однако, для того чтобы заполнить и отправить какую-либо Web-форму, потребуется использовать POST-запросы. Для примера, попробуем пройти авторизацию на сайте http://yandex.ru. Да не будет в обиде на меня Яндекс, выбрал я его случайным образом, потому что ничего другого в голову не пришло Для тестирования, я специально зарегистрировал себе новый логин. Для отправки запроса методом POST мы будем использовать уже знакомый нам класс HttpWebRequest. Метод запроса указывается в свойстве Method. Чтобы узнать, что и как нужно передавать, мы просто запустим локальный прокси-сервер, и пройдем процедуру авторизации на сайте через браузер (не забудьте настроить браузер), а затем детально изучим отчет проскика: Код: POST /passport?mode=auth HTTP/1.1 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */* Referer: http://www.yandex.ru/ Accept-Language: ru Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; MyIE2; .NET CLR 1.1.4322; .NET CLR 2.0.50727) Host: passport.yandex.ru Content-Length: 90 Proxy-Connection: Keep-Alive Pragma: no-cache Cookie: yandexuid=1125763961176350273; yabs-frequency=/1/0G00; L=Twgqw==.; my=Yw%3D; yandex_login= retpath=http%3A%2F%2Fmail.yandex.ru%2F?tamp=1185426616093&login=kbyte2007&passwd=**** В первой строке указан метод POST и страница, на которую отправляется форма - /passport?mode=auth, это путь относительно Host, в данном случае Host имеет значение passport.yandex.ru, следовательно, данные отправляются на страницу http://passport.yandex.ru/passport?mode=auth. Заголовок Referer содержит адрес страницы, с которой пришел пользователь, в данном случае - это http://yandex.ru. Заголовок Content-Length содержит объем передаваемых данных в байтах. Заголовок Cookies – содержит информацию о куках (Cookies), которые передает браузер серверу. Далее, через одну пустую строку (\r\n\r\n) начинается контент, который наш браузер передает серверу, т.е. это и есть данные формы. Мы видим, что передается 4 параметра: retpath, timestamp, login и passwd. Судя по всему, параметр retpath содержит адрес страницы, на который мы перейдем после успешной проверки логина и пароля. Параметр timestamp содержит штамп времени. Вообще, важной частью анализа является не только анализ заголовков, но и анализ всего остального трафика, который возвращает сервер (HTML и т.п). Если открыть HTML-код главной страницы Яндекс, то мы увидим, что все параметры, которые передаются, входят в состав формы «lonig», которая собственно и содержит в себе все передаваемые элементы (см. рис. 1). Обратите внимание, при отправке формы (на рисунке это 75 строка) идет вызов функции settime() – это клиентская функция, если вы попытаетесь найти ее на странице, то не найдете, так как она находится в отдельном файле JavaScript (см. рис. 2). Чтобы узнать, как эта функция может повлиять на успех выполнения поставленной задачи, нужно просто открыть файлик со скриптом, найти функцию settime() и посмотреть, что она делает (рис. 3). Как видите, эта функция присваивает элементу timestamp (см. рис. 1. строка 79) текущее unix-время клиента (т.к. JavaScript выполняется на стороне клиента), т.е. это количество секунд прошедшее с 1 января 1970 года, нам придется сделать то же самое. Все эти данные, также должны находится в логах Fiddler-а, однако смотреть их там не очень удобно С параметрами login и passwd, думаю, и так все понятно. Теперь давайте ознакомимся с ответом сервера: Код: HTTP/1.1 302 Found Date: Thu, 26 Jul 2007 05:10:41 GMT Server: Apache/1.3.33 (Debian GNU/Linux) mod_ssl/2.8.22 OpenSSL/0.9.7e mod_perl/1.29 P3P: policyref="/w3c/p3p.xml", CP="NON DSP ADM DEV PSD IVDo OUR IND STP PHY PRE NAV UNI" Set-Cookie: yandex_fio=%cf%f3%ef%ea%e8%ed%20%c2%e0%f1%e8%eb%e8%e9; path=/; domain=.yandex.ru Set-Cookie: yandex_login=kbyte2007; path=/; domain=.yandex.ru Set-Cookie: yandex_nickname=; path=/; domain=.yandex.ru Set-Cookie: yandex_mail=kbyte2007; path=/; domain=.yandex.ru Set-Cookie: L=QU0Rg==.118; path=/; domain=.yandex.ru; expires=Wednesday, 24-Oct-07 05:10:41 GMT Set-Cookie: Session_id=111; path=/; domain=.yandex.ru Location: /passport?mode=ckicheck&idkey=09w Connection: close Transfer-Encoding: chunked, chunked Content-Type: text/plain 0 Как видите, сервер возвращает 302 код (временно перемещено), т.е. это обычный редирект, причем если посмотреть на заголовок Location, то видно, что перенаправление идет на страницу /passport?mode=ckicheck&idkey=09w. Помимо этого, сервер передает куки (Set-Cookie) и идентификатор сессии (также в куках). Нам придется получить все эти заголовки и передать в следующем запросе, чтобы сервер принял наши данные и выдал желаемый результат. Если посмотреть далее, куда нас перенаправил Яндекс, то мы увидим нечто вроде этого: Код: HTTP/1.1 200 OK Date: Thu, 26 Jul 2007 06:24:00 GMT Server: Apache/1.3.33 (Debian GNU/Linux) mod_ssl/2.8.22 OpenSSL/0.9.7e mod_perl/1.29 Cache-Control: no-cache,no-store,max-age=0,must-revalidate Pragma: no-cache Expires: Thu, 26 Jul 2007 06:24:00 GMT Transfer-Encoding: chunked Content-Type: text/html; charset=windows-1251 Proxy-Connection: Close 247 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head><title>Яндекс: Паспорт</title> <meta http-equiv="content-type" content="text/html; charset=windows-1251"> <script language="JavaScript"> <!-- window.location.replace("http://mail.yandex.ru/"); // --> </script> <meta http-equiv="refresh" content="0;url=http://mail.yandex.ru/"> </head> <body> <p>Если вы видите эту страницу, это означает что ваш браузер не поддерживает автоматическое перенаправление.</p> <p>Чтобы продолжить, нажмите на <a href="http://mail.yandex.ru/">эту ссылку</a>.</p> </body> </html> Обратите внимание на HTML, здесь при помощи JavaScript идет перенаправление на страницу http://mail.yandex.ru/, которая была в первом запросе. Также редирект указан в мета-тэге «refresh». Нам придется вручную перенаправить пользователя на эту страницу, при этом очень важно не потерять куки, которые мы получили в предыдущих запросах, иначе Яндекс нас просто не пустит. Кодинг Пожалуй, хватит анализировать, займемся самым интересным – написанием кода. Первым делом нужно сформировать запрос, причем для начала на главную страницу Яндекс, поскольку именно там нам выдаются первые куки (Cookies), без которых дальнейшая работа в данном направлении попросту может завести нас в тупик. Запрос будет выполняться методом GET: VB .NET Код: Dim myHttpWebRequest As HttpWebRequest = HttpWebRequest.Create("http://yandex.ru") myHttpWebRequest.Proxy = New WebProxy("127.0.0.1", 8888) myHttpWebRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; MyIE2;" myHttpWebRequest.Accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*" myHttpWebRequest.Headers.Add("Accept-Language", "ru") Dim myHttpWebResponse As HttpWebResponse = myHttpWebRequest.GetResponse() myStream = myHttpWebResponse.GetResponseStream Dim sCookies As String = "" If Not String.IsNullOrEmpty(myHttpWebResponse.Headers("Set-Cookie")) Then sCookies = myHttpWebResponse.Headers("Set-Cookie") End If C# Код: HttpWebRequest myHttpWebRequest = (HttpWebRequest)HttpWebRequest.Create("http://yandex.ru"); myHttpWebRequest.Proxy = new WebProxy("127.0.0.1", 8888); myHttpWebRequest.UserAgent = "Mozila/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; MyIE2;"; myHttpWebRequest.Accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*"; myHttpWebRequest.Headers.Add("Accept-Language", "ru"); HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse(); string sCookies = ""; if (!String.IsNullOrEmpty(myHttpWebResponse.Headers["Set-Cookie"])) { sCookies = myHttpWebResponse.Headers["Set-Cookie"]; } Куки мы получаем из заголовка Set-Cookies и записываем их в переменную sCookies (строки – 8-11). Далее нам надо пройти процедуру авторизации на Яндекс, делать мы это будем уже методом POST: VB .NET Код: myHttpWebRequest = HttpWebRequest.Create("http://passport.yandex.ru/passport?mode=auth") myHttpWebRequest.Proxy = New WebProxy("127.0.0.1", 8888) myHttpWebRequest.Method = "POST" myHttpWebRequest.Referer = "http://yandex.ru" myHttpWebRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; MyIE2;" myHttpWebRequest.Accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*" myHttpWebRequest.Headers.Add("Accept-Language", "ru") myHttpWebRequest.ContentType = "application/x-www-form-urlencoded" 'передаем куки, полученные в предыдущем запросе If Not String.IsNullOrEmpty(sCookies) Then myHttpWebRequest.Headers.Add(HttpRequestHeader.Cookie, sCookies) End If 'ставим False, чтобы не делать автоматический редирект myHttpWebRequest.AllowAutoRedirect = False 'передаем параметры Dim sQueryString As String = "retpath=http%3A%2F%2Fmail.yandex.ru%2F?tamp=" & DateDiff(DateInterval.Second, New Date(1970, 1, 1), Now) & "&login=kbyte2007&passwd=****" Dim ByteArr As Byte() = Text.Encoding.GetEncoding(1251).GetBytes(sQueryString) myHttpWebRequest.ContentLength = ByteArr.Length() myHttpWebRequest.GetRequestStream().Write(ByteArr, 0, ByteArr.Length) 'делаем запрос myHttpWebResponse = myHttpWebRequest.GetResponse() C# Код: HttpWebRequest myHttpWebRequest = (HttpWebRequest)HttpWebRequest.Create("http://passport.yandex.ru/passport?mode=auth"); myHttpWebRequest.Proxy = new WebProxy("127.0.0.1", 8888); myHttpWebRequest.Method = "POST"; myHttpWebRequest.Referer = "http://yandex.ru"; myHttpWebRequest.UserAgent = "Mozila/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; MyIE2;"; myHttpWebRequest.Accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*"; myHttpWebRequest.Headers.Add("Accept-Language", "ru"); myHttpWebRequest.ContentType = "application/x-www-form-urlencoded"; // передаем куки, полученные в предыдущем запросе if (!String.IsNullOrEmpty(sCookies)) { myHttpWebRequest.Headers.Add(HttpRequestHeader.Cookie, sCookies); } // ставим False, чтобы при получении кода 302 не делать автоматический редирект myHttpWebRequest.AllowAutoRedirect = false; // передаем параметры TimeSpan ts = DateTime.Now - new DateTime(1970, 1, 1); string sQueryString = "retpath=http%3A%2F%2Fmail.yandex.ru%2F?tamp=" + Math.Floor(ts.TotalSeconds).ToString() + "&login=" + sLogin + "&passwd=" + sPassword; byte[] ByteArr = System.Text.Encoding.GetEncoding(1251).GetBytes(sQueryString); myHttpWebRequest.ContentLength = ByteArr.Length; myHttpWebRequest.GetRequestStream().Write(ByteArr, 0, ByteArr.Length); // делаем запрос HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse(); Очень важно не забыть у свойства AllowAutoRedirect указать значение False, если этого не сделать, то при получении кода 301/302, HttpWebRequest автоматически перейдет на указанную в заголовках страницу, при этом могут потеряться важные данные, например, куки, и следовательно, сервер не пустит нас на нужную страницу. Также не забудьте указать в строке запроса (переменная sQueryString) параметры login и passwd (только не звездочками ...). Если вы сделаете все правильно, то сервер возвратит код 302, в противном случае – код 200, со страницей, содержащей информацию об ошибке, либо форму авторизации.Далее, нам надо получить адрес страницы, на которую нас перенаправляет сервер, эта информация содержится в заголовке Location. Все последующие запросы будут выполняться методом GET: VB .NET Код: Dim sLocation As String = myHttpWebResponse.Headers("Location") 'получам куки sCookies = "" If Not String.IsNullOrEmpty(myHttpWebResponse.Headers("Set-Cookie")) Then sCookies = myHttpWebResponse.Headers("Set-Cookie") End If 'формируем запрос myHttpWebRequest = HttpWebRequest.Create("http://passport.yandex.ru" & sLocation) myHttpWebRequest.Proxy = New WebProxy("127.0.0.1", 8888) myHttpWebRequest.Referer = "http://passport.yandex.ru/passport?mode=auth" myHttpWebRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; MyIE2;" myHttpWebRequest.Accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*" myHttpWebRequest.Headers.Add("Accept-Language", "ru") myHttpWebRequest.ContentType = "text/plain" If Not String.IsNullOrEmpty(sCookies) Then myHttpWebRequest.Headers.Add(HttpRequestHeader.Cookie, sCookies) End If 'выполняем запрос myHttpWebResponse = myHttpWebRequest.GetResponse() C# Код: string sLocation = myHttpWebResponse.Headers["Location"]; // получам куки sCookies = ""; if (!String.IsNullOrEmpty(myHttpWebResponse.Headers["Set-Cookie"])) { sCookies = myHttpWebResponse.Headers["Set-Cookie"]; } // формируем запрос myHttpWebRequest = (HttpWebRequest)HttpWebRequest.Create("http://passport.yandex.ru" + sLocation); myHttpWebRequest.Proxy = new WebProxy("127.0.0.1", 8888); myHttpWebRequest.Referer = "http://passport.yandex.ru/passport?mode=auth"; myHttpWebRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; MyIE2;"; myHttpWebRequest.Accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*"; myHttpWebRequest.Headers.Add("Accept-Language", "ru"); myHttpWebRequest.ContentType = "text/plain"; if (!String.IsNullOrEmpty(sCookies)) { myHttpWebRequest.Headers.Add(HttpRequestHeader.Cookie, sCookies); } // выполняем запрос myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse(); Вот мы и приблизились к нашей цели, следующая страница, которую возвратит сервер, будет содержать информацию о том, что наш браузер не поддерживает автоматический редирек, и предложение нажать на ссылку, для входа в ящик. Редирект производится при помощи JavaScript, а также через мета-тэги. Мы будем отталкиваться от JavaScript, хотя принципиального значения это не имеет. Собственно адрес, на который нужно перейти, был в самом первом шаге, когда мы отправляли POST-запрос, вот на него мы и будем сейчас переходить. Здесь очень важно не потерять куки, поскольку в них содержится идентификатор сессии (Session), если мы потеряем куки, то сервер перенаправит нас на страницу авторизации. VB .NET Код: myHttpWebRequest = HttpWebRequest.Create("http://mail.yandex.ru/") myHttpWebRequest.Proxy = New WebProxy("127.0.0.1", 8888) myHttpWebRequest.Referer = "http://passport.yandex.ru" & sLocation myHttpWebRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; MyIE2;" myHttpWebRequest.Accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*" myHttpWebRequest.Headers.Add("Accept-Language", "ru") myHttpWebRequest.ContentType = "text/plain" If Not String.IsNullOrEmpty(sCookies) Then myHttpWebRequest.Headers.Add(HttpRequestHeader.Cookie, sCookies) End If 'выполняем запрос myHttpWebResponse = myHttpWebRequest.GetResponse() 'выводим результат в консоль Dim myStreamReader As New StreamReader(myHttpWebResponse.GetResponseStream, Encoding.GetEncoding(1251)) Console.WriteLine(myStreamReader.ReadToEnd()) Console.WriteLine("Нажмите любую клавишу для завершения.") Console.ReadKey() C# Код: myHttpWebRequest = (HttpWebRequest)HttpWebRequest.Create("http://mail.yandex.ru/"); myHttpWebRequest.Proxy = new WebProxy("127.0.0.1", 8888); myHttpWebRequest.Referer ="http://passport.yandex.ru" & sLocation; myHttpWebRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; MyIE2;"; myHttpWebRequest.Accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*"; myHttpWebRequest.Headers.Add("Accept-Language", "ru"); myHttpWebRequest.ContentType = "text/plain"; if (!String.IsNullOrEmpty(sCookies)) { myHttpWebRequest.Headers.Add(HttpRequestHeader.Cookie, sCookies); } // выполняем запрос myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse(); // выводим результат в консоль StreamReader myStreamReader = new StreamReader(myHttpWebResponse.GetResponseStream(), Encoding.GetEncoding(1251)); Console.WriteLine(myStreamReader.ReadToEnd()); Console.WriteLine("Нажмите любую клавишу для завершения."); Console.ReadKey(); Вот собственно и все, если запустить данный код и посмотреть на отчет прокси-сервера, то будет видно, что при первом запросе сервер выдает код 200, затем 302, и опять два раза 200, т.е. все точно так же, как если бы мы прошли процедуру авторизации через браузер. В окне консоли должно появится содержимое страницы сервиса работы с почтовым ящиком на Яндекс.Ру. Заключение Признаюсь честно, с первого раза написанный мой код не сработал, и со второго тоже, и даже с третьего раза Яндекс не принял мои запросы . Несмотря на то, что программно выполнить POST-запрос не сложно, иногда все же приходится помучиться. По большому счету, все трудности возникают из-за не качественного анализа трафика, или потери некоторых важных параметров при выполнении запросов. Если вы вдруг столкнетесь с подобными проблемами, то попробуйте просто передавать все полученные через обычный браузер заголовки один к одному, даже не смотря на то, что многие из них попросту не нужны. Продолжая данную тему, можно запросто при помощи регулярных выражений получить список сообщений в ящике, и даже написать новое сообщение, но конкретно тема работы с почтой это уже другая история, и совершенно никак не связанная с HttpWebRequest и HttpWebResponse, поскольку в .NET существуют другие более мощные, специально созданные для этого, средства. Пример с Яндекс я выбрал совершенно случайно, на его основе вы можете создать автоматическую «заполнялку» форм и «бродилку» по страницам любой степени сложности и вложенности. [---] Если у вас возникнут какие-либо вопросы, жалобы или предложения, пишите мне на e-mail, либо на форум, или в систему «Эксперт». И помните, никто не решит ваши проблемы лучше, чем вы сами © Скачать немного модифицированный пример для VB можно здесь, для Си шарп – здесь. Удачи! (c) Немиро Алексей (взято с _www.gotdotnet.ru) Оригинал
Взаимодействие с другими сайтами через HTTP, Cookies Подробнее о Cookies - C# Всю статью автор работал с Cookies посредством Http-заголовков. Иной способ работы с кукисами - это свойство HttpWebRequest.CookieContainer из System.Net, которое я попытаюсь поверхостно описать в этом продолжении. Но поскольку продолжение вполне самостоятельное, то не берусь заверять вас в его качественности, возможны ошибки и недочёты. По аналогии с вышеописанным, перед началом работы, нам потребуется импортировать пространство имен System.Net в проект: Код: using System.Net; Далее, как и, опять же, выше, необходимо создать HTTP-запрос, все свойства которого разработчик определяет самостоятельно, кроме одного "но". Это "но" и есть свойство CookieContainer, которое нам необходимо создать: Код: HttpWebRequest myHttpWebRequest = (HttpWebRequest)HttpWebRequest.Create("http://kbyte.ru"); myHttpWebRequest.CookieContainer = new CookieContainer(); Отослав запрос, получим ответ: Код: HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse(); myHttpWebRequest.Cookies вернёт целый массив нужных нам кукисов, извлечь которые можно простым циклом foreach: Код: foreach (Cookie myCookie in myHttpWebResponse.Cookies) { Console.WriteLine("{0}: {1}", myCookie.Name, myCookie.Value); } Это аналогично таким действиям в предыдущей статье: Код: string sCookies = ""; if (!String.IsNullOrEmpty(myHttpWebResponse.Headers["Set-Cookie"])) { sCookies = myHttpWebResponse.Headers["Set-Cookie"]; } Заметьте, что разделённые значения использовать куда удобнее. Отправка запроса с установленными кукисами также не составляет труда: Код: HttpWebRequest myHttpWebRequest = (HttpWebRequest)HttpWebRequest.Create("http://kbyte.ru"); myHttpWebRequest.CookieContainer = new CookieContainer(); Cookie myRequestCookie = new Cookie(); // ... myHttpWebRequest.CookieContainer.Add(myRequestCookie); HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse(); foreach (Cookie myResponseCookie in myHttpWebResponse.Cookies) { Console.WriteLine("{0}: {1}", myResponseCookie.Name, myResponseCookie.Value); } На место многоточия поставьте присвоение свойств для myRequestCookie. Напомню, что для работы с myRequestCookie и myResponseCookie, т.е. с представителями класса Cookie, пригодятся хоть даже и простейшие их свойства, с которыми, мне кажется проблем возникнет меньше всего: Comment - Возвращает или задает комментарий, который сервер может добавлять к Cookie. CommentUri - Возвращает или задает комментарий URI, который сервер может предоставлять с Cookie. Discard - Возвращает или задает флаг сброса, задаваемый сервером. Domain - Возвращает или задает URI, для которого действует Cookie. Expired - Возвращает или задает текущее состояние Cookie. Expires - Возвращает или задает для Cookie дату и время окончания действия в виде DateTime. HttpOnly - Определяет, может ли получить доступ к файлу cookie сценарий страницы или другое активное содержимое. Name - Возвращает или задает имя для Cookie. Path - Возвращает или задает идентификаторы URI, к которым применяется Cookie. Port - Возвращает или задает список TCP-портов, к которым применяется класс Cookie. Secure - Возвращает или задает уровень защиты класса Cookie. TimeStamp - Возвращает время выпуска файла cookie в виде DateTime. Value - Возвращает или задает свойство Value объекта Cookie. Version - Возвращает или задает версию сопровождения HTTP-состояния, которому соответствует файл cookie. Опять же хочется напомнить, что это дополнение поверхостно и всё же советую обратиться к MSDN, дабы иметь более полную информацию. (цЫ) E1B0 aka 11$ (www.xaker.name)
Взаимодействие с другими сайтами через HTTP, HTTP-состояния Подробнее о HTTP-состояниях - C# Для некоторой автоматизации также полезно использовать свойство HttpWebResponse.StatusCode, так как не всегда всё так гладко как хотелось бы, и при ожидаемом перемещении, обратившись к полю заголовка Location мы можем получить ошибку, если перенаправления не случится. Как и выше используем System.Net: Код: using Sytem.Net; Для избежания ошибок времени выполнения стоит включить небольшую обработку HTTP-состояния ответа: Код: HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create("http://kbyte.ru"); myHttpWebRequest.AllowAutoRedirect = false; HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse(); if (myHttpWebResponse.StatusCode == HttpStatusCode.Found) { string sLocation = myHttpWebResponse.Headers["Location"]; } myHttpWebRequest = (HttpWebRequest)HttpWebRequest.Create("http://kbyte.ru" + sLocation); myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse(); Основные значения перечисления System.Net.HttpStatusCode: HttpStatusCode.Continue - Эквивалентен HTTP-состоянию 100. Элемент Continue указывает, что клиент может продолжать выполнять свой запрос. HttpStatusCode.SwitchingProtocols - Эквивалентен HTTP-состоянию 101. Элемент SwitchingProtocols указывает, что изменяется протокол или версия протокола. HttpStatusCode.OK - Эквивалентен HTTP-состоянию 200. Элемент OK указывает, что запрос завершился успешно и запрашиваемые данные находятся в ответе. Это наиболее часто получаемый код состояния. HttpStatusCode.Created - Эквивалентен HTTP-состоянию 201. Элемент Created указывает, что запрос привел к созданию нового ресурса, до того как был послан ответ. HttpStatusCode.Accepted - Эквивалентен HTTP-состоянию 202. Элемент Accepted указывает, что запрос принят для дальнейшей обработки. HttpStatusCode.NonAuthoritativeInformation - Эквивалентен HTTP-состоянию 203. Элемент NonAuthoritativeInformation указывает, что возвращенные метаданные взяты из кэшированной копии вместо исходного сервера и поэтому могут быть неверными. HttpStatusCode.NoContent - Эквивалентен HTTP-состоянию 204. Элемент NoContent указывает, что запрос успешно обработан и что ответ является преднамеренно пустым. HttpStatusCode.ResetContent - Эквивалентен HTTP-состоянию 205. Элемент ResetContent указывает, что клиент должен восстановить в исходное состояние (не перегрузить) текущий ресурс. HttpStatusCode.PartialContent - Эквивалентен HTTP-состоянию 206. Элемент PartialContent указывает, что ответ является частичным ответом на запрос GET, содержащий диапазон байтов. HttpStatusCode.MultipleChoices - Эквивалентен HTTP-состоянию 300. Элемент MultipleChoices указывает, что запрашиваемые данные имеют несколько представлений. Действие, выполняемое по умолчанию, состоит в перенаправлении и отслеживании данных заголовка Location, связанного с этим ответом. HttpStatusCode.Ambiguous - Эквивалентен HTTP-состоянию 300. Элемент Ambiguous указывает, что запрашиваемые данные имеют несколько представлений. Действие, выполняемое по умолчанию, состоит в перенаправлении и отслеживании данных заголовка Location, связанного с этим ответом. HttpStatusCode.MovedPermanently - Эквивалентен HTTP-состоянию 301. Элемент MovedPermanently указывает, что запрашиваемые данные перемещены по адресу (URI), заданному в заголовке Location. Действие, выполняемое по умолчанию при получении этого состояния, состоит в отслеживании заголовка Location, связанного с ответом. HttpStatusCode.Moved - Эквивалентен HTTP-состоянию 301. Элемент Moved указывает, что запрашиваемые данные перемещены по адресу (URI), заданному в заголовке Location. Действие, выполняемое по умолчанию при получении этого состояния, состоит в отслеживании заголовка Location, связанного с ответом. Если для исходного запроса использовался метод POST, перенаправляемый запрос будет использовать метод GET. HttpStatusCode.Found - Эквивалентен HTTP-состоянию 302. Элемент Found указывает, что запрашиваемые данные находятся по URI, заданному в заголовке Location. Действие, выполняемое по умолчанию при получении этого состояния, состоит в отслеживании заголовка Location, связанного с ответом. Если для исходного запроса использовался метод POST, перенаправляемый запрос будет использовать метод GET. HttpStatusCode.Redirect - Эквивалентен HTTP-состоянию 302. Элемент Redirect указывает, что запрашиваемые данные находятся по URI, заданному в заголовке Location. Действие, выполняемое по умолчанию при получении этого состояния, состоит в отслеживании заголовка Location, связанного с ответом. Если для исходного запроса использовался метод POST, перенаправляемый запрос будет использовать метод GET. HttpStatusCode.SeeOther - Эквивалентен HTTP-состоянию 303. Элемент SeeOther в результате выполнения метода POST автоматически перенаправляет клиента на URI, заданный в заголовке Location. Запрос ресурса, задаваемого заголовком Location, выполняется с помощью метода GET. HttpStatusCode.RedirectMethod - Эквивалентен HTTP-состоянию 303. Элемент RedirectMethod в результате выполнения метода POST автоматически перенаправляет клиента на URI, заданный в заголовке Location. Запрос ресурса, задаваемого заголовком Location, выполняется с помощью метода GET. HttpStatusCode.NotModified - Эквивалентен HTTP-состоянию 304. Элемент NotModified указывает, что клиентская кэшированная копия является самой последней по времени. Данные ресурса не пересылаются. HttpStatusCode.UseProxy - Эквивалентен HTTP-состоянию 305. Элемент UseProxy указывает, что запрос должен использовать прокси-сервер по адресу (URI), заданному в заголовке Location. HttpStatusCode.Unused - Эквивалентен HTTP-состоянию 306. Элемент Unused является предлагаемым расширением спецификации HTTP/1.1, которая полностью не определена. HttpStatusCode.TemporaryRedirect - Эквивалентен HTTP-состоянию 307. Элемент TemporaryRedirect указывает, что данные запроса находятся по URI, заданному в заголовке Location. Действие, выполняемое по умолчанию при получении этого состояния, состоит в отслеживании заголовка Location, связанного с ответом. Если для исходного запроса использовался метод POST, перенаправляемый запрос будет также использовать метод POST. HttpStatusCode.RedirectKeepVerb - Эквивалентен HTTP-состоянию 307. Элемент RedirectKeepVerb указывает, что данные запроса находятся по URI, заданному в заголовке Location. Действие, выполняемое по умолчанию при получении этого состояния, состоит в отслеживании заголовка Location, связанного с ответом. Если для исходного запроса использовался метод POST, перенаправляемый запрос будет также использовать метод POST. HttpStatusCode.BadRequest - Эквивалентен HTTP-состоянию 400. Элемент BadRequest указывает, что запрос не может быть воспринят сервером. Элемент BadRequest посылается, когда не применима любая другая ошибка, или ошибка является неизвестной или не имеет собственного кода ошибки. HttpStatusCode.Unauthorized - Эквивалентен HTTP-состоянию 401. Элемент Unauthorized указывает, что запрашиваемый ресурс требует прохождения проверки подлинности. В заголовке WWW-Authenticate подробно излагается порядок выполнения проверки подлинности. HttpStatusCode.PaymentRequired - Эквивалентен HTTP-состоянию 402. Элемент PaymentRequired зарезервирован для будущих применений. HttpStatusCode.Forbidden - Эквивалентен HTTP-состоянию 403. Элемент Forbidden указывает, что сервер отказывается выполнять запрос. HttpStatusCode.NotFound - Эквивалентен HTTP-состоянию 404. Элемент NotFound указывает, что запрашиваемый ресурс отсутствует на сервере. HttpStatusCode.MethodNotAllowed - Эквивалентен HTTP-состоянию 405. Элемент MethodNotAllowed указывает, что метод запроса (POST или GET) не разрешен для запрашиваемого ресурса. HttpStatusCode.NotAcceptable - Эквивалентен HTTP-состоянию 406. Элемент NotAcceptable указывает, что клиент показывает с помощью заголовков Accept, что он не принимает любые из доступных представлений ресурса. HttpStatusCode.ProxyAuthenticationRequired - Эквивалентен HTTP-состоянию 407. Элемент ProxyAuthenticationRequired указывает, что запрашиваемый прокси требует прохождения проверки подлинности. В заголовке Proxy-authenticate подробно излагается порядок выполнения проверки подлинности. HttpStatusCode.RequestTimeout - Эквивалентен HTTP-состоянию 408. Элемент RequestTimeout указывает, что клиент не послал запрос в пределах времени, когда запрос ожидался сервером. HttpStatusCode.Conflict - Эквивалентен HTTP-состоянию 409. Элемент Conflict указывает, что запрос не может быть выполнен из-за конфликта на сервере. HttpStatusCode.Gone - Эквивалентен HTTP-состоянию 410. Элемент Gone указывает, что запрашиваемый ресурс больше не доступен. HttpStatusCode.LengthRequired - Эквивалентен HTTP-состоянию 411. Элемент LengthRequired указывает, что требуемый заголовок длины Content отсутствует. HttpStatusCode.PreconditionFailed - Эквивалентен HTTP-состоянию 412. Элемент PreconditionFailed указывает, что условие, установленное для этого запроса, не соблюдено и запрос не может быть выполнен. Условия задаются с помощью заголовков условного запроса, таких как If-Match, If-None-Match или If-Unmodified-Since. HttpStatusCode.RequestEntityTooLarge - Эквивалентен HTTP-состоянию 413. Элемент RequestEntityTooLarge указывает, что запрос слишком велик для сервера, чтобы быть обработанным. HttpStatusCode.RequestUriTooLong - Эквивалентен HTTP-состоянию 414. Элемент RequestUriTooLong указывает, что URI слишком длинный. HttpStatusCode.UnsupportedMediaType - Эквивалентен HTTP-состоянию 415. Элемент UnsupportedMediaType указывает, что запрос этого типа не поддерживается. HttpStatusCode.RequestedRangeNotSatisfiable - Эквивалентен HTTP-состоянию 416. Элемент RequestedRangeNotSatisfiable указывает, что диапазон данных, запрашиваемых из ресурса, не может быть возвращен из-за расположения начала диапазона до начала ресурса или из-за расположения конца диапазона после конца ресурса. HttpStatusCode.ExpectationFailed - Эквивалентен HTTP-состоянию 417. Элемент ExpectationFailed указывает, что ожидание, заданное в заголовке Expect, не может быть выполнено сервером. HttpStatusCode.InternalServerError - Эквивалентен HTTP-состоянию 500. Элемент InternalServerError указывает, что на сервере произошла групповая ошибка. HttpStatusCode.NotImplemented - Эквивалентен HTTP-состоянию 501. Элемент NotImplemented указывает, что сервер не поддерживает запрашиваемую функцию. HttpStatusCode.BadGateway - Эквивалентен HTTP-состоянию 502. Элемент BadGateway указывает, что промежуточный прокси-сервер получил неправильный ответ от другого прокси или исходного сервера. HttpStatusCode.ServiceUnavailable - Эквивалентен HTTP-состоянию 503. Элемент ServiceUnavailable указывает, что сервер временно недоступен, обычно из-за высокой нагрузки или из-за выполняемого обслуживания. HttpStatusCode.GatewayTimeout - Эквивалентен HTTP-состоянию 504. Элемент GatewayTimeout указывает, что промежуточный прокси-сервер простаивает, ожидая ответа от другого прокси или исходного сервера. HttpStatusCode.HttpVersionNotSupported - Эквивалентен HTTP-состоянию 505. Элемент HttpVersionNotSupported указывает, что запрашиваемая версия протокола HTTP не поддерживается сервером. Всё вышеописанное реально использовать в своих целях, в каких? А на какие вам хватит фантазии? Менее практично и полезно ещё одно свойство HTTP-ответа сервера, но тем не менее оно всё же иногда используется. Для получения описания состояния HTTP, можно обратиться к свойству HttpWebResponse.StatusDescription, который вернёт строку описания: Код: HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create("http://kbyte.ru"); HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse(); Console.WriteLine("{0}: {1}", myHttpWebResponse.StatusCode.ToString(), myHttpWebResponse.StatusDescription); Более подробная информация есть на MSDN, советую обратиться к его поиску, спасибо за внимание.. (цЫ) E1B0 aka 11$ (www.xaker.name)