Классы WebRequest и WebResponse

194

Класс WebRequest представляет запрос информации для отправки по определенному URI. Идентификатор URI передается в качестве параметра методу Create(). Класс WebResponse представляет данные, извлекаемые из сервера. Вызов метода WebRequest.GetResponse() на самом деле приводит к отправке запроса веб-серверу и к созданию объекта WebResponse для просмотра возвращенных данных. Как и в случае объекта WebClient, можно получить поток для представления данных, но для этого должен использоваться метод WebResponse.GetResponseStream().

Сначала рассмотрим класс WebRequest:

Методы и свойства класса 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.

Методы и свойства класса WebResponse
Методы и свойства Описание
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 и WebResponse

Подключаемые протоколы

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>
Использование FileWebRequest и FileWebResponse

Чтение из файлов

Обработчик щелчка по кнопке "Открыть" открывает файл и записывает содержание файла в многострочное текстовое поле. Передадим имя файла методу 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.

Пройди тесты
Лучший чат для C# программистов