diff --git a/Build/build.cmd b/Build/build.cmd
index 798519f9..ba32e3fc 100644
--- a/Build/build.cmd
+++ b/Build/build.cmd
@@ -11,7 +11,7 @@ echo Restoring dependicies was successful.
@set project=..\src\Flurl.Http.CodeGen\Flurl.Http.CodeGen.csproj
-@call dotnet run -c Release -p %project% ..\src\Flurl.Http\HttpExtensions.cs
+@call dotnet run -c Release -p %project% ..\src\Flurl.Http\GeneratedExtensions.cs
@if ERRORLEVEL 1 (
echo Error! Generation cs file failed.
exit /b 1
diff --git a/Build/test.cmd b/Build/test.cmd
index 0cd90821..566e9caf 100644
--- a/Build/test.cmd
+++ b/Build/test.cmd
@@ -1,3 +1,3 @@
@cd ..\test\Flurl.Test\
-
-@call dotnet test -c Release
\ No newline at end of file
+@call dotnet test -c Release
+@cd ..\..\Build\
\ No newline at end of file
diff --git a/Flurl.sln b/Flurl.sln
index 6f225d06..63ee2597 100644
--- a/Flurl.sln
+++ b/Flurl.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
-VisualStudioVersion = 15.0.26430.6
+VisualStudioVersion = 15.0.26730.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flurl", "src\Flurl\Flurl.csproj", "{117B6C6E-53F9-45AE-9439-F4FB7E21B116}"
EndProject
@@ -18,11 +18,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{86A5ACB4-F
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flurl.Test", "Test\Flurl.Test\Flurl.Test.csproj", "{DF68EB0E-9566-4577-B709-291520383F8D}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9B549ED2-56BB-46AC-A430-E39C2AE74BF3}"
- ProjectSection(SolutionItems) = preProject
- appveyor.yml = appveyor.yml
- EndProjectSection
-EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PackageTesters", "PackageTesters", "{9A136878-A43E-4154-9B5E-EDAF27E8628D}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "PackageTester.Shared", "PackageTesters\PackageTester.Shared\PackageTester.Shared.shproj", "{D4717AA7-5549-4BAD-81C5-406844A12990}"
@@ -33,12 +28,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PackageTester.NETCore", "Pa
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageTester.NET45", "PackageTesters\PackageTester.NET45\PackageTester.NET45.csproj", "{AA8792B6-E0FA-46BA-BA03-C7971745F577}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageTester.PCL", "PackageTesters\PackageTester.PCL\PackageTester.PCL.csproj", "{7018B9E0-AD5B-42E4-AD35-59F324ED8E56}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{B6BF9238-4541-4E1F-955E-C95F1C2A1F46}"
+ ProjectSection(SolutionItems) = preProject
+ appveyor.yml = appveyor.yml
+ Build\build.cmd = Build\build.cmd
+ Build\test.cmd = Build\test.cmd
+ EndProjectSection
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
- PackageTesters\PackageTester.Shared\PackageTester.Shared.projitems*{7018b9e0-ad5b-42e4-ad35-59f324ed8e56}*SharedItemsImports = 4
PackageTesters\PackageTester.Shared\PackageTester.Shared.projitems*{84fb572a-8b77-4b09-b825-2a240bce1b7a}*SharedItemsImports = 4
+ PackageTesters\PackageTester.Shared\PackageTester.Shared.projitems*{aa8792b6-e0fa-46ba-ba03-c7971745f577}*SharedItemsImports = 4
PackageTesters\PackageTester.Shared\PackageTester.Shared.projitems*{d4717aa7-5549-4bad-81c5-406844a12990}*SharedItemsImports = 13
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -74,10 +74,6 @@ Global
{AA8792B6-E0FA-46BA-BA03-C7971745F577}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AA8792B6-E0FA-46BA-BA03-C7971745F577}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AA8792B6-E0FA-46BA-BA03-C7971745F577}.Release|Any CPU.Build.0 = Release|Any CPU
- {7018B9E0-AD5B-42E4-AD35-59F324ED8E56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {7018B9E0-AD5B-42E4-AD35-59F324ED8E56}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {7018B9E0-AD5B-42E4-AD35-59F324ED8E56}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {7018B9E0-AD5B-42E4-AD35-59F324ED8E56}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -91,6 +87,8 @@ Global
{84FB572A-8B77-4B09-B825-2A240BCE1B7A} = {9A136878-A43E-4154-9B5E-EDAF27E8628D}
{0231607B-9CA3-4277-9F19-9925694D22E0} = {9A136878-A43E-4154-9B5E-EDAF27E8628D}
{AA8792B6-E0FA-46BA-BA03-C7971745F577} = {9A136878-A43E-4154-9B5E-EDAF27E8628D}
- {7018B9E0-AD5B-42E4-AD35-59F324ED8E56} = {9A136878-A43E-4154-9B5E-EDAF27E8628D}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {61289482-AC5A-44E1-AEA1-76A3F3CCB6A4}
EndGlobalSection
EndGlobal
diff --git a/PackageTesters/PackageTester.NET45/PackageTester.NET45.csproj b/PackageTesters/PackageTester.NET45/PackageTester.NET45.csproj
index 3a0ffa0d..7f8c90ff 100644
--- a/PackageTesters/PackageTester.NET45/PackageTester.NET45.csproj
+++ b/PackageTesters/PackageTester.NET45/PackageTester.NET45.csproj
@@ -31,11 +31,11 @@
4
-
- ..\..\packages\Flurl.2.4.0-pre\lib\net40\Flurl.dll
+
+ ..\..\packages\Flurl.2.5.0\lib\net40\Flurl.dll
-
- ..\..\packages\Flurl.Http.1.2.0-pre\lib\net45\Flurl.Http.dll
+
+ ..\..\packages\Flurl.Http.2.0.0-pre1\lib\net45\Flurl.Http.dll..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll
@@ -59,11 +59,6 @@
-
-
- {7018b9e0-ad5b-42e4-ad35-59f324ed8e56}
- PackageTester.PCL
-
-
+
\ No newline at end of file
diff --git a/PackageTesters/PackageTester.NET45/Program.cs b/PackageTesters/PackageTester.NET45/Program.cs
index 8b7344f2..abd09a22 100644
--- a/PackageTesters/PackageTester.NET45/Program.cs
+++ b/PackageTesters/PackageTester.NET45/Program.cs
@@ -1,5 +1,4 @@
using System;
-using PackageTester.PCL;
namespace PackageTester.NET45
{
@@ -7,9 +6,6 @@ public class Program
{
public static void Main(string[] args) {
new Tester().DoTestsAsync().Wait();
- Console.WriteLine();
- Console.WriteLine("Testing against PCL...");
- new PclTester().DoTestsAsync().Wait();
Console.ReadLine();
}
}
diff --git a/PackageTesters/PackageTester.NET45/packages.config b/PackageTesters/PackageTester.NET45/packages.config
index 39ae0a02..cebbe1f6 100644
--- a/PackageTesters/PackageTester.NET45/packages.config
+++ b/PackageTesters/PackageTester.NET45/packages.config
@@ -1,6 +1,6 @@
-
-
+
+
\ No newline at end of file
diff --git a/PackageTesters/PackageTester.NET461/PackageTester.NET461.csproj b/PackageTesters/PackageTester.NET461/PackageTester.NET461.csproj
index 5be0bfa8..e7a57446 100644
--- a/PackageTesters/PackageTester.NET461/PackageTester.NET461.csproj
+++ b/PackageTesters/PackageTester.NET461/PackageTester.NET461.csproj
@@ -33,11 +33,11 @@
4
-
- ..\..\packages\Flurl.2.4.0-pre\lib\net40\Flurl.dll
+
+ ..\..\packages\Flurl.2.5.0\lib\net40\Flurl.dll
-
- ..\..\packages\Flurl.Http.1.2.0-pre\lib\net45\Flurl.Http.dll
+
+ ..\..\packages\Flurl.Http.2.0.0-pre1\lib\net45\Flurl.Http.dll..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll
diff --git a/PackageTesters/PackageTester.NET461/packages.config b/PackageTesters/PackageTester.NET461/packages.config
index feb04626..6d7e87b9 100644
--- a/PackageTesters/PackageTester.NET461/packages.config
+++ b/PackageTesters/PackageTester.NET461/packages.config
@@ -1,6 +1,6 @@
-
-
+
+
\ No newline at end of file
diff --git a/PackageTesters/PackageTester.NETCore/PackageTester.NETCore.csproj b/PackageTesters/PackageTester.NETCore/PackageTester.NETCore.csproj
index fdeb0265..ca76ca1e 100644
--- a/PackageTesters/PackageTester.NETCore/PackageTester.NETCore.csproj
+++ b/PackageTesters/PackageTester.NETCore/PackageTester.NETCore.csproj
@@ -8,7 +8,7 @@
-
+
\ No newline at end of file
diff --git a/PackageTesters/PackageTester.PCL/PackageTester.PCL.csproj b/PackageTesters/PackageTester.PCL/PackageTester.PCL.csproj
deleted file mode 100644
index 2117f55e..00000000
--- a/PackageTesters/PackageTester.PCL/PackageTester.PCL.csproj
+++ /dev/null
@@ -1,65 +0,0 @@
-
-
-
-
- Debug
- AnyCPU
- {7018B9E0-AD5B-42E4-AD35-59F324ED8E56}
- Library
- Properties
- PackageTester.PCL
- PackageTester.PCL
- v4.5
- 512
-
-
-
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
- false
-
-
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
- false
-
-
-
- ..\..\packages\Flurl.2.4.0-pre\lib\net40\Flurl.dll
-
-
- ..\..\packages\Flurl.Http.1.2.0-pre\lib\net45\Flurl.Http.dll
-
-
- ..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/PackageTesters/PackageTester.PCL/PclTester.cs b/PackageTesters/PackageTester.PCL/PclTester.cs
deleted file mode 100644
index 8d792efc..00000000
--- a/PackageTesters/PackageTester.PCL/PclTester.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-namespace PackageTester.PCL
-{
- public class PclTester : Tester { }
-}
\ No newline at end of file
diff --git a/PackageTesters/PackageTester.PCL/packages.config b/PackageTesters/PackageTester.PCL/packages.config
deleted file mode 100644
index 39ae0a02..00000000
--- a/PackageTesters/PackageTester.PCL/packages.config
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Test/Flurl.Test/CommonExtensionsTests.cs b/Test/Flurl.Test/CommonExtensionsTests.cs
index 0f54d3a3..9621761b 100644
--- a/Test/Flurl.Test/CommonExtensionsTests.cs
+++ b/Test/Flurl.Test/CommonExtensionsTests.cs
@@ -6,7 +6,7 @@
namespace Flurl.Test
{
- [TestFixture]
+ [TestFixture, Parallelizable]
public class CommonExtensionsTests
{
[Test]
diff --git a/Test/Flurl.Test/Flurl.Test.csproj b/Test/Flurl.Test/Flurl.Test.csproj
index fbcfaced..b35ff87a 100644
--- a/Test/Flurl.Test/Flurl.Test.csproj
+++ b/Test/Flurl.Test/Flurl.Test.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/Test/Flurl.Test/Http/ClientConfigTests.cs b/Test/Flurl.Test/Http/ClientConfigTests.cs
deleted file mode 100644
index 8fdb3906..00000000
--- a/Test/Flurl.Test/Http/ClientConfigTests.cs
+++ /dev/null
@@ -1,222 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Threading.Tasks;
-using Flurl.Http;
-using Flurl.Http.Configuration;
-using Flurl.Http.Testing;
-using NUnit.Framework;
-
-namespace Flurl.Test.Http
-{
- [TestFixture]
- public class ClientConfigTestsBase : ConfigTestsBase
- {
- protected override FlurlHttpSettings GetSettings()
- {
- return GetClient().Settings;
- }
-
- [Test]
- public void can_set_timeout()
- {
- var client = "http://www.api.com".WithTimeout(TimeSpan.FromSeconds(15));
- Assert.AreEqual(client.HttpClient.Timeout, TimeSpan.FromSeconds(15));
- }
-
- [Test]
- public void can_set_timeout_in_seconds()
- {
- var client = "http://www.api.com".WithTimeout(15);
- Assert.AreEqual(client.HttpClient.Timeout, TimeSpan.FromSeconds(15));
- }
-
- [Test]
- public void can_set_header()
- {
- var client = "http://www.api.com".WithHeader("a", 1);
-
- var headers = client.HttpClient.DefaultRequestHeaders.ToList();
- Assert.AreEqual(1, headers.Count);
- Assert.AreEqual("a", headers[0].Key);
- CollectionAssert.AreEqual(headers[0].Value, new[] { "1" });
- }
-
- [Test]
- public void can_set_headers_from_anon_object()
- {
- var client = "http://www.api.com".WithHeaders(new { a = "b", one = 2 });
-
- var headers = client.HttpClient.DefaultRequestHeaders.ToList();
- Assert.AreEqual(2, headers.Count);
- Assert.AreEqual("a", headers[0].Key);
- CollectionAssert.AreEqual(headers[0].Value, new[] { "b" });
- Assert.AreEqual("one", headers[1].Key);
- CollectionAssert.AreEqual(headers[1].Value, new[] { "2" });
- }
-
- [Test]
- public void can_set_headers_from_dictionary()
- {
- var client = "http://www.api.com".WithHeaders(new Dictionary { { "a", "b" }, { "one", 2 } });
-
- var headers = client.HttpClient.DefaultRequestHeaders.ToList();
- Assert.AreEqual(2, headers.Count);
- Assert.AreEqual("a", headers[0].Key);
- CollectionAssert.AreEqual(headers[0].Value, new[] { "b" });
- Assert.AreEqual("one", headers[1].Key);
- CollectionAssert.AreEqual(headers[1].Value, new[] { "2" });
- }
-
- [Test]
- public void can_setup_basic_auth()
- {
- var client = "http://www.api.com".WithBasicAuth("user", "pass");
-
- var header = client.HttpClient.DefaultRequestHeaders.First();
- Assert.AreEqual("Authorization", header.Key);
- Assert.AreEqual("Basic dXNlcjpwYXNz", header.Value.First());
- }
-
- [Test]
- public void can_setup_oauth_bearer_token()
- {
- var client = "http://www.api.com".WithOAuthBearerToken("mytoken");
-
- var header = client.HttpClient.DefaultRequestHeaders.First();
- Assert.AreEqual("Authorization", header.Key);
- Assert.AreEqual("Bearer mytoken", header.Value.First());
- }
-
- [Test]
- public async Task can_allow_specific_http_status()
- {
- using (var test = new HttpTest())
- {
- test.RespondWith("Nothing to see here", 404);
- // no exception = pass
- await "http://www.api.com"
- .AllowHttpStatus(HttpStatusCode.Conflict, HttpStatusCode.NotFound)
- .DeleteAsync();
- }
- }
-
- [Test]
- public void can_clear_non_success_status()
- {
- using (var test = new HttpTest())
- {
- test.RespondWith("I'm a teapot", 418);
- // allow 4xx
- var client = "http://www.api.com".AllowHttpStatus("4xx");
- // but then disallow it
- client.Settings.AllowedHttpStatusRange = null;
- Assert.ThrowsAsync(async () => await client.GetAsync());
- }
- }
-
- [Test]
- public async Task can_allow_any_http_status()
- {
- using (var test = new HttpTest())
- {
- test.RespondWith("epic fail", 500);
- try
- {
- var result = await "http://www.api.com".AllowAnyHttpStatus().GetAsync();
- Assert.IsFalse(result.IsSuccessStatusCode);
- }
- catch (Exception)
- {
- Assert.Fail("Exception should not have been thrown.");
- }
- }
- }
-
- [Test]
- public void can_override_settings_fluently()
- {
- using (var test = new HttpTest())
- {
- FlurlHttp.GlobalSettings.AllowedHttpStatusRange = "*";
- test.RespondWith("epic fail", 500);
- Assert.ThrowsAsync(async () => await "http://www.api.com".ConfigureClient(c => c.AllowedHttpStatusRange = "2xx").GetAsync());
- }
- }
-
- [Test]
- public void WithUrl_shares_HttpClient_but_not_Url()
- {
- var client1 = new FlurlClient("http://www.api.com/for-client1").WithCookie("mycookie", "123");
- var client2 = client1.WithUrl("http://www.api.com/for-client2");
- var client3 = client1.WithUrl("http://www.api.com/for-client3");
- var client4 = client2.WithUrl("http://www.api.com/for-client4");
-
- CollectionAssert.AreEquivalent(client1.Cookies, client2.Cookies);
- CollectionAssert.AreEquivalent(client1.Cookies, client3.Cookies);
- CollectionAssert.AreEquivalent(client1.Cookies, client4.Cookies);
- var urls = new[] { client1, client2, client3, client4 }.Select(c => c.Url.ToString());
- CollectionAssert.AllItemsAreUnique(urls);
- }
-
- [Test]
- public void WithUrl_doesnt_propagate_HttpClient_disposal()
- {
- var client1 = new FlurlClient("http://www.api.com/for-client1").WithCookie("mycookie", "123");
- var client2 = client1.WithUrl("http://www.api.com/for-client2");
- var client3 = client1.WithUrl("http://www.api.com/for-client3");
- var client4 = client2.WithUrl("http://www.api.com/for-client4");
-
- client2.Dispose();
- client3.Dispose();
-
- CollectionAssert.IsEmpty(client2.Cookies);
- CollectionAssert.IsEmpty(client3.Cookies);
-
- CollectionAssert.IsNotEmpty(client1.Cookies);
- CollectionAssert.IsNotEmpty(client4.Cookies);
- }
-
- [Test]
- public void WithClient_shares_HttpClient_but_not_Url()
- {
- var client1 = new FlurlClient("http://www.api.com/for-client1").WithCookie("mycookie", "123");
- var client2 = "http://www.api.com/for-client2".WithClient(client1);
- var client3 = "http://www.api.com/for-client3".WithClient(client1);
- var client4 = "http://www.api.com/for-client4".WithClient(client1);
-
- CollectionAssert.AreEquivalent(client1.Cookies, client2.Cookies);
- CollectionAssert.AreEquivalent(client1.Cookies, client3.Cookies);
- CollectionAssert.AreEquivalent(client1.Cookies, client4.Cookies);
- var urls = new[] { client1, client2, client3, client4 }.Select(c => c.Url.ToString());
- CollectionAssert.AllItemsAreUnique(urls);
- }
-
- [Test]
- public void WithClient_doesnt_propagate_HttpClient_disposal()
- {
- var client1 = new FlurlClient("http://www.api.com/for-client1").WithCookie("mycookie", "123");
- var client2 = "http://www.api.com/for-client2".WithClient(client1);
- var client3 = "http://www.api.com/for-client3".WithClient(client1);
- var client4 = "http://www.api.com/for-client4".WithClient(client1);
-
- client2.Dispose();
- client3.Dispose();
-
- CollectionAssert.IsEmpty(client2.Cookies);
- CollectionAssert.IsEmpty(client3.Cookies);
-
- CollectionAssert.IsNotEmpty(client1.Cookies);
- CollectionAssert.IsNotEmpty(client4.Cookies);
- }
-
- [Test]
- public void can_use_uri_with_WithUrl()
- {
- var uri = new System.Uri("http://www.mysite.com/foo?x=1");
- var fc = new FlurlClient().WithUrl(uri);
- Assert.AreEqual(uri.ToString(), fc.Url.ToString());
- }
- }
-}
\ No newline at end of file
diff --git a/Test/Flurl.Test/Http/ClientLifetimeTests.cs b/Test/Flurl.Test/Http/ClientLifetimeTests.cs
deleted file mode 100644
index cf9ff78b..00000000
--- a/Test/Flurl.Test/Http/ClientLifetimeTests.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Net.Http;
-using System.Threading.Tasks;
-using Flurl.Http;
-using Flurl.Http.Testing;
-using NUnit.Framework;
-
-namespace Flurl.Test.Http
-{
- [TestFixture, Parallelizable]
- public class ClientLifetimeTests
- {
- [Test]
- public async Task autodispose_true_creates_new_httpclients() {
- var fac = new TestHttpClientFactoryWithCounter();
- var fc = new FlurlClient("http://www.mysite.com") {
- Settings = { HttpClientFactory = fac, AutoDispose = true }
- };
- var x = await fc.GetAsync();
- var y = await fc.GetAsync();
- var z = await fc.GetAsync();
- Assert.AreEqual(3, fac.NewClientCount);
- }
-
- [Test]
- public async Task autodispose_false_reuses_httpclient() {
- var fac = new TestHttpClientFactoryWithCounter();
- var fc = new FlurlClient("http://www.mysite.com") {
- Settings = { HttpClientFactory = fac, AutoDispose = false }
- };
- var x = await fc.GetAsync();
- var y = await fc.GetAsync();
- var z = await fc.GetAsync();
- Assert.AreEqual(1, fac.NewClientCount);
- }
-
- private class TestHttpClientFactoryWithCounter : TestHttpClientFactory
- {
- public int NewClientCount { get; set; }
-
- public override HttpClient CreateClient(Url url, HttpMessageHandler handler) {
- NewClientCount++;
- return base.CreateClient(url, handler);
- }
- }
- }
-}
diff --git a/Test/Flurl.Test/Http/FlurlClientTests.cs b/Test/Flurl.Test/Http/FlurlClientTests.cs
index 5c342882..85f8142f 100644
--- a/Test/Flurl.Test/Http/FlurlClientTests.cs
+++ b/Test/Flurl.Test/Http/FlurlClientTests.cs
@@ -1,4 +1,5 @@
-using System.Linq;
+using System;
+using System.Linq;
using System.Reflection;
using Flurl.Http;
using NUnit.Framework;
@@ -11,24 +12,84 @@ public class FlurlClientTests
[Test]
// check that for every FlurlClient extension method, we have an equivalent Url and string extension
public void extension_methods_consistently_supported() {
- var fcExts = ReflectionHelper.GetAllExtensionMethods(typeof(FlurlClient).GetTypeInfo().Assembly);
- var urlExts = ReflectionHelper.GetAllExtensionMethods(typeof(FlurlClient).GetTypeInfo().Assembly);
- var stringExts = ReflectionHelper.GetAllExtensionMethods(typeof(FlurlClient).GetTypeInfo().Assembly);
- var whitelist = new[] { "WithUrl" }; // cases where Url method of the same name was excluded intentionally
+ var frExts = ReflectionHelper.GetAllExtensionMethods(typeof(FlurlClient).GetTypeInfo().Assembly)
+ // URL builder methods on IFlurlClient get a free pass. We're looking for things like HTTP calling methods.
+ .Where(mi => mi.DeclaringType != typeof(UrlBuilderExtensions))
+ .ToList();
+ var urlExts = ReflectionHelper.GetAllExtensionMethods(typeof(FlurlClient).GetTypeInfo().Assembly).ToList();
+ var stringExts = ReflectionHelper.GetAllExtensionMethods(typeof(FlurlClient).GetTypeInfo().Assembly).ToList();
- foreach (var method in fcExts) {
- if (whitelist.Contains(method.Name))
- continue;
+ Assert.That(frExts.Count > 20, $"IFlurlRequest only has {frExts.Count} extension methods? Something's wrong here.");
+ // Url and string should contain all extension methods that IFlurlRequest has
+ foreach (var method in frExts) {
if (!urlExts.Any(m => ReflectionHelper.AreSameMethodSignatures(method, m))) {
var args = string.Join(", ", method.GetParameters().Select(p => p.ParameterType.Name));
- Assert.Fail($"No equivalent Url extension method found for FlurlClient.{method.Name}({args})");
+ Assert.Fail($"No equivalent Url extension method found for IFlurlRequest.{method.Name}({args})");
}
if (!stringExts.Any(m => ReflectionHelper.AreSameMethodSignatures(method, m))) {
var args = string.Join(", ", method.GetParameters().Select(p => p.ParameterType.Name));
- Assert.Fail($"No equivalent string extension method found for FlurlClient.{method.Name}({args})");
+ Assert.Fail($"No equivalent string extension method found for IFlurlRequest.{method.Name}({args})");
}
}
}
+
+ [Test]
+ public void can_create_request_without_base_url() {
+ var cli = new FlurlClient();
+ var req = cli.Request("http://myapi.com/foo?x=1&y=2#foo");
+ Assert.AreEqual("http://myapi.com/foo?x=1&y=2#foo", req.Url.ToString());
+ }
+
+ [Test]
+ public void can_create_request_with_base_url() {
+ var cli = new FlurlClient("http://myapi.com");
+ var req = cli.Request("foo", "bar");
+ Assert.AreEqual("http://myapi.com/foo/bar", req.Url.ToString());
+ }
+
+ [Test]
+ public void request_with_full_url_overrides_base_url() {
+ var cli = new FlurlClient("http://myapi.com");
+ var req = cli.Request("http://otherapi.com", "foo");
+ Assert.AreEqual("http://otherapi.com/foo", req.Url.ToString());
+ }
+
+ [Test]
+ public void can_create_request_with_base_url_and_no_segments() {
+ var cli = new FlurlClient("http://myapi.com");
+ var req = cli.Request();
+ Assert.AreEqual("http://myapi.com", req.Url.ToString());
+ }
+
+ [Test]
+ public void cannot_create_request_without_base_url_or_segments() {
+ var cli = new FlurlClient();
+ Assert.Throws(() => {
+ var req = cli.Request();
+ });
+ }
+
+ [Test]
+ public void cannot_create_request_without_base_url_or_segments_comprising_full_url() {
+ var cli = new FlurlClient();
+ Assert.Throws(() => {
+ var req = cli.Request("foo", "bar");
+ });
+ }
+
+ [Test]
+ public void default_factory_doesnt_reuse_disposed_clients() {
+ var cli1 = "http://api.com".WithHeader("foo", "1").Client;
+ var cli2 = "http://api.com".WithHeader("foo", "2").Client;
+ cli1.Dispose();
+ var cli3 = "http://api.com".WithHeader("foo", "3").Client;
+
+ Assert.AreEqual(cli1, cli2);
+ Assert.IsTrue(cli1.IsDisposed);
+ Assert.IsTrue(cli2.IsDisposed);
+ Assert.AreNotEqual(cli1, cli3);
+ Assert.IsFalse(cli3.IsDisposed);
+ }
}
}
\ No newline at end of file
diff --git a/Test/Flurl.Test/Http/FlurlHttpExceptionTests.cs b/Test/Flurl.Test/Http/FlurlHttpExceptionTests.cs
new file mode 100644
index 00000000..efab246b
--- /dev/null
+++ b/Test/Flurl.Test/Http/FlurlHttpExceptionTests.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Flurl.Http;
+using NUnit.Framework;
+
+namespace Flurl.Test.Http
+{
+ [TestFixture, Parallelizable]
+ class FlurlHttpExceptionTests : HttpTestFixtureBase
+ {
+ [Test]
+ public async Task exception_message_is_nice() {
+ HttpTest.RespondWithJson(new { message = "bad data!" }, 400);
+
+ try {
+ await "http://myapi.com".PostJsonAsync(new { data = "bad" });
+ Assert.Fail("should have thrown 400.");
+ }
+ catch (FlurlHttpException ex) {
+ Assert.AreEqual("POST http://myapi.com failed with status code 400 (Bad Request).\r\nRequest body:\r\n{\"data\":\"bad\"}\r\nResponse body:\r\n{\"message\":\"bad data!\"}", ex.Message);
+ }
+ }
+
+ [Test]
+ public async Task exception_message_excludes_request_response_labels_when_body_empty() {
+ HttpTest.RespondWith("", 400);
+
+ try {
+ await "http://myapi.com".GetAsync();
+ Assert.Fail("should have thrown 400.");
+ }
+ catch (FlurlHttpException ex) {
+ // no "Request body:", "Response body:", or line breaks
+ Assert.AreEqual("GET http://myapi.com failed with status code 400 (Bad Request).", ex.Message);
+ }
+ }
+ }
+}
diff --git a/Test/Flurl.Test/Http/GetTests.cs b/Test/Flurl.Test/Http/GetTests.cs
index 1ae6e6f3..6d6fd23e 100644
--- a/Test/Flurl.Test/Http/GetTests.cs
+++ b/Test/Flurl.Test/Http/GetTests.cs
@@ -9,7 +9,7 @@
namespace Flurl.Test.Http
{
- [TestFixture]
+ [TestFixture, Parallelizable]
public class GetTests : HttpTestFixtureBase
{
[Test]
@@ -132,7 +132,7 @@ public async Task can_get_error_json_untyped() {
public async Task can_get_null_json_when_timeout_and_exception_handled() {
HttpTest.SimulateTimeout();
var data = await "http://api.com"
- .ConfigureClient(c => c.OnError = call => call.ExceptionHandled = true)
+ .ConfigureRequest(c => c.OnError = call => call.ExceptionHandled = true)
.GetJsonAsync();
Assert.IsNull(data);
}
diff --git a/Test/Flurl.Test/Http/GlobalConfigTests.cs b/Test/Flurl.Test/Http/GlobalConfigTests.cs
deleted file mode 100644
index 182009f1..00000000
--- a/Test/Flurl.Test/Http/GlobalConfigTests.cs
+++ /dev/null
@@ -1,148 +0,0 @@
-using System;
-using System.Net.Http;
-using System.Threading.Tasks;
-using Flurl.Http;
-using Flurl.Http.Configuration;
-using Flurl.Http.Testing;
-using NUnit.Framework;
-
-namespace Flurl.Test.Http
-{
- ///
- /// All global settings can also be set at the client level, so this base class allows ClientConfigTests to
- /// inherit all the same tests.
- ///
- [Parallelizable]
- public abstract class ConfigTestsBase
- {
- protected abstract FlurlHttpSettings GetSettings();
-
- private FlurlClient _client;
- protected FlurlClient GetClient() {
- if (_client == null)
- _client = new FlurlClient("http://www.api.com");
- return _client;
- }
-
- [TearDown]
- public void ResetDefaults() {
- GetSettings().ResetDefaults();
- _client = null;
- }
-
- [Test]
- public void can_provide_custom_httpclient_factory() {
- GetSettings().HttpClientFactory = new SomeCustomHttpClientFactory();
- Assert.IsInstanceOf(GetClient().HttpClient);
- Assert.IsInstanceOf(GetClient().HttpMessageHandler);
- }
-
- [Test]
- public async Task can_allow_non_success_status() {
- using (var test = new HttpTest()) {
- GetSettings().AllowedHttpStatusRange = "4xx";
- test.RespondWith("I'm a teapot", 418);
- try {
- var result = await GetClient().GetAsync();
- Assert.IsFalse(result.IsSuccessStatusCode);
- }
- catch (Exception) {
- Assert.Fail("Exception should not have been thrown.");
- }
- }
- }
-
- [Test]
- public async Task can_set_pre_callback() {
- var callbackCalled = false;
- using (var test = new HttpTest()) {
- test.RespondWith("ok");
- GetSettings().BeforeCall = req => {
- CollectionAssert.IsNotEmpty(test.ResponseQueue); // verifies that callback is running before HTTP call is made
- callbackCalled = true;
- };
- Assert.IsFalse(callbackCalled);
- await GetClient().GetAsync();
- Assert.IsTrue(callbackCalled);
- }
- }
-
- [Test]
- public async Task can_set_post_callback() {
- var callbackCalled = false;
- using (var test = new HttpTest()) {
- test.RespondWith("ok");
- GetSettings().AfterCall = call => {
- CollectionAssert.IsEmpty(test.ResponseQueue); // verifies that callback is running after HTTP call is made
- callbackCalled = true;
- };
- Assert.IsFalse(callbackCalled);
- await GetClient().GetAsync();
- Assert.IsTrue(callbackCalled);
- }
- }
-
- [TestCase(true)]
- [TestCase(false)]
- public async Task can_set_error_callback(bool markExceptionHandled) {
- var callbackCalled = false;
- using (var test = new HttpTest()) {
- test.RespondWith("server error", 500);
- GetSettings().OnError = call => {
- CollectionAssert.IsEmpty(test.ResponseQueue); // verifies that callback is running after HTTP call is made
- callbackCalled = true;
- call.ExceptionHandled = markExceptionHandled;
- };
- Assert.IsFalse(callbackCalled);
- try {
- await GetClient().GetAsync();
- Assert.IsTrue(callbackCalled, "OnError was never called");
- Assert.IsTrue(markExceptionHandled, "ExceptionHandled was marked false in callback, but exception was not propagated.");
- }
- catch (FlurlHttpException) {
- Assert.IsTrue(callbackCalled, "OnError was never called");
- Assert.IsFalse(markExceptionHandled, "ExceptionHandled was marked true in callback, but exception was propagated.");
- }
- }
- }
-
- [Test]
- public async Task can_disable_exception_behavior() {
- using (var test = new HttpTest()) {
- GetSettings().OnError = call => {
- call.ExceptionHandled = true;
- };
- test.RespondWith("server error", 500);
- try {
- var result = await GetClient().GetAsync();
- Assert.IsFalse(result.IsSuccessStatusCode);
- }
- catch (FlurlHttpException) {
- Assert.Fail("Flurl should not have thrown exception.");
- }
- }
- }
-
- private class SomeCustomHttpClientFactory : IHttpClientFactory
- {
- public HttpClient CreateClient(Url url, HttpMessageHandler handler) {
- return new SomeCustomHttpClient();
- }
-
- public HttpMessageHandler CreateMessageHandler() {
- return new SomeCustomMessageHandler();
- }
- }
-
- private class SomeCustomHttpClient : HttpClient { }
- private class SomeCustomMessageHandler : HttpClientHandler { }
- }
-
- [TestFixture]
- public class GlobalConfigTestsBase : ConfigTestsBase
- {
- protected override FlurlHttpSettings GetSettings() {
- return FlurlHttp.GlobalSettings;
- }
- }
-}
diff --git a/Test/Flurl.Test/Http/HeadTests.cs b/Test/Flurl.Test/Http/HeadTests.cs
index 851ac1a5..3189875f 100644
--- a/Test/Flurl.Test/Http/HeadTests.cs
+++ b/Test/Flurl.Test/Http/HeadTests.cs
@@ -6,7 +6,7 @@
namespace Flurl.Test.Http
{
- [TestFixture]
+ [TestFixture, Parallelizable]
public class HeadTests : HttpTestFixtureBase
{
[Test]
diff --git a/Test/Flurl.Test/Http/HttpTestFixtureBase.cs b/Test/Flurl.Test/Http/HttpTestFixtureBase.cs
index ce03aa65..5cadba8a 100644
--- a/Test/Flurl.Test/Http/HttpTestFixtureBase.cs
+++ b/Test/Flurl.Test/Http/HttpTestFixtureBase.cs
@@ -6,7 +6,6 @@
namespace Flurl.Test.Http
{
- [Parallelizable]
public abstract class HttpTestFixtureBase
{
protected HttpTest HttpTest { get; private set; }
diff --git a/Test/Flurl.Test/Http/PostTests.cs b/Test/Flurl.Test/Http/PostTests.cs
index 60c9c4a6..cba272f6 100644
--- a/Test/Flurl.Test/Http/PostTests.cs
+++ b/Test/Flurl.Test/Http/PostTests.cs
@@ -5,7 +5,7 @@
namespace Flurl.Test.Http
{
- [TestFixture]
+ [TestFixture, Parallelizable]
public class PostTests : HttpTestFixtureBase
{
[Test]
@@ -33,11 +33,11 @@ public async Task can_post_object_as_json() {
[Test]
public async Task can_post_url_encoded() {
- await "http://some-api.com".PostUrlEncodedAsync(new { a = 1, b = 2, c = "hi there" });
+ await "http://some-api.com".PostUrlEncodedAsync(new { a = 1, b = 2, c = "hi there", d = new[] { 1, 2, 3 } });
HttpTest.ShouldHaveCalled("http://some-api.com")
.WithVerb(HttpMethod.Post)
.WithContentType("application/x-www-form-urlencoded")
- .WithRequestBody("a=1&b=2&c=hi+there")
+ .WithRequestBody("a=1&b=2&c=hi+there&d=1&d=2&d=3")
.Times(1);
}
diff --git a/Test/Flurl.Test/Http/RealHttpTests.cs b/Test/Flurl.Test/Http/RealHttpTests.cs
index 762af15d..fbb1401a 100644
--- a/Test/Flurl.Test/Http/RealHttpTests.cs
+++ b/Test/Flurl.Test/Http/RealHttpTests.cs
@@ -1,18 +1,18 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Flurl.Http;
+using Flurl.Http.Configuration;
using Flurl.Http.Testing;
using NUnit.Framework;
namespace Flurl.Test.Http
{
///
- /// Most HTTP tests in this project are with Flurl in fake mode. These are some real ones, mostly using the handy site
- /// http://httpbin.org. One important aspect these verify is that AutoDispose behavior is not preventing us from getting
- /// stuff out of the response (i.e. that we're not disposing too early).
+ /// Most HTTP tests in this project are with Flurl in fake mode. These are some real ones, mostly using http://httpbin.org.
///
[TestFixture, Parallelizable]
public class RealHttpTests
@@ -20,7 +20,7 @@ public class RealHttpTests
[Test]
public async Task can_download_file() {
var folder = "c:\\flurl-test-" + Guid.NewGuid(); // random so parallel tests don't trip over each other
- var path = await "http://www.google.com".DownloadFileAsync(folder, "google.txt");
+ var path = await "https://www.google.com".DownloadFileAsync(folder, "google.txt");
Assert.AreEqual($@"{folder}\google.txt", path);
Assert.That(File.Exists(path));
File.Delete(path);
@@ -29,7 +29,8 @@ public async Task can_download_file() {
[Test]
public async Task can_set_request_cookies() {
- var resp = await "http://httpbin.org/cookies".WithCookies(new { x = 1, y = 2 }).GetJsonAsync();
+ var cli = new FlurlClient();
+ var resp = await cli.Request("https://httpbin.org/cookies").WithCookies(new { x = 1, y = 2 }).GetJsonAsync();
// httpbin returns json representation of cookies that were set on the server.
Assert.AreEqual("1", resp.cookies.x);
@@ -38,62 +39,46 @@ public async Task can_set_request_cookies() {
[Test]
public async Task can_set_cookies_before_setting_url() {
- var fc = new FlurlClient().WithCookie("z", "999");
- var resp = await fc.WithUrl("http://httpbin.org/cookies").GetJsonAsync();
+ var cli = new FlurlClient().WithCookie("z", "999");
+ var resp = await cli.Request("https://httpbin.org/cookies").GetJsonAsync();
Assert.AreEqual("999", resp.cookies.z);
}
[Test]
public async Task can_get_response_cookies() {
- var fc = new FlurlClient().EnableCookies();
- await fc.WithUrl("https://httpbin.org/cookies/set?z=999").HeadAsync();
- Assert.AreEqual("999", fc.Cookies["z"].Value);
+ var cli = new FlurlClient().EnableCookies();
+ await cli.Request("https://httpbin.org/cookies/set?z=999").HeadAsync();
+ Assert.AreEqual("999", cli.Cookies["z"].Value);
}
[Test]
- public async Task cant_persist_cookies_without_resuing_client() {
- var fc = "http://httpbin.org/cookies".WithCookie("z", 999);
+ public async Task can_persist_cookies() {
+ var cli = new FlurlClient("https://httpbin.org/cookies");
+ var req = cli.Request().WithCookie("z", 999);
// cookie should be set
- Assert.AreEqual("999", fc.Cookies["z"].Value);
+ Assert.AreEqual("999", cli.Cookies["z"].Value);
+ Assert.AreEqual("999", req.Cookies["z"].Value);
- await fc.HeadAsync();
- // FlurlClient was auto-disposed, so cookie should be gone
- CollectionAssert.IsEmpty(fc.Cookies);
+ await req.HeadAsync();
+ // FlurlClient should be re-used, so cookie should stick
+ Assert.AreEqual("999", cli.Cookies["z"].Value);
+ Assert.AreEqual("999", req.Cookies["z"].Value);
// httpbin returns json representation of cookies that were set on the server.
- var resp = await "http://httpbin.org/cookies".GetJsonAsync();
- Assert.IsFalse((resp.cookies as IDictionary).ContainsKey("z"));
- }
-
- [Test]
- public async Task can_persist_cookies() {
- using (var fc = new FlurlClient()) {
- var fc2 = "http://httpbin.org/cookies".WithClient(fc).WithCookie("z", 999);
- // cookie should be set
- Assert.AreEqual("999", fc.Cookies["z"].Value);
- Assert.AreEqual("999", fc2.Cookies["z"].Value);
-
- await fc2.HeadAsync();
- // FlurlClient should be re-used, so cookie should stick
- Assert.AreEqual("999", fc.Cookies["z"].Value);
- Assert.AreEqual("999", fc2.Cookies["z"].Value);
-
- // httpbin returns json representation of cookies that were set on the server.
- var resp = await "http://httpbin.org/cookies".WithClient(fc).GetJsonAsync();
- Assert.AreEqual("999", resp.cookies.z);
- }
+ var resp = await cli.Request().GetJsonAsync();
+ Assert.AreEqual("999", resp.cookies.z);
}
[Test]
public async Task can_post_and_receive_json() {
- var result = await "http://httpbin.org/post".PostJsonAsync(new { a = 1, b = 2 }).ReceiveJson();
+ var result = await "https://httpbin.org/post".PostJsonAsync(new { a = 1, b = 2 }).ReceiveJson();
Assert.AreEqual(result.json.a, 1);
Assert.AreEqual(result.json.b, 2);
}
[Test]
public async Task can_get_stream() {
- using (var stream = await "http://www.google.com".GetStreamAsync())
+ using (var stream = await "https://www.google.com".GetStreamAsync())
using (var ms = new MemoryStream()) {
stream.CopyTo(ms);
Assert.Greater(ms.Length, 0);
@@ -102,37 +87,24 @@ public async Task can_get_stream() {
[Test]
public async Task can_get_string() {
- var s = await "http://www.google.com".GetStringAsync();
+ var s = await "https://www.google.com".GetStringAsync();
Assert.Greater(s.Length, 0);
}
[Test]
public async Task can_get_byte_array() {
- var bytes = await "http://www.google.com".GetBytesAsync();
+ var bytes = await "https://www.google.com".GetBytesAsync();
Assert.Greater(bytes.Length, 0);
}
[Test]
public void fails_on_non_success_status() {
- Assert.ThrowsAsync(async () => await "http://httpbin.org/status/418".GetAsync());
+ Assert.ThrowsAsync(async () => await "https://httpbin.org/status/418".GetAsync());
}
[Test]
public async Task can_allow_non_success_status() {
- await "http://httpbin.org/status/418".AllowHttpStatus("4xx").GetAsync();
- }
-
- [Test]
- public void can_cancel_request() {
- var ex = Assert.ThrowsAsync(async () =>
- {
- var cts = new CancellationTokenSource();
- var task = "http://www.google.com".GetStringAsync(cts.Token);
- cts.Cancel();
- await task;
- });
-
- Assert.IsNotNull(ex.InnerException as TaskCanceledException);
+ await "https://httpbin.org/status/418".AllowHttpStatus("4xx").GetAsync();
}
[Test]
@@ -148,7 +120,7 @@ public async Task can_post_multipart() {
try {
using (var stream = File.OpenRead(path2)) {
- var resp = await "http://httpbin.org/post"
+ var resp = await "https://httpbin.org/post"
.PostMultipartAsync(content => {
content
.AddStringParts(new { a = 1, b = 2 })
@@ -177,34 +149,159 @@ public async Task can_post_multipart() {
public async Task can_handle_error() {
var handlerCalled = false;
- FlurlHttp.Configure(c => {
- c.OnError = call => {
- call.ExceptionHandled = true;
- handlerCalled = true;
- };
- });
-
try {
- await "https://httpbin.org/status/500".GetAsync();
+ await "https://httpbin.org/status/500".ConfigureRequest(c => {
+ c.OnError = call => {
+ call.ExceptionHandled = true;
+ handlerCalled = true;
+ };
+ }).GetAsync();
Assert.IsTrue(handlerCalled, "error handler shoule have been called.");
}
catch (FlurlHttpException) {
Assert.Fail("exception should have been supressed.");
}
- finally {
- FlurlHttp.Configure(c => c.ResetDefaults());
- }
}
[Test]
public async Task can_comingle_real_and_fake_tests() {
// do a fake call while a real call is running
var realTask = "https://www.google.com/".GetStringAsync();
- using (new HttpTest()) {
+ using (var test = new HttpTest()) {
+ test.RespondWith("fake!");
var fake = await "https://www.google.com/".GetStringAsync();
- Assert.AreEqual("", fake);
+ Assert.AreEqual("fake!", fake);
+ }
+ Assert.AreNotEqual("fake!", await realTask);
+ }
+
+ [Test]
+ public void can_set_timeout() {
+ var ex = Assert.ThrowsAsync(async () => {
+ await "https://httpbin.org/delay/5"
+ .WithTimeout(TimeSpan.FromMilliseconds(50))
+ .HeadAsync();
+ });
+ Assert.That(ex.InnerException is TaskCanceledException);
+ }
+
+ [Test]
+ public void can_cancel_request() {
+ var cts = new CancellationTokenSource();
+ var ex = Assert.ThrowsAsync(async () => {
+ var task = "https://httpbin.org/delay/5".GetAsync(cts.Token);
+ cts.Cancel();
+ await task;
+ });
+ Assert.That(ex.InnerException is TaskCanceledException);
+ }
+
+ [Test] // make sure the 2 tokens in play are playing nicely together
+ public void can_set_timeout_and_cancellation_token() {
+ // cancellation with timeout value set
+ var cts = new CancellationTokenSource();
+ var ex = Assert.ThrowsAsync(async () => {
+ var task = "https://httpbin.org/delay/5"
+ .WithTimeout(TimeSpan.FromMilliseconds(50))
+ .GetAsync(cts.Token);
+ cts.Cancel();
+ await task;
+ });
+ Assert.That(ex.InnerException is TaskCanceledException);
+ Assert.IsTrue(cts.Token.IsCancellationRequested);
+
+ // timeout with cancellation token set
+ cts = new CancellationTokenSource();
+ ex = Assert.ThrowsAsync(async () => {
+ await "https://httpbin.org/delay/5"
+ .WithTimeout(TimeSpan.FromMilliseconds(50))
+ .GetAsync(cts.Token);
+ });
+ Assert.That(ex.InnerException is TaskCanceledException);
+ Assert.IsFalse(cts.Token.IsCancellationRequested);
+ }
+
+ [Test]
+ public async Task can_set_request_cookies_with_a_delegating_handler() {
+ var resp = await new FlurlClient("http://httpbin.org")
+ .Configure(settings => settings.HttpClientFactory = new DelegatingHandlerHttpClientFactory())
+ .Request("cookies")
+ .WithCookies(new { x = 1, y = 2 })
+ .GetJsonAsync();
+
+ // httpbin returns json representation of cookies that were set on the server.
+ Assert.AreEqual("1", resp.cookies.x);
+ Assert.AreEqual("2", resp.cookies.y);
+ }
+
+ [Test]
+ public async Task can_get_response_cookies_with_a_delegating_handler() {
+ var cli = new FlurlClient("https://httpbin.org")
+ .Configure(settings => settings.HttpClientFactory = new DelegatingHandlerHttpClientFactory())
+ .EnableCookies();
+
+ await cli.Request("cookies/set?z=999").HeadAsync();
+ Assert.AreEqual("999", cli.Cookies["z"].Value);
+ }
+
+ [Test]
+ public async Task connection_lease_timeout_doesnt_disrupt_calls() {
+ // Specific behavior associated with ConnectionLeaseTimeout is coverd in SettingsTests.
+ // Here let's just make sure it isn't disruptive in any way in real calls.
+
+ var cli = new FlurlClient("http://www.google.com");
+ cli.Settings.ConnectionLeaseTimeout = TimeSpan.FromMilliseconds(20);
+
+ // initiate a call to google every 10ms for 100ms.
+ var tasks = new List();
+ for (var i = 0; i < 10; i++) {
+ tasks.Add(cli.Request().GetAsync());
+ await Task.Delay(10);
+ }
+ await Task.WhenAll(tasks); // failed HTTP status, etc, would throw here and fail the test.
+ }
+
+ [Test]
+ public async Task test_settings_override_client_settings() {
+ var cli1 = new FlurlClient();
+ cli1.Settings.HttpClientFactory = new DefaultHttpClientFactory();
+ var h = cli1.HttpClient; // force (lazy) instantiation
+
+ using (var test = new HttpTest()) {
+ test.Settings.CookiesEnabled = false;
+
+ test.RespondWith("foo!");
+ var s = await cli1.Request("http://www.google.com")
+ .EnableCookies() // test says cookies are off, and test should always win
+ .GetStringAsync();
+ Assert.AreEqual("foo!", s);
+ Assert.IsFalse(cli1.Settings.CookiesEnabled);
+
+ var cli2 = new FlurlClient();
+ cli2.Settings.HttpClientFactory = new DefaultHttpClientFactory();
+ h = cli2.HttpClient;
+
+ test.RespondWith("foo 2!");
+ s = await cli2.Request("http://www.google.com")
+ .EnableCookies() // test says cookies are off, and test should always win
+ .GetStringAsync();
+ Assert.AreEqual("foo 2!", s);
+ Assert.IsFalse(cli2.Settings.CookiesEnabled);
+ }
+ }
+
+ public class DelegatingHandlerHttpClientFactory : DefaultHttpClientFactory
+ {
+ public override HttpMessageHandler CreateMessageHandler() {
+ var handler = base.CreateMessageHandler();
+
+ return new PassThroughDelegatingHandler(new PassThroughDelegatingHandler(handler));
+ }
+
+ public class PassThroughDelegatingHandler : DelegatingHandler
+ {
+ public PassThroughDelegatingHandler(HttpMessageHandler innerHandler) : base(innerHandler) { }
}
- Assert.AreNotEqual("", await realTask);
}
}
}
\ No newline at end of file
diff --git a/Test/Flurl.Test/Http/SettingsExtensionsTests.cs b/Test/Flurl.Test/Http/SettingsExtensionsTests.cs
new file mode 100644
index 00000000..3b3b70c9
--- /dev/null
+++ b/Test/Flurl.Test/Http/SettingsExtensionsTests.cs
@@ -0,0 +1,183 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Threading.Tasks;
+using Flurl.Http;
+using Flurl.Http.Testing;
+using NUnit.Framework;
+
+namespace Flurl.Test.Http
+{
+ // IFlurlClient and IFlurlRequest both implement IHttpSettingsContainer, which defines a number
+ // of settings-related extension methods. This abstract test class allows those methods to be
+ // tested against both both client-level and request-level implementations.
+ public abstract class SettingsExtensionsTests where T : IHttpSettingsContainer
+ {
+ protected abstract T GetSettingsContainer();
+ protected abstract IFlurlRequest GetRequest(T sc);
+
+ [Test]
+ public void can_set_timeout() {
+ var sc = GetSettingsContainer().WithTimeout(TimeSpan.FromSeconds(15));
+ Assert.AreEqual(TimeSpan.FromSeconds(15), sc.Settings.Timeout);
+ }
+
+ [Test]
+ public void can_set_timeout_in_seconds() {
+ var sc = GetSettingsContainer().WithTimeout(15);
+ Assert.AreEqual(sc.Settings.Timeout, TimeSpan.FromSeconds(15));
+ }
+
+ [Test]
+ public void can_set_header() {
+ var sc = GetSettingsContainer().WithHeader("a", 1);
+ Assert.AreEqual(1, sc.Headers.Count);
+ Assert.AreEqual(1, sc.Headers["a"]);
+ }
+
+ [Test]
+ public void can_set_headers_from_anon_object() {
+ var sc = GetSettingsContainer().WithHeaders(new { a = "b", one = 2 });
+ Assert.AreEqual(2, sc.Headers.Count);
+ Assert.AreEqual("b", sc.Headers["a"]);
+ Assert.AreEqual(2, sc.Headers["one"]);
+ }
+
+ [Test]
+ public void can_set_headers_from_dictionary() {
+ var sc = GetSettingsContainer().WithHeaders(new Dictionary { { "a", "b" }, { "one", 2 } });
+ Assert.AreEqual(2, sc.Headers.Count);
+ Assert.AreEqual("b", sc.Headers["a"]);
+ Assert.AreEqual(2, sc.Headers["one"]);
+ }
+
+ [Test]
+ public void underscores_in_properties_convert_to_hyphens_in_header_names() {
+ var sc = GetSettingsContainer().WithHeaders(new { User_Agent = "Flurl", Cache_Control = "no-cache" });
+ Assert.IsTrue(sc.Headers.ContainsKey("User-Agent"));
+ Assert.IsTrue(sc.Headers.ContainsKey("Cache-Control"));
+
+ // make sure we can disable the behavior
+ sc.WithHeaders(new { no_i_really_want_underscores = "foo" }, false);
+ Assert.IsTrue(sc.Headers.ContainsKey("no_i_really_want_underscores"));
+
+ // dictionaries don't get this behavior since you can use hyphens explicitly
+ sc.WithHeaders(new Dictionary { { "exclude_dictionaries", "bar" } });
+ Assert.IsTrue(sc.Headers.ContainsKey("exclude_dictionaries"));
+
+ // same with strings
+ sc.WithHeaders("exclude_strings=123");
+ Assert.IsTrue(sc.Headers.ContainsKey("exclude_strings"));
+ }
+
+ [Test]
+ public void can_setup_oauth_bearer_token() {
+ var sc = GetSettingsContainer().WithOAuthBearerToken("mytoken");
+ Assert.AreEqual(1, sc.Headers.Count);
+ Assert.AreEqual("Bearer mytoken", sc.Headers["Authorization"]);
+ }
+
+ [Test]
+ public void can_setup_basic_auth() {
+ var sc = GetSettingsContainer().WithBasicAuth("user", "pass");
+ Assert.AreEqual(1, sc.Headers.Count);
+ Assert.AreEqual("Basic dXNlcjpwYXNz", sc.Headers["Authorization"]);
+ }
+
+ [Test]
+ public async Task can_allow_specific_http_status() {
+ using (var test = new HttpTest()) {
+ test.RespondWith("Nothing to see here", 404);
+ var sc = GetSettingsContainer().AllowHttpStatus(HttpStatusCode.Conflict, HttpStatusCode.NotFound);
+ await GetRequest(sc).DeleteAsync(); // no exception = pass
+ }
+ }
+
+ [Test]
+ public void can_clear_non_success_status() {
+ using (var test = new HttpTest()) {
+ test.RespondWith("I'm a teapot", 418);
+ // allow 4xx
+ var sc = GetSettingsContainer().AllowHttpStatus("4xx");
+ // but then disallow it
+ sc.Settings.AllowedHttpStatusRange = null;
+ Assert.ThrowsAsync(async () => await GetRequest(sc).GetAsync());
+ }
+ }
+
+ [Test]
+ public async Task can_allow_any_http_status() {
+ using (var test = new HttpTest()) {
+ test.RespondWith("epic fail", 500);
+ try {
+ var sc = GetSettingsContainer().AllowAnyHttpStatus();
+ var result = await GetRequest(sc).GetAsync();
+ Assert.IsFalse(result.IsSuccessStatusCode);
+ }
+ catch (Exception) {
+ Assert.Fail("Exception should not have been thrown.");
+ }
+ }
+ }
+ }
+
+ [TestFixture, Parallelizable]
+ public class ClientSettingsExtensionsTests : SettingsExtensionsTests
+ {
+ protected override IFlurlClient GetSettingsContainer() => new FlurlClient();
+ protected override IFlurlRequest GetRequest(IFlurlClient client) => client.Request("http://api.com");
+
+ [Test]
+ public void WithUrl_shares_client_but_not_Url() {
+ var cli = new FlurlClient().WithCookie("mycookie", "123");
+ var req1 = cli.Request("http://www.api.com/for-req1");
+ var req2 = cli.Request("http://www.api.com/for-req2");
+ var req3 = cli.Request("http://www.api.com/for-req3");
+
+ CollectionAssert.AreEquivalent(req1.Cookies, req2.Cookies);
+ CollectionAssert.AreEquivalent(req1.Cookies, req3.Cookies);
+ var urls = new[] { req1, req2, req3 }.Select(c => c.Url.ToString());
+ CollectionAssert.AllItemsAreUnique(urls);
+ }
+
+ [Test]
+ public void WithClient_shares_client_but_not_Url() {
+ var cli = new FlurlClient().WithCookie("mycookie", "123");
+ var req1 = "http://www.api.com/for-req1".WithClient(cli);
+ var req2 = "http://www.api.com/for-req2".WithClient(cli);
+ var req3 = "http://www.api.com/for-req3".WithClient(cli);
+
+ CollectionAssert.AreEquivalent(req1.Cookies, req2.Cookies);
+ CollectionAssert.AreEquivalent(req1.Cookies, req3.Cookies);
+ var urls = new[] { req1, req2, req3 }.Select(c => c.Url.ToString());
+ CollectionAssert.AllItemsAreUnique(urls);
+ }
+
+ [Test]
+ public void can_use_uri_with_WithUrl() {
+ var uri = new System.Uri("http://www.mysite.com/foo?x=1");
+ var req = new FlurlClient().Request(uri);
+ Assert.AreEqual(uri.ToString(), req.Url.ToString());
+ }
+
+ [Test]
+ public void can_override_settings_fluently() {
+ using (var test = new HttpTest()) {
+ var cli = new FlurlClient().Configure(s => s.AllowedHttpStatusRange = "*");
+ test.RespondWith("epic fail", 500);
+ Assert.ThrowsAsync(async () => await "http://www.api.com"
+ .ConfigureRequest(c => c.AllowedHttpStatusRange = "2xx")
+ .WithClient(cli) // client-level settings shouldn't win
+ .GetAsync());
+ }
+ }
+ }
+
+ [TestFixture, Parallelizable]
+ public class RequestSettingsExtensionsTests : SettingsExtensionsTests
+ {
+ protected override IFlurlRequest GetSettingsContainer() => new FlurlRequest("http://api.com");
+ protected override IFlurlRequest GetRequest(IFlurlRequest req) => req;
+ }
+}
\ No newline at end of file
diff --git a/Test/Flurl.Test/Http/SettingsTests.cs b/Test/Flurl.Test/Http/SettingsTests.cs
new file mode 100644
index 00000000..1d283b93
--- /dev/null
+++ b/Test/Flurl.Test/Http/SettingsTests.cs
@@ -0,0 +1,278 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Flurl.Http;
+using Flurl.Http.Configuration;
+using Flurl.Http.Testing;
+using NUnit.Framework;
+
+namespace Flurl.Test.Http
+{
+ ///
+ /// FlurlHttpSettings are available at the global, test, client, and request level. This abstract class
+ /// allows the same tests to be run against settings at all 4 levels.
+ ///
+ public abstract class SettingsTestsBase
+ {
+ protected abstract FlurlHttpSettings GetSettings();
+ protected abstract IFlurlRequest GetRequest();
+
+ [Test]
+ public async Task can_allow_non_success_status() {
+ using (var test = new HttpTest()) {
+ GetSettings().AllowedHttpStatusRange = "4xx";
+ test.RespondWith("I'm a teapot", 418);
+ try {
+ var result = await GetRequest().GetAsync();
+ Assert.IsFalse(result.IsSuccessStatusCode);
+ }
+ catch (Exception) {
+ Assert.Fail("Exception should not have been thrown.");
+ }
+ }
+ }
+
+ [Test]
+ public async Task can_set_pre_callback() {
+ var callbackCalled = false;
+ using (var test = new HttpTest()) {
+ test.RespondWith("ok");
+ GetSettings().BeforeCall = call => {
+ CollectionAssert.IsNotEmpty(test.ResponseQueue); // verifies that callback is running before HTTP call is made
+ callbackCalled = true;
+ };
+ Assert.IsFalse(callbackCalled);
+ await GetRequest().GetAsync();
+ Assert.IsTrue(callbackCalled);
+ }
+ }
+
+ [Test]
+ public async Task can_set_post_callback() {
+ var callbackCalled = false;
+ using (var test = new HttpTest()) {
+ test.RespondWith("ok");
+ GetSettings().AfterCall = call => {
+ CollectionAssert.IsEmpty(test.ResponseQueue); // verifies that callback is running after HTTP call is made
+ callbackCalled = true;
+ };
+ Assert.IsFalse(callbackCalled);
+ await GetRequest().GetAsync();
+ Assert.IsTrue(callbackCalled);
+ }
+ }
+
+ [TestCase(true)]
+ [TestCase(false)]
+ public async Task can_set_error_callback(bool markExceptionHandled) {
+ var callbackCalled = false;
+ using (var test = new HttpTest()) {
+ test.RespondWith("server error", 500);
+ GetSettings().OnError = call => {
+ CollectionAssert.IsEmpty(test.ResponseQueue); // verifies that callback is running after HTTP call is made
+ callbackCalled = true;
+ call.ExceptionHandled = markExceptionHandled;
+ };
+ Assert.IsFalse(callbackCalled);
+ try {
+ await GetRequest().GetAsync();
+ Assert.IsTrue(callbackCalled, "OnError was never called");
+ Assert.IsTrue(markExceptionHandled, "ExceptionHandled was marked false in callback, but exception was not propagated.");
+ }
+ catch (FlurlHttpException) {
+ Assert.IsTrue(callbackCalled, "OnError was never called");
+ Assert.IsFalse(markExceptionHandled, "ExceptionHandled was marked true in callback, but exception was propagated.");
+ }
+ }
+ }
+
+ [Test]
+ public async Task can_disable_exception_behavior() {
+ using (var test = new HttpTest()) {
+ GetSettings().OnError = call => {
+ call.ExceptionHandled = true;
+ };
+ test.RespondWith("server error", 500);
+ try {
+ var result = await GetRequest().GetAsync();
+ Assert.IsFalse(result.IsSuccessStatusCode);
+ }
+ catch (FlurlHttpException) {
+ Assert.Fail("Flurl should not have thrown exception.");
+ }
+ }
+ }
+
+ [Test]
+ public void can_reset_defaults() {
+ GetSettings().JsonSerializer = null;
+ GetSettings().CookiesEnabled = true;
+ GetSettings().BeforeCall = (call) => Console.WriteLine("Before!");
+
+ Assert.IsNull(GetSettings().JsonSerializer);
+ Assert.IsTrue(GetSettings().CookiesEnabled);
+ Assert.IsNotNull(GetSettings().BeforeCall);
+
+ GetSettings().ResetDefaults();
+
+ Assert.That(GetSettings().JsonSerializer is NewtonsoftJsonSerializer);
+ Assert.IsFalse(GetSettings().CookiesEnabled);
+ Assert.IsNull(GetSettings().BeforeCall);
+ }
+ }
+
+ [TestFixture, NonParallelizable] // touches global settings, so can't run in parallel
+ public class GlobalSettingsTests : SettingsTestsBase
+ {
+ protected override FlurlHttpSettings GetSettings() => FlurlHttp.GlobalSettings;
+ protected override IFlurlRequest GetRequest() => new FlurlRequest("http://api.com");
+
+ [TearDown]
+ public void ResetDefaults() => FlurlHttp.GlobalSettings.ResetDefaults();
+
+ [Test]
+ public void settings_propagate_correctly() {
+ FlurlHttp.GlobalSettings.CookiesEnabled = false;
+ FlurlHttp.GlobalSettings.AllowedHttpStatusRange = "4xx";
+
+ var client1 = new FlurlClient();
+ client1.Settings.CookiesEnabled = true;
+ Assert.AreEqual("4xx", client1.Settings.AllowedHttpStatusRange);
+ client1.Settings.AllowedHttpStatusRange = "5xx";
+
+ var req = client1.Request("http://myapi.com");
+ Assert.IsTrue(req.Settings.CookiesEnabled, "request should inherit client settings when not set at request level");
+ Assert.AreEqual("5xx", req.Settings.AllowedHttpStatusRange, "request should inherit client settings when not set at request level");
+
+ var client2 = new FlurlClient();
+ client2.Settings.CookiesEnabled = false;
+
+ req.WithClient(client2);
+ Assert.IsFalse(req.Settings.CookiesEnabled, "request should inherit client settings when not set at request level");
+ Assert.AreEqual("4xx", req.Settings.AllowedHttpStatusRange, "request should inherit global settings when not set at request or client level");
+
+ client2.Settings.CookiesEnabled = true;
+ client2.Settings.AllowedHttpStatusRange = "3xx";
+ Assert.IsTrue(req.Settings.CookiesEnabled, "request should inherit client sttings when not set at request level");
+ Assert.AreEqual("3xx", req.Settings.AllowedHttpStatusRange, "request should inherit client sttings when not set at request level");
+
+ req.Settings.CookiesEnabled = false;
+ req.Settings.AllowedHttpStatusRange = "6xx";
+ Assert.IsFalse(req.Settings.CookiesEnabled, "request-level settings should override any defaults");
+ Assert.AreEqual("6xx", req.Settings.AllowedHttpStatusRange, "request-level settings should override any defaults");
+
+ req.Settings.ResetDefaults();
+ Assert.IsTrue(req.Settings.CookiesEnabled, "request should inherit client sttings when cleared at request level");
+ Assert.AreEqual("3xx", req.Settings.AllowedHttpStatusRange, "request should inherit client sttings when cleared request level");
+
+ client2.Settings.ResetDefaults();
+ Assert.IsFalse(req.Settings.CookiesEnabled, "request should inherit global settings when cleared at request and client level");
+ Assert.AreEqual("4xx", req.Settings.AllowedHttpStatusRange, "request should inherit global settings when cleared at request and client level");
+ }
+
+ [Test]
+ public void can_provide_custom_client_factory() {
+ FlurlHttp.GlobalSettings.HttpClientFactory = new SomeCustomHttpClientFactory();
+ Assert.IsInstanceOf(GetRequest().Client.HttpClient);
+ Assert.IsInstanceOf(GetRequest().Client.HttpMessageHandler);
+ }
+
+ [Test]
+ public void can_configure_global_from_FlurlHttp_object() {
+ FlurlHttp.Configure(settings => settings.CookiesEnabled = true);
+ Assert.IsTrue(FlurlHttp.GlobalSettings.CookiesEnabled);
+ }
+
+ [Test]
+ public void can_configure_client_from_FlurlHttp_object() {
+ FlurlHttp.ConfigureClient("http://host1.com/foo", settings => settings.CookiesEnabled = true);
+ Assert.IsTrue(new FlurlRequest("https://host1.com/bar").Client.Settings.CookiesEnabled); // different URL but same host, so should use same client
+ Assert.IsFalse(new FlurlRequest("http://host2.com").Client.Settings.CookiesEnabled);
+ }
+ }
+
+ [TestFixture, Parallelizable]
+ public class HttpTestSettingsTests : SettingsTestsBase
+ {
+ private HttpTest _test;
+
+ [SetUp]
+ public void CreateTest() => _test = new HttpTest();
+
+ [TearDown]
+ public void DisposeTest() => _test.Dispose();
+
+ protected override FlurlHttpSettings GetSettings() => HttpTest.Current.Settings;
+ protected override IFlurlRequest GetRequest() => new FlurlRequest("http://api.com");
+ }
+
+ [TestFixture, Parallelizable]
+ public class ClientSettingsTests : SettingsTestsBase
+ {
+ private readonly Lazy _client = new Lazy(() => new FlurlClient());
+
+ protected override FlurlHttpSettings GetSettings() => _client.Value.Settings;
+ protected override IFlurlRequest GetRequest() => _client.Value.Request("http://api.com");
+
+ [Test]
+ public void can_provide_custom_client_factory() {
+ var cli = new FlurlClient();
+ cli.Settings.HttpClientFactory = new SomeCustomHttpClientFactory();
+ Assert.IsInstanceOf(cli.HttpClient);
+ Assert.IsInstanceOf(cli.HttpMessageHandler);
+ }
+
+ [Test]
+ public async Task connection_lease_timeout_sets_connection_close_header() {
+ using (var test = new HttpTest()) {
+ var cli = new FlurlClient("http://api.com");
+ cli.Settings.ConnectionLeaseTimeout = TimeSpan.FromMilliseconds(50);
+
+ await cli.Request("1").GetAsync();
+ test.ShouldHaveCalled("http://api.com/1").WithoutHeader("Connection");
+
+ // exceed the timeout
+ await Task.Delay(51);
+
+ // slam it many times concurrently
+ await Task.WhenAll(
+ cli.Request("2").GetAsync(),
+ cli.Request("2").GetAsync(),
+ cli.Request("2").GetAsync(),
+ cli.Request("2").GetAsync(),
+ cli.Request("2").GetAsync(),
+ cli.Request("2").GetAsync(),
+ cli.Request("2").GetAsync(),
+ cli.Request("2").GetAsync());
+
+ // connection:close header should get sent exactly once
+ test.ShouldHaveCalled("http://api.com/2").WithHeader("Connection", "close").Times(1);
+
+ await Task.Delay(10);
+
+ await cli.Request("3").GetAsync();
+ test.ShouldHaveCalled("http://api.com/3").WithoutHeader("Connection");
+ }
+ }
+ }
+
+ [TestFixture, Parallelizable]
+ public class RequestSettingsTests : SettingsTestsBase
+ {
+ private readonly Lazy _req = new Lazy(() => new FlurlRequest("http://api.com"));
+
+ protected override FlurlHttpSettings GetSettings() => _req.Value.Settings;
+ protected override IFlurlRequest GetRequest() => _req.Value;
+ }
+
+ public class SomeCustomHttpClientFactory : IHttpClientFactory
+ {
+ public HttpClient CreateHttpClient(HttpMessageHandler handler) => new SomeCustomHttpClient();
+ public HttpMessageHandler CreateMessageHandler() => new SomeCustomMessageHandler();
+ }
+
+ public class SomeCustomHttpClient : HttpClient { }
+ public class SomeCustomMessageHandler : HttpClientHandler { }
+}
diff --git a/Test/Flurl.Test/Http/TestingTests.cs b/Test/Flurl.Test/Http/TestingTests.cs
index e4b1b9e1..48e25d90 100644
--- a/Test/Flurl.Test/Http/TestingTests.cs
+++ b/Test/Flurl.Test/Http/TestingTests.cs
@@ -9,7 +9,7 @@
namespace Flurl.Test.Http
{
- [TestFixture]
+ [TestFixture, Parallelizable]
public class TestingTests : HttpTestFixtureBase
{
[Test]
@@ -123,6 +123,24 @@ public async Task can_assert_multiple_occurances_of_query_param() {
HttpTest.ShouldHaveMadeACall().WithQueryParamValues(new { x = new[] { 1, 2, 4 } }));
}
+ [Test]
+ public async Task can_assert_headers() {
+ await "http://api.com".WithHeaders(new { h1 = "val1", h2 = "val2" }).GetAsync();
+
+ HttpTest.ShouldHaveMadeACall().WithHeader("h1");
+ HttpTest.ShouldHaveMadeACall().WithHeader("h2", "val2");
+ HttpTest.ShouldHaveMadeACall().WithHeader("h1", "val*");
+
+ HttpTest.ShouldHaveMadeACall().WithoutHeader("h3");
+ HttpTest.ShouldHaveMadeACall().WithoutHeader("h2", "val1");
+ HttpTest.ShouldHaveMadeACall().WithoutHeader("h1", "foo*");
+
+ Assert.Throws(() =>
+ HttpTest.ShouldHaveMadeACall().WithHeader("h3"));
+ Assert.Throws(() =>
+ HttpTest.ShouldHaveMadeACall().WithoutHeader("h1"));
+ }
+
[Test]
public async Task can_simulate_timeout() {
HttpTest.SimulateTimeout();
@@ -140,7 +158,7 @@ public async Task can_simulate_timeout() {
public async Task can_simulate_timeout_with_exception_handled() {
HttpTest.SimulateTimeout();
var result = await "http://www.api.com"
- .ConfigureClient(c => c.OnError = call => call.ExceptionHandled = true)
+ .ConfigureRequest(c => c.OnError = call => call.ExceptionHandled = true)
.GetAsync();
Assert.IsNull(result);
}
@@ -159,28 +177,10 @@ public async Task can_fake_headers() {
public async Task can_fake_cookies() {
HttpTest.RespondWith(cookies: new { c1 = "foo" });
- var fc = "http://www.api.com".EnableCookies();
- await fc.GetAsync();
- Assert.AreEqual(1, fc.Cookies.Count());
- Assert.AreEqual("foo", fc.Cookies["c1"].Value);
- }
-
- // https://github.com/tmenier/Flurl/issues/169
- [Test]
- public async Task cannot_inspect_RequestBody_with_uncaptured_content() {
- using (var httpTest = new HttpTest()) {
- // use StringContent instead of CapturedStringContent
- await "http://api.com".SendAsync(HttpMethod.Post, new StringContent("foo", null, "text/plain"));
- try {
- httpTest.ShouldHaveMadeACall().WithRequestBody("foo");
- Assert.Fail("Asserting RequestBody with uncaptured content should have thrown FlurlHttpException.");
- }
- catch (FlurlHttpException ex) {
- // message should mention RequestBody and CapturedStringContent
- StringAssert.Contains("RequestBody", ex.Message);
- StringAssert.Contains("CapturedStringContent", ex.Message);
- }
- }
+ var rec = "http://www.api.com".EnableCookies();
+ await rec.GetAsync();
+ Assert.AreEqual(1, rec.Cookies.Count);
+ Assert.AreEqual("foo", rec.Cookies["c1"].Value);
}
// https://github.com/tmenier/Flurl/issues/175
@@ -195,8 +195,16 @@ public async Task can_deserialize_default_response_more_than_once() {
Assert.IsNull(resp);
}
- // parallel testing not supported in PCL
-#if !PORTABLE
+ [Test]
+ public void can_configure_settings_for_test() {
+ Assert.IsFalse(new FlurlRequest().Settings.CookiesEnabled);
+ using (new HttpTest().Configure(settings => settings.CookiesEnabled = true)) {
+ Assert.IsTrue(new FlurlRequest().Settings.CookiesEnabled);
+ }
+ // test disposed, should revert back to global settings
+ Assert.IsFalse(new FlurlRequest().Settings.CookiesEnabled);
+ }
+
[Test]
public async Task can_test_in_parallel() {
await Task.WhenAll(
@@ -206,7 +214,6 @@ await Task.WhenAll(
CallAndAssertCountAsync(4),
CallAndAssertCountAsync(6));
}
-#endif
private async Task CallAndAssertCountAsync(int calls) {
using (var test = new HttpTest()) {
diff --git a/Test/Flurl.Test/UrlBuilderTests.cs b/Test/Flurl.Test/UrlBuilderTests.cs
index 66db08e5..66b136db 100644
--- a/Test/Flurl.Test/UrlBuilderTests.cs
+++ b/Test/Flurl.Test/UrlBuilderTests.cs
@@ -8,7 +8,7 @@
namespace Flurl.Test
{
- [TestFixture]
+ [TestFixture, Parallelizable]
public class UrlBuilderTests
{
[Test]
diff --git a/src/Flurl.Http.CodeGen/ExtensionMethodModel.cs b/src/Flurl.Http.CodeGen/HttpExtensionMethod.cs
similarity index 90%
rename from src/Flurl.Http.CodeGen/ExtensionMethodModel.cs
rename to src/Flurl.Http.CodeGen/HttpExtensionMethod.cs
index 7a822602..23272bc5 100644
--- a/src/Flurl.Http.CodeGen/ExtensionMethodModel.cs
+++ b/src/Flurl.Http.CodeGen/HttpExtensionMethod.cs
@@ -3,19 +3,19 @@
namespace Flurl.Http.CodeGen
{
- public class ExtensionMethodModel
+ public class HttpExtensionMethod
{
- public static IEnumerable GetAll() {
+ public static IEnumerable GetAll() {
return
from httpVerb in new[] { null, "Get", "Post", "Head", "Put", "Delete", "Patch" }
from bodyType in new[] { null, "Json", /*"Xml",*/ "String", "UrlEncoded" }
- from extensionType in new[] { "IFlurlClient", "Url", "string" }
+ from extensionType in new[] { "IFlurlRequest", "Url", "string" }
where SupportedCombo(httpVerb, bodyType, extensionType)
from deserializeType in new[] { null, "Json", "JsonList", /*"Xml",*/ "String", "Stream", "Bytes" }
where httpVerb == "Get" || deserializeType == null
from isGeneric in new[] { true, false }
where AllowDeserializeToGeneric(deserializeType) || !isGeneric
- select new ExtensionMethodModel {
+ select new HttpExtensionMethod {
HttpVerb = httpVerb,
BodyType = bodyType,
ExtentionOfType = extensionType,
@@ -27,7 +27,7 @@ where AllowDeserializeToGeneric(deserializeType) || !isGeneric
private static bool SupportedCombo(string verb, string bodyType, string extensionType) {
switch (verb) {
case null: // Send
- return bodyType != null || extensionType != "IFlurlClient";
+ return bodyType != null || extensionType != "IFlurlRequest";
case "Post":
return true;
case "Put":
diff --git a/src/Flurl.Http.CodeGen/Program.cs b/src/Flurl.Http.CodeGen/Program.cs
index 62234b13..01d8934d 100644
--- a/src/Flurl.Http.CodeGen/Program.cs
+++ b/src/Flurl.Http.CodeGen/Program.cs
@@ -8,11 +8,12 @@ namespace Flurl.Http.CodeGen
class Program
{
static int Main(string[] args) {
- var codePath = (args.Length > 0) ? args[0] : @"..\Flurl.Http.Shared\HttpExtensions.cs";
+ var codePath = (args.Length > 0) ? args[0] : @"..\Flurl.Http\GeneratedExtensions.cs";
if (!File.Exists(codePath)) {
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Code file not found: " + Path.GetFullPath(codePath));
+ Console.ReadLine();
return 2;
}
@@ -22,20 +23,22 @@ static int Main(string[] args) {
{
writer
.WriteLine("// This file was auto-generated by Flurl.Http.CodeGen. Do not edit directly.")
- .WriteLine()
- .WriteLine("using System.Collections.Generic;")
+ .WriteLine("using System;")
+ .WriteLine("using System.Collections.Generic;")
.WriteLine("using System.IO;")
- .WriteLine("using System.Net.Http;")
+ .WriteLine("using System.Net;")
+ .WriteLine("using System.Net.Http;")
.WriteLine("using System.Threading;")
.WriteLine("using System.Threading.Tasks;")
- .WriteLine("using Flurl.Http.Content;")
+ .WriteLine("using Flurl.Http.Configuration;")
+ .WriteLine("using Flurl.Http.Content;")
.WriteLine("")
.WriteLine("namespace Flurl.Http")
.WriteLine("{")
- .WriteLine(" /// ")
- .WriteLine("/// Http extensions for Flurl Client.")
+ .WriteLine("/// ")
+ .WriteLine("/// Auto-generated fluent extension methods on String, Url, and IFlurlRequest.")
.WriteLine("/// ")
- .WriteLine("public static class HttpExtensions")
+ .WriteLine("public static class GeneratedExtensions")
.WriteLine("{");
WriteExtensionMethods(writer);
@@ -51,6 +54,7 @@ static int Main(string[] args) {
catch (Exception ex) {
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(ex);
+ Console.ReadLine();
return 2;
}
}
@@ -58,7 +62,7 @@ static int Main(string[] args) {
private static void WriteExtensionMethods(CodeWriter writer)
{
string name = null;
- foreach (var xm in ExtensionMethodModel.GetAll()) {
+ foreach (var xm in HttpExtensionMethod.GetAll()) {
var hasRequestBody = (xm.HttpVerb == "Post" || xm.HttpVerb == "Put" || xm.HttpVerb == "Patch" || xm.HttpVerb == null);
if (xm.Name != name) {
@@ -66,14 +70,14 @@ private static void WriteExtensionMethods(CodeWriter writer)
name = xm.Name;
}
writer.WriteLine("/// ");
- var summaryStart = (xm.ExtentionOfType == "IFlurlClient") ? "Sends" : "Creates a FlurlClient from the URL and sends";
+ var summaryStart = (xm.ExtentionOfType == "IFlurlRequest") ? "Sends" : "Creates a FlurlRequest from the URL and sends";
if (xm.HttpVerb == null)
writer.WriteLine("/// @0 an asynchronous request.", summaryStart);
else
writer.WriteLine("/// @0 an asynchronous @1 request.", summaryStart, xm.HttpVerb.ToUpperInvariant());
writer.WriteLine("/// ");
- if (xm.ExtentionOfType == "IFlurlClient")
- writer.WriteLine("/// The IFlurlClient instance.");
+ if (xm.ExtentionOfType == "IFlurlRequest")
+ writer.WriteLine("/// The IFlurlRequest instance.");
if (xm.ExtentionOfType == "Url" || xm.ExtentionOfType == "string")
writer.WriteLine("/// The URL.");
if (xm.HttpVerb == null)
@@ -87,7 +91,7 @@ private static void WriteExtensionMethods(CodeWriter writer)
writer.WriteLine("/// A Task whose result is @0.", xm.ReturnTypeDescription);
var args = new List();
- args.Add("this " + xm.ExtentionOfType + (xm.ExtentionOfType == "IFlurlClient" ? " client" : " url"));
+ args.Add("this " + xm.ExtentionOfType + (xm.ExtentionOfType == "IFlurlRequest" ? " request" : " url"));
if (xm.HttpVerb == null)
args.Add("HttpMethod verb");
if (xm.BodyType != null)
@@ -101,7 +105,7 @@ private static void WriteExtensionMethods(CodeWriter writer)
writer.WriteLine("public static Task<@0> @1@2(@3) {", xm.TaskArg, xm.Name, xm.IsGeneric ? "" : "", string.Join(", ", args));
- if (xm.ExtentionOfType == "IFlurlClient")
+ if (xm.ExtentionOfType == "IFlurlRequest")
{
args.Clear();
args.Add(
@@ -118,22 +122,45 @@ private static void WriteExtensionMethods(CodeWriter writer)
if (xm.BodyType != null) {
writer.WriteLine("var content = new Captured@0Content(@1);",
xm.BodyType,
- xm.BodyType == "String" ? "data" : $"client.Settings.{xm.BodyType}Serializer.Serialize(data)");
+ xm.BodyType == "String" ? "data" : $"request.Settings.{xm.BodyType}Serializer.Serialize(data)");
}
- var client = (xm.ExtentionOfType == "IFlurlClient") ? "client" : "new FlurlClient(url, false)";
+ var request = (xm.ExtentionOfType == "IFlurlRequest") ? "request" : "new FlurlRequest(url)";
var receive = (xm.DeserializeToType == null) ? "" : string.Format(".Receive{0}{1}()", xm.DeserializeToType, xm.IsGeneric ? "" : "");
- writer.WriteLine("return @0.SendAsync(@1)@2;", client, string.Join(", ", args), receive);
+ writer.WriteLine("return @0.SendAsync(@1)@2;", request, string.Join(", ", args), receive);
}
else
{
- writer.WriteLine("return new FlurlClient(url, false).@0(@1);",
+ writer.WriteLine("return new FlurlRequest(url).@0(@1);",
xm.Name + (xm.IsGeneric ? "" : ""),
string.Join(", ", args.Skip(1).Select(a => a.Split(' ')[1])));
}
writer.WriteLine("}").WriteLine();
}
+
+ foreach (var xtype in new[] { "Url", "string" }) {
+ foreach (var xm in UrlExtensionMethod.GetAll()) {
+ if (xm.Name != name) {
+ Console.WriteLine($"writing {xm.Name}...");
+ name = xm.Name;
+ }
+
+ writer.WriteLine("/// ");
+ writer.WriteLine($"/// {xm.Description}");
+ writer.WriteLine("/// ");
+ writer.WriteLine("/// The URL.");
+ foreach (var p in xm.Params)
+ writer.WriteLine($"/// {p.Description}");
+ writer.WriteLine("/// The IFlurlRequest.");
+
+ var argList = new List { $"this {xtype} url" };
+ argList.AddRange(xm.Params.Select(p => $"{p.Type} {p.Name}" + (p.Default == null ? "" : $" = {p.Default}")));
+ writer.WriteLine($"public static IFlurlRequest {xm.Name}({string.Join(", ", argList)}) {{");
+ writer.WriteLine($"return new FlurlRequest(url).{xm.Name}({string.Join(", ", xm.Params.Select(p => p.Name))});");
+ writer.WriteLine("}");
+ }
+ }
}
}
}
\ No newline at end of file
diff --git a/src/Flurl.Http.CodeGen/UrlExtensionMethod.cs b/src/Flurl.Http.CodeGen/UrlExtensionMethod.cs
new file mode 100644
index 00000000..b523de9e
--- /dev/null
+++ b/src/Flurl.Http.CodeGen/UrlExtensionMethod.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Flurl.Http.CodeGen
+{
+ ///
+ /// Extension methods manually defined on IFlurlRequest and IFlurlClient. We'll auto-gen overloads for Url and string.
+ ///
+ public class UrlExtensionMethod
+ {
+ public static IEnumerable GetAll() {
+ // header extensions
+ yield return new UrlExtensionMethod("WithHeader", "Creates a new FlurlRequest with the URL and sets a request header.")
+ .AddParam("name", "string", "The header name.")
+ .AddParam("value", "object", "The header value.");
+ yield return new UrlExtensionMethod("WithHeaders", "Creates a new FlurlRequest with the URL and sets request headers based on property names/values of the provided object, or keys/values if object is a dictionary, to be sent")
+ .AddParam("headers", "object", "Names/values of HTTP headers to set. Typically an anonymous object or IDictionary.")
+ .AddParam("replaceUnderscoreWithHyphen", "bool", "If true, underscores in property names will be replaced by hyphens. Default is true.", "true");
+ yield return new UrlExtensionMethod("WithBasicAuth", "Creates a new FlurlRequest with the URL and sets the Authorization header according to Basic Authentication protocol.")
+ .AddParam("username", "string", "Username of authenticating user.")
+ .AddParam("password", "string", "Password of authenticating user.");
+ yield return new UrlExtensionMethod("WithOAuthBearerToken", "Creates a new FlurlRequest with the URL and sets the Authorization header with a bearer token according to OAuth 2.0 specification.")
+ .AddParam("token", "string", "The acquired oAuth bearer token.");
+
+ // cookie extensions
+ yield return new UrlExtensionMethod("EnableCookies", "Creates a new FlurlRequest with the URL and allows cookies to be sent and received. Not necessary to call when setting cookies via WithCookie/WithCookies.");
+ yield return new UrlExtensionMethod("WithCookie", "Creates a new FlurlRequest with the URL and sets an HTTP cookie to be sent")
+ .AddParam("cookie", "Cookie", "");
+ yield return new UrlExtensionMethod("WithCookie", "Creates a new FlurlRequest with the URL and sets an HTTP cookie to be sent.")
+ .AddParam("name", "string", "The cookie name.")
+ .AddParam("value", "object", "The cookie value.")
+ .AddParam("expires", "DateTime?", "The cookie expiration (optional). If excluded, cookie only lives for duration of session.", "null");
+ yield return new UrlExtensionMethod("WithCookies", "Creates a new FlurlRequest with the URL and sets HTTP cookies to be sent, based on property names / values of the provided object, or keys / values if object is a dictionary.")
+ .AddParam("cookies", "object", "Names/values of HTTP cookies to set. Typically an anonymous object or IDictionary.")
+ .AddParam("expires", "DateTime?", "Expiration for all cookies (optional). If excluded, cookies only live for duration of session.", "null");
+
+ // settings extensions
+ yield return new UrlExtensionMethod("ConfigureRequest", "Creates a new FlurlRequest with the URL and allows changing its Settings inline.")
+ .AddParam("action", "Action", "A delegate defining the Settings changes.");
+ yield return new UrlExtensionMethod("WithTimeout", "Creates a new FlurlRequest with the URL and sets the request timeout.")
+ .AddParam("timespan", "TimeSpan", "Time to wait before the request times out.");
+ yield return new UrlExtensionMethod("WithTimeout", "Creates a new FlurlRequest with the URL and sets the request timeout.")
+ .AddParam("seconds", "int", "Seconds to wait before the request times out.");
+ yield return new UrlExtensionMethod("AllowHttpStatus", "Creates a new FlurlRequest with the URL and adds a pattern representing an HTTP status code or range of codes which (in addition to 2xx) will NOT result in a FlurlHttpException being thrown.")
+ .AddParam("pattern", "string", "Examples: \"3xx\", \"100,300,600\", \"100-299,6xx\"");
+ yield return new UrlExtensionMethod("AllowHttpStatus", "Creates a new FlurlRequest with the URL and adds an HttpStatusCode which (in addtion to 2xx) will NOT result in a FlurlHttpException being thrown.")
+ .AddParam("statusCodes", "params HttpStatusCode[]", "The HttpStatusCode(s) to allow.");
+ yield return new UrlExtensionMethod("AllowAnyHttpStatus", "Creates a new FlurlRequest with the URL and configures it to allow any returned HTTP status without throwing a FlurlHttpException.");
+ }
+
+ public string Name { get; }
+ public string Description { get; }
+ public IList Params { get; } = new List();
+
+ public UrlExtensionMethod(string name, string description) {
+ Name = name;
+ Description = description;
+ }
+
+ public UrlExtensionMethod AddParam(string name, string type, string description, string defaultVal = null) {
+ Params.Add(new Param { Name = name, Type = type, Description = description, Default = defaultVal });
+ return this;
+ }
+
+ public class Param
+ {
+ public string Name { get; set; }
+ public string Type { get; set; }
+ public string Description { get; set; }
+ public string Default { get; set; }
+ }
+ }
+}
diff --git a/src/Flurl.Http/ClientConfigExtensions.cs b/src/Flurl.Http/ClientConfigExtensions.cs
deleted file mode 100644
index ede3fdd1..00000000
--- a/src/Flurl.Http/ClientConfigExtensions.cs
+++ /dev/null
@@ -1,413 +0,0 @@
-using System;
-using System.Linq;
-using System.Net;
-using System.Net.Http;
-using System.Net.Http.Headers;
-using System.Text;
-using Flurl.Http.Configuration;
-using Flurl.Util;
-
-namespace Flurl.Http
-{
- ///
- /// Extensions for configure the Flurl Client.
- ///
- public static class ClientConfigExtensions
- {
- ///
- /// Returns a new IFlurlClient where all state (HttpClient, etc) is shared but with a different URL.
- /// Allows you to re-use the underlying HttpClient instance (such as to share cookies, etc) with
- /// different URLs in a thread-safe way.
- ///
- /// The client.
- /// The Url to call.
- public static IFlurlClient WithUrl(this IFlurlClient client, Url url) {
- var fc = client.Clone();
- fc.Url = url;
- // prevent the new client from automatically disposing the parent's HttpClient
- fc.Settings.AutoDispose = false;
- return fc;
- }
-
- ///
- /// Fluently specify that an existing IFlurlClient should be used to call the Url, rather than creating a new one.
- /// Enables re-using the underlying HttpClient.
- ///
- /// The URL.
- /// The IFlurlClient to use in calling the Url
- public static IFlurlClient WithClient(this Url url, IFlurlClient client) {
- return client.WithUrl(url);
- }
-
- ///
- /// Fluently specify that an existing IFlurlClient should be used to call the Url, rather than creating a new one.
- /// Enables re-using the underlying HttpClient.
- ///
- /// The URL.
- /// The IFlurlClient to use in calling the Url
- public static IFlurlClient WithClient(this string url, IFlurlClient client) {
- return client.WithUrl(url);
- }
-
- ///
- /// Change FlurlHttpSettings for this client instance.
- ///
- /// The client.
- /// Action defining the settings changes.
- /// The IFlurlClient with the modified HttpClient
- public static IFlurlClient ConfigureClient(this IFlurlClient client, Action action) {
- action(client.Settings);
- return client;
- }
-
- ///
- /// Creates a FlurlClient from the URL and allows changing the FlurlHttpSettings associated with the instance.
- ///
- /// The URL.
- /// Action defining the settings changes.
- /// The IFlurlClient with the modified HttpClient
- public static IFlurlClient ConfigureClient(this Url url, Action action) {
- return new FlurlClient(url, true).ConfigureClient(action);
- }
-
- ///
- /// Creates a FlurlClient from the URL and allows changing the FlurlHttpSettings associated with the instance.
- ///
- /// The URL.
- /// Action defining the settings changes.
- /// The FlurlClient with the modified HttpClient
- public static IFlurlClient ConfigureClient(this string url, Action action) {
- return new FlurlClient(url, true).ConfigureClient(action);
- }
-
- ///
- /// Provides access to modifying the underlying HttpClient.
- ///
- /// The client.
- /// Action to perform on the HttpClient.
- /// The FlurlClient with the modified HttpClient
- public static IFlurlClient ConfigureHttpClient(this IFlurlClient client, Action action) {
- action(client.HttpClient);
- return client;
- }
-
- ///
- /// Creates a FlurlClient from the URL and provides access to modifying the underlying HttpClient.
- ///
- /// The URL.
- /// Action to perform on the HttpClient.
- /// The FlurlClient with the modified HttpClient
- public static IFlurlClient ConfigureHttpClient(this Url url, Action action) {
- return new FlurlClient(url, true).ConfigureHttpClient(action);
- }
-
- ///
- /// Creates a FlurlClient from the URL and provides access to modifying the underlying HttpClient.
- ///
- /// The URL.
- /// Action to perform on the HttpClient.
- /// The FlurlClient with the modified HttpClient
- public static IFlurlClient ConfigureHttpClient(this string url, Action action) {
- return new FlurlClient(url, true).ConfigureHttpClient(action);
- }
-
- ///
- /// Sets the client timout to the specified timespan.
- ///
- /// The client.
- /// Time to wait before the request times out.
- /// The modified FlurlClient.
- public static IFlurlClient WithTimeout(this IFlurlClient client, TimeSpan timespan) {
- client.HttpClient.Timeout = timespan;
- return client;
- }
-
- ///
- /// Creates a FlurlClient from the URL and sets the client timout to the specified timespan.
- ///
- /// The URL.
- /// Time to wait before the request times out.
- /// The created FlurlClient.
- public static IFlurlClient WithTimeout(this Url url, TimeSpan timespan) {
- return new FlurlClient(url, true).WithTimeout(timespan);
- }
-
- ///
- /// Creates a FlurlClient from the URL and sets the client timout to the specified timespan.
- ///
- /// The URL.
- /// Time to wait before the request times out.
- /// The created FlurlClient.
- public static IFlurlClient WithTimeout(this string url, TimeSpan timespan) {
- return new FlurlClient(url, true).WithTimeout(timespan);
- }
-
- ///
- /// Sets the client timout to the specified number of seconds.
- ///
- /// The client.
- /// Number of seconds to wait before the request times out.
- /// The modified FlurlClient.
- /// is less than or greater than .-or- is .-or- is .
- public static IFlurlClient WithTimeout(this IFlurlClient client, int seconds) {
- return client.WithTimeout(TimeSpan.FromSeconds(seconds));
- }
-
- ///
- /// Creates a FlurlClient from the URL and sets the client timout to the specified number of seconds.
- ///
- /// The URL.
- /// Number of seconds to wait before the request times out.
- /// The created FlurlClient.
- /// is less than or greater than .-or- is .-or- is .
- public static IFlurlClient WithTimeout(this Url url, int seconds) {
- return new FlurlClient(url, true).WithTimeout(seconds);
- }
-
- ///
- /// Creates a FlurlClient from the URL and sets the client timout to the specified number of seconds.
- ///
- /// The URL.
- /// Number of seconds to wait before the request times out.
- /// The created FlurlClient.
- /// is less than or greater than .-or- is .-or- is .
- public static IFlurlClient WithTimeout(this string url, int seconds) {
- return new FlurlClient(url, true).WithTimeout(seconds);
- }
-
- ///
- /// Sets an HTTP header to be sent with all requests made with this FlurlClient.
- ///
- /// The client.
- /// HTTP header name.
- /// HTTP header value.
- /// The modified FlurlClient.
- public static IFlurlClient WithHeader(this IFlurlClient client, string name, object value) {
- var values = new[] { value?.ToString() };
- client.HttpClient.DefaultRequestHeaders.Add(name, values);
- return client;
- }
-
- ///
- /// Creates a FlurlClient from the URL and sets an HTTP header to be sent with all requests made with this FlurlClient.
- ///
- /// The URL.
- /// HTTP header name.
- /// HTTP header value.
- /// The modified FlurlClient.
- public static IFlurlClient WithHeader(this Url url, string name, object value) {
- return new FlurlClient(url, true).WithHeader(name, value);
- }
-
- ///
- /// Creates a FlurlClient from the URL and sets an HTTP header to be sent with all requests made with this FlurlClient.
- ///
- /// The URL.
- /// HTTP header name.
- /// HTTP header value.
- /// The modified FlurlClient.
- public static IFlurlClient WithHeader(this string url, string name, object value) {
- return new FlurlClient(url, true).WithHeader(name, value);
- }
-
- ///
- /// Sets HTTP headers based on property names/values of the provided object, or keys/values if object is a dictionary, to be sent with all requests made with this FlurlClient.
- ///
- /// The client.
- /// Names/values of HTTP headers to set. Typically an anonymous object or IDictionary.
- /// The modified FlurlClient.
- public static IFlurlClient WithHeaders(this IFlurlClient client, object headers) {
- if (headers == null)
- return client;
-
- foreach (var kv in headers.ToKeyValuePairs()) {
- if (kv.Value == null)
- continue;
-
- client.HttpClient.DefaultRequestHeaders.Add(kv.Key, new[] { kv.Value.ToString() });
- }
-
- return client;
- }
-
- ///
- /// Creates a FlurlClient from the URL and sets HTTP headers based on property names/values of the provided object, or keys/values if object is a dictionary, to be sent with all requests made with this FlurlClient.
- ///
- /// The URL.
- /// Names/values of HTTP headers to set. Typically an anonymous object or IDictionary.
- /// The modified FlurlClient.
- public static IFlurlClient WithHeaders(this Url url, object headers) {
- return new FlurlClient(url, true).WithHeaders(headers);
- }
-
- ///
- /// Creates a FlurlClient from the URL and sets HTTP headers based on property names/values of the provided object, or keys/values if object is a dictionary, to be sent with all requests made with this FlurlClient.
- ///
- /// The URL.
- /// Names/values of HTTP headers to set. Typically an anonymous object or IDictionary.
- /// The modified FlurlClient.
- public static IFlurlClient WithHeaders(this string url, object headers) {
- return new FlurlClient(url, true).WithHeaders(headers);
- }
-
- ///
- /// Sets HTTP authorization header according to Basic Authentication protocol to be sent with all requests made with this FlurlClient.
- ///
- /// The client.
- /// Username of authenticating user.
- /// Password of authenticating user.
- /// The modified FlurlClient.
- public static IFlurlClient WithBasicAuth(this IFlurlClient client, string username, string password) {
- // http://stackoverflow.com/questions/14627399/setting-authorization-header-of-httpclient
- var value = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"));
- client.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", value);
- return client;
- }
-
- ///
- /// Creates a FlurlClient from the URL and sets HTTP authorization header according to Basic Authentication protocol to be sent with all requests made with this FlurlClient.
- ///
- /// The URL.
- /// Username of authenticating user.
- /// Password of authenticating user.
- /// The new IFlurlClient instance.
- public static IFlurlClient WithBasicAuth(this Url url, string username, string password) {
- return new FlurlClient(url, true).WithBasicAuth(username, password);
- }
-
- ///
- /// Creates a FlurlClient from the URL and sets HTTP authorization header according to Basic Authentication protocol to be sent with all requests made with this FlurlClient.
- ///
- /// The URL.
- /// Username of authenticating user.
- /// Password of authenticating user.
- /// The new IFlurlClient instance.
- public static IFlurlClient WithBasicAuth(this string url, string username, string password) {
- return new FlurlClient(url, true).WithBasicAuth(username, password);
- }
-
- ///
- /// Sets HTTP authorization header with acquired bearer token according to OAuth 2.0 specification to be sent with all requests made with this FlurlClient.
- ///
- /// The client.
- /// The acquired bearer token to pass.
- /// The modified FlurlClient.
- public static IFlurlClient WithOAuthBearerToken(this IFlurlClient client, string token) {
- client.HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
- return client;
- }
-
- ///
- /// Creates a FlurlClient from the URL and sets HTTP authorization header with acquired bearer token according to OAuth 2.0 specification to be sent with all requests made with this FlurlClient.
- ///
- /// The URL.
- /// The acquired bearer token to pass.
- /// The new IFlurlClient instance.
- public static IFlurlClient WithOAuthBearerToken(this Url url, string token) {
- return new FlurlClient(url, true).WithOAuthBearerToken(token);
- }
-
- ///
- /// Creates a FlurlClient from the URL and sets HTTP authorization header with acquired bearer token according to OAuth 2.0 specification to be sent with all requests made with this FlurlClient.
- ///
- /// The URL.
- /// The acquired bearer token to pass.
- /// The new IFlurlClient instance.
- public static IFlurlClient WithOAuthBearerToken(this string url, string token) {
- return new FlurlClient(url, true).WithOAuthBearerToken(token);
- }
-
- ///
- /// Adds a pattern representing an HTTP status code or range of codes which (in addition to 2xx) will NOT result in a FlurlHttpException being thrown.
- ///
- /// The client.
- /// Examples: "3xx", "100,300,600", "100-299,6xx"
- /// The modified FlurlClient.
- public static IFlurlClient AllowHttpStatus(this IFlurlClient client, string pattern) {
- if (!string.IsNullOrWhiteSpace(pattern)) {
- var current = client.Settings.AllowedHttpStatusRange;
- if (string.IsNullOrWhiteSpace(current))
- client.Settings.AllowedHttpStatusRange = pattern;
- else
- client.Settings.AllowedHttpStatusRange += "," + pattern;
- }
- return client;
- }
-
- ///
- /// Creates a FlurlClient from the URL and adds a pattern representing an HTTP status code or range of codes which (in addition to 2xx) will NOT result in a FlurlHttpException being thrown.
- ///
- /// The URL.
- /// Examples: "3xx", "100,300,600", "100-299,6xx"
- /// The new IFlurlClient instance.
- public static IFlurlClient AllowHttpStatus(this Url url, string pattern) {
- return new FlurlClient(url, true).AllowHttpStatus(pattern);
- }
-
- ///
- /// Creates a FlurlClient from the URL and adds a pattern representing an HTTP status code or range of codes which (in addition to 2xx) will NOT result in a FlurlHttpException being thrown.
- ///
- /// The URL.
- /// Examples: "3xx", "100,300,600", "100-299,6xx"
- /// The new IFlurlClient instance.
- public static IFlurlClient AllowHttpStatus(this string url, string pattern) {
- return new FlurlClient(url, true).AllowHttpStatus(pattern);
- }
-
- ///
- /// Adds an which (in addition to 2xx) will NOT result in a FlurlHttpException being thrown.
- ///
- /// The client.
- /// Examples: HttpStatusCode.NotFound
- /// The modified FlurlClient.
- public static IFlurlClient AllowHttpStatus(this IFlurlClient client, params HttpStatusCode[] statusCodes) {
- var pattern = string.Join(",", statusCodes.Select(c => (int)c));
- return AllowHttpStatus(client, pattern);
- }
-
- ///
- /// Adds an which (in addition to 2xx) will NOT result in a FlurlHttpException being thrown.
- ///
- /// The URL.
- /// Examples: HttpStatusCode.NotFound
- /// The new IFlurlClient instance.
- public static IFlurlClient AllowHttpStatus(this Url url, params HttpStatusCode[] statusCodes) {
- return new FlurlClient(url, true).AllowHttpStatus(statusCodes);
- }
-
- ///
- /// Adds an which (in addition to 2xx) will NOT result in a FlurlHttpException being thrown.
- ///
- /// The URL.
- /// Examples: HttpStatusCode.NotFound
- /// The new IFlurlClient instance.
- public static IFlurlClient AllowHttpStatus(this string url, params HttpStatusCode[] statusCodes) {
- return new FlurlClient(url, true).AllowHttpStatus(statusCodes);
- }
-
- ///
- /// Prevents a FlurlHttpException from being thrown on any completed response, regardless of the HTTP status code.
- ///
- /// The modified IFlurlClient.
- public static IFlurlClient AllowAnyHttpStatus(this IFlurlClient client) {
- client.Settings.AllowedHttpStatusRange = "*";
- return client;
- }
-
- ///
- /// Creates a FlurlClient from the URL and prevents a FlurlHttpException from being thrown on any completed response, regardless of the HTTP status code.
- ///
- /// The new IFlurlClient instance.
- public static IFlurlClient AllowAnyHttpStatus(this Url url) {
- return new FlurlClient(url, true).AllowAnyHttpStatus();
- }
-
- ///
- /// Creates a FlurlClient from the URL and prevents a FlurlHttpException from being thrown on any completed response, regardless of the HTTP status code.
- ///
- /// The new IFlurlClient instance.
- public static IFlurlClient AllowAnyHttpStatus(this string url) {
- return new FlurlClient(url, true).AllowAnyHttpStatus();
- }
- }
-}
\ No newline at end of file
diff --git a/src/Flurl.Http/Configuration/DefaultHttpClientFactory.cs b/src/Flurl.Http/Configuration/DefaultHttpClientFactory.cs
index 996fcdc6..44396763 100644
--- a/src/Flurl.Http/Configuration/DefaultHttpClientFactory.cs
+++ b/src/Flurl.Http/Configuration/DefaultHttpClientFactory.cs
@@ -1,4 +1,5 @@
-using System.Net.Http;
+using System;
+using System.Net.Http;
namespace Flurl.Http.Configuration
{
@@ -14,8 +15,11 @@ public class DefaultHttpClientFactory : IHttpClientFactory
/// In order not to lose Flurl.Http functionality, it is recommended to call base.CreateClient and
/// customize the result.
///
- public virtual HttpClient CreateClient(Url url, HttpMessageHandler handler) {
- return new HttpClient(new FlurlMessageHandler(handler));
+ public virtual HttpClient CreateHttpClient(HttpMessageHandler handler) {
+ return new HttpClient(handler) {
+ // Timeouts handled per request via FlurlHttpSettings.Timeout
+ Timeout = System.Threading.Timeout.InfiniteTimeSpan
+ };
}
///
@@ -27,4 +31,4 @@ public virtual HttpMessageHandler CreateMessageHandler() {
return new HttpClientHandler();
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Flurl.Http/Configuration/DefaultUrlEncodedSerializer.cs b/src/Flurl.Http/Configuration/DefaultUrlEncodedSerializer.cs
index d0068987..4a6387e3 100644
--- a/src/Flurl.Http/Configuration/DefaultUrlEncodedSerializer.cs
+++ b/src/Flurl.Http/Configuration/DefaultUrlEncodedSerializer.cs
@@ -1,6 +1,5 @@
using System;
using System.IO;
-using System.Text;
using Flurl.Util;
namespace Flurl.Http.Configuration
@@ -16,17 +15,13 @@ public class DefaultUrlEncodedSerializer : ISerializer
///
/// The object.
public string Serialize(object obj) {
- var sb = new StringBuilder();
- foreach (var kv in obj.ToKeyValuePairs()) {
- if (kv.Value == null)
- continue;
- if (sb.Length > 0)
- sb.Append('&');
- sb.Append(Url.EncodeQueryParamValue(kv.Key, true));
- sb.Append('=');
- sb.Append(Url.EncodeQueryParamValue(kv.Value, true));
- }
- return sb.ToString();
+ if (obj == null)
+ return null;
+
+ var qp = new QueryParamCollection();
+ foreach (var kv in obj.ToKeyValuePairs())
+ qp[kv.Key] = new QueryParameter(kv.Key, kv.Value);
+ return qp.ToString(true);
}
///
@@ -36,7 +31,7 @@ public string Serialize(object obj) {
/// The s.
/// Deserializing to UrlEncoded not supported.
public T Deserialize(string s) {
- throw new NotImplementedException("Deserializing to UrlEncoded not supported.");
+ throw new NotImplementedException("Deserializing to UrlEncoded is not supported.");
}
///
@@ -46,7 +41,7 @@ public T Deserialize(string s) {
/// The stream.
/// Deserializing to UrlEncoded not supported.
public T Deserialize(Stream stream) {
- throw new NotImplementedException("Deserializing to UrlEncoded not supported.");
+ throw new NotImplementedException("Deserializing to UrlEncoded is not supported.");
}
}
}
\ No newline at end of file
diff --git a/src/Flurl.Http/Configuration/FlurlClientFactoryBase.cs b/src/Flurl.Http/Configuration/FlurlClientFactoryBase.cs
new file mode 100644
index 00000000..b0e89291
--- /dev/null
+++ b/src/Flurl.Http/Configuration/FlurlClientFactoryBase.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Concurrent;
+
+namespace Flurl.Http.Configuration
+{
+ ///
+ /// Encapsulates a creation/caching strategy for IFlurlClient instances. Custom factories looking to extend
+ /// Flurl's behavior should inherit from this class, rather than implementing IFlurlClientFactory directly.
+ ///
+ public abstract class FlurlClientFactoryBase : IFlurlClientFactory
+ {
+ private readonly ConcurrentDictionary _clients = new ConcurrentDictionary();
+
+ ///
+ /// By defaykt, uses a caching strategy of one FlurlClient per host. This maximizes reuse of
+ /// underlying HttpClient/Handler while allowing things like cookies to be host-specific.
+ ///
+ /// The URL.
+ /// The FlurlClient instance.
+ public virtual IFlurlClient Get(Url url) {
+ return _clients.AddOrUpdate(
+ GetCacheKey(url),
+ u => Create(u),
+ (u, client) => client.IsDisposed ? Create(u) : client);
+ }
+
+ ///
+ /// Defines a strategy for getting a cache key based on a Url. Default implementation
+ /// returns the host part (i.e www.api.com) so that all calls to the same host use the
+ /// same FlurlClient (and HttpClient/HttpMessageHandler) instance.
+ ///
+ /// The URL.
+ /// The cache key
+ protected abstract string GetCacheKey(Url url);
+
+ ///
+ /// Creates a new FlurlClient
+ ///
+ /// The URL (not used)
+ ///
+ protected virtual IFlurlClient Create(Url url) => new FlurlClient();
+ }
+}
diff --git a/src/Flurl.Http/Configuration/FlurlHttpSettings.cs b/src/Flurl.Http/Configuration/FlurlHttpSettings.cs
index 68910ca7..ea4381b6 100644
--- a/src/Flurl.Http/Configuration/FlurlHttpSettings.cs
+++ b/src/Flurl.Http/Configuration/FlurlHttpSettings.cs
@@ -1,117 +1,244 @@
using System;
-using System.Net.Http;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using System.Reflection;
using System.Threading.Tasks;
+using Flurl.Http.Testing;
namespace Flurl.Http.Configuration
{
///
- /// A set of properties that affect Flurl.Http behavior. Generally set via static FlurlHttp.Configure method.
+ /// A set of properties that affect Flurl.Http behavior
///
public class FlurlHttpSettings
{
+ // We need to maintain order of precedence (request > client > global) in some tricky scenarios.
+ // e.g. if we explicitly set some FlurlRequest.Settings, then set the FlurlClient, we want the
+ // client-level settings to override the global settings but not the request-level settings.
+ private FlurlHttpSettings _defaults;
+
+ // Values are dictionary-backed so we can check for key existence. Can't do null-coalescing
+ // because if a setting is set to null at the request level, that should stick.
+ private readonly IDictionary _vals = new Dictionary();
+
///
- /// Initializes a new instance of the class.
+ /// Creates a new FlurlHttpSettings object using another FlurlHttpSettings object as its default values.
///
- public FlurlHttpSettings() {
- ResetDefaults();
+ public FlurlHttpSettings(FlurlHttpSettings defaults) {
+ _defaults = defaults;
}
///
- /// Gets or sets value indicating whether to automatically dispose underlying HttpClient immediately after each call.
+ /// Creates a new FlurlHttpSettings object.
///
- public bool AutoDispose { get; set; }
+ public FlurlHttpSettings() : this(FlurlHttp.GlobalSettings) { }
///
- /// Gets or sets the default timeout for every HTTP request.
+ /// Gets or sets the HTTP request timeout.
///
- public TimeSpan DefaultTimeout { get; set; }
+ public TimeSpan? Timeout {
+ get => Get(() => Timeout);
+ set => Set(() => Timeout, value);
+ }
///
/// Gets or sets a pattern representing a range of HTTP status codes which (in addtion to 2xx) will NOT result in Flurl.Http throwing an Exception.
/// Examples: "3xx", "100,300,600", "100-299,6xx", "*" (allow everything)
/// 2xx will never throw regardless of this setting.
///
- public string AllowedHttpStatusRange { get; set; }
+ public string AllowedHttpStatusRange {
+ get => Get(() => AllowedHttpStatusRange);
+ set => Set(() => AllowedHttpStatusRange, value);
+ }
///
/// Gets or sets a value indicating whether cookies should be sent/received with each HTTP request.
///
- public bool CookiesEnabled { get; set; }
-
- ///
- /// Gets or sets a factory used to create HttpClient object used in Flurl HTTP calls. Default value
- /// is an instance of DefaultHttpClientFactory. Custom factory implementations should generally
- /// inherit from DefaultHttpClientFactory, call base.CreateClient, and manipulate the returned HttpClient,
- /// otherwise functionality such as callbacks and most testing features will be lost.
- ///
- public IHttpClientFactory HttpClientFactory { get; set; }
+ public bool CookiesEnabled {
+ get => Get(() => CookiesEnabled);
+ set => Set(() => CookiesEnabled, value);
+ }
///
/// Gets or sets object used to serialize and deserialize JSON. Default implementation uses Newtonsoft Json.NET.
///
- public ISerializer JsonSerializer { get; set; }
+ public ISerializer JsonSerializer {
+ get => Get(() => JsonSerializer);
+ set => Set(() => JsonSerializer, value);
+ }
///
/// Gets or sets object used to serialize URL-encoded data. (Deserialization not supported in default implementation.)
///
- public ISerializer UrlEncodedSerializer { get; set; }
+ public ISerializer UrlEncodedSerializer {
+ get => Get(() => UrlEncodedSerializer);
+ set => Set(() => UrlEncodedSerializer, value);
+ }
///
/// Gets or sets a callback that is called immediately before every HTTP request is sent.
///
- public Action BeforeCall { get; set; }
+ public Action BeforeCall {
+ get => Get(() => BeforeCall);
+ set => Set(() => BeforeCall, value);
+ }
///
/// Gets or sets a callback that is asynchronously called immediately before every HTTP request is sent.
///
- public Func BeforeCallAsync { get; set; }
+ public Func BeforeCallAsync {
+ get => Get(() => BeforeCallAsync);
+ set => Set(() => BeforeCallAsync, value);
+ }
///
/// Gets or sets a callback that is called immediately after every HTTP response is received.
///
- public Action AfterCall { get; set; }
+ public Action AfterCall {
+ get => Get(() => AfterCall);
+ set => Set(() => AfterCall, value);
+ }
///
/// Gets or sets a callback that is asynchronously called immediately after every HTTP response is received.
///
- public Func AfterCallAsync { get; set; }
+ public Func AfterCallAsync {
+ get => Get(() => AfterCallAsync);
+ set => Set(() => AfterCallAsync, value);
+ }
///
/// Gets or sets a callback that is called when an error occurs during any HTTP call, including when any non-success
/// HTTP status code is returned in the response. Response should be null-checked if used in the event handler.
///
- public Action OnError { get; set; }
+ public Action OnError {
+ get => Get(() => OnError);
+ set => Set(() => OnError, value);
+ }
///
/// Gets or sets a callback that is asynchronously called when an error occurs during any HTTP call, including when any non-success
/// HTTP status code is returned in the response. Response should be null-checked if used in the event handler.
///
- public Func OnErrorAsync { get; set; }
+ public Func OnErrorAsync {
+ get => Get(() => OnErrorAsync);
+ set => Set(() => OnErrorAsync, value);
+ }
///
- /// Clear all custom global options and set default values.
+ /// Resets all overridden settings to their default values. For example, on a FlurlRequest,
+ /// all settings are reset to FlurlClient-level settings.
///
- public void ResetDefaults() {
- AutoDispose = false;
- DefaultTimeout = new HttpClient().Timeout;
- AllowedHttpStatusRange = null;
- CookiesEnabled = false;
- HttpClientFactory = new DefaultHttpClientFactory();
+ public virtual void ResetDefaults() {
+ _vals.Clear();
+ }
+
+ ///
+ /// Gets a settings value from this instance if explicitly set, otherwise from the default settings that back this instance.
+ ///
+ protected T Get(Expression> property) {
+ var p = (property.Body as MemberExpression).Member as PropertyInfo;
+ var testVals = HttpTest.Current?.Settings._vals;
+ return
+ testVals?.ContainsKey(p.Name) == true ? (T)testVals[p.Name] :
+ _vals.ContainsKey(p.Name) ? (T)_vals[p.Name] :
+ _defaults != null ? (T)p.GetValue(_defaults) :
+ default(T);
+ }
+
+ ///
+ /// Sets a settings value for this instance.
+ ///
+ protected void Set(Expression> property, T value) {
+ var p = (property.Body as MemberExpression).Member as PropertyInfo;
+ _vals[p.Name] = value;
+ }
+
+ ///
+ /// Merges other settings with this one. Overrides defaults, but does NOT override
+ /// this settings' explicitly set values.
+ ///
+ /// The settings to merge.
+ public FlurlHttpSettings Merge(FlurlHttpSettings other) {
+ _defaults = other;
+ return this;
+ }
+ }
+
+ ///
+ /// Client-level settings for Flurl.Http
+ ///
+ public class ClientFlurlHttpSettings : FlurlHttpSettings
+ {
+ ///
+ /// Creates a new FlurlHttpSettings object using another FlurlHttpSettings object as its default values.
+ ///
+ public ClientFlurlHttpSettings(FlurlHttpSettings defaults) : base(defaults) { }
+
+ ///
+ /// Specifies the time to keep the underlying HTTP/TCP conneciton open. When expired, a Connection: close header
+ /// is sent with the next request, which should force a new connection and DSN lookup to occur on the next call.
+ /// Default is null, effectively disabling the behavior.
+ ///
+ public TimeSpan? ConnectionLeaseTimeout {
+ get => Get(() => ConnectionLeaseTimeout);
+ set => Set(() => ConnectionLeaseTimeout, value);
+ }
+
+ ///
+ /// Gets or sets a factory used to create the HttpClient and HttpMessageHandler used for HTTP calls.
+ /// Whenever possible, custom factory implementations should inherit from DefaultHttpClientFactory,
+ /// only override the method(s) needed, call the base method, and modify the result.
+ ///
+ public IHttpClientFactory HttpClientFactory {
+ get => Get(() => HttpClientFactory);
+ set => Set(() => HttpClientFactory, value);
+ }
+ }
+
+ ///
+ /// Global default settings for Flurl.Http
+ ///
+ public class GlobalFlurlHttpSettings : ClientFlurlHttpSettings
+ {
+ internal GlobalFlurlHttpSettings() : base(null) {
+ ResetDefaults();
+ }
+
+ ///
+ /// Gets or sets the factory that defines creating, caching, and reusing FlurlClient instances and,
+ /// by proxy, HttpClient instances.
+ ///
+ public IFlurlClientFactory FlurlClientFactory {
+ get => Get(() => FlurlClientFactory);
+ set => Set(() => FlurlClientFactory, value);
+ }
+
+ ///
+ /// Resets all global settings to their Flurl.Http-defined default values.
+ ///
+ public override void ResetDefaults() {
+ base.ResetDefaults();
+ Timeout = TimeSpan.FromSeconds(100); // same as HttpClient
JsonSerializer = new NewtonsoftJsonSerializer(null);
UrlEncodedSerializer = new DefaultUrlEncodedSerializer();
- BeforeCall = null;
- BeforeCallAsync = null;
- AfterCall = null;
- AfterCallAsync = null;
- OnError = null;
- OnErrorAsync = null;
+ FlurlClientFactory = new PerHostFlurlClientFactory();
+ HttpClientFactory = new DefaultHttpClientFactory();
}
+ }
+ ///
+ /// Settings overrides within the context of an HttpTest
+ ///
+ public class TestFlurlHttpSettings : GlobalFlurlHttpSettings
+ {
///
- /// Clones this instance.
+ /// Resets all test settings to their Flurl.Http-defined default values.
///
- public FlurlHttpSettings Clone() {
- return (FlurlHttpSettings)MemberwiseClone();
+ public override void ResetDefaults() {
+ base.ResetDefaults();
+ FlurlClientFactory = new TestFlurlClientFactory();
+ HttpClientFactory = new TestHttpClientFactory();
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Flurl.Http/Configuration/FlurlMessageHandler.cs b/src/Flurl.Http/Configuration/FlurlMessageHandler.cs
deleted file mode 100644
index 4aacf7dc..00000000
--- a/src/Flurl.Http/Configuration/FlurlMessageHandler.cs
+++ /dev/null
@@ -1,68 +0,0 @@
-using System;
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
-using Flurl.Http.Content;
-
-namespace Flurl.Http.Configuration
-{
- ///
- /// HTTP message handler used by default in all Flurl-created HttpClients.
- ///
- public class FlurlMessageHandler : DelegatingHandler
- {
- ///
- /// Initializes a new instance of the class.
- ///
- public FlurlMessageHandler() { }
-
- ///
- /// Initializes a new instance of the class with a specific inner handler.
- ///
- /// The inner handler.
- public FlurlMessageHandler(HttpMessageHandler innerHandler) : base(innerHandler) { }
-
- ///
- /// Send request asynchronous.
- ///
- /// The request.
- /// The cancellation token.
- protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
- var call = HttpCall.Get(request);
-
- await FlurlHttp.RaiseEventAsync(request, FlurlEventType.BeforeCall).ConfigureAwait(false);
- call.StartedUtc = DateTime.UtcNow;
- try {
- call.Response = await InnerSendAsync(call, request, cancellationToken).ConfigureAwait(false);
- call.Response.RequestMessage = request;
- if (call.Succeeded)
- return call.Response;
-
- if (call.Response.Content != null)
- call.ErrorResponseBody = await call.Response.Content.StripCharsetQuotes().ReadAsStringAsync().ConfigureAwait(false);
- throw new FlurlHttpException(call, null);
- }
- catch (Exception ex) {
- call.Exception = ex;
- await FlurlHttp.RaiseEventAsync(request, FlurlEventType.OnError).ConfigureAwait(false);
- throw;
- }
- finally {
- call.EndedUtc = DateTime.UtcNow;
- await FlurlHttp.RaiseEventAsync(request, FlurlEventType.AfterCall).ConfigureAwait(false);
- }
- }
-
- private async Task InnerSendAsync(HttpCall call, HttpRequestMessage request, CancellationToken cancellationToken) {
- try {
- return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
- }
- catch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested) {
- throw new FlurlHttpTimeoutException(call, ex);
- }
- catch (Exception ex) {
- throw new FlurlHttpException(call, ex);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/Flurl.Http/Configuration/IFlurlClientFactory.cs b/src/Flurl.Http/Configuration/IFlurlClientFactory.cs
new file mode 100644
index 00000000..b702edb2
--- /dev/null
+++ b/src/Flurl.Http/Configuration/IFlurlClientFactory.cs
@@ -0,0 +1,16 @@
+namespace Flurl.Http.Configuration
+{
+ ///
+ /// Interface for defining a strategy for creating, caching, and reusing IFlurlClient instances and,
+ /// by proxy, their underlying HttpClient instances.
+ ///
+ public interface IFlurlClientFactory
+ {
+ ///
+ /// Strategy to create a FlurlClient or reuse an exisitng one, based on URL being called.
+ ///
+ /// The URL being called.
+ ///
+ IFlurlClient Get(Url url);
+ }
+}
\ No newline at end of file
diff --git a/src/Flurl.Http/Configuration/IHttpClientFactory.cs b/src/Flurl.Http/Configuration/IHttpClientFactory.cs
index 1a85eeec..fcb68970 100644
--- a/src/Flurl.Http/Configuration/IHttpClientFactory.cs
+++ b/src/Flurl.Http/Configuration/IHttpClientFactory.cs
@@ -11,15 +11,16 @@ namespace Flurl.Http.Configuration
public interface IHttpClientFactory
{
///
- /// Creates the client.
+ /// Defines how HttpClient should be instantiated and configured by default. Do NOT attempt
+ /// to cache/reuse HttpClient instances here - that should be done at the FlurlClient level
+ /// via a custom FlurlClientFactory that gets registered globally.
///
- /// The URL.
- /// The handler.
+ /// The HttpMessageHandler used to construct the HttpClient.
///
- HttpClient CreateClient(Url url, HttpMessageHandler handler);
-
+ HttpClient CreateHttpClient(HttpMessageHandler handler);
+
///
- /// Creates the message handler.
+ /// Defines how the
///
///
HttpMessageHandler CreateMessageHandler();
diff --git a/src/Flurl.Http/Configuration/PerBaseUrlFlurlClientFactory.cs b/src/Flurl.Http/Configuration/PerBaseUrlFlurlClientFactory.cs
new file mode 100644
index 00000000..b599b940
--- /dev/null
+++ b/src/Flurl.Http/Configuration/PerBaseUrlFlurlClientFactory.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Flurl.Http.Configuration
+{
+ ///
+ /// An IFlurlClientFactory implementation that caches and reuses the same IFlurlClient instance
+ /// per URL requested, which it assumes is a "base" URL, and sets the IFlurlClient.BaseUrl property
+ /// to that value. Ideal for use with IoC containers - register as a singleton, inject into a service
+ /// that wraps some web service, and use to set a private IFlurlClient field in the constructor.
+ ///
+ public class PerBaseUrlFlurlClientFactory : FlurlClientFactoryBase
+ {
+ ///
+ /// Returns the entire URL, which is assumed to be some "base" URL for a service.
+ ///
+ /// The URL.
+ /// The cache key
+ protected override string GetCacheKey(Url url) => url.ToString();
+
+ ///
+ /// Returns a new new FlurlClient with BaseUrl set to the URL passed.
+ ///
+ /// The URL
+ ///
+ protected override IFlurlClient Create(Url url) => new FlurlClient(url);
+ }
+}
diff --git a/src/Flurl.Http/Configuration/PerHostFlurlClientFactory.cs b/src/Flurl.Http/Configuration/PerHostFlurlClientFactory.cs
new file mode 100644
index 00000000..34191fdc
--- /dev/null
+++ b/src/Flurl.Http/Configuration/PerHostFlurlClientFactory.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Flurl.Http.Configuration
+{
+ ///
+ /// An IFlurlClientFactory implementation that caches and reuses the same one instance of
+ /// FlurlClient per host being called. Maximizes reuse of underlying HttpClient/Handler
+ /// while allowing things like cookies to be host-specific. This is the default
+ /// implementation used when calls are made fluently off Urls/strings.
+ ///
+ public class PerHostFlurlClientFactory : FlurlClientFactoryBase
+ {
+ ///
+ /// Returns the host part of the URL (i.e. www.api.com) so that all calls to the same
+ /// host use the same FlurlClient (and HttpClient/HttpMessageHandler) instance.
+ ///
+ /// The URL.
+ /// The cache key
+ protected override string GetCacheKey(Url url) => new Uri(url).Host;
+ }
+}
diff --git a/src/Flurl.Http/Content/CapturedMultipartContent.cs b/src/Flurl.Http/Content/CapturedMultipartContent.cs
index a10c758b..cbdb55ca 100644
--- a/src/Flurl.Http/Content/CapturedMultipartContent.cs
+++ b/src/Flurl.Http/Content/CapturedMultipartContent.cs
@@ -24,17 +24,11 @@ public class CapturedMultipartContent : MultipartContent
///
/// Initializes a new instance of the class.
///
- /// The FlurlHttpSettings used to serialize each content part.
- public CapturedMultipartContent(FlurlHttpSettings settings) : base("form-data") {
- _settings = settings;
+ /// The FlurlHttpSettings used to serialize each content part. (Defaults to FlurlHttp.GlobalSettings.)
+ public CapturedMultipartContent(FlurlHttpSettings settings = null) : base("form-data") {
+ _settings = settings ?? FlurlHttp.GlobalSettings;
}
- ///
- /// Initializes a new instance of the class, using FlurlHttp.GlobalSettings
- /// to determine how to serialize each content part.
- ///
- public CapturedMultipartContent() : this(FlurlHttp.GlobalSettings) { }
-
///
/// Add a content part to the multipart request.
///
diff --git a/src/Flurl.Http/CookieExtensions.cs b/src/Flurl.Http/CookieExtensions.cs
index 88ec4554..ff99e24f 100644
--- a/src/Flurl.Http/CookieExtensions.cs
+++ b/src/Flurl.Http/CookieExtensions.cs
@@ -1,151 +1,68 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Net;
+using System.Text;
+using System.Threading.Tasks;
using Flurl.Util;
namespace Flurl.Http
{
///
- /// Cookie extension for Flurl Client.
+ /// Fluent extension methods for working with HTTP cookies.
///
public static class CookieExtensions
{
- ///
- /// Allows cookies to be sent and received in calls made with this client. Not necessary to call when setting cookies via WithCookie/WithCookies.
- ///
- public static IFlurlClient EnableCookies(this IFlurlClient client) {
- client.Settings.CookiesEnabled = true;
- return client;
- }
-
- ///
- /// Allows cookies to be sent and received in calls made to this Url. Not necessary to call when setting cookies via WithCookie/WithCookies.
- ///
- public static IFlurlClient EnableCookies(this Url url) {
- return new FlurlClient(url).EnableCookies();
- }
-
///
- /// Allows cookies to be sent and received in calls made to this Url. Not necessary to call when setting cookies via WithCookie/WithCookies.
+ /// Allows cookies to be sent and received. Not necessary to call when setting cookies via WithCookie/WithCookies.
///
- public static IFlurlClient EnableCookies(this string url) {
- return new FlurlClient(url).EnableCookies();
+ /// The IFlurlClient or IFlurlRequest.
+ /// This IFlurlClient.
+ public static T EnableCookies(this T clientOrRequest) where T : IHttpSettingsContainer {
+ clientOrRequest.Settings.CookiesEnabled = true;
+ return clientOrRequest;
}
///
- /// Sets an HTTP cookie to be sent with all requests made with this FlurlClient.
+ /// Sets an HTTP cookie to be sent with this IFlurlRequest or all requests made with this IFlurlClient.
///
- /// The client.
+ /// The IFlurlClient or IFlurlRequest.
/// The cookie to set.
- /// The modified FlurlClient.
- /// is null.
- public static IFlurlClient WithCookie(this IFlurlClient client, Cookie cookie) {
- client.Settings.CookiesEnabled = true;
- client.Cookies[cookie.Name] = cookie;
- return client;
- }
-
- ///
- /// Creates a FlurlClient from the URL and sets an HTTP cookie to be sent with all requests made with this FlurlClient.
- ///
- /// The URL.
- /// the cookie to set.
- /// The modified FlurlClient.
- /// is null.
- public static IFlurlClient WithCookie(this string url, Cookie cookie) {
- return new FlurlClient(url, true).WithCookie(cookie);
+ /// This IFlurlClient.
+ public static T WithCookie(this T clientOrRequest, Cookie cookie) where T : IHttpSettingsContainer {
+ clientOrRequest.Settings.CookiesEnabled = true;
+ clientOrRequest.Cookies[cookie.Name] = cookie;
+ return clientOrRequest;
}
///
- /// Creates a FlurlClient from the URL and sets an HTTP cookie to be sent with all requests made with this FlurlClient.
+ /// Sets an HTTP cookie to be sent with this IFlurlRequest or all requests made with this IFlurlClient.
///
- /// The URL.
- /// the cookie to set.
- /// The modified FlurlClient.
- /// is null.
- public static IFlurlClient WithCookie(this Url url, Cookie cookie) {
- return new FlurlClient(url, true).WithCookie(cookie);
+ /// The IFlurlClient or IFlurlRequest.
+ /// The cookie name.
+ /// The cookie value.
+ /// The cookie expiration (optional). If excluded, cookie only lives for duration of session.
+ /// This IFlurlClient.
+ public static T WithCookie(this T clientOrRequest, string name, object value, DateTime? expires = null) where T : IHttpSettingsContainer {
+ var cookie = new Cookie(name, value?.ToInvariantString()) { Expires = expires ?? DateTime.MinValue };
+ return clientOrRequest.WithCookie(cookie);
}
///
- /// Sets an HTTP cookie to be sent with all requests made with this FlurlClient.
+ /// Sets HTTP cookies to be sent with this IFlurlRequest or all requests made with this IFlurlClient, based on property names/values of the provided object, or keys/values if object is a dictionary.
///
- /// The client.
- /// cookie name.
- /// cookie value.
- /// cookie expiration (optional). If excluded, cookie only lives for duration of session.
- /// The modified FlurlClient.
- /// is null.
- public static IFlurlClient WithCookie(this IFlurlClient client, string name, object value, DateTime? expires = null) {
- var cookie = new Cookie(name, value?.ToInvariantString()) { Expires = expires ?? DateTime.MinValue };
- return client.WithCookie(cookie);
- }
-
- ///
- /// Creates a FlurlClient from the URL and sets an HTTP cookie to be sent with all requests made with this FlurlClient.
- ///
- /// The URL.
- /// cookie name.
- /// cookie value.
- /// cookie expiration (optional). If excluded, cookie only lives for duration of session.
- /// The modified FlurlClient.
- /// is null.
- public static IFlurlClient WithCookie(this string url, string name, object value, DateTime? expires = null) {
- return new FlurlClient(url, true).WithCookie(name, value, expires);
- }
-
- ///
- /// Creates a FlurlClient from the URL and sets an HTTP cookie to be sent with all requests made with this FlurlClient.
- ///
- /// The URL.
- /// cookie name.
- /// cookie value.
- /// cookie expiration (optional). If excluded, cookie only lives for duration of session.
- /// The modified FlurlClient.
- /// is null.
- public static IFlurlClient WithCookie(this Url url, string name, object value, DateTime? expires = null) {
- return new FlurlClient(url, true).WithCookie(name, value, expires);
- }
-
- ///
- /// Sets HTTP cookies based on property names/values of the provided object, or keys/values if object is a dictionary, to be sent with all requests made with this FlurlClient.
- ///
- /// The client.
- /// Names/values of HTTP cookies to set. Typically an anonymous object or IDictionary.
- /// Expiration for all cookies (optional). If excluded, cookies only live for duration of session.
- /// The modified FlurlClient.
- /// is null.
- public static IFlurlClient WithCookies(this IFlurlClient client, object cookies, DateTime? expires = null) {
+ /// The IFlurlClient or IFlurlRequest.
+ /// Names/values of HTTP cookies to set. Typically an anonymous object or IDictionary.
+ /// Expiration for all cookies (optional). If excluded, cookies only live for duration of session.
+ /// This IFlurlClient.
+ public static T WithCookies(this T clientOrRequest, object cookies, DateTime? expires = null) where T : IHttpSettingsContainer {
if (cookies == null)
- return client;
+ return clientOrRequest;
foreach (var kv in cookies.ToKeyValuePairs())
- client.WithCookie(kv.Key, kv.Value, expires);
-
- return client;
- }
-
- ///
- /// Creates a FlurlClient from the URL and sets HTTP cookies based on property names/values of the provided object, or keys/values if object is a dictionary, to be sent with all requests made with this FlurlClient.
- ///
- /// The URL.
- /// Names/values of HTTP cookies to set. Typically an anonymous object or IDictionary.
- /// Expiration for all cookies (optional). If excluded, cookies only live for duration of session.
- /// The modified FlurlClient.
- /// is null.
- public static IFlurlClient WithCookies(this Url url, object cookies, DateTime? expires = null) {
- return new FlurlClient(url, true).WithCookies(cookies);
- }
+ clientOrRequest.WithCookie(kv.Key, kv.Value, expires);
- ///
- /// Creates a FlurlClient from the URL and sets HTTP cookies based on property names/values of the provided object, or keys/values if object is a dictionary, to be sent with all requests made with this FlurlClient.
- ///
- /// The URL.
- /// Names/values of HTTP cookies to set. Typically an anonymous object or IDictionary.
- /// Expiration for all cookies (optional). If excluded, cookies only live for duration of session.
- /// The modified FlurlClient.
- /// is null.
- public static IFlurlClient WithCookies(this string url, object cookies, DateTime? expires = null) {
- return new FlurlClient(url, true).WithCookies(cookies);
+ return clientOrRequest;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Flurl.Http/DownloadExtensions.cs b/src/Flurl.Http/DownloadExtensions.cs
index 6be61bb2..1b9259ff 100644
--- a/src/Flurl.Http/DownloadExtensions.cs
+++ b/src/Flurl.Http/DownloadExtensions.cs
@@ -5,42 +5,31 @@
namespace Flurl.Http
{
///
- /// Download extensions for the Flurl Client.
+ /// Fluent extension methods for downloading a file.
///
public static class DownloadExtensions
{
///
/// Asynchronously downloads a file at the specified URL.
///
- /// The flurl client.
+ /// The flurl request.
/// Path of local folder where file is to be downloaded.
/// Name of local file. If not specified, the source filename (last segment of the URL) is used.
/// Buffer size in bytes. Default is 4096.
/// A Task whose result is the local path of the downloaded file.
- public static async Task DownloadFileAsync(this IFlurlClient client, string localFolderPath, string localFileName = null, int bufferSize = 4096) {
+ public static async Task DownloadFileAsync(this IFlurlRequest request, string localFolderPath, string localFileName = null, int bufferSize = 4096) {
if (localFileName == null)
- localFileName = client.Url.Path.Split('/').Last();
+ localFileName = request.Url.Path.Split('/').Last();
- // need to temporarily disable autodispose if set, otherwise reading from stream will fail
- var autoDispose = client.Settings.AutoDispose;
- client.Settings.AutoDispose = false;
+ var response = await request.SendAsync(HttpMethod.Get, completionOption: HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
- try {
- var response = await client.SendAsync(HttpMethod.Get, completionOption: HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
-
- // http://codereview.stackexchange.com/a/18679
- using (var httpStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
- using (var filestream = await FileUtil.OpenWriteAsync(localFolderPath, localFileName, bufferSize).ConfigureAwait(false)) {
- await httpStream.CopyToAsync(filestream, bufferSize).ConfigureAwait(false);
- }
-
- return FileUtil.CombinePath(localFolderPath, localFileName);
- }
- finally {
- client.Settings.AutoDispose = autoDispose;
- if (client.Settings.AutoDispose)
- client.Dispose();
+ // http://codereview.stackexchange.com/a/18679
+ using (var httpStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
+ using (var filestream = await FileUtil.OpenWriteAsync(localFolderPath, localFileName, bufferSize).ConfigureAwait(false)) {
+ await httpStream.CopyToAsync(filestream, bufferSize).ConfigureAwait(false);
}
+
+ return FileUtil.CombinePath(localFolderPath, localFileName);
}
///
@@ -52,7 +41,7 @@ public static async Task DownloadFileAsync(this IFlurlClient client, str
/// Buffer size in bytes. Default is 4096.
/// A Task whose result is the local path of the downloaded file.
public static Task DownloadFileAsync(this string url, string localFolderPath, string localFileName = null, int bufferSize = 4096) {
- return new FlurlClient(url, true).DownloadFileAsync(localFolderPath, localFileName, bufferSize);
+ return new FlurlRequest(url).DownloadFileAsync(localFolderPath, localFileName, bufferSize);
}
///
@@ -64,7 +53,7 @@ public static Task DownloadFileAsync(this string url, string localFolder
/// Buffer size in bytes. Default is 4096.
/// A Task whose result is the local path of the downloaded file.
public static Task DownloadFileAsync(this Url url, string localFolderPath, string localFileName = null, int bufferSize = 4096) {
- return new FlurlClient(url, true).DownloadFileAsync(localFolderPath, localFileName, bufferSize);
+ return new FlurlRequest(url).DownloadFileAsync(localFolderPath, localFileName, bufferSize);
}
}
}
\ No newline at end of file
diff --git a/src/Flurl.Http/FileUtil.cs b/src/Flurl.Http/FileUtil.cs
index 064af7b1..c565b0b9 100644
--- a/src/Flurl.Http/FileUtil.cs
+++ b/src/Flurl.Http/FileUtil.cs
@@ -1,12 +1,14 @@
using System.IO;
+#if NETSTANDARD1_1
using System.Linq;
+#endif
using System.Threading.Tasks;
namespace Flurl.Http
{
internal static class FileUtil
{
-#if PORTABLE
+#if NETSTANDARD1_1
internal static string GetFileName(string path) {
return path?.Split(PCLStorage.PortablePath.DirectorySeparatorChar).Last();
}
diff --git a/src/Flurl.Http/Flurl.Http.csproj b/src/Flurl.Http/Flurl.Http.csproj
index 804d572d..35d4b625 100644
--- a/src/Flurl.Http/Flurl.Http.csproj
+++ b/src/Flurl.Http/Flurl.Http.csproj
@@ -1,54 +1,20 @@
- net45;netstandard1.3;portable-net45+win8+wpa81+wp8
+ net45;netstandard1.3;netstandard1.1;True
- 1.2.0
+ Flurl.Http
+ 2.0.0Todd Menier
- A fluent, portable, testable HTTP client library that extends Flurl's URL builder chain.
+ WARNING: 2.0 CONTAINS BREAKING CHANGES - REVIEW RELEASE NOTES CAREFULLY! Flurl.Http is a fluent, portable, testable HTTP client library that extends Flurl's URL builder chain.http://tmenier.github.io/Flurlhttps://pbs.twimg.com/profile_images/534024476296376320/IuPGZ_bX_400x400.pnghttps://raw.githubusercontent.com/tmenier/Flurl/master/LICENSEhttps://github.com/tmenier/Flurl.gitgit
- httpclient rest json http fluent portable url uri tdd assert async
-
- 1.2.0 - Up'd solution to VS2017, up'd Flurl dependency to 2.4.0
- 1.1.3 - Minor fixes (github #175, #182, #186)
- 1.1.2 - Minor bug fixes (github #159 & #169), up'd Flurl dependency to 2.3.0
- 1.1.1 - More query param assert variations (github #102), HttpCall.Url now a Flurl.Url instance
- 1.1.0 - Parallel testing (github #67), better DI/IoC/mocking support (github #146), assert query params (github #102)
- 1.0.3 - .NET Core 1.0.1, fixed assembly references (github #131)
- 1.0.2 - Updated Flurl dependency to 2.2.1 https://www.nuget.org/packages/Flurl/2.2.1
- 1.0.1 - Updated Flurl dependency to 2.2 https://www.nuget.org/packages/Flurl/2.2.0
- 1.0.0 - Many updates and new features: https://github.com/tmenier/Flurl/releases/tag/Flurl.Http.1.0.0
- 0.10.1 - DLL version fix (github #90)
- 0.10.0 - Lib updates, including Flurl 2.0 which contains breaking changes: https://github.com/tmenier/Flurl/wiki/Release-Notes
- 0.9.0 - BREAKING CHANGES: https://github.com/tmenier/Flurl/wiki/Release-Notes
- 0.8.0 - .NET Core support (github #61, thx @kroniak)
- 0.7.0 - BREAKING CHANGES: https://github.com/tmenier/Flurl/wiki/Release-Notes
- 0.6.4 - nuspec fix for Xamarin/non-PCL, AllowHttpStatus overloads with HttpStatusCode enum.
- 0.6.3 - Updated Flurl dependency to 1.0.9.
- 0.6.2 - Respect Json.NET's JsonConvert.DefaultSettings
- 0.6.1 - Fixed possibly dictionary lookup bug (github #34).
- 0.6.0 - Added support for cancellation tokens, PATCH, string request bodies.
- 0.5.3 - Updated Flurl dependency to 1.0.7.
- 0.5.2 - Allowed HTTP status at the individual client/call level, i.e. url.AllowHttpStatus("3xx"), url.AllowAnyHttpStatus()
- 0.5.1 - Configure which HTTP statuses won't throw exceptions, i.e. FlurlHttp.Configure(c => c.AllowedHttpStatusRange = "100-299,4xx");
- 0.5.0 - Added deserialization helpers for error responses (FlurlHttpException.FlurlHttpException.GetResponseJson, etc).
- 0.4.2 - Updated Flurl dependency to 1.0.6.
- 0.4.1 - GitHub #25 - some exceptions not triggering global OnError.
- 0.4.0 - Client lifetime management - see http://bit.ly/1zqmuLA.
- 0.3.0 - Added support for sending cookies (WithCookie/WithCookies), including breaking change to IHttpClientFactory.
- 0.2.5 - Added hook to create HttpClientHandler from custom factory, updated Flurl dependency.
- 0.2.4 - Updated Flurl dependency for PCL to 1.0.2.
- 0.2.3 - New properties added to HttpCall: Url, Completed, Succeeded, HttpStatus.
- 0.2.2 - Updated Flurl dependency for PCL to 1.0.1.
- 0.2.1 - Added support for getting streams and byte arrays.
- 0.2.0 - Added .NET 4.5 specific version with fewer dependencies.
- 0.1.3 - Added support for HEAD requests via HeadAsync (thanks to @benb1n).
-
- false
+ httpclient rest json http fluent url uri tdd assert async
+ See https://github.com/tmenier/Flurl/releases
+ false
@@ -61,40 +27,28 @@
-
+
-
- NETSTANDARD
-
-
-
-
-
+
+
-
- PORTABLE
- .NETPortable
- v4.5
- Profile259
- .NETPortable,Version=v0.0,Profile=Profile259
- C:\Program Files (x86)\MSBuild\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets
+
+ portable-net45+win8+wp8
-
-
-
-
-
-
-
-
+
+ bin\Debug\net45\Flurl.Http.xml
+
+
+
+
diff --git a/src/Flurl.Http/FlurlClient.cs b/src/Flurl.Http/FlurlClient.cs
index fffc2c07..94565bfd 100644
--- a/src/Flurl.Http/FlurlClient.cs
+++ b/src/Flurl.Http/FlurlClient.cs
@@ -1,267 +1,152 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Net;
using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
+using System.Linq;
using Flurl.Http.Configuration;
using Flurl.Http.Testing;
+using Flurl.Util;
namespace Flurl.Http
{
///
/// Interface defining FlurlClient's contract (useful for mocking and DI)
///
- public interface IFlurlClient : IDisposable {
+ public interface IFlurlClient : IHttpSettingsContainer, IDisposable {
///
- /// Creates a copy of this FlurlClient with a shared instance of HttpClient and HttpMessageHandler
+ /// Gets or sets the FlurlHttpSettings object used by this client.
///
- IFlurlClient Clone();
+ new ClientFlurlHttpSettings Settings { get; set; }
///
- /// Gets or sets the FlurlHttpSettings object used by this client.
+ /// Gets the HttpClient to be used in subsequent HTTP calls. Creation (when necessary) is delegated
+ /// to FlurlHttp.FlurlClientFactory. Reused for the life of the FlurlClient.
///
- FlurlHttpSettings Settings { get; set; }
+ HttpClient HttpClient { get; }
///
- /// Gets or sets the URL to be called.
+ /// Gets the HttpMessageHandler to be used in subsequent HTTP calls. Creation (when necessary) is delegated
+ /// to FlurlHttp.FlurlClientFactory.
///
- Url Url { get; set; }
+ HttpMessageHandler HttpMessageHandler { get; }
///
- /// Collection of HttpCookies sent and received.
+ /// Gets or sets base URL associated with this client.
///
- IDictionary Cookies { get; }
+ string BaseUrl { get; set; }
///
- /// Gets the HttpClient to be used in subsequent HTTP calls. Creation (when necessary) is delegated
- /// to FlurlHttp.HttpClientFactory. Reused for the life of the FlurlClient.
+ /// Instantiates a new IFlurClient, optionally appending path segments to the BaseUrl.
///
- HttpClient HttpClient { get; }
+ /// The URL or URL segments for the request. If BaseUrl is defined, it is assumed that these are path segments off that base.
+ /// A new IFlurlRequest
+ IFlurlRequest Request(params object[] urlSegments);
///
- /// Gets the HttpMessageHandler to be used in subsequent HTTP calls. Creation (when necessary) is delegated
- /// to FlurlHttp.HttpClientFactory.
+ /// Checks whether the connection lease timeout (as specified in Settings.ConnectionLeaseTimeout) has passed since
+ /// connection was opened. If it has, resets the interval and returns true.
///
- HttpMessageHandler HttpMessageHandler { get; }
+ bool CheckAndRenewConnectionLease();
///
- /// Creates and asynchronously sends an HttpRequestMethod, disposing HttpClient if AutoDispose it true.
- /// Mainly used to implement higher-level extension methods (GetJsonAsync, etc).
+ /// Gets a value indicating whether this instance (and its underlying HttpClient) has been disposed.
///
- /// The HTTP method used to make the request.
- /// Contents of the request body.
- /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
- /// The HttpCompletionOption used in the request. Optional.
- /// A Task whose result is the received HttpResponseMessage.
- Task SendAsync(HttpMethod verb, HttpContent content = null, CancellationToken? cancellationToken = null, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead);
+ bool IsDisposed { get; }
}
///
- /// A chainable wrapper around HttpClient and Flurl.Url.
+ /// A reusable object for making HTTP calls.
///
public class FlurlClient : IFlurlClient
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The FlurlHttpSettings associated with this instance.
- public FlurlClient(FlurlHttpSettings settings = null) {
- Settings = settings ?? FlurlHttp.GlobalSettings.Clone();
- }
+ private readonly Lazy _httpClient;
+ private readonly Lazy _httpMessageHandler;
///
/// Initializes a new instance of the class.
///
- /// Action allowing you to overide default settings inline.
- public FlurlClient(Action configure) : this() {
- configure(Settings);
+ /// The base URL associated with this client.
+ public FlurlClient(string baseUrl = null) {
+ BaseUrl = baseUrl;
+ Settings = new ClientFlurlHttpSettings(FlurlHttp.GlobalSettings);
+ _httpClient = new Lazy(() => Settings.HttpClientFactory.CreateHttpClient(HttpMessageHandler));
+ _httpMessageHandler = new Lazy(() => Settings.HttpClientFactory.CreateMessageHandler());
}
- ///
- /// Initializes a new instance of the class.
- ///
- /// The URL to call with this FlurlClient instance.
- public FlurlClient(Url url) : this() {
- Url = url;
- }
+ ///
+ public string BaseUrl { get; set; }
- ///
- /// Initializes a new instance of the class.
- ///
- /// The URL to call with this FlurlClient instance.
- public FlurlClient(string url) : this() {
- Url = new Url(url);
- }
+ ///
+ public ClientFlurlHttpSettings Settings { get; set; }
- ///
- /// Initializes a new instance of the class.
- ///
- /// The URL to call with this FlurlClient instance.
- /// Indicates whether to automatically dispose underlying HttpClient immediately after each call.
- public FlurlClient(Url url, bool autoDispose) : this(url) {
- Settings.AutoDispose = autoDispose;
- }
+ ///
+ public IDictionary Headers { get; } = new Dictionary();
- ///
- /// Initializes a new instance of the class.
- ///
- /// The URL to call with this FlurlClient instance.
- /// Indicates whether to automatically dispose underlying HttpClient immediately after each call.
- public FlurlClient(string url, bool autoDispose) : this(url) {
- Settings.AutoDispose = autoDispose;
- }
+ ///
+ public IDictionary Cookies { get; } = new Dictionary();
- ///
- /// Creates a copy of this FlurlClient with a shared instance of HttpClient and HttpMessageHandler
- ///
- public IFlurlClient Clone() {
- return new FlurlClient {
- _httpClient = _httpClient,
- _httpMessageHandler = _httpMessageHandler,
- _parent = this,
- Settings = Settings,
- Url = Url,
- Cookies = Cookies
- };
- }
+ ///
+ public HttpClient HttpClient => HttpTest.Current?.HttpClient ?? _httpClient.Value;
- private HttpClient _httpClient;
- private HttpMessageHandler _httpMessageHandler;
- private FlurlClient _parent;
+ ///
+ public HttpMessageHandler HttpMessageHandler => HttpTest.Current?.HttpMessageHandler ?? _httpMessageHandler.Value;
- ///
- /// Gets or sets the FlurlHttpSettings object used by this client.
- ///
- public FlurlHttpSettings Settings { get; set; }
-
- ///
- /// Gets or sets the URL to be called.
- ///
- public Url Url { get; set; }
+ ///
+ public IFlurlRequest Request(params object[] urlSegments) {
+ var parts = new List(urlSegments.Select(s => s.ToInvariantString()));
+ if (!Url.IsValid(parts.FirstOrDefault()) && !string.IsNullOrEmpty(BaseUrl))
+ parts.Insert(0, BaseUrl);
- ///
- /// Collection of HttpCookies sent and received.
- ///
- public IDictionary Cookies { get; private set; } = new Dictionary();
-
- ///
- /// Gets the HttpClient to be used in subsequent HTTP calls. Creation (when necessary) is delegated
- /// to FlurlHttp.HttpClientFactory. Reused for the life of the FlurlClient.
- ///
- public HttpClient HttpClient => EnsureHttpClient();
+ if (!parts.Any())
+ throw new ArgumentException("Cannot create a Request. BaseUrl is not defined and no segments were passed.");
+ if (!Url.IsValid(parts[0]))
+ throw new ArgumentException("Cannot create a Request. Neither BaseUrl nor the first segment passed is a valid URL.");
- ///
- /// Gets the HttpMessageHandler to be used in subsequent HTTP calls. Creation (when necessary) is delegated
- /// to FlurlHttp.HttpClientFactory.
- ///
- public HttpMessageHandler HttpMessageHandler => EnsureHttpMessageHandler();
+ return new FlurlRequest(Url.Combine(parts.ToArray())).WithClient(this);
+ }
- private HttpClient EnsureHttpClient(HttpClient hc = null) {
- if (_httpClient == null) {
- if (hc == null) {
- hc = Settings.HttpClientFactory.CreateClient(Url, HttpMessageHandler);
- hc.Timeout = Settings.DefaultTimeout;
- }
- _httpClient = hc;
- _parent?.EnsureHttpClient(hc);
- }
- return _httpClient;
+ FlurlHttpSettings IHttpSettingsContainer.Settings {
+ get => Settings;
+ set => Settings = value as ClientFlurlHttpSettings;
}
- private HttpMessageHandler EnsureHttpMessageHandler(HttpMessageHandler handler = null) {
- if (_httpMessageHandler == null) {
- if (handler == null) {
- handler = (HttpTest.Current == null) ?
- Settings.HttpClientFactory.CreateMessageHandler() :
- new FakeHttpMessageHandler();
+ private Lazy _connectionLeaseStart = new Lazy(() => DateTime.UtcNow);
+ private readonly object _connectionLeaseLock = new object();
+
+ private bool IsConnectionLeaseExpired =>
+ Settings.ConnectionLeaseTimeout.HasValue &&
+ DateTime.UtcNow - _connectionLeaseStart.Value > Settings.ConnectionLeaseTimeout;
+
+ ///
+ public bool CheckAndRenewConnectionLease() {
+ // do double-check locking to avoid lock overhead most of the time
+ if (IsConnectionLeaseExpired) {
+ lock (_connectionLeaseLock) {
+ if (IsConnectionLeaseExpired) {
+ _connectionLeaseStart = new Lazy(() => DateTime.UtcNow);
+ return true;
+ }
}
- _httpMessageHandler = handler;
- _parent?.EnsureHttpMessageHandler(handler);
}
- return _httpMessageHandler;
+ return false;
}
+ ///
+ public bool IsDisposed { get; private set; }
+
///
- /// Creates and asynchronously sends an HttpRequestMethod, disposing HttpClient if AutoDispose it true.
- /// Mainly used to implement higher-level extension methods (GetJsonAsync, etc).
+ /// Disposes the underlying HttpClient and HttpMessageHandler.
///
- /// The HTTP method used to make the request.
- /// Contents of the request body.
- /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
- /// The HttpCompletionOption used in the request. Optional.
- /// A Task whose result is the received HttpResponseMessage.
- public async Task SendAsync(HttpMethod verb, HttpContent content = null, CancellationToken? cancellationToken = null, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- var request = new HttpRequestMessage(verb, Url) { Content = content };
- var call = new HttpCall(request, Settings);
+ public virtual void Dispose() {
+ if (IsDisposed)
+ return;
- try {
- if (Settings.CookiesEnabled)
- WriteRequestCookies(request);
- return await HttpClient.SendAsync(request, completionOption, cancellationToken ?? CancellationToken.None).ConfigureAwait(false);
- }
- catch (Exception) when (call.ExceptionHandled) {
- return call.Response;
- }
- finally {
- if (Settings.CookiesEnabled)
- ReadResponseCookies(call.Response);
- if (Settings.AutoDispose)
- Dispose();
- }
- }
-
- private void WriteRequestCookies(HttpRequestMessage request) {
- if (!Cookies.Any()) return;
- var uri = request.RequestUri;
- var cookieHandler = HttpMessageHandler as HttpClientHandler;
-
- // if the inner handler is an HttpClientHandler (which it usually is), put the cookies in the CookieContainer.
- if (cookieHandler != null && cookieHandler.UseCookies) {
- if (cookieHandler.CookieContainer == null)
- cookieHandler.CookieContainer = new CookieContainer();
- foreach (var cookie in Cookies.Values)
- cookieHandler.CookieContainer.Add(uri, cookie);
- }
- else {
- // http://stackoverflow.com/a/15588878/62600
- request.Headers.TryAddWithoutValidation("Cookie", string.Join("; ", Cookies.Values));
- }
- }
-
- private void ReadResponseCookies(HttpResponseMessage response) {
- var uri = response.RequestMessage.RequestUri;
+ if (_httpMessageHandler.IsValueCreated)
+ _httpMessageHandler.Value.Dispose();
+ if (_httpClient.IsValueCreated)
+ _httpClient.Value.Dispose();
- // if the inner handler is an HttpClientHandler (which it usually is), it's already plucked the
- // cookies out of the headers and put them in the CookieContainer.
- var jar = (HttpMessageHandler as HttpClientHandler)?.CookieContainer;
- if (jar == null) {
- // http://stackoverflow.com/a/15588878/62600
- IEnumerable cookieHeaders;
- if (!response.Headers.TryGetValues("Set-Cookie", out cookieHeaders))
- return;
-
- jar = new CookieContainer();
- foreach (string header in cookieHeaders) {
- jar.SetCookies(uri, header);
- }
- }
-
- foreach (var cookie in jar.GetCookies(uri).Cast())
- Cookies[cookie.Name] = cookie;
- }
-
- ///
- /// Disposes the underlying HttpClient and HttpMessageHandler, setting both properties to null.
- /// This FlurlClient can still be reused, but those underlying objects will be re-created as needed. Previously set headers, etc, will be lost.
- ///
- public void Dispose() {
- _httpMessageHandler?.Dispose();
- _httpClient?.Dispose();
- _httpMessageHandler = null;
- _httpClient = null;
- Cookies = new Dictionary();
+ IsDisposed = true;
}
}
}
\ No newline at end of file
diff --git a/src/Flurl.Http/FlurlHttp.cs b/src/Flurl.Http/FlurlHttp.cs
index 506c2591..9386fba5 100644
--- a/src/Flurl.Http/FlurlHttp.cs
+++ b/src/Flurl.Http/FlurlHttp.cs
@@ -1,6 +1,4 @@
using System;
-using System.Net.Http;
-using System.Threading.Tasks;
using Flurl.Http.Configuration;
namespace Flurl.Http
@@ -12,73 +10,35 @@ public static class FlurlHttp
{
private static readonly object _configLock = new object();
- private static Lazy _settings =
- new Lazy(() => new FlurlHttpSettings());
+ private static Lazy _settings =
+ new Lazy(() => new GlobalFlurlHttpSettings());
///
/// Globally configured Flurl.Http settings. Should normally be written to by calling FlurlHttp.Configure once application at startup.
///
- public static FlurlHttpSettings GlobalSettings {
- get { return _settings.Value; }
- }
+ public static GlobalFlurlHttpSettings GlobalSettings => _settings.Value;
///
/// Provides thread-safe access to Flurl.Http's global configuration settings. Should only be called once at application startup.
///
- ///
- /// A delegate callback throws an exception.
- public static void Configure(Action configAction) {
+ /// the action to perform against the GlobalSettings
+ public static void Configure(Action configAction) {
lock (_configLock) {
configAction(GlobalSettings);
}
}
///
- /// Triggers the specified sync and async event handlers, usually defined on
+ /// Provides thread-safe access to the Settings associated with a specific IFlurlClient. The URL is used to find the client,
+ /// but keep in mind that the same client will be used in all calls to the same host by default.
///
- public static Task RaiseEventAsync(HttpRequestMessage request, FlurlEventType eventType) {
- var call = HttpCall.Get(request);
- var settings = call?.Settings;
-
- if (settings == null)
- return NoOpTask.Instance;
-
- switch (eventType) {
- case FlurlEventType.BeforeCall:
- return HandleEventAsync(settings.BeforeCall, settings.BeforeCallAsync, call);
- case FlurlEventType.AfterCall:
- return HandleEventAsync(settings.AfterCall, settings.AfterCallAsync, call);
- case FlurlEventType.OnError:
- return HandleEventAsync(settings.OnError, settings.OnErrorAsync, call);
- default:
- return NoOpTask.Instance;
+ /// the URL used to find the IFlurlClient
+ /// the action to perform against the IFlurlClient's Settings
+ public static void ConfigureClient(string url, Action configAction) {
+ var client = GlobalSettings.FlurlClientFactory.Get(url);
+ lock (_configLock) {
+ configAction(client.Settings);
}
}
-
- private static Task HandleEventAsync(Action syncHandler, Func asyncHandler, HttpCall call) {
- if (syncHandler != null)
- syncHandler(call);
- if (asyncHandler != null)
- return asyncHandler(call);
- return NoOpTask.Instance;
- }
- }
-
- ///
- /// Flurl event types/
- ///
- public enum FlurlEventType {
- ///
- /// The before call
- ///
- BeforeCall,
- ///
- /// The after call
- ///
- AfterCall,
- ///
- /// The on error
- ///
- OnError
}
}
\ No newline at end of file
diff --git a/src/Flurl.Http/FlurlHttpException.cs b/src/Flurl.Http/FlurlHttpException.cs
index ccc67a9a..6b19c251 100644
--- a/src/Flurl.Http/FlurlHttpException.cs
+++ b/src/Flurl.Http/FlurlHttpException.cs
@@ -1,5 +1,6 @@
using System;
using System.Dynamic;
+using System.Text;
namespace Flurl.Http
{
@@ -38,18 +39,22 @@ public FlurlHttpException(HttpCall call, Exception inner) : this(call, BuildMess
public FlurlHttpException(HttpCall call) : this(call, BuildMessage(call, null), null) { }
private static string BuildMessage(HttpCall call, Exception inner) {
- if (call.Response != null && !call.Succeeded) {
- return string.Format("Request to {0} failed with status code {1} ({2}).",
- call.Request.RequestUri.AbsoluteUri,
- (int)call.Response.StatusCode,
- call.Response.ReasonPhrase);
- }
- if (inner != null) {
- return $"Request to {call.Request.RequestUri.AbsoluteUri} failed. {inner.Message}";
- }
+ var sb = new StringBuilder();
- // in theory we should never get here.
- return $"Request to {call.Request.RequestUri.AbsoluteUri} failed.";
+ if (call.Response != null && !call.Succeeded)
+ sb.AppendLine($"{call} failed with status code {(int)call.Response.StatusCode} ({call.Response.ReasonPhrase}).");
+ else if (inner != null)
+ sb.AppendLine($"{call} failed. {inner.Message}");
+ else // in theory we should never get here.
+ sb.AppendLine($"{call} failed.");
+
+ if (!string.IsNullOrWhiteSpace(call.RequestBody))
+ sb.AppendLine("Request body:").AppendLine(call.RequestBody);
+
+ if (!string.IsNullOrWhiteSpace(call.ErrorResponseBody))
+ sb.AppendLine("Response body:").AppendLine(call.ErrorResponseBody);
+
+ return sb.ToString().Trim();
}
///
@@ -67,8 +72,8 @@ public string GetResponseString() {
public T GetResponseJson() {
return
Call?.ErrorResponseBody == null ? default(T) :
- Call.Settings?.JsonSerializer == null ? default(T) :
- Call.Settings.JsonSerializer.Deserialize(Call.ErrorResponseBody);
+ Call?.FlurlRequest?.Settings?.JsonSerializer == null ? default(T) :
+ Call.FlurlRequest.Settings.JsonSerializer.Deserialize(Call.ErrorResponseBody);
}
///
@@ -93,7 +98,7 @@ public class FlurlHttpTimeoutException : FlurlHttpException
public FlurlHttpTimeoutException(HttpCall call, Exception inner) : base(call, BuildMessage(call), inner) { }
private static string BuildMessage(HttpCall call) {
- return $"Request to {call} timed out.";
+ return $"{call} timed out.";
}
}
}
\ No newline at end of file
diff --git a/src/Flurl.Http/FlurlRequest.cs b/src/Flurl.Http/FlurlRequest.cs
new file mode 100644
index 00000000..5cbbd896
--- /dev/null
+++ b/src/Flurl.Http/FlurlRequest.cs
@@ -0,0 +1,236 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Flurl.Http.Configuration;
+using Flurl.Util;
+
+namespace Flurl.Http
+{
+ ///
+ /// Interface defining FlurlRequest's contract (useful for mocking and DI)
+ ///
+ public interface IFlurlRequest : IHttpSettingsContainer
+ {
+ ///
+ /// Gets or sets the IFlurlClient to use when sending the request.
+ ///
+ IFlurlClient Client { get; set; }
+
+ ///
+ /// Gets or sets the URL to be called.
+ ///
+ Url Url { get; set; }
+
+ ///
+ /// Creates and asynchronously sends an HttpRequestMethod.
+ /// Mainly used to implement higher-level extension methods (GetJsonAsync, etc).
+ ///
+ /// The HTTP method used to make the request.
+ /// Contents of the request body.
+ /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
+ /// The HttpCompletionOption used in the request. Optional.
+ /// A Task whose result is the received HttpResponseMessage.
+ Task SendAsync(HttpMethod verb, HttpContent content = null, CancellationToken? cancellationToken = null, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead);
+ }
+
+ ///
+ /// A chainable wrapper around HttpClient and Flurl.Url.
+ ///
+ public class FlurlRequest : IFlurlRequest
+ {
+ private IFlurlClient _client;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The URL to call with this FlurlRequest instance.
+ public FlurlRequest(Url url = null) {
+ Settings = new FlurlHttpSettings();
+ Url = url;
+ }
+
+ ///
+ /// Gets or sets the FlurlHttpSettings used by this request.
+ ///
+ public FlurlHttpSettings Settings { get; set; }
+
+ ///
+ /// Gets or sets the IFlurlClient to use when sending the request.
+ ///
+ public IFlurlClient Client {
+ get {
+ if (_client == null) {
+ _client = FlurlHttp.GlobalSettings.FlurlClientFactory.Get(Url);
+ Settings.Merge(_client.Settings);
+ }
+ return _client;
+ }
+ set {
+ _client = value;
+ Settings.Merge(_client?.Settings ?? FlurlHttp.GlobalSettings);
+ }
+ }
+
+ ///
+ /// Gets or sets the URL to be called.
+ ///
+ public Url Url { get; set; }
+
+ ///
+ /// Collection of headers sent on this request.
+ ///
+ public IDictionary Headers { get; } = new Dictionary();
+
+ ///
+ /// Collection of HttpCookies sent and received by the IFlurlClient associated with this request.
+ ///
+ public IDictionary Cookies => Client.Cookies;
+
+ ///
+ /// Creates and asynchronously sends an HttpRequestMessage.
+ /// Mainly used to implement higher-level extension methods (GetJsonAsync, etc).
+ ///
+ /// The HTTP method used to make the request.
+ /// Contents of the request body.
+ /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
+ /// The HttpCompletionOption used in the request. Optional.
+ /// A Task whose result is the received HttpResponseMessage.
+ public async Task SendAsync(HttpMethod verb, HttpContent content = null, CancellationToken? cancellationToken = null, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
+ var request = new HttpRequestMessage(verb, Url) { Content = content };
+ var call = new HttpCall(this, request);
+
+ await HandleEventAsync(Settings.BeforeCall, Settings.BeforeCallAsync, call).ConfigureAwait(false);
+ request.RequestUri = new Uri(Url); // in case it was modifed in the handler above
+
+ var userToken = cancellationToken ?? CancellationToken.None;
+ var token = userToken;
+
+ if (Settings.Timeout.HasValue) {
+ var cts = CancellationTokenSource.CreateLinkedTokenSource(userToken);
+ cts.CancelAfter(Settings.Timeout.Value);
+ token = cts.Token;
+ }
+
+ call.StartedUtc = DateTime.UtcNow;
+ try {
+ WriteHeaders(request);
+ if (Settings.CookiesEnabled)
+ WriteRequestCookies(request);
+
+ if (Client.CheckAndRenewConnectionLease())
+ request.Headers.ConnectionClose = true;
+
+ call.Response = await Client.HttpClient.SendAsync(request, completionOption, token).ConfigureAwait(false);
+ call.Response.RequestMessage = request;
+
+ if (call.Succeeded)
+ return call.Response;
+
+ // response content is only awaited here if the call failed.
+ if (call.Response.Content != null)
+ call.ErrorResponseBody = await call.Response.Content.StripCharsetQuotes().ReadAsStringAsync().ConfigureAwait(false);
+
+ throw new FlurlHttpException(call, null);
+ }
+ catch (Exception ex) {
+ call.Exception = ex;
+ await HandleEventAsync(Settings.OnError, Settings.OnErrorAsync, call).ConfigureAwait(false);
+
+ if (call.ExceptionHandled)
+ return call.Response;
+
+ if (ex is OperationCanceledException && !userToken.IsCancellationRequested)
+ throw new FlurlHttpTimeoutException(call, ex);
+
+ if (ex is FlurlHttpException)
+ throw;
+
+ throw new FlurlHttpException(call, ex);
+ }
+ finally {
+ request.Dispose();
+ if (Settings.CookiesEnabled)
+ ReadResponseCookies(call.Response);
+
+ call.EndedUtc = DateTime.UtcNow;
+ await HandleEventAsync(Settings.AfterCall, Settings.AfterCallAsync, call).ConfigureAwait(false);
+ }
+ }
+
+ private void WriteHeaders(HttpRequestMessage request) {
+ Headers.Merge(Client.Headers);
+ foreach (var header in Headers.Where(h => h.Value != null))
+ request.Headers.TryAddWithoutValidation(header.Key, header.Value.ToInvariantString());
+ }
+
+ private void WriteRequestCookies(HttpRequestMessage request) {
+ if (!Cookies.Any()) return;
+ var uri = request.RequestUri;
+ var cookieHandler = FindHttpClientHandler(Client.HttpMessageHandler);
+
+ // if the handler is an HttpClientHandler (which it usually is), put the cookies in the CookieContainer.
+ if (cookieHandler != null && cookieHandler.UseCookies) {
+ if (cookieHandler.CookieContainer == null)
+ cookieHandler.CookieContainer = new CookieContainer();
+
+ Cookies.Merge(Client.Cookies);
+ foreach (var cookie in Cookies.Values)
+ cookieHandler.CookieContainer.Add(uri, cookie);
+ }
+ else {
+ // http://stackoverflow.com/a/15588878/62600
+ request.Headers.TryAddWithoutValidation("Cookie", string.Join("; ", Cookies.Values));
+ }
+ }
+
+ private void ReadResponseCookies(HttpResponseMessage response) {
+ var uri = response?.RequestMessage?.RequestUri;
+ if (uri == null)
+ return;
+
+ // if the handler is an HttpClientHandler (which it usually is), it's already plucked the
+ // cookies out of the headers and put them in the CookieContainer.
+ var jar = FindHttpClientHandler(Client.HttpMessageHandler)?.CookieContainer;
+ if (jar == null) {
+ // http://stackoverflow.com/a/15588878/62600
+ IEnumerable cookieHeaders;
+ if (!response.Headers.TryGetValues("Set-Cookie", out cookieHeaders))
+ return;
+
+ jar = new CookieContainer();
+ foreach (string header in cookieHeaders) {
+ jar.SetCookies(uri, header);
+ }
+ }
+
+ foreach (var cookie in jar.GetCookies(uri).Cast())
+ Cookies[cookie.Name] = cookie;
+ }
+
+ private HttpClientHandler FindHttpClientHandler(HttpMessageHandler handler) {
+ // if it's an HttpClientHandler, return it
+ var httpClientHandler = handler as HttpClientHandler;
+ if (httpClientHandler != null)
+ return httpClientHandler;
+
+ // if it's a DelegatingHandler, check the InnerHandler recursively
+ var delegatingHandler = handler as DelegatingHandler;
+ if (delegatingHandler != null)
+ return FindHttpClientHandler(delegatingHandler.InnerHandler);
+
+ // it's neither
+ return null;
+ }
+
+ private static Task HandleEventAsync(Action syncHandler, Func asyncHandler, HttpCall call) {
+ syncHandler?.Invoke(call);
+ if (asyncHandler != null)
+ return asyncHandler(call);
+ return Task.FromResult(0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Flurl.Http/HttpExtensions.cs b/src/Flurl.Http/GeneratedExtensions.cs
similarity index 61%
rename from src/Flurl.Http/HttpExtensions.cs
rename to src/Flurl.Http/GeneratedExtensions.cs
index 9ddbc857..3b535c9e 100644
--- a/src/Flurl.Http/HttpExtensions.cs
+++ b/src/Flurl.Http/GeneratedExtensions.cs
@@ -1,21 +1,23 @@
// This file was auto-generated by Flurl.Http.CodeGen. Do not edit directly.
-
+using System;
using System.Collections.Generic;
using System.IO;
+using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
+using Flurl.Http.Configuration;
using Flurl.Http.Content;
namespace Flurl.Http
{
///
- /// Http extensions for Flurl Client.
+ /// Auto-generated fluent extension methods on String, Url, and IFlurlRequest.
///
- public static class HttpExtensions
+ public static class GeneratedExtensions
{
///
- /// Creates a FlurlClient from the URL and sends an asynchronous request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous request.
///
/// The URL.
/// The HTTP method used to make the request.
@@ -24,11 +26,11 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task SendAsync(this Url url, HttpMethod verb, HttpContent content, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).SendAsync(verb, content, cancellationToken, completionOption);
+ return new FlurlRequest(url).SendAsync(verb, content, cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous request.
///
/// The URL.
/// The HTTP method used to make the request.
@@ -37,25 +39,25 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task SendAsync(this string url, HttpMethod verb, HttpContent content, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).SendAsync(verb, content, cancellationToken, completionOption);
+ return new FlurlRequest(url).SendAsync(verb, content, cancellationToken, completionOption);
}
///
/// Sends an asynchronous request.
///
- /// The IFlurlClient instance.
+ /// The IFlurlRequest instance.
/// The HTTP method used to make the request.
/// Contents of the request body.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
- public static Task SendJsonAsync(this IFlurlClient client, HttpMethod verb, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- var content = new CapturedJsonContent(client.Settings.JsonSerializer.Serialize(data));
- return client.SendAsync(verb, content: content, cancellationToken: cancellationToken, completionOption: completionOption);
+ public static Task SendJsonAsync(this IFlurlRequest request, HttpMethod verb, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
+ var content = new CapturedJsonContent(request.Settings.JsonSerializer.Serialize(data));
+ return request.SendAsync(verb, content: content, cancellationToken: cancellationToken, completionOption: completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous request.
///
/// The URL.
/// The HTTP method used to make the request.
@@ -64,11 +66,11 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task SendJsonAsync(this Url url, HttpMethod verb, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).SendJsonAsync(verb, data, cancellationToken, completionOption);
+ return new FlurlRequest(url).SendJsonAsync(verb, data, cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous request.
///
/// The URL.
/// The HTTP method used to make the request.
@@ -77,25 +79,25 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task SendJsonAsync(this string url, HttpMethod verb, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).SendJsonAsync(verb, data, cancellationToken, completionOption);
+ return new FlurlRequest(url).SendJsonAsync(verb, data, cancellationToken, completionOption);
}
///
/// Sends an asynchronous request.
///
- /// The IFlurlClient instance.
+ /// The IFlurlRequest instance.
/// The HTTP method used to make the request.
/// Contents of the request body.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
- public static Task SendStringAsync(this IFlurlClient client, HttpMethod verb, string data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
+ public static Task SendStringAsync(this IFlurlRequest request, HttpMethod verb, string data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
var content = new CapturedStringContent(data);
- return client.SendAsync(verb, content: content, cancellationToken: cancellationToken, completionOption: completionOption);
+ return request.SendAsync(verb, content: content, cancellationToken: cancellationToken, completionOption: completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous request.
///
/// The URL.
/// The HTTP method used to make the request.
@@ -104,11 +106,11 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task SendStringAsync(this Url url, HttpMethod verb, string data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).SendStringAsync(verb, data, cancellationToken, completionOption);
+ return new FlurlRequest(url).SendStringAsync(verb, data, cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous request.
///
/// The URL.
/// The HTTP method used to make the request.
@@ -117,25 +119,25 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task SendStringAsync(this string url, HttpMethod verb, string data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).SendStringAsync(verb, data, cancellationToken, completionOption);
+ return new FlurlRequest(url).SendStringAsync(verb, data, cancellationToken, completionOption);
}
///
/// Sends an asynchronous request.
///
- /// The IFlurlClient instance.
+ /// The IFlurlRequest instance.
/// The HTTP method used to make the request.
/// Contents of the request body.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
- public static Task SendUrlEncodedAsync(this IFlurlClient client, HttpMethod verb, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- var content = new CapturedUrlEncodedContent(client.Settings.UrlEncodedSerializer.Serialize(data));
- return client.SendAsync(verb, content: content, cancellationToken: cancellationToken, completionOption: completionOption);
+ public static Task SendUrlEncodedAsync(this IFlurlRequest request, HttpMethod verb, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
+ var content = new CapturedUrlEncodedContent(request.Settings.UrlEncodedSerializer.Serialize(data));
+ return request.SendAsync(verb, content: content, cancellationToken: cancellationToken, completionOption: completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous request.
///
/// The URL.
/// The HTTP method used to make the request.
@@ -144,11 +146,11 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task SendUrlEncodedAsync(this Url url, HttpMethod verb, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).SendUrlEncodedAsync(verb, data, cancellationToken, completionOption);
+ return new FlurlRequest(url).SendUrlEncodedAsync(verb, data, cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous request.
///
/// The URL.
/// The HTTP method used to make the request.
@@ -157,254 +159,254 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task SendUrlEncodedAsync(this string url, HttpMethod verb, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).SendUrlEncodedAsync(verb, data, cancellationToken, completionOption);
+ return new FlurlRequest(url).SendUrlEncodedAsync(verb, data, cancellationToken, completionOption);
}
///
/// Sends an asynchronous GET request.
///
- /// The IFlurlClient instance.
+ /// The IFlurlRequest instance.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
- public static Task GetAsync(this IFlurlClient client, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return client.SendAsync(HttpMethod.Get, cancellationToken: cancellationToken, completionOption: completionOption);
+ public static Task GetAsync(this IFlurlRequest request, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
+ return request.SendAsync(HttpMethod.Get, cancellationToken: cancellationToken, completionOption: completionOption);
}
///
/// Sends an asynchronous GET request.
///
- /// The IFlurlClient instance.
+ /// The IFlurlRequest instance.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the JSON response body deserialized to an object of type T.
- public static Task GetJsonAsync(this IFlurlClient client, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return client.SendAsync(HttpMethod.Get, cancellationToken: cancellationToken, completionOption: completionOption).ReceiveJson();
+ public static Task GetJsonAsync(this IFlurlRequest request, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
+ return request.SendAsync(HttpMethod.Get, cancellationToken: cancellationToken, completionOption: completionOption).ReceiveJson();
}
///
/// Sends an asynchronous GET request.
///
- /// The IFlurlClient instance.
+ /// The IFlurlRequest instance.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the JSON response body deserialized to a dynamic.
- public static Task GetJsonAsync(this IFlurlClient client, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return client.SendAsync(HttpMethod.Get, cancellationToken: cancellationToken, completionOption: completionOption).ReceiveJson();
+ public static Task GetJsonAsync(this IFlurlRequest request, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
+ return request.SendAsync(HttpMethod.Get, cancellationToken: cancellationToken, completionOption: completionOption).ReceiveJson();
}
///
/// Sends an asynchronous GET request.
///
- /// The IFlurlClient instance.
+ /// The IFlurlRequest instance.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the JSON response body deserialized to a list of dynamics.
- public static Task> GetJsonListAsync(this IFlurlClient client, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return client.SendAsync(HttpMethod.Get, cancellationToken: cancellationToken, completionOption: completionOption).ReceiveJsonList();
+ public static Task> GetJsonListAsync(this IFlurlRequest request, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
+ return request.SendAsync(HttpMethod.Get, cancellationToken: cancellationToken, completionOption: completionOption).ReceiveJsonList();
}
///
/// Sends an asynchronous GET request.
///
- /// The IFlurlClient instance.
+ /// The IFlurlRequest instance.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the response body as a string.
- public static Task GetStringAsync(this IFlurlClient client, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return client.SendAsync(HttpMethod.Get, cancellationToken: cancellationToken, completionOption: completionOption).ReceiveString();
+ public static Task GetStringAsync(this IFlurlRequest request, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
+ return request.SendAsync(HttpMethod.Get, cancellationToken: cancellationToken, completionOption: completionOption).ReceiveString();
}
///
/// Sends an asynchronous GET request.
///
- /// The IFlurlClient instance.
+ /// The IFlurlRequest instance.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the response body as a Stream.
- public static Task GetStreamAsync(this IFlurlClient client, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return client.SendAsync(HttpMethod.Get, cancellationToken: cancellationToken, completionOption: completionOption).ReceiveStream();
+ public static Task GetStreamAsync(this IFlurlRequest request, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
+ return request.SendAsync(HttpMethod.Get, cancellationToken: cancellationToken, completionOption: completionOption).ReceiveStream();
}
///
/// Sends an asynchronous GET request.
///
- /// The IFlurlClient instance.
+ /// The IFlurlRequest instance.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the response body as a byte array.
- public static Task GetBytesAsync(this IFlurlClient client, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return client.SendAsync(HttpMethod.Get, cancellationToken: cancellationToken, completionOption: completionOption).ReceiveBytes();
+ public static Task GetBytesAsync(this IFlurlRequest request, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
+ return request.SendAsync(HttpMethod.Get, cancellationToken: cancellationToken, completionOption: completionOption).ReceiveBytes();
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous GET request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous GET request.
///
/// The URL.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task GetAsync(this Url url, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).GetAsync(cancellationToken, completionOption);
+ return new FlurlRequest(url).GetAsync(cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous GET request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous GET request.
///
/// The URL.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the JSON response body deserialized to an object of type T.
public static Task GetJsonAsync(this Url url, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).GetJsonAsync(cancellationToken, completionOption);
+ return new FlurlRequest(url).GetJsonAsync(cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous GET request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous GET request.
///
/// The URL.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the JSON response body deserialized to a dynamic.
public static Task GetJsonAsync(this Url url, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).GetJsonAsync(cancellationToken, completionOption);
+ return new FlurlRequest(url).GetJsonAsync(cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous GET request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous GET request.
///
/// The URL.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the JSON response body deserialized to a list of dynamics.
public static Task> GetJsonListAsync(this Url url, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).GetJsonListAsync(cancellationToken, completionOption);
+ return new FlurlRequest(url).GetJsonListAsync(cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous GET request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous GET request.
///
/// The URL.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the response body as a string.
public static Task GetStringAsync(this Url url, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).GetStringAsync(cancellationToken, completionOption);
+ return new FlurlRequest(url).GetStringAsync(cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous GET request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous GET request.
///
/// The URL.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the response body as a Stream.
public static Task GetStreamAsync(this Url url, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).GetStreamAsync(cancellationToken, completionOption);
+ return new FlurlRequest(url).GetStreamAsync(cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous GET request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous GET request.
///
/// The URL.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the response body as a byte array.
public static Task GetBytesAsync(this Url url, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).GetBytesAsync(cancellationToken, completionOption);
+ return new FlurlRequest(url).GetBytesAsync(cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous GET request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous GET request.
///
/// The URL.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task GetAsync(this string url, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).GetAsync(cancellationToken, completionOption);
+ return new FlurlRequest(url).GetAsync(cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous GET request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous GET request.
///
/// The URL.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the JSON response body deserialized to an object of type T.
public static Task GetJsonAsync(this string url, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).GetJsonAsync(cancellationToken, completionOption);
+ return new FlurlRequest(url).GetJsonAsync(cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous GET request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous GET request.
///
/// The URL.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the JSON response body deserialized to a dynamic.
public static Task GetJsonAsync(this string url, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).GetJsonAsync(cancellationToken, completionOption);
+ return new FlurlRequest(url).GetJsonAsync(cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous GET request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous GET request.
///
/// The URL.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the JSON response body deserialized to a list of dynamics.
public static Task> GetJsonListAsync(this string url, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).GetJsonListAsync(cancellationToken, completionOption);
+ return new FlurlRequest(url).GetJsonListAsync(cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous GET request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous GET request.
///
/// The URL.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the response body as a string.
public static Task GetStringAsync(this string url, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).GetStringAsync(cancellationToken, completionOption);
+ return new FlurlRequest(url).GetStringAsync(cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous GET request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous GET request.
///
/// The URL.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the response body as a Stream.
public static Task GetStreamAsync(this string url, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).GetStreamAsync(cancellationToken, completionOption);
+ return new FlurlRequest(url).GetStreamAsync(cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous GET request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous GET request.
///
/// The URL.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the response body as a byte array.
public static Task GetBytesAsync(this string url, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).GetBytesAsync(cancellationToken, completionOption);
+ return new FlurlRequest(url).GetBytesAsync(cancellationToken, completionOption);
}
///
/// Sends an asynchronous POST request.
///
- /// The IFlurlClient instance.
+ /// The IFlurlRequest instance.
/// Contents of the request body.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
- public static Task PostAsync(this IFlurlClient client, HttpContent content, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return client.SendAsync(HttpMethod.Post, content: content, cancellationToken: cancellationToken, completionOption: completionOption);
+ public static Task PostAsync(this IFlurlRequest request, HttpContent content, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
+ return request.SendAsync(HttpMethod.Post, content: content, cancellationToken: cancellationToken, completionOption: completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous POST request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous POST request.
///
/// The URL.
/// Contents of the request body.
@@ -412,11 +414,11 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task PostAsync(this Url url, HttpContent content, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).PostAsync(content, cancellationToken, completionOption);
+ return new FlurlRequest(url).PostAsync(content, cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous POST request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous POST request.
///
/// The URL.
/// Contents of the request body.
@@ -424,24 +426,24 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task PostAsync(this string url, HttpContent content, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).PostAsync(content, cancellationToken, completionOption);
+ return new FlurlRequest(url).PostAsync(content, cancellationToken, completionOption);
}
///
/// Sends an asynchronous POST request.
///
- /// The IFlurlClient instance.
+ /// The IFlurlRequest instance.
/// Contents of the request body.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
- public static Task PostJsonAsync(this IFlurlClient client, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- var content = new CapturedJsonContent(client.Settings.JsonSerializer.Serialize(data));
- return client.SendAsync(HttpMethod.Post, content: content, cancellationToken: cancellationToken, completionOption: completionOption);
+ public static Task PostJsonAsync(this IFlurlRequest request, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
+ var content = new CapturedJsonContent(request.Settings.JsonSerializer.Serialize(data));
+ return request.SendAsync(HttpMethod.Post, content: content, cancellationToken: cancellationToken, completionOption: completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous POST request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous POST request.
///
/// The URL.
/// Contents of the request body.
@@ -449,11 +451,11 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task PostJsonAsync(this Url url, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).PostJsonAsync(data, cancellationToken, completionOption);
+ return new FlurlRequest(url).PostJsonAsync(data, cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous POST request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous POST request.
///
/// The URL.
/// Contents of the request body.
@@ -461,24 +463,24 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task PostJsonAsync(this string url, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).PostJsonAsync(data, cancellationToken, completionOption);
+ return new FlurlRequest(url).PostJsonAsync(data, cancellationToken, completionOption);
}
///
/// Sends an asynchronous POST request.
///
- /// The IFlurlClient instance.
+ /// The IFlurlRequest instance.
/// Contents of the request body.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
- public static Task PostStringAsync(this IFlurlClient client, string data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
+ public static Task PostStringAsync(this IFlurlRequest request, string data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
var content = new CapturedStringContent(data);
- return client.SendAsync(HttpMethod.Post, content: content, cancellationToken: cancellationToken, completionOption: completionOption);
+ return request.SendAsync(HttpMethod.Post, content: content, cancellationToken: cancellationToken, completionOption: completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous POST request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous POST request.
///
/// The URL.
/// Contents of the request body.
@@ -486,11 +488,11 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task PostStringAsync(this Url url, string data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).PostStringAsync(data, cancellationToken, completionOption);
+ return new FlurlRequest(url).PostStringAsync(data, cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous POST request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous POST request.
///
/// The URL.
/// Contents of the request body.
@@ -498,24 +500,24 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task PostStringAsync(this string url, string data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).PostStringAsync(data, cancellationToken, completionOption);
+ return new FlurlRequest(url).PostStringAsync(data, cancellationToken, completionOption);
}
///
/// Sends an asynchronous POST request.
///
- /// The IFlurlClient instance.
+ /// The IFlurlRequest instance.
/// Contents of the request body.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
- public static Task PostUrlEncodedAsync(this IFlurlClient client, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- var content = new CapturedUrlEncodedContent(client.Settings.UrlEncodedSerializer.Serialize(data));
- return client.SendAsync(HttpMethod.Post, content: content, cancellationToken: cancellationToken, completionOption: completionOption);
+ public static Task PostUrlEncodedAsync(this IFlurlRequest request, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
+ var content = new CapturedUrlEncodedContent(request.Settings.UrlEncodedSerializer.Serialize(data));
+ return request.SendAsync(HttpMethod.Post, content: content, cancellationToken: cancellationToken, completionOption: completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous POST request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous POST request.
///
/// The URL.
/// Contents of the request body.
@@ -523,11 +525,11 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task PostUrlEncodedAsync(this Url url, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).PostUrlEncodedAsync(data, cancellationToken, completionOption);
+ return new FlurlRequest(url).PostUrlEncodedAsync(data, cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous POST request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous POST request.
///
/// The URL.
/// Contents of the request body.
@@ -535,56 +537,56 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task PostUrlEncodedAsync(this string url, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).PostUrlEncodedAsync(data, cancellationToken, completionOption);
+ return new FlurlRequest(url).PostUrlEncodedAsync(data, cancellationToken, completionOption);
}
///
/// Sends an asynchronous HEAD request.
///
- /// The IFlurlClient instance.
+ /// The IFlurlRequest instance.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
- public static Task HeadAsync(this IFlurlClient client, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return client.SendAsync(HttpMethod.Head, cancellationToken: cancellationToken, completionOption: completionOption);
+ public static Task HeadAsync(this IFlurlRequest request, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
+ return request.SendAsync(HttpMethod.Head, cancellationToken: cancellationToken, completionOption: completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous HEAD request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous HEAD request.
///
/// The URL.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task HeadAsync(this Url url, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).HeadAsync(cancellationToken, completionOption);
+ return new FlurlRequest(url).HeadAsync(cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous HEAD request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous HEAD request.
///
/// The URL.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task HeadAsync(this string url, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).HeadAsync(cancellationToken, completionOption);
+ return new FlurlRequest(url).HeadAsync(cancellationToken, completionOption);
}
///
/// Sends an asynchronous PUT request.
///
- /// The IFlurlClient instance.
+ /// The IFlurlRequest instance.
/// Contents of the request body.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
- public static Task PutAsync(this IFlurlClient client, HttpContent content, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return client.SendAsync(HttpMethod.Put, content: content, cancellationToken: cancellationToken, completionOption: completionOption);
+ public static Task PutAsync(this IFlurlRequest request, HttpContent content, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
+ return request.SendAsync(HttpMethod.Put, content: content, cancellationToken: cancellationToken, completionOption: completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous PUT request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous PUT request.
///
/// The URL.
/// Contents of the request body.
@@ -592,11 +594,11 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task PutAsync(this Url url, HttpContent content, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).PutAsync(content, cancellationToken, completionOption);
+ return new FlurlRequest(url).PutAsync(content, cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous PUT request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous PUT request.
///
/// The URL.
/// Contents of the request body.
@@ -604,24 +606,24 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task PutAsync(this string url, HttpContent content, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).PutAsync(content, cancellationToken, completionOption);
+ return new FlurlRequest(url).PutAsync(content, cancellationToken, completionOption);
}
///
/// Sends an asynchronous PUT request.
///
- /// The IFlurlClient instance.
+ /// The IFlurlRequest instance.
/// Contents of the request body.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
- public static Task PutJsonAsync(this IFlurlClient client, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- var content = new CapturedJsonContent(client.Settings.JsonSerializer.Serialize(data));
- return client.SendAsync(HttpMethod.Put, content: content, cancellationToken: cancellationToken, completionOption: completionOption);
+ public static Task PutJsonAsync(this IFlurlRequest request, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
+ var content = new CapturedJsonContent(request.Settings.JsonSerializer.Serialize(data));
+ return request.SendAsync(HttpMethod.Put, content: content, cancellationToken: cancellationToken, completionOption: completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous PUT request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous PUT request.
///
/// The URL.
/// Contents of the request body.
@@ -629,11 +631,11 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task PutJsonAsync(this Url url, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).PutJsonAsync(data, cancellationToken, completionOption);
+ return new FlurlRequest(url).PutJsonAsync(data, cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous PUT request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous PUT request.
///
/// The URL.
/// Contents of the request body.
@@ -641,24 +643,24 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task PutJsonAsync(this string url, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).PutJsonAsync(data, cancellationToken, completionOption);
+ return new FlurlRequest(url).PutJsonAsync(data, cancellationToken, completionOption);
}
///
/// Sends an asynchronous PUT request.
///
- /// The IFlurlClient instance.
+ /// The IFlurlRequest instance.
/// Contents of the request body.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
- public static Task PutStringAsync(this IFlurlClient client, string data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
+ public static Task PutStringAsync(this IFlurlRequest request, string data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
var content = new CapturedStringContent(data);
- return client.SendAsync(HttpMethod.Put, content: content, cancellationToken: cancellationToken, completionOption: completionOption);
+ return request.SendAsync(HttpMethod.Put, content: content, cancellationToken: cancellationToken, completionOption: completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous PUT request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous PUT request.
///
/// The URL.
/// Contents of the request body.
@@ -666,11 +668,11 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task PutStringAsync(this Url url, string data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).PutStringAsync(data, cancellationToken, completionOption);
+ return new FlurlRequest(url).PutStringAsync(data, cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous PUT request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous PUT request.
///
/// The URL.
/// Contents of the request body.
@@ -678,56 +680,56 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task PutStringAsync(this string url, string data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).PutStringAsync(data, cancellationToken, completionOption);
+ return new FlurlRequest(url).PutStringAsync(data, cancellationToken, completionOption);
}
///
/// Sends an asynchronous DELETE request.
///
- /// The IFlurlClient instance.
+ /// The IFlurlRequest instance.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
- public static Task DeleteAsync(this IFlurlClient client, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return client.SendAsync(HttpMethod.Delete, cancellationToken: cancellationToken, completionOption: completionOption);
+ public static Task DeleteAsync(this IFlurlRequest request, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
+ return request.SendAsync(HttpMethod.Delete, cancellationToken: cancellationToken, completionOption: completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous DELETE request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous DELETE request.
///
/// The URL.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task DeleteAsync(this Url url, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).DeleteAsync(cancellationToken, completionOption);
+ return new FlurlRequest(url).DeleteAsync(cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous DELETE request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous DELETE request.
///
/// The URL.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task DeleteAsync(this string url, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).DeleteAsync(cancellationToken, completionOption);
+ return new FlurlRequest(url).DeleteAsync(cancellationToken, completionOption);
}
///
/// Sends an asynchronous PATCH request.
///
- /// The IFlurlClient instance.
+ /// The IFlurlRequest instance.
/// Contents of the request body.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
- public static Task PatchAsync(this IFlurlClient client, HttpContent content, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return client.SendAsync(new HttpMethod("PATCH"), content: content, cancellationToken: cancellationToken, completionOption: completionOption);
+ public static Task PatchAsync(this IFlurlRequest request, HttpContent content, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
+ return request.SendAsync(new HttpMethod("PATCH"), content: content, cancellationToken: cancellationToken, completionOption: completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous PATCH request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous PATCH request.
///
/// The URL.
/// Contents of the request body.
@@ -735,11 +737,11 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task PatchAsync(this Url url, HttpContent content, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).PatchAsync(content, cancellationToken, completionOption);
+ return new FlurlRequest(url).PatchAsync(content, cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous PATCH request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous PATCH request.
///
/// The URL.
/// Contents of the request body.
@@ -747,24 +749,24 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task PatchAsync(this string url, HttpContent content, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).PatchAsync(content, cancellationToken, completionOption);
+ return new FlurlRequest(url).PatchAsync(content, cancellationToken, completionOption);
}
///
/// Sends an asynchronous PATCH request.
///
- /// The IFlurlClient instance.
+ /// The IFlurlRequest instance.
/// Contents of the request body.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
- public static Task PatchJsonAsync(this IFlurlClient client, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- var content = new CapturedJsonContent(client.Settings.JsonSerializer.Serialize(data));
- return client.SendAsync(new HttpMethod("PATCH"), content: content, cancellationToken: cancellationToken, completionOption: completionOption);
+ public static Task PatchJsonAsync(this IFlurlRequest request, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
+ var content = new CapturedJsonContent(request.Settings.JsonSerializer.Serialize(data));
+ return request.SendAsync(new HttpMethod("PATCH"), content: content, cancellationToken: cancellationToken, completionOption: completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous PATCH request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous PATCH request.
///
/// The URL.
/// Contents of the request body.
@@ -772,11 +774,11 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task PatchJsonAsync(this Url url, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).PatchJsonAsync(data, cancellationToken, completionOption);
+ return new FlurlRequest(url).PatchJsonAsync(data, cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous PATCH request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous PATCH request.
///
/// The URL.
/// Contents of the request body.
@@ -784,24 +786,24 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task PatchJsonAsync(this string url, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).PatchJsonAsync(data, cancellationToken, completionOption);
+ return new FlurlRequest(url).PatchJsonAsync(data, cancellationToken, completionOption);
}
///
/// Sends an asynchronous PATCH request.
///
- /// The IFlurlClient instance.
+ /// The IFlurlRequest instance.
/// Contents of the request body.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
- public static Task PatchStringAsync(this IFlurlClient client, string data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
+ public static Task PatchStringAsync(this IFlurlRequest request, string data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
var content = new CapturedStringContent(data);
- return client.SendAsync(new HttpMethod("PATCH"), content: content, cancellationToken: cancellationToken, completionOption: completionOption);
+ return request.SendAsync(new HttpMethod("PATCH"), content: content, cancellationToken: cancellationToken, completionOption: completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous PATCH request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous PATCH request.
///
/// The URL.
/// Contents of the request body.
@@ -809,11 +811,11 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task PatchStringAsync(this Url url, string data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).PatchStringAsync(data, cancellationToken, completionOption);
+ return new FlurlRequest(url).PatchStringAsync(data, cancellationToken, completionOption);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous PATCH request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous PATCH request.
///
/// The URL.
/// Contents of the request body.
@@ -821,8 +823,268 @@ public static class HttpExtensions
/// The HttpCompletionOption used in the request. Optional.
/// A Task whose result is the received HttpResponseMessage.
public static Task PatchStringAsync(this string url, string data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) {
- return new FlurlClient(url, false).PatchStringAsync(data, cancellationToken, completionOption);
+ return new FlurlRequest(url).PatchStringAsync(data, cancellationToken, completionOption);
}
+ ///
+ /// Creates a new FlurlRequest with the URL and sets a request header.
+ ///
+ /// The URL.
+ /// The header name.
+ /// The header value.
+ /// The IFlurlRequest.
+ public static IFlurlRequest WithHeader(this Url url, string name, object value) {
+ return new FlurlRequest(url).WithHeader(name, value);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and sets request headers based on property names/values of the provided object, or keys/values if object is a dictionary, to be sent
+ ///
+ /// The URL.
+ /// Names/values of HTTP headers to set. Typically an anonymous object or IDictionary.
+ /// If true, underscores in property names will be replaced by hyphens. Default is true.
+ /// The IFlurlRequest.
+ public static IFlurlRequest WithHeaders(this Url url, object headers, bool replaceUnderscoreWithHyphen = true) {
+ return new FlurlRequest(url).WithHeaders(headers, replaceUnderscoreWithHyphen);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and sets the Authorization header according to Basic Authentication protocol.
+ ///
+ /// The URL.
+ /// Username of authenticating user.
+ /// Password of authenticating user.
+ /// The IFlurlRequest.
+ public static IFlurlRequest WithBasicAuth(this Url url, string username, string password) {
+ return new FlurlRequest(url).WithBasicAuth(username, password);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and sets the Authorization header with a bearer token according to OAuth 2.0 specification.
+ ///
+ /// The URL.
+ /// The acquired oAuth bearer token.
+ /// The IFlurlRequest.
+ public static IFlurlRequest WithOAuthBearerToken(this Url url, string token) {
+ return new FlurlRequest(url).WithOAuthBearerToken(token);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and allows cookies to be sent and received. Not necessary to call when setting cookies via WithCookie/WithCookies.
+ ///
+ /// The URL.
+ /// The IFlurlRequest.
+ public static IFlurlRequest EnableCookies(this Url url) {
+ return new FlurlRequest(url).EnableCookies();
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and sets an HTTP cookie to be sent
+ ///
+ /// The URL.
+ ///
+ /// The IFlurlRequest.
+ public static IFlurlRequest WithCookie(this Url url, Cookie cookie) {
+ return new FlurlRequest(url).WithCookie(cookie);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and sets an HTTP cookie to be sent.
+ ///
+ /// The URL.
+ /// The cookie name.
+ /// The cookie value.
+ /// The cookie expiration (optional). If excluded, cookie only lives for duration of session.
+ /// The IFlurlRequest.
+ public static IFlurlRequest WithCookie(this Url url, string name, object value, DateTime? expires = null) {
+ return new FlurlRequest(url).WithCookie(name, value, expires);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and sets HTTP cookies to be sent, based on property names / values of the provided object, or keys / values if object is a dictionary.
+ ///
+ /// The URL.
+ /// Names/values of HTTP cookies to set. Typically an anonymous object or IDictionary.
+ /// Expiration for all cookies (optional). If excluded, cookies only live for duration of session.
+ /// The IFlurlRequest.
+ public static IFlurlRequest WithCookies(this Url url, object cookies, DateTime? expires = null) {
+ return new FlurlRequest(url).WithCookies(cookies, expires);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and allows changing its Settings inline.
+ ///
+ /// The URL.
+ /// A delegate defining the Settings changes.
+ /// The IFlurlRequest.
+ public static IFlurlRequest ConfigureRequest(this Url url, Action action) {
+ return new FlurlRequest(url).ConfigureRequest(action);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and sets the request timeout.
+ ///
+ /// The URL.
+ /// Time to wait before the request times out.
+ /// The IFlurlRequest.
+ public static IFlurlRequest WithTimeout(this Url url, TimeSpan timespan) {
+ return new FlurlRequest(url).WithTimeout(timespan);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and sets the request timeout.
+ ///
+ /// The URL.
+ /// Seconds to wait before the request times out.
+ /// The IFlurlRequest.
+ public static IFlurlRequest WithTimeout(this Url url, int seconds) {
+ return new FlurlRequest(url).WithTimeout(seconds);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and adds a pattern representing an HTTP status code or range of codes which (in addition to 2xx) will NOT result in a FlurlHttpException being thrown.
+ ///
+ /// The URL.
+ /// Examples: "3xx", "100,300,600", "100-299,6xx"
+ /// The IFlurlRequest.
+ public static IFlurlRequest AllowHttpStatus(this Url url, string pattern) {
+ return new FlurlRequest(url).AllowHttpStatus(pattern);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and adds an HttpStatusCode which (in addtion to 2xx) will NOT result in a FlurlHttpException being thrown.
+ ///
+ /// The URL.
+ /// The HttpStatusCode(s) to allow.
+ /// The IFlurlRequest.
+ public static IFlurlRequest AllowHttpStatus(this Url url, params HttpStatusCode[] statusCodes) {
+ return new FlurlRequest(url).AllowHttpStatus(statusCodes);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and configures it to allow any returned HTTP status without throwing a FlurlHttpException.
+ ///
+ /// The URL.
+ /// The IFlurlRequest.
+ public static IFlurlRequest AllowAnyHttpStatus(this Url url) {
+ return new FlurlRequest(url).AllowAnyHttpStatus();
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and sets a request header.
+ ///
+ /// The URL.
+ /// The header name.
+ /// The header value.
+ /// The IFlurlRequest.
+ public static IFlurlRequest WithHeader(this string url, string name, object value) {
+ return new FlurlRequest(url).WithHeader(name, value);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and sets request headers based on property names/values of the provided object, or keys/values if object is a dictionary, to be sent
+ ///
+ /// The URL.
+ /// Names/values of HTTP headers to set. Typically an anonymous object or IDictionary.
+ /// If true, underscores in property names will be replaced by hyphens. Default is true.
+ /// The IFlurlRequest.
+ public static IFlurlRequest WithHeaders(this string url, object headers, bool replaceUnderscoreWithHyphen = true) {
+ return new FlurlRequest(url).WithHeaders(headers, replaceUnderscoreWithHyphen);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and sets the Authorization header according to Basic Authentication protocol.
+ ///
+ /// The URL.
+ /// Username of authenticating user.
+ /// Password of authenticating user.
+ /// The IFlurlRequest.
+ public static IFlurlRequest WithBasicAuth(this string url, string username, string password) {
+ return new FlurlRequest(url).WithBasicAuth(username, password);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and sets the Authorization header with a bearer token according to OAuth 2.0 specification.
+ ///
+ /// The URL.
+ /// The acquired oAuth bearer token.
+ /// The IFlurlRequest.
+ public static IFlurlRequest WithOAuthBearerToken(this string url, string token) {
+ return new FlurlRequest(url).WithOAuthBearerToken(token);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and allows cookies to be sent and received. Not necessary to call when setting cookies via WithCookie/WithCookies.
+ ///
+ /// The URL.
+ /// The IFlurlRequest.
+ public static IFlurlRequest EnableCookies(this string url) {
+ return new FlurlRequest(url).EnableCookies();
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and sets an HTTP cookie to be sent
+ ///
+ /// The URL.
+ ///
+ /// The IFlurlRequest.
+ public static IFlurlRequest WithCookie(this string url, Cookie cookie) {
+ return new FlurlRequest(url).WithCookie(cookie);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and sets an HTTP cookie to be sent.
+ ///
+ /// The URL.
+ /// The cookie name.
+ /// The cookie value.
+ /// The cookie expiration (optional). If excluded, cookie only lives for duration of session.
+ /// The IFlurlRequest.
+ public static IFlurlRequest WithCookie(this string url, string name, object value, DateTime? expires = null) {
+ return new FlurlRequest(url).WithCookie(name, value, expires);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and sets HTTP cookies to be sent, based on property names / values of the provided object, or keys / values if object is a dictionary.
+ ///
+ /// The URL.
+ /// Names/values of HTTP cookies to set. Typically an anonymous object or IDictionary.
+ /// Expiration for all cookies (optional). If excluded, cookies only live for duration of session.
+ /// The IFlurlRequest.
+ public static IFlurlRequest WithCookies(this string url, object cookies, DateTime? expires = null) {
+ return new FlurlRequest(url).WithCookies(cookies, expires);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and allows changing its Settings inline.
+ ///
+ /// The URL.
+ /// A delegate defining the Settings changes.
+ /// The IFlurlRequest.
+ public static IFlurlRequest ConfigureRequest(this string url, Action action) {
+ return new FlurlRequest(url).ConfigureRequest(action);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and sets the request timeout.
+ ///
+ /// The URL.
+ /// Time to wait before the request times out.
+ /// The IFlurlRequest.
+ public static IFlurlRequest WithTimeout(this string url, TimeSpan timespan) {
+ return new FlurlRequest(url).WithTimeout(timespan);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and sets the request timeout.
+ ///
+ /// The URL.
+ /// Seconds to wait before the request times out.
+ /// The IFlurlRequest.
+ public static IFlurlRequest WithTimeout(this string url, int seconds) {
+ return new FlurlRequest(url).WithTimeout(seconds);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and adds a pattern representing an HTTP status code or range of codes which (in addition to 2xx) will NOT result in a FlurlHttpException being thrown.
+ ///
+ /// The URL.
+ /// Examples: "3xx", "100,300,600", "100-299,6xx"
+ /// The IFlurlRequest.
+ public static IFlurlRequest AllowHttpStatus(this string url, string pattern) {
+ return new FlurlRequest(url).AllowHttpStatus(pattern);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and adds an HttpStatusCode which (in addtion to 2xx) will NOT result in a FlurlHttpException being thrown.
+ ///
+ /// The URL.
+ /// The HttpStatusCode(s) to allow.
+ /// The IFlurlRequest.
+ public static IFlurlRequest AllowHttpStatus(this string url, params HttpStatusCode[] statusCodes) {
+ return new FlurlRequest(url).AllowHttpStatus(statusCodes);
+ }
+ ///
+ /// Creates a new FlurlRequest with the URL and configures it to allow any returned HTTP status without throwing a FlurlHttpException.
+ ///
+ /// The URL.
+ /// The IFlurlRequest.
+ public static IFlurlRequest AllowAnyHttpStatus(this string url) {
+ return new FlurlRequest(url).AllowAnyHttpStatus();
+ }
}
}
diff --git a/src/Flurl.Http/HeaderExtensions.cs b/src/Flurl.Http/HeaderExtensions.cs
new file mode 100644
index 00000000..0d854d52
--- /dev/null
+++ b/src/Flurl.Http/HeaderExtensions.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Flurl.Util;
+
+namespace Flurl.Http
+{
+ ///
+ /// Fluent extension methods for working with HTTP request headers.
+ ///
+ public static class HeaderExtensions
+ {
+ ///
+ /// Sets an HTTP header to be sent with this IFlurlRequest or all requests made with this IFlurlClient.
+ ///
+ /// The IFlurlClient or IFlurlRequest.
+ /// HTTP header name.
+ /// HTTP header value.
+ /// This IFlurlClient or IFlurlRequest.
+ public static T WithHeader(this T clientOrRequest, string name, object value) where T : IHttpSettingsContainer {
+ clientOrRequest.Headers[name] = value;
+ return clientOrRequest;
+ }
+
+ ///
+ /// Sets HTTP headers based on property names/values of the provided object, or keys/values if object is a dictionary, to be sent with this IFlurlRequest or all requests made with this IFlurlClient.
+ ///
+ /// The IFlurlClient or IFlurlRequest.
+ /// Names/values of HTTP headers to set. Typically an anonymous object or IDictionary.
+ /// If true, underscores in property names will be replaced by hyphens. Default is true.
+ /// This IFlurlClient or IFlurlRequest.
+ public static T WithHeaders(this T clientOrRequest, object headers, bool replaceUnderscoreWithHyphen = true) where T : IHttpSettingsContainer {
+ if (headers == null)
+ return clientOrRequest;
+
+ // underscore replacement only applies when object properties are parsed to kv pairs
+ replaceUnderscoreWithHyphen = replaceUnderscoreWithHyphen && !(headers is string) && !(headers is IEnumerable);
+
+ foreach (var kv in headers.ToKeyValuePairs()) {
+ var key = replaceUnderscoreWithHyphen ? kv.Key.Replace("_", "-") : kv.Key;
+ clientOrRequest.WithHeader(key, kv.Value);
+ }
+
+ return clientOrRequest;
+ }
+
+ ///
+ /// Sets HTTP authorization header according to Basic Authentication protocol to be sent with this IFlurlRequest or all requests made with this IFlurlClient.
+ ///
+ /// The IFlurlClient or IFlurlRequest.
+ /// Username of authenticating user.
+ /// Password of authenticating user.
+ /// This IFlurlClient or IFlurlRequest.
+ public static T WithBasicAuth(this T clientOrRequest, string username, string password) where T : IHttpSettingsContainer {
+ // http://stackoverflow.com/questions/14627399/setting-authorization-header-of-httpclient
+ var encodedCreds = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"));
+ return clientOrRequest.WithHeader("Authorization", $"Basic {encodedCreds}");
+ }
+
+ ///
+ /// Sets HTTP authorization header with acquired bearer token according to OAuth 2.0 specification to be sent with this IFlurlRequest or all requests made with this IFlurlClient.
+ ///
+ /// The IFlurlClient or IFlurlRequest.
+ /// The acquired bearer token to pass.
+ /// This IFlurlClient or IFlurlRequest.
+ public static T WithOAuthBearerToken(this T clientOrRequest, string token) where T : IHttpSettingsContainer {
+ return clientOrRequest.WithHeader("Authorization", $"Bearer {token}");
+ }
+ }
+}
diff --git a/src/Flurl.Http/HttpCall.cs b/src/Flurl.Http/HttpCall.cs
index 39c01418..3176a1f4 100644
--- a/src/Flurl.Http/HttpCall.cs
+++ b/src/Flurl.Http/HttpCall.cs
@@ -1,7 +1,6 @@
using System;
using System.Net;
using System.Net.Http;
-using Flurl.Http.Configuration;
using Flurl.Http.Content;
namespace Flurl.Http
@@ -12,15 +11,11 @@ namespace Flurl.Http
///
public class HttpCall
{
- private readonly Lazy _url;
-
- internal HttpCall(HttpRequestMessage request, FlurlHttpSettings settings) {
+ internal HttpCall(IFlurlRequest flurlRequest, HttpRequestMessage request) {
+ FlurlRequest = flurlRequest;
Request = request;
if (request?.Properties != null)
request.Properties["FlurlHttpCall"] = this;
-
- Settings = settings;
- _url = new Lazy(() => new Url(Request.RequestUri.AbsoluteUri));
}
internal static HttpCall Get(HttpRequestMessage request) {
@@ -31,26 +26,19 @@ internal static HttpCall Get(HttpRequestMessage request) {
}
///
- /// FlurlHttpSettings used for this call.
+ /// The IFlurlRequest associated with this call.
///
- public FlurlHttpSettings Settings { get; }
+ public IFlurlRequest FlurlRequest { get; }
///
- /// HttpRequestMessage associated with this call.
+ /// The HttpRequestMessage associated with this call.
///
public HttpRequestMessage Request { get; }
///
- /// Captured request body. Available only if Request.Content is a Flurl.Http.Content.CapturedStringContent.
+ /// Captured request body. Available ONLY if Request.Content is a Flurl.Http.Content.CapturedStringContent.
///
- public string RequestBody {
- get {
- var csc = Request.Content as CapturedStringContent;
- if (csc == null)
- throw new FlurlHttpException(this, "RequestBody is only available when Request.Content derives from Flurl.Http.Content.CapturedStringContent.", null);
- return csc.Content;
- }
- }
+ public string RequestBody => (Request.Content as CapturedStringContent)?.Content;
///
/// HttpResponseMessage associated with the call if the call completed, otherwise null.
@@ -83,11 +71,6 @@ public string RequestBody {
///
public TimeSpan? Duration => EndedUtc - StartedUtc;
- ///
- /// The URL being called.
- ///
- public Url Url => _url.Value;
-
///
/// True if a response was received, regardless of whether it is an error status.
///
@@ -97,7 +80,7 @@ public string RequestBody {
/// True if a response with a successful HTTP status was received.
///
public bool Succeeded => Completed &&
- (Response.IsSuccessStatusCode || HttpStatusRangeParser.IsMatch(Settings.AllowedHttpStatusRange, Response.StatusCode));
+ (Response.IsSuccessStatusCode || HttpStatusRangeParser.IsMatch(FlurlRequest.Settings.AllowedHttpStatusRange, Response.StatusCode));
///
/// HttpStatusCode of the response if the call completed, otherwise null.
@@ -108,5 +91,13 @@ public string RequestBody {
/// Body of the HTTP response if unsuccessful, otherwise null. (Successful responses are not captured as strings, mainly for performance reasons.)
///
public string ErrorResponseBody { get; set; }
+
+ ///
+ /// Returns the verb and absolute URI associated with this call.
+ ///
+ ///
+ public override string ToString() {
+ return $"{Request.Method:U} {FlurlRequest.Url}";
+ }
}
}
diff --git a/src/Flurl.Http/HttpResponseMessageExtensions.cs b/src/Flurl.Http/HttpResponseMessageExtensions.cs
index a927de77..6c6eb305 100644
--- a/src/Flurl.Http/HttpResponseMessageExtensions.cs
+++ b/src/Flurl.Http/HttpResponseMessageExtensions.cs
@@ -3,7 +3,7 @@
using System.Dynamic;
using System.IO;
using System.Net.Http;
-#if NETSTANDARD
+#if NETSTANDARD1_3
using System.Text;
#endif
using System.Threading.Tasks;
@@ -29,7 +29,7 @@ public static async Task ReceiveJson(this Task respon
var call = HttpCall.Get(resp.RequestMessage);
try {
using (var stream = await resp.Content.ReadAsStreamAsync().ConfigureAwait(false))
- return call.Settings.JsonSerializer.Deserialize(stream);
+ return call.FlurlRequest.Settings.JsonSerializer.Deserialize(stream);
}
catch (Exception ex) {
call.Exception = ex;
@@ -64,7 +64,7 @@ public static async Task> ReceiveJsonList(this TaskA Task whose result is the response body as a string.
/// s = await url.PostAsync(data).ReceiveString()
public static async Task ReceiveString(this Task response) {
-#if NETSTANDARD
+#if NETSTANDARD1_3
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
#endif
var resp = await response.ConfigureAwait(false);
diff --git a/src/Flurl.Http/IHttpSettingsContainer.cs b/src/Flurl.Http/IHttpSettingsContainer.cs
new file mode 100644
index 00000000..7036f191
--- /dev/null
+++ b/src/Flurl.Http/IHttpSettingsContainer.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+using Flurl.Http.Configuration;
+using Flurl.Util;
+
+namespace Flurl.Http
+{
+ ///
+ /// Defines stateful aspects (headers, cookies, etc) common to both IFlurlClient and IFlurlRequest
+ ///
+ public interface IHttpSettingsContainer
+ {
+ ///
+ /// Gets or sets the FlurlHttpSettings object used by this client.
+ ///
+ FlurlHttpSettings Settings { get; set; }
+
+ ///
+ /// Collection of headers sent on all requests using this client.
+ ///
+ IDictionary Headers { get; }
+
+ ///
+ /// Collection of HttpCookies sent and received with all requests using this client.
+ ///
+ IDictionary Cookies { get; }
+ }
+}
diff --git a/src/Flurl.Http/MultipartExtensions.cs b/src/Flurl.Http/MultipartExtensions.cs
index ec05a6d0..20ad91cc 100644
--- a/src/Flurl.Http/MultipartExtensions.cs
+++ b/src/Flurl.Http/MultipartExtensions.cs
@@ -7,7 +7,7 @@
namespace Flurl.Http
{
///
- /// MultipartExtensions
+ /// Fluent extension menthods for sending multipart/form-data requests.
///
public static class MultipartExtensions
{
@@ -15,35 +15,35 @@ public static class MultipartExtensions
/// Sends an asynchronous multipart/form-data POST request.
///
/// A delegate for building the content parts.
- /// The Flurl client.
+ /// The IFlurlRequest.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
/// A Task whose result is the received HttpResponseMessage.
- public static Task PostMultipartAsync(this IFlurlClient client, Action buildContent, CancellationToken cancellationToken = default(CancellationToken)) {
- var cmc = new CapturedMultipartContent(client.Settings);
+ public static Task PostMultipartAsync(this IFlurlRequest request, Action buildContent, CancellationToken cancellationToken = default(CancellationToken)) {
+ var cmc = new CapturedMultipartContent(request.Settings);
buildContent(cmc);
- return client.SendAsync(HttpMethod.Post, cmc, cancellationToken);
+ return request.SendAsync(HttpMethod.Post, cmc, cancellationToken);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous multipart/form-data POST request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous multipart/form-data POST request.
///
/// A delegate for building the content parts.
/// The URL.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
/// A Task whose result is the received HttpResponseMessage.
public static Task PostMultipartAsync(this Url url, Action buildContent, CancellationToken cancellationToken = default(CancellationToken)) {
- return new FlurlClient(url, false).PostMultipartAsync(buildContent, cancellationToken);
+ return new FlurlRequest(url).PostMultipartAsync(buildContent, cancellationToken);
}
///
- /// Creates a FlurlClient from the URL and sends an asynchronous multipart/form-data POST request.
+ /// Creates a FlurlRequest from the URL and sends an asynchronous multipart/form-data POST request.
///
/// A delegate for building the content parts.
/// The URL.
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
/// A Task whose result is the received HttpResponseMessage.
public static Task PostMultipartAsync(this string url, Action buildContent, CancellationToken cancellationToken = default(CancellationToken)) {
- return new FlurlClient(url, false).PostMultipartAsync(buildContent, cancellationToken);
+ return new FlurlRequest(url).PostMultipartAsync(buildContent, cancellationToken);
}
}
}
diff --git a/src/Flurl.Http/NoOpTask.cs b/src/Flurl.Http/NoOpTask.cs
deleted file mode 100644
index 599f72ab..00000000
--- a/src/Flurl.Http/NoOpTask.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System.Threading.Tasks;
-
-namespace Flurl.Http
-{
- internal static class NoOpTask
- {
-#if !PORTABLE
- public static readonly Task Instance = Task.FromResult(0);
-#elif PORTABLE
- public static readonly Task Instance = TaskEx.FromResult(0);
-#endif
- }
-}
\ No newline at end of file
diff --git a/src/Flurl.Http/SettingsExtensions.cs b/src/Flurl.Http/SettingsExtensions.cs
new file mode 100644
index 00000000..7fa4e38d
--- /dev/null
+++ b/src/Flurl.Http/SettingsExtensions.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Linq;
+using System.Net;
+using Flurl.Http.Configuration;
+
+namespace Flurl.Http
+{
+ ///
+ /// Fluent extension methods for tweaking FlurlHttpSettings
+ ///
+ public static class SettingsExtensions
+ {
+ ///
+ /// Change FlurlHttpSettings for this IFlurlClient.
+ ///
+ /// The IFlurlClient.
+ /// Action defining the settings changes.
+ /// The IFlurlClient with the modified Settings
+ public static IFlurlClient Configure(this IFlurlClient client, Action action) {
+ action(client.Settings);
+ return client;
+ }
+
+ ///
+ /// Change FlurlHttpSettings for this IFlurlRequest.
+ ///
+ /// The IFlurlRequest.
+ /// Action defining the settings changes.
+ /// The IFlurlRequest with the modified Settings
+ public static IFlurlRequest ConfigureRequest(this IFlurlRequest request, Action action) {
+ action(request.Settings);
+ return request;
+ }
+
+ ///
+ /// Fluently specify the IFlurlClient to use with this IFlurlRequest.
+ ///
+ /// The IFlurlRequest.
+ /// The IFlurlClient to use when sending the request.
+ /// A new IFlurlRequest to use in calling the Url
+ public static IFlurlRequest WithClient(this IFlurlRequest request, IFlurlClient client) {
+ request.Client = client;
+ return request;
+ }
+
+ ///
+ /// Fluently returns a new IFlurlRequest that can be used to call this Url with the given client.
+ ///
+ ///
+ /// The IFlurlClient to use to call the Url.
+ /// A new IFlurlRequest to use in calling the Url
+ public static IFlurlRequest WithClient(this Url url, IFlurlClient client) {
+ return client.Request(url);
+ }
+
+ ///
+ /// Fluently returns a new IFlurlRequest that can be used to call this Url with the given client.
+ ///
+ ///
+ /// The IFlurlClient to use to call the Url.
+ /// A new IFlurlRequest to use in calling the Url
+ public static IFlurlRequest WithClient(this string url, IFlurlClient client) {
+ return client.Request(url);
+ }
+
+ ///
+ /// Sets the timeout for this IFlurlRequest or all requests made with this IFlurlClient.
+ ///
+ /// The IFlurlClient or IFlurlRequest.
+ /// Time to wait before the request times out.
+ /// This IFlurlClient or IFlurlRequest.
+ public static T WithTimeout(this T obj, TimeSpan timespan) where T : IHttpSettingsContainer {
+ obj.Settings.Timeout = timespan;
+ return obj;
+ }
+
+ ///
+ /// Sets the timeout for this IFlurlRequest or all requests made with this IFlurlClient.
+ ///
+ /// The IFlurlClient or IFlurlRequest.
+ /// Seconds to wait before the request times out.
+ /// This IFlurlClient or IFlurlRequest.
+ public static T WithTimeout(this T obj, int seconds) where T : IHttpSettingsContainer {
+ obj.Settings.Timeout = TimeSpan.FromSeconds(seconds);
+ return obj;
+ }
+
+ ///
+ /// Adds a pattern representing an HTTP status code or range of codes which (in addition to 2xx) will NOT result in a FlurlHttpException being thrown.
+ ///
+ /// The IFlurlClient or IFlurlRequest.
+ /// Examples: "3xx", "100,300,600", "100-299,6xx"
+ /// This IFlurlClient or IFlurlRequest.
+ public static T AllowHttpStatus(this T obj, string pattern) where T : IHttpSettingsContainer {
+ if (!string.IsNullOrWhiteSpace(pattern)) {
+ var current = obj.Settings.AllowedHttpStatusRange;
+ if (string.IsNullOrWhiteSpace(current))
+ obj.Settings.AllowedHttpStatusRange = pattern;
+ else
+ obj.Settings.AllowedHttpStatusRange += "," + pattern;
+ }
+ return obj;
+ }
+
+ ///
+ /// Adds an which (in addition to 2xx) will NOT result in a FlurlHttpException being thrown.
+ ///
+ /// The IFlurlClient or IFlurlRequest.
+ /// Examples: HttpStatusCode.NotFound
+ /// This IFlurlClient or IFlurlRequest.
+ public static T AllowHttpStatus(this T obj, params HttpStatusCode[] statusCodes) where T : IHttpSettingsContainer {
+ var pattern = string.Join(",", statusCodes.Select(c => (int)c));
+ return AllowHttpStatus(obj, pattern);
+ }
+
+ ///
+ /// Prevents a FlurlHttpException from being thrown on any completed response, regardless of the HTTP status code.
+ ///
+ /// This IFlurlClient or IFlurlRequest.
+ public static T AllowAnyHttpStatus(this T obj) where T : IHttpSettingsContainer {
+ obj.Settings.AllowedHttpStatusRange = "*";
+ return obj;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Flurl.Http/Testing/HttpCallAssertion.cs b/src/Flurl.Http/Testing/HttpCallAssertion.cs
index 3ac6567a..46a23b7a 100644
--- a/src/Flurl.Http/Testing/HttpCallAssertion.cs
+++ b/src/Flurl.Http/Testing/HttpCallAssertion.cs
@@ -51,7 +51,7 @@ public HttpCallAssertion WithUrlPattern(string urlPattern) {
return this;
}
_expectedConditions.Add($"URL pattern {urlPattern}");
- return With(c => MatchesPattern(c.Url, urlPattern));
+ return With(c => MatchesPattern(c.FlurlRequest.Url, urlPattern));
}
///
@@ -61,7 +61,7 @@ public HttpCallAssertion WithUrlPattern(string urlPattern) {
///
public HttpCallAssertion WithQueryParam(string name) {
_expectedConditions.Add($"query parameter {name}");
- return With(c => c.Url.QueryParams.Any(q => q.Name == name));
+ return With(c => c.FlurlRequest.Url.QueryParams.Any(q => q.Name == name));
}
///
@@ -71,7 +71,7 @@ public HttpCallAssertion WithQueryParam(string name) {
///
public HttpCallAssertion WithoutQueryParam(string name) {
_expectedConditions.Add($"no query parameter {name}");
- return Without(c => c.Url.QueryParams.Any(q => q.Name == name));
+ return Without(c => c.FlurlRequest.Url.QueryParams.Any(q => q.Name == name));
}
///
@@ -82,7 +82,7 @@ public HttpCallAssertion WithoutQueryParam(string name) {
public HttpCallAssertion WithQueryParams(params string[] names) {
if (!names.Any()) {
_expectedConditions.Add("any query parameters");
- return With(c => c.Url.QueryParams.Any());
+ return With(c => c.FlurlRequest.Url.QueryParams.Any());
}
return names.Select(WithQueryParam).LastOrDefault() ?? this;
}
@@ -95,7 +95,7 @@ public HttpCallAssertion WithQueryParams(params string[] names) {
public HttpCallAssertion WithoutQueryParams(params string[] names) {
if (!names.Any()) {
_expectedConditions.Add("no query parameters");
- return Without(c => c.Url.QueryParams.Any());
+ return Without(c => c.FlurlRequest.Url.QueryParams.Any());
}
return names.Select(WithoutQueryParam).LastOrDefault() ?? this;
}
@@ -113,7 +113,7 @@ public HttpCallAssertion WithQueryParamValue(string name, object value) {
return this;
}
_expectedConditions.Add($"query parameter {name}={value}");
- return With(c => c.Url.QueryParams.Any(qp => QueryParamMatches(qp, name, value)));
+ return With(c => c.FlurlRequest.Url.QueryParams.Any(qp => QueryParamMatches(qp, name, value)));
}
///
@@ -129,7 +129,7 @@ public HttpCallAssertion WithoutQueryParamValue(string name, object value) {
return this;
}
_expectedConditions.Add($"no query parameter {name}={value}");
- return Without(c => c.Url.QueryParams.Any(qp => QueryParamMatches(qp, name, value)));
+ return Without(c => c.FlurlRequest.Url.QueryParams.Any(qp => QueryParamMatches(qp, name, value)));
}
///
@@ -203,6 +203,32 @@ public HttpCallAssertion WithOAuthBearerToken(string token) {
&& c.Request.Headers.Authorization?.Parameter == token);
}
+ ///
+ /// Asserts whther the calls were made containing the given request header.
+ ///
+ /// Expected header name
+ /// Expected header value pattern
+ ///
+ public HttpCallAssertion WithHeader(string name, string valuePattern = "*") {
+ _expectedConditions.Add($"header {name}: {valuePattern}");
+ return With(c =>
+ c.Request.Headers.TryGetValues(name, out var vals) &&
+ vals.Any(v => MatchesPattern(v, valuePattern)));
+ }
+
+ ///
+ /// Asserts whther the calls were made that do not contain the given request header.
+ ///
+ /// Expected header name
+ /// Expected header value pattern
+ ///
+ public HttpCallAssertion WithoutHeader(string name, string valuePattern = "*") {
+ _expectedConditions.Add($"no header {name}: {valuePattern}");
+ return Without(c =>
+ c.Request.Headers.TryGetValues(name, out var vals) &&
+ vals.Any(v => MatchesPattern(v, valuePattern)));
+ }
+
///
/// Asserts whether the Authorization header was set with basic auth.
///
diff --git a/src/Flurl.Http/Testing/HttpTest.cs b/src/Flurl.Http/Testing/HttpTest.cs
index 28ffe8b0..da8adc95 100644
--- a/src/Flurl.Http/Testing/HttpTest.cs
+++ b/src/Flurl.Http/Testing/HttpTest.cs
@@ -3,6 +3,7 @@
using System.Linq;
using System.Net;
using System.Net.Http;
+using Flurl.Http.Configuration;
using Flurl.Http.Content;
using Flurl.Util;
@@ -14,21 +15,55 @@ namespace Flurl.Http.Testing
///
public class HttpTest : IDisposable
{
+ private readonly Lazy _httpClient;
+ private readonly Lazy _httpMessageHandler;
+
///
- /// Gets the current HttpTest from the logical (async) call context
+ /// Initializes a new instance of the class.
///
- public static HttpTest Current => GetCurrentTest();
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// A delegate callback throws an exception.
- public HttpTest() {
+ /// A delegate callback throws an exception.
+ public HttpTest() {
+ Settings = new TestFlurlHttpSettings();
ResponseQueue = new Queue();
CallLog = new List();
+ _httpClient = new Lazy(() => Settings.HttpClientFactory.CreateHttpClient(HttpMessageHandler));
+ _httpMessageHandler = new Lazy(() => Settings.HttpClientFactory.CreateMessageHandler());
SetCurrentTest(this);
}
+ internal HttpClient HttpClient => _httpClient.Value;
+ internal HttpMessageHandler HttpMessageHandler => _httpMessageHandler.Value;
+
+ ///
+ /// Gets or sets the FlurlHttpSettings object used by this test.
+ ///
+ public GlobalFlurlHttpSettings Settings { get; set; }
+
+ ///
+ /// Gets the current HttpTest from the logical (async) call context
+ ///
+ public static HttpTest Current => GetCurrentTest();
+
+ ///
+ /// Queue of HttpResponseMessages to be returned in place of real responses during testing.
+ ///
+ public Queue ResponseQueue { get; set; }
+
+ ///
+ /// List of all (fake) HTTP calls made since this HttpTest was created.
+ ///
+ public List CallLog { get; }
+
+ ///
+ /// Change FlurlHttpSettings for the scope of this HttpTest.
+ ///
+ /// Action defining the settings changes.
+ /// This HttpTest
+ public HttpTest Configure(Action action) {
+ action(Settings);
+ return this;
+ }
+
///
/// Adds an HttpResponseMessage to the response queue.
///
@@ -50,7 +85,7 @@ public HttpTest RespondWith(string body, int status = 200, object headers = null
/// The simulated response cookies (optional).
/// The current HttpTest object (so more responses can be chained).
public HttpTest RespondWithJson(object body, int status = 200, object headers = null, object cookies = null) {
- var content = new CapturedJsonContent(FlurlHttp.GlobalSettings.JsonSerializer.Serialize(body));
+ var content = new CapturedJsonContent(Settings.JsonSerializer.Serialize(body));
return RespondWith(content, status, headers, cookies);
}
@@ -89,11 +124,6 @@ public HttpTest SimulateTimeout() {
return this;
}
- ///
- /// Queue of HttpResponseMessages to be returned in place of real responses during testing.
- ///
- public Queue ResponseQueue { get; set; }
-
internal HttpResponseMessage GetNextResponse() {
return ResponseQueue.Any() ? ResponseQueue.Dequeue() : new HttpResponseMessage {
StatusCode = HttpStatusCode.OK,
@@ -101,11 +131,6 @@ internal HttpResponseMessage GetNextResponse() {
};
}
- ///
- /// List of all (fake) HTTP calls made since this HttpTest was created.
- ///
- public List CallLog { get; private set; }
-
///
/// Asserts whether matching URL was called, throwing HttpCallAssertException if it wasn't.
///
@@ -141,20 +166,19 @@ public void ShouldNotHaveMadeACall() {
///
public void Dispose() {
SetCurrentTest(null);
- FlurlHttp.GlobalSettings.ResetDefaults();
}
-#if PORTABLE
- private static HttpTest _test;
- private static void SetCurrentTest(HttpTest test) => _test = test;
- private static HttpTest GetCurrentTest() => _test;
-#elif NET45
+#if NET45
private static void SetCurrentTest(HttpTest test) => System.Runtime.Remoting.Messaging.CallContext.LogicalSetData("FlurlHttpTest", test);
private static HttpTest GetCurrentTest() => System.Runtime.Remoting.Messaging.CallContext.LogicalGetData("FlurlHttpTest") as HttpTest;
-#else
+#elif NETSTANDARD1_3
private static System.Threading.AsyncLocal _test = new System.Threading.AsyncLocal();
private static void SetCurrentTest(HttpTest test) => _test.Value = test;
private static HttpTest GetCurrentTest() => _test.Value;
+#elif NETSTANDARD1_1
+ private static HttpTest _test;
+ private static void SetCurrentTest(HttpTest test) => _test = test;
+ private static HttpTest GetCurrentTest() => _test;
#endif
}
}
\ No newline at end of file
diff --git a/src/Flurl.Http/Testing/TestFactories.cs b/src/Flurl.Http/Testing/TestFactories.cs
new file mode 100644
index 00000000..98e6b5ac
--- /dev/null
+++ b/src/Flurl.Http/Testing/TestFactories.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Net.Http;
+using Flurl.Http.Configuration;
+
+namespace Flurl.Http.Testing
+{
+ ///
+ /// IHttpClientFactory implementation used to fake and record calls in tests.
+ ///
+ public class TestHttpClientFactory : DefaultHttpClientFactory
+ {
+ ///
+ /// Creates an instance of FakeHttpMessageHander, which prevents actual HTTP calls from being made.
+ ///
+ ///
+ public override HttpMessageHandler CreateMessageHandler() {
+ return new FakeHttpMessageHandler();
+ }
+ }
+
+ ///
+ /// IFlurlClientFactory implementation used to fake and record calls in tests.
+ ///
+ public class TestFlurlClientFactory : FlurlClientFactoryBase
+ {
+ private readonly Lazy _client = new Lazy(() => new FlurlClient());
+
+ ///
+ /// Returns the FlurlClient sigleton used for testing
+ ///
+ /// The URL.
+ /// The FlurlClient instance.
+ public override IFlurlClient Get(Url url) {
+ return _client.Value;
+ }
+
+ ///
+ /// Not used. Singleton FlurlClient used for lifetime of test.
+ ///
+ ///
+ ///
+ protected override string GetCacheKey(Url url) {
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Flurl.Http/Testing/TestHttpClientFactory.cs b/src/Flurl.Http/Testing/TestHttpClientFactory.cs
deleted file mode 100644
index a90b52c8..00000000
--- a/src/Flurl.Http/Testing/TestHttpClientFactory.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using System.Net.Http;
-using Flurl.Http.Configuration;
-
-namespace Flurl.Http.Testing
-{
- ///
- /// Fake http client factory.
- ///
- public class TestHttpClientFactory : DefaultHttpClientFactory
- {
- ///
- /// Creates an instance of FakeHttpMessageHander, which prevents actual HTTP calls from being made.
- ///
- ///
- public override HttpMessageHandler CreateMessageHandler() {
- return new FakeHttpMessageHandler();
- }
- }
-}
\ No newline at end of file
diff --git a/src/Flurl.Http/UrlBuilderExtensions.cs b/src/Flurl.Http/UrlBuilderExtensions.cs
new file mode 100644
index 00000000..64345208
--- /dev/null
+++ b/src/Flurl.Http/UrlBuilderExtensions.cs
@@ -0,0 +1,172 @@
+using System;
+using System.Collections.Generic;
+
+namespace Flurl.Http
+{
+ ///
+ /// URL builder extension methods on FlurlRequest
+ ///
+ public static class UrlBuilderExtensions
+ {
+ ///
+ /// Appends a segment to the URL path, ensuring there is one and only one '/' character as a seperator.
+ ///
+ /// The IFlurlRequest associated with the URL
+ /// The segment to append
+ /// This IFlurlRequest
+ /// is .
+ public static IFlurlRequest AppendPathSegment(this IFlurlRequest request, object segment) {
+ request.Url.AppendPathSegment(segment);
+ return request;
+ }
+
+ ///
+ /// Appends multiple segments to the URL path, ensuring there is one and only one '/' character as a seperator.
+ ///
+ /// The IFlurlRequest associated with the URL
+ /// The segments to append
+ /// This IFlurlRequest
+ public static IFlurlRequest AppendPathSegments(this IFlurlRequest request, params object[] segments) {
+ request.Url.AppendPathSegments(segments);
+ return request;
+ }
+
+ ///
+ /// Appends multiple segments to the URL path, ensuring there is one and only one '/' character as a seperator.
+ ///
+ /// The IFlurlRequest associated with the URL
+ /// The segments to append
+ /// This IFlurlRequest
+ public static IFlurlRequest AppendPathSegments(this IFlurlRequest request, IEnumerable
public static class CommonExtensions
{
- ///
- /// Converts an object's public properties to a collection of string-based key-value pairs. If the object happens
- /// to be an IDictionary, the IDictionary's keys and values converted to strings and returned.
- ///
- /// The object to parse into key-value pairs
- ///
- /// is .
- public static IEnumerable> ToKeyValuePairs(this object obj) {
+ ///
+ /// Converts an object's public properties to a collection of string-based key-value pairs. If the object happens
+ /// to be an IDictionary, the IDictionary's keys and values converted to strings and returned.
+ ///
+ /// The object to parse into key-value pairs
+ ///
+ /// is .
+ public static IEnumerable> ToKeyValuePairs(this object obj) {
if (obj == null)
throw new ArgumentNullException(nameof(obj));
@@ -37,10 +38,11 @@ public static IEnumerable> ToKeyValuePairs(this obj
public static string ToInvariantString(this object obj) {
// inspired by: http://stackoverflow.com/a/19570016/62600
+#if !NETSTANDARD1_0
var c = obj as IConvertible;
if (c != null)
return c.ToString(CultureInfo.InvariantCulture);
-
+#endif
var f = obj as IFormattable;
if (f != null)
return f.ToString(null, CultureInfo.InvariantCulture);
@@ -48,13 +50,13 @@ public static string ToInvariantString(this object obj) {
return obj.ToString();
}
- ///
- /// Splits at the first occurence of the given seperator.
- ///
- /// The string to split.
- /// The separator to split on.
- /// Array of at most 2 strings. (1 if separator is not found.)
- public static string[] SplitOnFirstOccurence(this string s, char separator) {
+ ///
+ /// Splits at the first occurence of the given seperator.
+ ///
+ /// The string to split.
+ /// The separator to split on.
+ /// Array of at most 2 strings. (1 if separator is not found.)
+ public static string[] SplitOnFirstOccurence(this string s, char separator) {
// Needed because full PCL profile doesn't support Split(char[], int) (#119)
if (string.IsNullOrEmpty(s))
return new[] { s };
@@ -71,9 +73,15 @@ private static IEnumerable> StringToKV(string s) {
}
private static IEnumerable> ObjectToKV(object obj) {
+#if NETSTANDARD1_0
+ return from prop in obj.GetType().GetRuntimeProperties()
+ let val = prop.GetValue(obj, null)
+ select new KeyValuePair(prop.Name, val);
+#else
return from prop in obj.GetType().GetProperties()
let val = prop.GetValue(obj, null)
select new KeyValuePair(prop.Name, val);
+#endif
}
private static IEnumerable> CollectionToKV(IEnumerable col) {
@@ -86,8 +94,13 @@ private static IEnumerable> CollectionToKV(IEnumera
object val;
var type = item.GetType();
+#if NETSTANDARD1_0
+ var keyProp = type.GetRuntimeProperty("Key") ?? type.GetRuntimeProperty("key") ?? type.GetRuntimeProperty("Name") ?? type.GetRuntimeProperty("name");
+ var valProp = type.GetRuntimeProperty("Value") ?? type.GetRuntimeProperty("value");
+#else
var keyProp = type.GetProperty("Key") ?? type.GetProperty("key") ?? type.GetProperty("Name") ?? type.GetProperty("name");
var valProp = type.GetProperty("Value") ?? type.GetProperty("value");
+#endif
if (keyProp != null && valProp != null) {
key = keyProp.GetValue(item, null)?.ToInvariantString();
@@ -102,5 +115,13 @@ private static IEnumerable> CollectionToKV(IEnumera
yield return new KeyValuePair(key, val);
}
}
+
+ ///
+ /// Merges the key/value pairs from d2 into d1, without overwriting those already set in d1.
+ ///
+ public static void Merge(this IDictionary d1, IDictionary d2) {
+ foreach (var kv in d2.Where(x => !d1.Keys.Contains(x.Key)))
+ d1.Add(kv);
+ }
}
}
\ No newline at end of file