Sunday, April 6, 2008

Чтение HTTP в .Net и Silverlight

Ну-с, для разнообразия сугубо технический пост. Да-да, знаю, тривиально. Не нравится - не ешьте. Просто, кому-то умному, но кому это было на фиг не нужно, будет приятно увидеть что-то готовое, а не вычислять и разбираться. Или, кому-то умному, кому это пару лет не нужно было. Скажем, мне самому через пару лет. В общем, не придирайтесь, вещь тривиальная, но полезная. Даже просто для чисто бытовых потребностей, скажем там, какую онлайн библиотеку книжек скачать...

Итак, для HTTP в .Net и Silverlight есть два главных класса, которые делают примерно одно и то же: HttpWebRequest и WebClient. Пример ниже показывает как использовать их оба (есть мелкие косметические отличия).

 Итак, сначала тестовая программа, которая их будет выполнять:

using System;

namespace HttpCalls
{
     class Program
     {
          static void Main(string[] args)
          {
               HttpWebRequestBased r1 = new HttpWebRequestBased();
               r1.Get("
http://blogs.technet.com/eldar/default.aspx");
               Console.ReadLine();

               WebClientBased r2 = new WebClientBased();
               r2.Get("
http://blogs.technet.com/eldar/default.aspx");
              
Console.ReadLine();

               HttpRequestBasedSync r3 = new HttpRequestBasedSync();
               r3.Get("
http://blogs.technet.com/eldar/default.aspx");
              
Console
.ReadLine();

          }
     }
}

Теперь начнем с последнего - синхронного использования HttpWebRequest:

using System;
using System.Net;
using System.IO;

namespace HttpCalls
{
     class HttpRequestBasedSync
     {
          public void Get(string URL)
          {
               HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(URL);
               HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
               Stream stream = resp.GetResponseStream();
               StreamReader sr = new StreamReader(stream);
               string res = sr.ReadToEnd();
               sr.Close();
               Console.WriteLine("Success. Press any key to see the result:");
               Console.ReadLine();
               Console.WriteLine(res.Substring(0, 500));
          }
     }
}

Простенько и со вкусом... хм-м-м... ну, ладно, о вкусах не спорят! По крайней мере вполне компактно. Кстати, в Silverlight это дело не сработает, там синхронного GetResponseStream нету, чтобы не блокировать UI thread, на котором в Silverlight выполняется почти все интересное. Так что же делать? А делать то же самое асинхронно:

using System;
using System.Net;
using System.IO;

namespace HttpCalls
{
     class HttpWebRequestBased
     {
          public void Get(string URL)
          {
               req = (
HttpWebRequest)HttpWebRequest.Create(URL);
               req.BeginGetResponse(
new AsyncCallback(callback), null);
          }

          private void callback(IAsyncResult ar)
          {
               if (ar.IsCompleted)
               {
                    HttpWebResponse resp = (HttpWebResponse) req.GetResponse();
                    Stream stream = resp.GetResponseStream();
                    StreamReader sr = new StreamReader(stream);
                    string res = sr.ReadToEnd();
                    sr.Close();
                    Console.WriteLine("Success. Result:");
                    //Console.ReadLine();
                    Console.WriteLine(res.Substring(0,500));
               }
          }

          private HttpWebRequest req;

     }
}

К слову, callback вызывается на самом деле ровно один раз, так что не раскатывайте губу на то, чтобы показывать всякие progress bars в процессе. В Silverlight callback вызывается на UI thread, но опять же, учтите, что это может измениться, так что менять UI элементы в этом вызове лучше  избегать. Так... на всякий случай.

Ну и наконец, то же самое асинхронно с WebClient. Этот класс предполагается самым "продвинутым" и "удобынм", так что не удивляйтесь, что он требует больше всего кода.

using System;
using System.Net;
using System.IO;

namespace HttpCalls
{
     class WebClientBased
     {
          public void Get(string URL)
          {
               WebClient client = new WebClient();
               client.DownloadProgressChanged +=
new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
               client.OpenReadCompleted +=
new OpenReadCompletedEventHandler(client_OpenReadCompleted);
               client.OpenReadAsync(
new Uri(URL));
          }

          void client_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
          {
               if (e.Cancelled || e.Error != null)
               {
                    Console.WriteLine("Error reading the file");
               }
               else
               {
                    Stream stream = e.Result;
                    StreamReader sr = new StreamReader(stream);
                    string res = sr.ReadToEnd();
                    sr.Close();
                    Console.WriteLine("Success. Result:");
                    //Console.ReadLine();
                    Console.WriteLine(res.Substring(0, 500));
               }
          }

          void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
          {
               Console.WriteLine("Progress is {0}, {1}% out of {2} bytes.", e.BytesReceived, e.ProgressPercentage, e.TotalBytesToReceive);
          }
     }
}

Вызывается callback в Silverlight тоже на UI thread, по крайней мере пока что. В Silverlight у вас может случиться вызов client_DownloadProgressChanged с промежуточным прогрессом, в обычном .Net я этого не видел. Впрочем, даже если вызов и случается, вы только узнаете, сколько прочитано, доступа к реальным данным (Stream) у вас все равно нет до тех пор, пока чтение Http ответа не закончится.

В Silverlight во всех трех случаях Stream это по сути wrapper вокруг куска памяти, выделенного в native коде, так что всякие Seek() работают как часы. Очень удобно.

Заметьте, что это все на основании опубликованной информации на данный момент. Когда выйдет бета 2 Silverlight v.2, я опубликую подправленный вариант с изменениями и новыми интересными деталями.

No comments: