This stream does not support seek operations. HttpWebResponse
Asked Answered
G

3

6

I'm making a program which downloads files over http.

I've got it downloading, however I want to be able to pause the downloads, close the program and resume them again at a later date.

I know the location i'm downloading them from supports this.

I'm downloading the file through HttpWebResponse and reading the response into a Stream using GetResponseStream.

When i close the app and restart it, I'm stuck as to how resume the download. I've tried doing a seek on the stream but it states its not supported.

What would be the best way to do this?

Glory answered 17/9, 2009 at 19:55 Comment(1)
Keep in mind that you have to tell the server to restart - you can't just do it yourself. That's the reason for the AddRange answer below.Premarital
T
9

If the server supports this you have to send the Range Http header with your request using the AddRange method:

request.AddRange(1024);

This will instruct the server to start sending the file after the 1st kilobyte. Then just read the response stream as normal.

To test if a server supports resuming you can send a HEAD request and test if it sends the Accept-Ranges: bytes header.

Turbosupercharger answered 17/9, 2009 at 20:0 Comment(1)
Do you want to add a link to the MSDN docs? msdn.microsoft.com/en-us/library/…Bornstein
T
3

How about an HTTPRangeStream class?

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;

namespace Ionic.Kewl
{
    public class HTTPRangeStream : Stream
    {
        private string url;
        private long length;
        private long position;
        private long totalBytesRead;
        private int totalReads;

        public HTTPRangeStream(string URL)
        {
            url = URL;
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
            HttpWebResponse result = (HttpWebResponse)request.GetResponse();
            length = result.ContentLength;
        }

        public long TotalBytesRead    { get { return totalBytesRead; } }
        public long TotalReads        { get { return totalReads; } }
        public override bool CanRead  { get { return true; } }
        public override bool CanSeek  { get { return true; } }
        public override bool CanWrite { get { return false; } }
        public override long Length   { get { return length; } }

        public override bool CanTimeout
        {
            get
            {
                return base.CanTimeout;
            }
        }


        public override long Position
        {
            get
            {
                return position;
            }
            set
            {
                if (value < 0) throw new ArgumentException();
                position = value;
            }
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            switch (origin)
            {
                case SeekOrigin.Begin:
                    position = offset;
                    break;
                case SeekOrigin.Current:
                    position += offset;
                    break;
                case SeekOrigin.End:
                    position = Length + offset;
                    break;
                default:
                    break;
            }
            return Position;
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
            request.AddRange(Convert.ToInt32(position), Convert.ToInt32(position) + count);
            HttpWebResponse result = (HttpWebResponse)request.GetResponse();
            using (Stream stream = result.GetResponseStream())
            {
                stream.Read(buffer, offset, count);
                stream.Close();
            }
            totalBytesRead += count;
            totalReads++;
            Position += count;
            return count;
        }


        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new NotSupportedException();
        }

        public override void SetLength(long value)
        {
            throw new NotSupportedException();
        }
        public override void Flush()
        {
            throw new NotSupportedException();
        }

    }
}
Tyra answered 18/9, 2009 at 1:35 Comment(3)
Does this class work even if the server does not accept resuming?Fula
I don't know what "resuming" is. HTTP 1.1 defines a Range header, which this class relies on. if the server does HTTP 1.1, then it does Range, and can be contacted by this class.Tyra
by resuming i meant range header, thanks for the clarificationFula
S
0

Your solution is fine, but it will only work for the cases where the server sends a Content-Length header. This header will not be present in dynamically generated content.

Also, this solution is send a request for each Read. If the content changes on the server between the requests, then you will get inconsistent results.

I would improve upon this, by storing the data locally - either on disk or in memory. Then, you can seek into it all you want. There wont be any problem of inconsistency, and you need only one HttpWebRequest to download it.

Similarity answered 19/9, 2009 at 3:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.