Классы WebRequest и WebResponse
194C# и .NET --- Сетевое программирование --- Классы WebRequest и WebResponse
Класс WebRequest представляет запрос информации для отправки по определенному URI. Идентификатор URI передается в качестве параметра методу Create(). Класс WebResponse представляет данные, извлекаемые из сервера. Вызов метода WebRequest.GetResponse() на самом деле приводит к отправке запроса веб-серверу и к созданию объекта WebResponse для просмотра возвращенных данных. Как и в случае объекта WebClient, можно получить поток для представления данных, но для этого должен использоваться метод WebResponse.GetResponseStream().
Сначала рассмотрим класс WebRequest:
Методы и свойства | Описание |
---|---|
Create() и CreateDefault() | В классе WebRequest нет открытого конструктора. Вместо конструктора для создания экземпляров класса могут использоваться статические методы Create() и CreateDefault(). Эти методы в действительности создают не объект типа WebRequest, а новый объект класса, производного от WebRequest, такого как HttpWebRequest или FileWebRequest. |
RegisterPrefix() | Используя метод RegisterPrefix(), можно зарегистрировать класс для обработки специфического протокола. Объекты этого класса будут создаваться методом WebRequest.Create(). Этот механизм называется "подключаемыми протоколами" (pluggable protocols). |
GetRequestStream() | Метод GetRequestStream() возвращает объект потока, который может использоваться для отправки некоторых данных на сервер. |
BeginGetRequestStream() и EndGetRequestStream() | Асинхронный доступ к потоку запроса выполняется методами BeginGetRequestStream() и EndGetRequestStream(). |
GetResponse() | Метод GetResponse() возвращает объект WebResponse, который может использоваться для чтения данных, полученных от сервера. |
BeginGetResponse() и EndGetResponse() | Как и для потока запроса, имеются асинхронные методы дпя получения потока ответа. |
Abort() | Если метод BeginXX() начал асинхронную обработку, ее можно остановить методом Abort(). |
RequestUri | RequestUri - свойство только для чтения, возвращающее URI, связанный с WebRequest. Этот URI может быть установлен в статическом методе Create() данного класса. |
Method | Свойство Method используется, чтобы получить или установить метод для конкретного запроса. Объект HttpWebRequest поддерживает HTTP-методы GET, POST, HEAD и т. д. |
Headers | В зависимости от используемого протокола серверу может передаваться и от сервера может получаться различная информация в заголовках. Информация заголовка протокола содержится в коллекции WebHeaderCollection, к которой можно обращаться через свойство Headers. |
ContentType и ContentLength | Тип данных, отправленных серверу, определяется в свойстве ContentType. Могут быть разные типы данных такой длины, чтобы данные могли разместиться в массиве байтов. Тип содержания обычно определяет МIМЕ-тип данных: image/jpeg, image/gif, text/html или text/xml. |
Credentials | Если серверу требуется аутентификация пользователя, удостоверения личности пользователя можно установить через свойство Credentials. |
PreAuthenticate | Для протоколов, поддерживающих предварительную аутентификацию, в свойстве PreAuthenticate можно установить значение true. По умолчанию Web-браузер сначала пытается обратиться к странице Web-сайта без аутентификации. Если Web-сайту требуется аутентификация, сервер отвечает, что для неидентифицированных пользователей доступ отклонен. Следующий запрос, выполняемый клиентом, содержит информацию аутентификации. Этого дополнительного цикла обмена можно избежать, если установить в свойстве PreAuthenticate значение true. |
Proxy | В свойстве Proxy можно установить Web-прокси, который используется для этого запроса. |
ConnectionGroupName | В свойстве ConnectionGroupName можно определить пул соединений, который должен использоваться с этим объектом WebRequest. |
Timeout | Свойство Timeout определяет время в миллисекундах, которое необходимо для ответа от сервера. По умолчанию установлено значение 100 000 млс. Если в течение этого времени сервер не отвечает, порождается исключение WebException. |
Класс WebResponse используется для чтения данных от сервера. Объект этого класса возвращается методом GetResponse(), как видно при рассмотрении класса WebRequest.
Методы и свойства | Описание |
---|---|
GetResponseStream() | Метод GetResponseStream() возвращает объект потока, который используется для чтения ответа от сервера. |
Close() | Если объект ответа больше не нужен, его следует закрыть методом Close(). |
ResponseUri | С помощью свойства ResponseUri мы можем считать URI, связанный с объектом ответа. Он может совпадать с URI объекта WebRequest, но может и отличаться, если сервер переадресовал запрос к другому ресурсу. |
Headers | Свойство Headers возвращает коллекцию WebHeaderCollection, которая включает специфичную для протокола информацию о заголовках, возвращаемых от сервера. |
Давайте рассмотрим небольшой пример использования этих классов в WPF-приложении:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition/>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="8">
<TextBlock VerticalAlignment="Center">URL-адрес</TextBlock>
<TextBox x:Name="txb_url" Margin="14,0" Width="250" VerticalContentAlignment="Center" Text="http://professorweb.ru/"/>
<Button Click="request_Click" Padding="5" Content="Получить информацию"/>
</StackPanel>
<TextBlock Margin="8" Text="Исходный код страницы: " Grid.Row="1"/>
<TextBox x:Name="txb_sourceCode" Grid.Row="2" Padding="5" Margin="8" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible"/>
<GridSplitter Grid.Row="3" HorizontalAlignment="Stretch" Height="3" Margin="8,0" Background="#aaa"/>
<TextBox x:Name="txb_serverInfo" Grid.Row="4" Padding="5" Margin="8" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible"/>
</Grid>
private void request_Click(object sender, RoutedEventArgs e)
{
// Создать объект запроса
WebRequest request = WebRequest.Create(txb_url.Text);
// Получить ответ с сервера
WebResponse response = request.GetResponse();
// Получаем поток данных из ответа
using (StreamReader stream = new StreamReader(response.GetResponseStream()))
{
// Выводим исходный код страницы
string line;
while ((line = stream.ReadLine()) != null)
txb_sourceCode.Text += line + "\n";
}
// Получаем некоторые данные о сервере
string messageServer = "Целевой URL: \t" + request.RequestUri + "\nМетод запроса: \t" + request.Method +
"\nТип полученных данных: \t" + response.ContentType + "\nДлина ответа: \t" + response.ContentLength + "\nЗаголовки";
// Получаем заголовки, используем LINQ
WebHeaderCollection whc = response.Headers;
var headers = Enumerable.Range(0, whc.Count)
.Select(p =>
{
return new
{
Key = whc.GetKey(p),
Names = whc.GetValues(p)
};
});
foreach (var item in headers)
{
messageServer += "\n " + item.Key + ":";
foreach (var n in item.Names)
messageServer += "\t" + n;
}
txb_serverInfo.Text = messageServer;
}
Подключаемые протоколы
WebRequest — это абстрактный класс, поэтому метод WebRequest.Create() не может создать объект типа WebRequest — вместо этого создается объект класса, производного от WebRequest. При передаче HTTP-запроса методу WebRequest.Create() создается объект HttpWebRequest. При передаче запроса со схемой файла создается объект FileWebRequest.
Как показано далее, схемы http, https и file предопределены в конфигурационном файле .NET, файле machine.config. Конфигурационный файл можно найти в каталоге <windows>\Microsoft.NET\Framework\<version>\CONFIG.
Набор протоколов, используемых классом WebRequest, можно расширить программно или добавив элемент в конфигурационный файл. Для поддержки нового протокола, отличного от схем http, https и file, нужно создать новый класс, производный от WebRequest, например FtpWebRequest для протокола FTP. Этот класс должен переопределить методы и свойства базового класса и в них реализовать специфичное для протокола поведение. Кроме того, требуется определить класс-инициатор (factory class), создающий объекты класса FtpWebRequest. Такой класс-инициатор, используемый классом WebRequest, должен реализовать интерфейс IWebRequestCreate. Назовем этот класс FtpWebRequestCreator. Экземпляр этого класса должен быть зарегистрирован для схемы ftp с помощью класса WebRequest:
WebRequest.RegisterPrefix("ftp", new FtpWebRequestCreator);
Если теперь схема ftp используется с методом WebRequest.Create(), создается и возвращается в программу новый экземпляр класса FtpWebRequest. Теперь объект request можно использовать для копирования файлов с FTP-cepвера и на FTP-сервер. Здесь мы не собираемся заниматься реализацией класса FtpWebRequestCreator, но вы можете это сделать самостоятельно. Для программирования FTP-клиента требуется использовать классы сокетов с соединением TCP.
FileWebRequest и FileWebResponse
Чтение и запись локальных файлов или файлов, находящихся на совместно используемых устройствах, не очень отличаются от чтения и записи файлов, расположенных на Web-серверах. Чтобы считывать и записывать файлы, используем классы FileWebRequest и FileWebResponse. Однако многие методы и свойства, определенные в базовых классах WebRequest и WebResponse, не используются в производных классах, и в документации MSDN они лишь перечисляются, как "зарезервированные для использования в будущем".
Для демонстрации возможного использования классов FileWebRequest и FileWebResponse создается простое приложение WPF, в котором имя открываемого файла можно ввести в текстовом поле, после чего файл открывается и отображается в многострочном текстовом поле. В открытый файл можно будет записать новый текст:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Открыть файл" VerticalAlignment="Center" Margin="8"/>
<TextBox x:Name="txb_fileuri" Margin="14,8" VerticalContentAlignment="Center" Grid.Column="1"/>
<Button Content="Открыть" Click="openFile_Click" Padding="5" MinWidth="120" Grid.Column="2" Margin="0,8,8,8"/>
<TextBlock Text="Записать в файл" VerticalAlignment="Center" Grid.Row="1" Margin="8"/>
<TextBox x:Name="txb_writefile" Margin="14,8" VerticalContentAlignment="Center" Grid.Row="1" Grid.Column="1"/>
<Button Content="Записать" Click="writeFile_Click" Padding="5" MinWidth="120" Grid.Row="1" Grid.Column="2" Margin="0,8,8,8"/>
<TextBox x:Name="txb_fileContent" TextWrapping="Wrap" Margin="8" Padding="5" Grid.ColumnSpan="3" Grid.Row="2"
VerticalScrollBarVisibility="Visible"/>
</Grid>
Чтение из файлов
Обработчик щелчка по кнопке "Открыть" открывает файл и записывает содержание файла в многострочное текстовое поле. Передадим имя файла методу WebRequest.Create(). Ставить схему file:// перед именем файла необязательно. Класс WebRequest создает объект Uri и использует его свойство AbsolutePath. Как указывалось ранее, класс Uri автоматически предпосылает имени файла корректную схему. Поэтому передача имени файла классу WebRequest создаст объект FileWebRequest , и требуется лишь привести его тип. Метод GetResponse() возвращает объект FileWebResponse, который сразу же используется для создания методом GetResponseStream() объекта Stream. Обычными методами класса StreamReader считывается поток. Данные всего файла считываем в строку и передаем ее в свойство Text многострочного текстового поля txb_fileContent:
private void openFile_Click(object sender, RoutedEventArgs e)
{
string filename = txb_fileuri.Text;
FileWebRequest request =
(FileWebRequest)WebRequest.Create(filename);
using (StreamReader sr = new StreamReader(request.GetResponse().GetResponseStream()))
{
txb_fileContent.Text = sr.ReadToEnd();
}
}
Запись в файлы
Для записи данных обратно в файл реализуем обработчик щелчка по кнопке "Записать". Как и раньше, создадим объект WebRequest, передавая имя файла. Теперь вместо StreamReader используем StreamWriter. Кроме этого есть еще одно существенное изменение в коде. Чтобы сделать поток "записываемым", следует установить в свойстве Method значение "PUT". По умолчанию это свойство имеет значение "GET", указывая, что поток можно только считывать:
private void writeFile_Click(object sender, RoutedEventArgs e)
{
WebRequest request = WebRequest.Create(txb_fileuri.Text);
request.Method = "PUT";
using (StreamWriter sw = new StreamWriter(request.GetRequestStream()))
{
sw.Write(txb_writefile.Text);
}
}
Асинхронные запросы страниц
Дополнительным средством класса WebRequest является способность запрашивать страницы асинхронно. Это средство существенно, потому что между отправкой запроса на хост и получением ответа может существовать ощутимая задержка. Такие методы, как WebClient.DownloadData() и WebRequest.GetResponse(), не вернут управления, пока не будет готов ответ сервера. Вряд ли захочется "замораживать" приложение на длительный период, и потому в таких сценариях лучше применять методы BeginGetResponse() и EndGetResponse().
Метод BeginGetResponse() работает асинхронно и возвращает управление практически мгновенно. "За кулисами" исполняющая среда асинхронно управляет фоновым потоком, чтобы получить ответ от сервера. Вместо возврата объекта WebResponse, метод BeginGetResponse() возвращает объект, реализующий интерфейс IAsyncResult. С этим интерфейсом можете продолжить работу или подождать, пока не станет доступным ответ, и затем вызвать EndGetResponse() для сбора результатов.
Можно также передать делегат обратного вызова в метод BeginGetResponse(). Целью делегата обратного вызова должен быть метод, возвращающий void и принимающий ссылку IAsyncResult в качестве параметра. Когда рабочий поток завершает получение результата, исполняющая среда вызывает делегат обратного вызова, чтобы проинформировать о завершении работы. Как показано в следующем коде, вызов EndGetResponse() в методе обратного вызова позволяет извлечь объект WebResponse (модифицируем первый пример):
private void request_Click(object sender, RoutedEventArgs e)
{
// Создать объект запроса
WebRequest request = WebRequest.Create(txb_url.Text);
request.BeginGetResponse(new AsyncCallback(OnResponse), request);
}
protected void OnResponse(IAsyncResult ar)
{
WebRequest request = (WebRequest)ar.AsyncState;
WebResponse response = request.EndGetResponse(ar);
// Читаем ответ
...
}
Обратите внимание, что для извлечения исходного объекта WebRequest методу BeginGetResponse() можно передать этот объект во втором параметре. Второй параметр является ссылкой на объект, и он известен как параметр состояния. Во время выполнения метода обратного вызова тот же объект состояния можно извлечь с использованием свойства AsyncState интерфейса IAsyncResult.