Can I read a file from an FTP server or HTTP server as a stream?

Is it possible to read a file via FTP, like System.IO.Stream?

using (Stream s = Ftp.OpenFile(url....))
{
    s.Seek(offset, SeekOrigin.Begin);
    int n = s.Read(...);
}

and similarly with HTTP?

using (Stream s = Http.OpenFile(url....))
{
    s.Seek(offset, SeekOrigin.Begin);
    int n = s.Read(...);
}
+3
source share
3 answers

As an exercise, I wrote some examples:

  • FtpStream
    Reads a file via FTP as a stream. Continuous reads use a single server connection. If you call Seek (), this connection is dropped and a new one is created.

  • HttpStream
    Reads a resource via HTTP (GET) as a stream. Support for searching using the Range header. Each read is a new HTTP GET. This needs to be optimized to avoid a new connection during continuous reading.

+1
source

Yes and no.

, URL- WebRequest/WebResponse, . WebRequest.Create().

+2

WebResponse:

WebRequest request = HttpWebRequest.Create("http://example.com/file.txt");
using (WebResponse response = request.GetResponse())
{
    using (StreamReader reader = new
        StreamReader(response.GetResponseStream()))
    {
        // Read the stream here
    }
}

( ) Seek, . - "" , . StreamReader.

FTP , FtpWebRequest HttpWebRequest. a WebResponse GetResponse.

FTP- REST (restart), - (.. ). HTTP, HttpWebRequest.AddRange, Range ( HTTP 1.1).


, HTTP:

public class RangedHttpWebStream : Stream
{
    private Stream realStream;
    private long startPosition;
    private long? requestedLength;
    private HttpWebRequest request;

    public RangedHttpWebStream(HttpWebRequest request)
    {
        if (request == null)
            throw new ArgumentNullException("request");
        this.request = request;
    }

    public override bool CanRead
    {
        get { return true; }
    }

    public override bool CanSeek
    {
        get { return (realStream == null); }
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override void Flush()
    {
    }

    public override long Length
    {
        get { return requestedLength ?? -1; }
    }

    public override long Position
    {
        get { return startPosition; }
        set { Seek(value, SeekOrigin.Begin); }
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        if (realStream == null)
        {
            UpdateRange();
            WebResponse response = request.GetResponse();
            realStream = response.GetResponseStream();
        }
        return realStream.Read(buffer, offset, count);
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        if (realStream != null)
            throw new InvalidOperationException("Seek cannot be performed " +
                "once reading has started.");
        switch (origin)
        {
            case SeekOrigin.Begin:
                startPosition = offset;
                break;
            case SeekOrigin.Current:
                startPosition += offset;
                break;
            default:
                throw new NotSupportedException("Seek can only be performed " +
                    "from the beginning of the stream or current position.");
        }
        return startPosition;
    }

    public override void SetLength(long value)
    {
        if (value < 0)
            throw new ArgumentOutOfRangeException("Parameter 'value' " +
                "cannot be less than zero.");
        if (value > Int32.MaxValue)
            throw new ArgumentOutOfRangeException("Parameter 'value' " +
                "cannot be greater than Int32.MaxValue.");
        requestedLength = value;
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        throw new NotSupportedException("The stream does not support writing.");
    }

    protected override void Dispose(bool disposing)
    {
        if ((disposing) && (realStream != null))
            realStream.Dispose();
        base.Dispose(disposing);
    }

    private void UpdateRange()
    {
        if (startPosition < 0)
            throw new IOException("Attempted to seek before " +
                "beginning of stream.");
        if (startPosition > Int32.MaxValue)
            throw new IOException("Attempted to seek past Int32.MaxValue.  " +
                "This is invalid for an HTTP stream.");
        if (requestedLength != null)
        {
            long endPosition = startPosition + requestedLength.Value;
            if (endPosition > Int32.MaxValue)
                throw new IOException("Attempted to read past " +
                    "Int32.MaxValue.  This is invalid for an HTTP stream.");
            request.AddRange((int)startPosition, (int)endPosition);
        }
        else
        {
            request.AddRange((int)-startPosition);
        }
    }
}

- - , / .

HttpWebRequest, :

public static class HttpExtensions
{
    public static Stream GetSmartStream(this HttpWebRequest request)
    {
        return new RangedHttpWebStream(request);
    }
}

The testing program (actually tested) is as follows:

static void Main(string[] args)
{
    var request = (HttpWebRequest)HttpWebRequest.Create(
        "http://localhost/test.txt");
    using (Stream stream = request.GetSmartStream())
    {
        stream.Seek(20, SeekOrigin.Begin);
        stream.Seek(1, SeekOrigin.Current);
        stream.SetLength(100);
        using (StreamReader reader = new StreamReader(stream))
        {
            string content = reader.ReadToEnd();
            Console.Write(content);
        }
    }
    Console.ReadLine();
}

This is basically a copy-paste operation for FTP. The corresponding request property for modifying FtpWebRequest.ContentOffset . Unlike HTTP, you cannot set the final offset, so you have to change the property SetLengthto throw NotSupportedException.

+1
source

Source: https://habr.com/ru/post/1739182/


All Articles