Skip to content

Commit 2d62cc8

Browse files
authored
Merge pull request #35 from dotnet-campus/t/lindexi/request
加入慢网测试
2 parents b861975 + 6913064 commit 2d62cc8

File tree

3 files changed

+235
-12
lines changed

3 files changed

+235
-12
lines changed

src/FileDownloader.Tests/SegmentFileDownloaderTest.cs

Lines changed: 129 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
using System;
2+
using System.Diagnostics;
23
using System.IO;
34
using System.Net;
5+
using System.Net.Cache;
6+
using System.Runtime.CompilerServices;
7+
using System.Runtime.Serialization;
8+
using System.Threading;
49
using System.Threading.Tasks;
510
using dotnetCampus.FileDownloader;
611
using Microsoft.Extensions.Logging;
@@ -34,6 +39,83 @@ public void CreateWebRequest()
3439

3540
mock.Verify(downloader => downloader.CreateWebRequest(It.IsAny<string>()), Times.AtLeastOnce);
3641
});
42+
43+
"测试进入慢网环境下载,能成功下载文件".Test(() =>
44+
{
45+
var url = $"https://blog.lindexi.com";
46+
var file = new FileInfo(Path.GetTempFileName());
47+
var slowlySegmentFileDownloader = new SlowSegmentFileDownloader(url, file);
48+
var task = slowlySegmentFileDownloader.DownloadFileAsync();
49+
50+
Task.WaitAny(task, Task.Delay(TimeSpan.FromSeconds(20)));
51+
52+
if (task.IsCompleted)
53+
{
54+
file.Refresh();
55+
Assert.AreEqual(100, file.Length);
56+
}
57+
else
58+
{
59+
// 测试设备太渣
60+
}
61+
});
62+
}
63+
64+
class SlowStream : Stream
65+
{
66+
public override void Flush()
67+
{
68+
}
69+
70+
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
71+
{
72+
return Task.FromResult(Read(buffer, offset, count));
73+
}
74+
75+
public override int Read(byte[] buffer, int offset, int count)
76+
{
77+
Thread.Sleep(100);
78+
buffer[0] = 0xFF;
79+
return 1;
80+
}
81+
82+
public override long Seek(long offset, SeekOrigin origin)
83+
{
84+
return offset;
85+
}
86+
87+
public override void SetLength(long value)
88+
{
89+
throw new NotImplementedException();
90+
}
91+
92+
public override void Write(byte[] buffer, int offset, int count)
93+
{
94+
throw new NotImplementedException();
95+
}
96+
97+
public override bool CanRead => true;
98+
public override bool CanSeek => false;
99+
public override bool CanWrite => false;
100+
public override long Length => 100;
101+
public override long Position { get; set; }
102+
}
103+
104+
class SlowSegmentFileDownloader : SegmentFileDownloader
105+
{
106+
public SlowSegmentFileDownloader(string url, FileInfo file, ILogger<SegmentFileDownloader>? logger = null, IProgress<DownloadProgress>? progress = null, ISharedArrayPool? sharedArrayPool = null, int bufferLength = UInt16.MaxValue, TimeSpan? stepTimeOut = null) : base(url, file, logger, progress, sharedArrayPool, bufferLength, stepTimeOut)
107+
{
108+
}
109+
110+
protected override WebRequest CreateWebRequest(string url)
111+
{
112+
var fakeWebResponse = new FakeWebResponse()
113+
{
114+
Stream = new SlowStream()
115+
};
116+
117+
return new FakeHttpWebRequest(fakeWebResponse);
118+
}
37119
}
38120

39121
class FakeSegmentFileDownloader : SegmentFileDownloader
@@ -49,21 +131,64 @@ public FakeSegmentFileDownloader(IMockSegmentFileDownloader mockSegmentFileDownl
49131

50132
public IMockSegmentFileDownloader MockSegmentFileDownloader { get; }
51133

52-
protected override HttpWebRequest CreateWebRequest(string url)
134+
protected override WebRequest CreateWebRequest(string url)
53135
{
54136
return MockSegmentFileDownloader.CreateWebRequest(url);
55137
}
56138

57-
protected override HttpWebRequest OnWebRequestSet(HttpWebRequest webRequest)
139+
protected override WebRequest OnWebRequestSet(WebRequest webRequest)
58140
{
59141
return MockSegmentFileDownloader.OnWebRequestSet(webRequest);
60142
}
61143
}
62144

63145
public interface IMockSegmentFileDownloader
64146
{
65-
HttpWebRequest CreateWebRequest(string url);
66-
HttpWebRequest OnWebRequestSet(HttpWebRequest webRequest);
147+
WebRequest CreateWebRequest(string url);
148+
WebRequest OnWebRequestSet(WebRequest webRequest);
149+
}
150+
}
151+
152+
class FakeHttpWebRequest : WebRequest
153+
{
154+
public FakeHttpWebRequest(FakeWebResponse fakeWebResponse)
155+
{
156+
FakeWebResponse = fakeWebResponse;
157+
}
158+
159+
public override Task<WebResponse> GetResponseAsync()
160+
{
161+
return Task.FromResult(GetResponse());
162+
}
163+
164+
public override string Method { get; set; }
165+
public override RequestCachePolicy CachePolicy { get; set; }
166+
public override string ConnectionGroupName { get; set; }
167+
public override long ContentLength { get; set; }
168+
public override string ContentType { get; set; }
169+
public override ICredentials Credentials { get; set; }
170+
public override WebHeaderCollection Headers { get; set; }
171+
public override bool PreAuthenticate { get; set; }
172+
public override Uri RequestUri { get; }
173+
public override int Timeout { get; set; }
174+
public override bool UseDefaultCredentials { get; set; }
175+
176+
private FakeWebResponse FakeWebResponse { get; }
177+
178+
public override WebResponse GetResponse()
179+
{
180+
return FakeWebResponse;
181+
}
182+
}
183+
184+
class FakeWebResponse : WebResponse
185+
{
186+
public Stream Stream { set; get; }
187+
public override long ContentLength => Stream.Length;
188+
189+
public override Stream GetResponseStream()
190+
{
191+
return Stream;
67192
}
68193
}
69194
}

src/dotnetCampus.FileDownloader/SegmentFileDownloader.cs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Net;
55
using System.Threading;
66
using System.Threading.Tasks;
7+
using dotnetCampus.FileDownloader.Utils;
78
using dotnetCampus.Threading;
89
using Microsoft.Extensions.Logging;
910

@@ -200,7 +201,7 @@ public async Task DownloadFileAsync()
200201
if (supportSegment)
201202
{
202203
// 先根据文件的大小,大概是 1M 让一个线程下载,至少需要开两个线程,最多是 10 个线程
203-
threadCount = Math.Max(Math.Min(2, (int) (contentLength / 1024 / 1024)), MaxThreadCount);
204+
threadCount = Math.Min(MaxThreadCount, Math.Max(2, (int) (contentLength / 1024 / 1024)));
204205
}
205206
else
206207
{
@@ -253,18 +254,18 @@ public async Task DownloadFileAsync()
253254
}
254255

255256
/// <summary>
256-
/// 通过 Url 创建出对应的 HttpWebRequest 实例
257+
/// 通过 Url 创建出对应的 <see cref="WebRequest"/> 实例
257258
/// </summary>
258259
/// <param name="url"></param>
259260
/// <returns></returns>
260-
protected virtual HttpWebRequest CreateWebRequest(string url) => (HttpWebRequest) WebRequest.Create(url);
261+
protected virtual WebRequest CreateWebRequest(string url) => (WebRequest) WebRequest.Create(url);
261262

262263
/// <summary>
263-
/// 在 <see cref="HttpWebRequest"/> 经过了应用设置之后调用,应用的设置包括下载的 Range 等值,调用这个方法之后的下一步将会是使用这个方法的返回值去下载文件
264+
/// 在 <see cref="WebRequest"/> 经过了应用设置之后调用,应用的设置包括下载的 Range 等值,调用这个方法之后的下一步将会是使用这个方法的返回值去下载文件
264265
/// </summary>
265266
/// <param name="webRequest"></param>
266267
/// <returns></returns>
267-
protected virtual HttpWebRequest OnWebRequestSet(HttpWebRequest webRequest) => webRequest;
268+
protected virtual WebRequest OnWebRequestSet(WebRequest webRequest) => webRequest;
268269

269270
/// <summary>
270271
/// 这是给我自己开发调试用的
@@ -277,7 +278,7 @@ private void LogDebugInternal(string message, params object[] args)
277278
_logger.LogDebug(message, args);
278279
}
279280

280-
private async Task<WebResponse?> GetWebResponseAsync(Action<HttpWebRequest>? action = null)
281+
private async Task<WebResponse?> GetWebResponseAsync(Action<WebRequest>? action = null)
281282
{
282283
var id = Interlocked.Increment(ref _idGenerator);
283284

@@ -297,7 +298,12 @@ private void LogDebugInternal(string message, params object[] args)
297298
// 即使下载速度再慢,只有要在下载,也不能算超时
298299
// 如果下载 BufferLength 长度 默认 65535 字节时间超过 10 秒,基本上也断开也差不多
299300
webRequest.Timeout = (int) StepTimeOut.TotalMilliseconds;
300-
webRequest.ReadWriteTimeout = (int) StepTimeOut.TotalMilliseconds;
301+
302+
if (webRequest is HttpWebRequest httpWebRequest)
303+
{
304+
// ReadWriteTimeout设置的是从建立连接开始,到下载数据完毕所历经的时间
305+
httpWebRequest.ReadWriteTimeout = (int) StepTimeOut.TotalMilliseconds;
306+
}
301307

302308
LogDebugInternal("[GetWebResponseAsync] [{0}] Enter action.", id);
303309
action?.Invoke(webRequest);
@@ -367,7 +373,9 @@ protected virtual Task<WebResponse> GetResponseAsync(WebRequest request)
367373
// 为什么不使用 StartPoint 而是使用 CurrentDownloadPoint 是因为需要处理重试
368374

369375
var response = await GetWebResponseAsync(webRequest =>
370-
webRequest.AddRange(downloadSegment.CurrentDownloadPoint, downloadSegment.RequirementDownloadPoint));
376+
{
377+
webRequest.AddRange(downloadSegment.CurrentDownloadPoint, downloadSegment.RequirementDownloadPoint);
378+
});
371379
return response;
372380
}
373381

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using System;
2+
using System.Globalization;
3+
using System.Net;
4+
5+
namespace dotnetCampus.FileDownloader.Utils
6+
{
7+
static class WebRequestExtension
8+
{
9+
public static void AddRange(this WebRequest webRequest, int from, int to)
10+
{
11+
webRequest.AddRange("bytes", from, to);
12+
}
13+
14+
15+
public static void AddRange(this WebRequest webRequest, long from, long to)
16+
{
17+
webRequest.AddRange("bytes", from, to);
18+
}
19+
20+
public static void AddRange(this WebRequest webRequest, string rangeSpecifier, long from, long to)
21+
{
22+
if (rangeSpecifier == null)
23+
{
24+
throw new ArgumentNullException(nameof(rangeSpecifier));
25+
}
26+
if ((from < 0) || (to < 0))
27+
{
28+
throw new ArgumentOutOfRangeException(from < 0 ? nameof(from) : nameof(to), "Range 太小了");
29+
}
30+
if (from > to)
31+
{
32+
throw new ArgumentOutOfRangeException(nameof(from), "传入的 From 比 To 大");
33+
}
34+
35+
if (!AddRange(webRequest,rangeSpecifier, from.ToString(NumberFormatInfo.InvariantInfo), to.ToString(NumberFormatInfo.InvariantInfo)))
36+
{
37+
throw new InvalidOperationException();
38+
}
39+
}
40+
41+
public static void AddRange(this WebRequest webRequest, string rangeSpecifier, int range)
42+
{
43+
webRequest.AddRange(rangeSpecifier, (long) range);
44+
}
45+
46+
public static void AddRange(this WebRequest webRequest, string rangeSpecifier, long range)
47+
{
48+
if (rangeSpecifier == null)
49+
{
50+
throw new ArgumentNullException(nameof(rangeSpecifier));
51+
}
52+
53+
if (!AddRange(webRequest,rangeSpecifier, range.ToString(NumberFormatInfo.InvariantInfo), (range >= 0) ? "" : null))
54+
{
55+
throw new InvalidOperationException();
56+
}
57+
}
58+
59+
private static bool AddRange(WebRequest webRequest,string rangeSpecifier, string from, string? to)
60+
{
61+
var webHeaderCollection = webRequest.Headers;
62+
string? curRange = webHeaderCollection[HttpKnownHeaderNames.Range];
63+
64+
if ((curRange == null) || (curRange.Length == 0))
65+
{
66+
curRange = rangeSpecifier + "=";
67+
}
68+
else
69+
{
70+
if (!string.Equals(curRange.Substring(0, curRange.IndexOf('=')), rangeSpecifier, StringComparison.OrdinalIgnoreCase))
71+
{
72+
return false;
73+
}
74+
curRange = string.Empty;
75+
}
76+
curRange += @from;
77+
if (to != null)
78+
{
79+
curRange += "-" + to;
80+
}
81+
webHeaderCollection[HttpKnownHeaderNames.Range] = curRange;
82+
return true;
83+
}
84+
85+
internal static class HttpKnownHeaderNames
86+
{
87+
public const string Range = "Range";
88+
}
89+
}
90+
}

0 commit comments

Comments
 (0)