Загрузка отслеживания прогресса HTTP

У меня есть приложение WPF. Я пишу эти файлы сообщений в одну из социальных сетей. Загрузите себя, работая отлично, но я хотел бы указать, насколько далеко я продвигаюсь с загрузкой.

Я попробовал несколько способов сделать это:

1) Метод HttpWebRequest.GetStream:

using (
 var FS = File.Open(
  localFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    long len = FS.Length;
    HttpWebRequest request = (HttpWebRequest) WebRequest.Create(url);
    request.Method = "POST";
    request.ProtocolVersion = HttpVersion.Version11;
    request.ContentType = "multipart/form-data; boundary=--AaB03x";
    //predata and postdata is two byte[] arrays, that contains
    //strings for MIME file upload (defined above and is not important)
    request.ContentLength = predata.Length + FS.Length + postdata.Length;
    request.AllowWriteStreamBuffering = false;
    using (var reqStream = request.GetRequestStream())
    {
        reqStream.Write(predata, 0, predata.Length);
        int bytesRead = 0;
        int totalRead = 0;
        do
        {
            bytesRead = FS.Read(fileData, 0, MaxContentSize);
            totalRead += bytesRead;
            reqStream.Write(fileData, 0, bytesRead);
            reqStream.Flush(); //trying with and without this
            //this part will show progress in percents
            sop.prct = (int) ((100*totalRead)/len);
        } while (bytesRead > 0);
        reqStream.Write(postdata, 0, postdata.Length);
    }
    HttpWebResponse responce = (HttpWebResponse) request.GetResponse();
    using (var respStream = responce.GetResponseStream())
    {
        //do things
    }
}

2) Способ WebClient (намного короче):

void UploadFile (url, localFilePath)
{
    ...
    WebClient client = new WebClient();
    client.UploadProgressChanged += new UploadProgressChangedEventHandler(UploadPartDone);
    client.UploadFileCompleted += new UploadFileCompletedEventHandler(UploadComplete);
    client.UploadFileAsync(new Uri(url), localFilePath);
    done.WaitOne();

    //do things with responce, received from UploadComplete
    JavaScriptSerializer jssSer = new JavaScriptSerializer();
    return jssSer.Deserialize<UniversalJSONAnswer>(utf8.GetString(UploadFileResponce));
    //so on...
    ...
}

void UploadComplete(object sender, UploadFileCompletedEventArgs e)
{
    UploadFileResponce=e.Result;
    done.Set();
}

void UploadPartDone(object sender, UploadProgressChangedEventArgs e)
{
    //this part expected to show progress
    sop.prct=(int)(100*e.BytesSent/e.TotalBytesToSend);
}

3) Даже способ TcpClient:

using (
 var FS = File.Open(
  localFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    long len = FS.Length;
    long totalRead = 0;
    using (var client = new TcpClient(urli.Host, urli.Port))
    {
        using (var clearstream = client.GetStream())
        {
            using (var writer = new StreamWriter(clearstream))
            using (var reader = new StreamReader(clearstream))
            {
                //set progress to 0
                sop.prct = 0;
                // Send request headers
                writer.WriteLine("POST " + urli.AbsoluteUri + " HTTP/1.1");
                writer.WriteLine("Content-Type: multipart/form-data; boundary=--AaB03x");
                writer.WriteLine("Host: " + urli.Host);
                writer.WriteLine("Content-Length: " + (predata.Length + len + postdata.Length).ToString());
                writer.WriteLine();
                //some data for MIME
                writer.Write(utf8.GetString(predata));
                writer.Flush();
                int bytesRead;
                do
                {
                    bytesRead = FS.Read(fileData, 0, MaxContentSize);
                    totalRead += bytesRead;
                    writer.BaseStream.Write(fileData, 0, bytesRead);
                    writer.BaseStream.Flush();
                    sop.prct = (int) ((100*totalRead)/len);
                } while (bytesRead > 0)
                writer.Write(utf8.GetString(postdata));
                writer.Flush();
                //read line of response and do other thigs...
                respStr = reader.ReadLine();
                ...
            }
        }
    }
}

Во всех случаях файл был успешно отправлен на сервер. Но всегда прогресс выглядит так: в течение нескольких секунд он работает от 0 до 100, а затем ждет, пока файл не будет загружен (около 5 минут - файл 400 МБ).

Итак, я думаю, что данные из файла буферизуются где-то, и я отслеживаю не загрузку, а буферизацию данных. И затем нужно подождать, пока он не будет загружен.

Мои вопросы:

1) Есть ли способ отслеживать фактическую загрузку данных? То, что метод Stream.Write() или Flush() (который, как я читал где-то, не работает для NetworkStream), не возвращался, пока не получит подтверждение от сервера, что полученные пакеты TCP.

2) Или я могу отказаться от буферизации (AllowWriteStreamBUffering для HttpWebRequest не работает)?

3) И имеет ли смысл идти дальше "вниз" и попробовать с помощью сокетов?

обновление:

Чтобы избежать каких-либо сомнений в отношении отображения прогресса в пользовательском интерфейсе, я переписал код для регистрации файла. так что вот код:

using (var LogStream=File.Open("C:\\123.txt",FileMode.Create,FileAccess.Write,FileShare.Read))
using (var LogWriter=new StreamWriter(LogStream))
using (var FS = File.Open(localFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    long len = FS.Length;
    HttpWebRequest request = (HttpWebRequest) WebRequest.Create(url);
    request.Timeout = 7200000; //2 hour timeout
    request.Method = "POST";
    request.ProtocolVersion = HttpVersion.Version11;
    request.ContentType = "multipart/form-data; boundary=--AaB03x";
    //predata and postdata is two byte[] arrays, that contains
    //strings for MIME file upload (defined above and is not important)
    request.ContentLength = predata.Length + FS.Length + postdata.Length;
    request.AllowWriteStreamBuffering = false;
    LogWriter.WriteLine(DateTime.Now.ToString("o") + " Start write into request stream. ");
    using (var reqStream = request.GetRequestStream())
    {
        reqStream.Write(predata, 0, predata.Length);
        int bytesRead = 0;
        int totalRead = 0;
        do
        {
            bytesRead = FS.Read(fileData, 0, MaxContentSize);
            totalRead += bytesRead;
            reqStream.Write(fileData, 0, bytesRead);
            reqStream.Flush(); //trying with and without this
            //sop.prct = (int) ((100*totalRead)/len); //this part will show progress in percents
            LogWriter.WriteLine(DateTime.Now.ToString("o") + " totalRead= " + totalRead.ToString() + " / " + len.ToString());
        } while (bytesRead > 0);
        reqStream.Write(postdata, 0, postdata.Length);
    }
    LogWriter.WriteLine(DateTime.Now.ToString("o") + " All sent!!! Waiting for responce... ");
    LogWriter.Flush();
    HttpWebResponse responce = (HttpWebResponse) request.GetResponse();
    LogWriter.WriteLine(DateTime.Now.ToString("o") + " Responce received! ");
    using (var respStream = responce.GetResponseStream())
    {
        if (respStream == null) return null;
        using (var streamReader = new StreamReader(respStream))
        {
            string resp = streamReader.ReadToEnd();
            JavaScriptSerializer jssSer = new JavaScriptSerializer();
            return jssSer.Deserialize<UniversalJSONAnswer>(resp);
        }
    }
}

и вот результат (я срезаю середину):

2011-11-19T22:00:54.5964408+04:00 Start write into request stream. 
2011-11-19T22:00:54.6404433+04:00 totalRead= 1048576 / 410746880
2011-11-19T22:00:54.6424434+04:00 totalRead= 2097152 / 410746880
2011-11-19T22:00:54.6434435+04:00 totalRead= 3145728 / 410746880
2011-11-19T22:00:54.6454436+04:00 totalRead= 4194304 / 410746880
2011-11-19T22:00:54.6464437+04:00 totalRead= 5242880 / 410746880
2011-11-19T22:00:54.6494438+04:00 totalRead= 6291456 / 410746880
.......    
2011-11-19T22:00:55.3434835+04:00 totalRead= 408944640 / 410746880
2011-11-19T22:00:55.3434835+04:00 totalRead= 409993216 / 410746880
2011-11-19T22:00:55.3464837+04:00 totalRead= 410746880 / 410746880
2011-11-19T22:00:55.3464837+04:00 totalRead= 410746880 / 410746880
2011-11-19T22:00:55.3464837+04:00 All sent!!! Waiting for responce... 
2011-11-19T22:07:23.0616597+04:00 Responce received! 

как вы можете видеть, программа думает, что она загрузила ~ 400 МБ в течение примерно 2 секунд. И через 7 минут файл фактически загружается, и я получаю ответ.

обновлено:

Кажется, это происходит под WIndows 7 (не shure около x64 или x86). Когда я запускаю свой код uder XP, все работает отлично, и прогресс отображается абсолютно правильно

Ответы

Ответ 1

это больше, чем год с момента публикации этого вопроса, но я думаю, что мой пост может быть полезен для кого-то.

У меня была та же проблема с показом прогресса, и он вел себя так, как вы описали. Поэтому я решил использовать HttpClient, который показывает правильность загрузки. Затем я столкнулся с интересной ошибкой - когда я запустил Fiddler, HttpClient начал демонстрировать свой прогресс загрузки неожиданным образом, как в WebClient/HttpWebRequest, поэтому я подумал, может быть, это была проблема того, почему WebClient не показывал загрузочные программы не правильно (я думаю, он был запущен). Поэтому я снова попытался с WebClient (без запуска скриптовых приложений), и все работает так, как должно, загружать прогресс имеет правильные значения. Я тестировал несколько ПК с win7 и XP, и во всех случаях прогресс показывался правильно.

Итак, я думаю, что такая программа, как Fiddler (возможно, не только скрипач), влияет на то, как WebClient и другие .net-классы показывают прогресс загрузки.

это обсуждение одобряет это:

HttpWebRequest не работает, кроме случаев, когда работает скрипач

Ответ 2

Вы можете использовать WebClient UploadFile для загрузки файла, а не для записи файла в виде потока файлов. Чтобы отслеживать процент полученных и загруженных данных, вы можете использовать UploadFileAsyn и подписаться на его события.

В приведенном ниже коде я использовал UploadFileAsyn для загрузки файлов синхронно, но он не должен быть синхронным, поскольку вы не размещаете экземпляр загрузчика.

class FileUploader : IDisposable
{
    private readonly WebClient _client;
    private readonly Uri _address;
    private readonly string _filePath;
    private bool _uploadCompleted;
    private bool _uploadStarted;
    private bool _status;

    public FileUploader(string address, string filePath)
    {
        _client = new WebClient();
        _address = new Uri(address);
        _filePath = filePath;
        _client.UploadProgressChanged += FileUploadProgressChanged;
        _client.UploadFileCompleted += FileUploadFileCompleted;
    }

    private void FileUploadFileCompleted(object sender, UploadFileCompletedEventArgs e)
    {
        _status = (e.Cancelled || e.Error == null) ? false : true;
        _uploadCompleted = true;
    }

    private void FileUploadProgressChanged(object sender, UploadProgressChangedEventArgs e)
    {
        if(e.ProgressPercentage % 10 == 0)
        {
            //This writes the pecentage data uploaded and downloaded
            Console.WriteLine("Send: {0}, Received: {1}", e.BytesSent, e.BytesReceived);
            //You can have a delegate or a call back to update your UI about the percentage uploaded
            //If you don't have the condition (i.e e.ProgressPercentage % 10 == 0 )for the pecentage of the process 
            //the callback will slow you upload process down
        }
    }

    public bool Upload()
    {

        if (!_uploadStarted)
        {
            _uploadStarted = true;
            _client.UploadFileAsync(_address, _filePath);
        }
        while (!_uploadCompleted)
        {
            Thread.Sleep(1000);
        }
        return _status;
    }

    public void Dispose()
    {
        _client.Dispose();
    }
}

Код клиента:

            using (FileUploader uploader = new FileUploader("http://www.google.com", @"C:\test.txt"))
        {
            uploader.Upload();
        }

Вы можете зарегистрировать собственный обратный вызов (может быть делегатом) в обработчике события FileUploadProgressChanged для обновления вашего интерфейса WPF.

Процесс изменения загрузки изменился, вызывается чаще, если ваш обратный вызов для события делает IO, что замедляет процесс загрузки. Лучше всего иметь редкое обновление, например. следующее обновление кода обновляется только на 10%.

    private int _percentageDownloaded;

    private void FileUploadProgressChanged(object sender, UploadProgressChangedEventArgs e)
    {
        if (e.ProgressPercentage % 10 == 0 && e.ProgressPercentage > _percentageDownloaded)
        {

            _percentageDownloaded = e.ProgressPercentage;
            //Any callback instead of printline
            Console.WriteLine("Send: {0} Received: {1}", e.BytesSent, e.BytesReceived);
        }
    }

Ответ 3

Мое предложение состоит в том, чтобы использовать новый класс HTTPClient (доступный в .NET 4.5). Он поддерживает прогресс.

Эта статья очень помогла мне в этом: http://www.strathweb.com/2012/06/drag-and-drop-files-to-wpf-application-and-asynchronously-upload-to-asp-net-web-api/

Мой код для загрузки файла:

    private void HttpSendProgress(object sender, HttpProgressEventArgs e)
    {
        HttpRequestMessage request = sender as HttpRequestMessage;
        Console.WriteLine(e.BytesTransferred);
    }

    private void Window_Loaded_1(object sender, RoutedEventArgs e)
    {
        ProgressMessageHandler progress = new ProgressMessageHandler();
        progress.HttpSendProgress += new EventHandler<HttpProgressEventArgs>(HttpSendProgress);

        HttpRequestMessage message = new HttpRequestMessage();
        StreamContent streamContent = new StreamContent(new FileStream("e:\\somefile.zip", FileMode.Open));

        message.Method = HttpMethod.Put;
        message.Content = streamContent;
        message.RequestUri = new Uri("{Here your link}");

        var client = HttpClientFactory.Create(progress);

        client.SendAsync(message).ContinueWith(task =>
        {
            if (task.Result.IsSuccessStatusCode)
            { 

            }
        });
    }

Ответ 4

Этот меня прослушивал хотя бы один день. Я начал с использования WebClient.UploadFileAsync, затем попробовал ProgressMessageHandler для HttpClient, а затем перевернул собственный HttpContent для API HttpClient. Ни один из этих подходов не работал (для меня).

Появляется HttpWebRequest, который находится внизу большинства (всех?) абстракций .NET Http, таких как WebClient и HttpClient, по умолчанию загружает поток запросов и ответов, который я подтвердил, посмотрев на него в ILSpy.

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

Я обнаружил, что необходимо очистить поток запросов после каждого отправленного мной блока, чтобы точно отразить прогресс отправки, иначе ваши данные будут просто буферизоваться на один шаг дальше по конвейеру (возможно, где-то в NetworkStream или ОС, не проверял). Пример кода ниже работает для меня, а также выполняет минимальную работу по переводу обратно из HttpWebResponse в HttpResponseMessage (что вам может и не понадобиться, YMMV).

public async Task<HttpResponseMessage> UploadFileAsync( string uploadUrl, string absoluteFilePath, Action<int> progressPercentCallback )
    {
        var length = new FileInfo( absoluteFilePath ).Length;

        var request = new HttpWebRequest( new Uri(uploadUrl) ) {
            Method = "PUT",
            AllowWriteStreamBuffering = false,
            AllowReadStreamBuffering = false,
            ContentLength = length
        };

        const int chunkSize = 4096;
        var buffer = new byte[chunkSize];

        using (var req = await request.GetRequestStreamAsync())
        using (var readStream = File.OpenRead(absoluteFilePath))
        {
            progressPercentCallback(0);
            int read = 0;
            for (int i = 0; i < length; i += read)
            {
                read = await readStream.ReadAsync( buffer, 0, chunkSize );
                await req.WriteAsync( buffer, 0, read );
                await req.FlushAsync(); // flushing is required or else we jump to 100% very fast
                progressPercentCallback((int)(100.0 * i / length));
            }
            progressPercentCallback(100);
        }

        var response = (HttpWebResponse)await request.GetResponseAsync();
        var result = new HttpResponseMessage( response.StatusCode );
        result.Content = new StreamContent( response.GetResponseStream() );

        return result; 
    }

Ответ 5

По быстрому угадыванию вы запускаете этот код в потоке пользовательского интерфейса. Вам нужно запустить загрузку нового потока. В этот момент у вас есть 2 варианта. 1) Вы запускаете таймер в потоке пользовательского интерфейса и обновляете интерфейс. 2) Вы обновляете пользовательский интерфейс, используя Invoke (потому что вы не можете получить доступ к пользовательскому интерфейсу из другого потока) вызывает обновление интерфейса.

Ответ 6

В первом примере я думаю, что ваш индикатор выполнения показывает, как быстро вы записываете в поток из файла на диске, а не фактический уровень загрузки (вот почему все это происходит на 100% действительно быстро, а затем загружает chugs на *).

Возможно, я ошибаюсь ^^ и не имею опыта WPF, но я загрузил массивные файлы из Silverlight в WCF, а используемая там модель (как и вы) разбивает файл на блоки. Отправляйте каждый блок. Когда вы получаете ответ от сервера ( "блок 26 получил одобрение" ), обновите индикатор выполнения, как и в самом деле, вы не можете (или не должны) обновлять индикатор выполнения, если вы не знаете/что блок x сделал это, - и хороший способ узнать, что если сервер говорит, что получил его.

* Хотел бы я загрузить 400 Мб за 5 минут. Я буду принимать меня весь день...

Ответ 7

У меня была та же проблема. Я потратил много времени и решил проблему следующим образом: Антивирус AVAST. Когда я отключу его, моя программа отлично работает...