This stream does not support seek operations. Http

2019-04-28 05:16发布

问题:

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?

回答1:

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.



回答2:

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();
        }

    }
}


回答3:

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.