Skip to content

Commit 753c114

Browse files
committed
Merge pull request #2 from joelverhagen/issue-1
Implement HttpClientRequester
2 parents 6d29def + 4763a5e commit 753c114

File tree

8 files changed

+656
-1
lines changed

8 files changed

+656
-1
lines changed

AngleSharp.Io.Tests/AngleSharp.Io.Tests.csproj

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<AppDesignerFolder>Properties</AppDesignerFolder>
99
<RootNamespace>AngleSharp.Io.Tests</RootNamespace>
1010
<AssemblyName>AngleSharp.Io.Tests</AssemblyName>
11-
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
11+
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
1212
<FileAlignment>512</FileAlignment>
1313
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
1414
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
@@ -40,6 +40,14 @@
4040
<HintPath>..\packages\AngleSharp.0.9.2\lib\net45\AngleSharp.dll</HintPath>
4141
<Private>True</Private>
4242
</Reference>
43+
<Reference Include="FluentAssertions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
44+
<HintPath>..\packages\FluentAssertions.4.0.0\lib\net45\FluentAssertions.dll</HintPath>
45+
<Private>True</Private>
46+
</Reference>
47+
<Reference Include="FluentAssertions.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
48+
<HintPath>..\packages\FluentAssertions.4.0.0\lib\net45\FluentAssertions.Core.dll</HintPath>
49+
<Private>True</Private>
50+
</Reference>
4351
<Reference Include="nunit.core, Version=2.6.4.14350, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
4452
<SpecificVersion>False</SpecificVersion>
4553
<HintPath>..\packages\NUnitTestAdapter.2.0.0\lib\nunit.core.dll</HintPath>
@@ -65,6 +73,9 @@
6573
<Private>False</Private>
6674
</Reference>
6775
<Reference Include="System" />
76+
<Reference Include="System.Net.Http" />
77+
<Reference Include="System.Xml" />
78+
<Reference Include="System.Xml.Linq" />
6879
</ItemGroup>
6980
<Choose>
7081
<When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">
@@ -81,6 +92,9 @@
8192
</ProjectReference>
8293
</ItemGroup>
8394
<ItemGroup>
95+
<Compile Include="Helper.cs" />
96+
<Compile Include="Network\HttpClientRequesterTests.cs" />
97+
<Compile Include="Network\ResponseTests.cs" />
8498
<Compile Include="Properties\AssemblyInfo.cs" />
8599
</ItemGroup>
86100
<ItemGroup>

AngleSharp.Io.Tests/Helper.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
namespace AngleSharp.Io.Tests
2+
{
3+
using System;
4+
using System.IO;
5+
using System.Net.NetworkInformation;
6+
using NUnit.Framework;
7+
8+
/// <summary>
9+
/// Small (but quite useable) code to enable / disable some
10+
/// test(s) depending on the current network status.
11+
/// Taken from
12+
/// http://stackoverflow.com/questions/520347/c-sharp-how-do-i-check-for-a-network-connection
13+
/// </summary>
14+
class Helper
15+
{
16+
/// <summary>
17+
/// Indicates whether any network connection is available
18+
/// Filter connections below a specified speed, as well as virtual network cards.
19+
/// Additionally writes an inconclusive message if no network is available.
20+
/// </summary>
21+
/// <returns>True if a network connection is available; otherwise false.</returns>
22+
public static Boolean IsNetworkAvailable()
23+
{
24+
if (IsNetworkAvailable(0))
25+
return true;
26+
27+
Assert.Inconclusive("No network has been detected. Test skipped.");
28+
return false;
29+
}
30+
31+
/// <summary>
32+
/// Indicates whether any network connection is available.
33+
/// Filter connections below a specified speed, as well as virtual network cards.
34+
/// </summary>
35+
/// <param name="minimumSpeed">The minimum speed required. Passing 0 will not filter connection using speed.</param>
36+
/// <returns>True if a network connection is available; otherwise false.</returns>
37+
public static Boolean IsNetworkAvailable(Int64 minimumSpeed)
38+
{
39+
if (!NetworkInterface.GetIsNetworkAvailable())
40+
return false;
41+
42+
foreach (var ni in NetworkInterface.GetAllNetworkInterfaces())
43+
{
44+
// discard because of standard reasons
45+
if ((ni.OperationalStatus != OperationalStatus.Up) ||
46+
(ni.NetworkInterfaceType == NetworkInterfaceType.Loopback) ||
47+
(ni.NetworkInterfaceType == NetworkInterfaceType.Tunnel))
48+
continue;
49+
50+
// this allow to filter modems, serial, etc.
51+
// I use 10000000 as a minimum speed for most cases
52+
if (ni.Speed < minimumSpeed)
53+
continue;
54+
55+
// discard virtual cards (virtual box, virtual pc, etc.)
56+
if ((ni.Description.IndexOf("virtual", StringComparison.OrdinalIgnoreCase) >= 0) ||
57+
(ni.Name.IndexOf("virtual", StringComparison.OrdinalIgnoreCase) >= 0))
58+
continue;
59+
60+
// discard "Microsoft Loopback Adapter", it will not show as NetworkInterfaceType.Loopback but as Ethernet Card.
61+
if (ni.Description.Equals("Microsoft Loopback Adapter", StringComparison.OrdinalIgnoreCase))
62+
continue;
63+
64+
return true;
65+
}
66+
67+
return false;
68+
}
69+
}
70+
}
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
namespace AngleSharp.Io.Tests.Network
2+
{
3+
using System;
4+
using System.Collections.Generic;
5+
using System.IO;
6+
using System.Linq;
7+
using System.Net;
8+
using System.Net.Http;
9+
using System.Text;
10+
using System.Threading;
11+
using System.Threading.Tasks;
12+
using AngleSharp.Io.Network;
13+
using AngleSharp.Network;
14+
using AngleSharp.Services.Default;
15+
using FluentAssertions;
16+
using NUnit.Framework;
17+
using NetHttpMethod = System.Net.Http.HttpMethod;
18+
using AngleSharpHttpMethod = AngleSharp.Network.HttpMethod;
19+
20+
21+
[TestFixture]
22+
public class HttpClientRequesterTests
23+
{
24+
[Test]
25+
public async Task RequestWithContent()
26+
{
27+
// ARRANGE
28+
var ts = new TestState();
29+
30+
// ACT
31+
await ts.Target.RequestAsync(ts.Request, CancellationToken.None);
32+
33+
// ASSERT
34+
ts.HttpRequestMessage.Version.Should().Be(new Version(1, 1));
35+
ts.HttpRequestMessage.Method.Should().Be(NetHttpMethod.Post);
36+
ts.HttpRequestMessage.RequestUri.Should().Be(new Uri("http://example/path?query=value"));
37+
Encoding.UTF8.GetString(ts.HttpRequestMessageContent).Should().Be("\"request\"");
38+
ts.HttpRequestMessage.Content.Headers.Select(p => p.Key).ShouldBeEquivalentTo(new[] {"Content-Type", "Content-Length"});
39+
ts.HttpRequestMessage.Content.Headers.ContentType.ToString().Should().Be("application/json");
40+
ts.HttpRequestMessage.Content.Headers.ContentLength.Should().Be(9);
41+
ts.HttpRequestMessage.Properties.Should().BeEmpty();
42+
ts.HttpRequestMessage.Headers.Select(p => p.Key).ShouldBeEquivalentTo(new[] {"User-Agent", "Cookie"});
43+
ts.HttpRequestMessage.Headers.UserAgent.ToString().Should().Be("Foo/2.0");
44+
ts.HttpRequestMessage.Headers.Single(p => p.Key == "Cookie").Value.ShouldBeEquivalentTo(new[] {"foo=bar"});
45+
}
46+
47+
[Test]
48+
public async Task RequestWithoutContent()
49+
{
50+
// ARRANGE
51+
var ts = new TestState {Request = {Content = null, Method = AngleSharpHttpMethod.Get}};
52+
53+
// ACT
54+
await ts.Target.RequestAsync(ts.Request, CancellationToken.None);
55+
56+
// ASSERT
57+
ts.HttpRequestMessage.Version.Should().Be(new Version(1, 1));
58+
ts.HttpRequestMessage.Method.Should().Be(NetHttpMethod.Get);
59+
ts.HttpRequestMessage.RequestUri.Should().Be(new Uri("http://example/path?query=value"));
60+
ts.HttpRequestMessage.Content.Should().BeNull();
61+
ts.HttpRequestMessage.Properties.Should().BeEmpty();
62+
ts.HttpRequestMessage.Headers.Select(p => p.Key).ShouldBeEquivalentTo(new[] {"User-Agent", "Cookie"});
63+
ts.HttpRequestMessage.Headers.UserAgent.ToString().Should().Be("Foo/2.0");
64+
ts.HttpRequestMessage.Headers.Single(p => p.Key == "Cookie").Value.ShouldBeEquivalentTo(new[] {"foo=bar"});
65+
}
66+
67+
[Test]
68+
public async Task ResponseWithContent()
69+
{
70+
// ARRANGE
71+
var ts = new TestState();
72+
73+
// ACT
74+
var response = await ts.Target.RequestAsync(ts.Request, CancellationToken.None);
75+
76+
// ASSERT
77+
response.Address.ShouldBeEquivalentTo(ts.Request.Address);
78+
response.StatusCode.Should().Be(ts.HttpResponseMessage.StatusCode);
79+
response.Headers.Keys.ShouldBeEquivalentTo(new[] {"Server", "X-Powered-By", "X-CSV", "Content-Type", "Content-Length"});
80+
response.Headers["Server"].Should().Be("Fake");
81+
response.Headers["X-Powered-By"].Should().Be("Magic");
82+
response.Headers["X-CSV"].Should().Be("foo, bar");
83+
response.Headers["Content-Type"].Should().Be("application/json; charset=utf-8");
84+
response.Headers["Content-Length"].Should().Be("10");
85+
new StreamReader(response.Content, Encoding.UTF8).ReadToEnd().Should().Be("\"response\"");
86+
}
87+
88+
[Test]
89+
public async Task ResponseWithoutContent()
90+
{
91+
// ARRANGE
92+
var ts = new TestState {HttpResponseMessage = {Content = null}};
93+
94+
// ACT
95+
var response = await ts.Target.RequestAsync(ts.Request, CancellationToken.None);
96+
97+
// ASSERT
98+
response.Address.ShouldBeEquivalentTo(ts.Request.Address);
99+
response.StatusCode.Should().Be(ts.HttpResponseMessage.StatusCode);
100+
response.Headers.Keys.ShouldBeEquivalentTo(new[] {"Server", "X-Powered-By", "X-CSV"});
101+
response.Headers["Server"].Should().Be("Fake");
102+
response.Headers["X-Powered-By"].Should().Be("Magic");
103+
response.Headers["X-CSV"].Should().Be("foo, bar");
104+
response.Content.Should().BeNull();
105+
}
106+
107+
[Test]
108+
public void SupportsHttp()
109+
{
110+
// ARRANGE, ACT, ASSERT
111+
new TestState().Target.SupportsProtocol("HTTP").Should().BeTrue();
112+
}
113+
114+
[Test]
115+
public void SupportsHttps()
116+
{
117+
// ARRANGE, ACT, ASSERT
118+
new TestState().Target.SupportsProtocol("HTTPS").Should().BeTrue();
119+
}
120+
121+
[Test]
122+
public async Task EndToEnd()
123+
{
124+
if (Helper.IsNetworkAvailable())
125+
{
126+
// ARRANGE
127+
var httpClient = new HttpClient();
128+
var requester = new HttpClientRequester(httpClient);
129+
var configuration = new Configuration(new[] { new LoaderService(new[] { requester }) });
130+
var context = BrowsingContext.New(configuration);
131+
var request = DocumentRequest.Get(Url.Create("http://httpbin.org/html"));
132+
133+
// ACT
134+
var response = await context.Loader.LoadAsync(request, CancellationToken.None);
135+
var document = await context.OpenAsync(response, CancellationToken.None);
136+
137+
// ASSERT
138+
document.QuerySelector("h1").ToHtml().Should().Be("<h1>Herman Melville - Moby-Dick</h1>");
139+
}
140+
}
141+
142+
class TestState
143+
{
144+
public TestState()
145+
{
146+
// dependencies
147+
148+
TestHandler = new TestHandler(this);
149+
HttpClient = new HttpClient(TestHandler);
150+
151+
// data
152+
Request = new Request
153+
{
154+
Method = AngleSharpHttpMethod.Post,
155+
Address = new Url("http://example/path?query=value"),
156+
Headers = new Dictionary<String, String>
157+
{
158+
{"User-Agent", "Foo/2.0"},
159+
{"Cookie", "foo=bar"},
160+
{"Content-Type", "application/json"},
161+
{"Content-Length", "9"}
162+
},
163+
Content = new MemoryStream(Encoding.UTF8.GetBytes("\"request\""))
164+
};
165+
HttpResponseMessage = new HttpResponseMessage(HttpStatusCode.OK)
166+
{
167+
Content = new StringContent("\"response\"", Encoding.UTF8, "application/json"),
168+
Headers =
169+
{
170+
{"Server", "Fake"},
171+
{"X-Powered-By", "Magic"},
172+
{"X-CSV", new[] {"foo", "bar"}}
173+
}
174+
};
175+
176+
// setup
177+
Target = new HttpClientRequester(HttpClient);
178+
}
179+
180+
public Request Request
181+
{
182+
get;
183+
}
184+
185+
public HttpClientRequester Target
186+
{
187+
get;
188+
}
189+
190+
public HttpClient HttpClient
191+
{
192+
get;
193+
}
194+
195+
public TestHandler TestHandler
196+
{
197+
get;
198+
}
199+
200+
public HttpResponseMessage HttpResponseMessage
201+
{
202+
get;
203+
}
204+
205+
public HttpRequestMessage HttpRequestMessage
206+
{
207+
get;
208+
set;
209+
}
210+
211+
public Byte[] HttpRequestMessageContent
212+
{
213+
get;
214+
set;
215+
}
216+
}
217+
218+
class TestHandler : DelegatingHandler
219+
{
220+
readonly TestState _testState;
221+
222+
public TestHandler(TestState testState)
223+
{
224+
_testState = testState;
225+
}
226+
227+
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
228+
{
229+
_testState.HttpRequestMessage = request;
230+
if (request.Content != null)
231+
_testState.HttpRequestMessageContent = await request.Content.ReadAsByteArrayAsync();
232+
233+
_testState.HttpResponseMessage.RequestMessage = request;
234+
return _testState.HttpResponseMessage;
235+
}
236+
}
237+
238+
class Request : IRequest
239+
{
240+
public AngleSharpHttpMethod Method
241+
{
242+
get;
243+
set;
244+
}
245+
246+
public Url Address
247+
{
248+
get;
249+
set;
250+
}
251+
252+
public Dictionary<String, String> Headers
253+
{
254+
get;
255+
set;
256+
}
257+
258+
public Stream Content
259+
{
260+
get;
261+
set;
262+
}
263+
}
264+
}
265+
}

0 commit comments

Comments
 (0)