SeekableHttpStream.cs 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. using System.Net.Http.Headers;
  2. namespace Bmp.Core.Common.Net
  3. {
  4. public class SeekableHttpStream : Stream
  5. {
  6. private readonly string _url;
  7. private long _length;
  8. private long _position = 0;
  9. private HttpClient? _httpClient;
  10. private HttpResponseMessage? _httpResponseMessage;
  11. private HttpContent? _httpContent;
  12. private Stream? _underlyingHttpStream;
  13. public SeekableHttpStream(string url)
  14. {
  15. _url = url;
  16. Init();
  17. }
  18. private void Init()
  19. {
  20. DeInit();
  21. _httpClient = new HttpClient();
  22. var req = new HttpRequestMessage(HttpMethod.Get, _url);
  23. if (_position != 0)
  24. {
  25. req.Headers.Range ??= new();
  26. req.Headers.Range!.Ranges.Add(new RangeItemHeaderValue(_position, null));
  27. }
  28. _httpResponseMessage = _httpClient.Send(req, HttpCompletionOption.ResponseHeadersRead);
  29. _httpResponseMessage!.EnsureSuccessStatusCode();
  30. _httpContent = _httpResponseMessage.Content;
  31. if (_httpContent.Headers.ContentLength.HasValue) _length = _position + _httpContent.Headers.ContentLength.Value;
  32. _underlyingHttpStream = _httpContent.ReadAsStream();
  33. }
  34. private void DeInit()
  35. {
  36. _underlyingHttpStream?.Close();
  37. _httpContent?.Dispose();
  38. _httpClient?.Dispose();
  39. }
  40. public override void Close()
  41. {
  42. base.Close();
  43. DeInit();
  44. }
  45. public override int Read(byte[] buffer, int offset, int count)
  46. {
  47. var read = _underlyingHttpStream!.Read(buffer, offset, count);
  48. _position += read;
  49. return read;
  50. }
  51. public override long Seek(long offset, SeekOrigin origin)
  52. {
  53. switch (origin)
  54. {
  55. case SeekOrigin.Begin:
  56. _position = offset;
  57. Init();
  58. break;
  59. case SeekOrigin.Current:
  60. _position += offset;
  61. Init();
  62. break;
  63. case SeekOrigin.End:
  64. _position = _length + offset;
  65. Init();
  66. break;
  67. default: throw new ArgumentOutOfRangeException(nameof(offset));
  68. }
  69. return _position;
  70. }
  71. //-----------------------------------------------
  72. public override void Flush()
  73. {
  74. throw new NotSupportedException();
  75. }
  76. public override void SetLength(long value)
  77. {
  78. throw new NotSupportedException();
  79. }
  80. public override void Write(byte[] buffer, int offset, int count)
  81. {
  82. throw new NotSupportedException();
  83. }
  84. public override bool CanRead => true;
  85. public override bool CanSeek => true;
  86. public override bool CanWrite => false;
  87. public override long Length => _length;
  88. public override long Position { get => _position; set => Seek(value, SeekOrigin.Begin); }
  89. }
  90. }