├── Build
├── test.cmd
├── test.sh
├── build.cmd
├── build.sh
└── Flurl.netstandard.sln
├── src
├── Flurl.Http
│ ├── Testing
│ │ ├── TimeoutResponseMessage.cs
│ │ ├── TestHttpClientFactory.cs
│ │ ├── FakeHttpMessageHandler.cs
│ │ ├── HttpCallAssertException.cs
│ │ └── HttpTest.cs
│ ├── Configuration
│ │ ├── IFlurlClientFactory.cs
│ │ ├── ISerializer.cs
│ │ ├── PerHostFlurlClientFactory.cs
│ │ ├── IHttpClientFactory.cs
│ │ ├── PerBaseUrlFlurlClientFactory.cs
│ │ ├── DefaultHttpClientFactory.cs
│ │ ├── DefaultUrlEncodedSerializer.cs
│ │ ├── NewtonsoftJsonSerializer.cs
│ │ ├── FlurlClientFactoryBase.cs
│ │ └── FlurlHttpSettings.cs
│ ├── Content
│ │ ├── CapturedJsonContent.cs
│ │ ├── CapturedUrlEncodedContent.cs
│ │ ├── CapturedStringContent.cs
│ │ ├── FileContent.cs
│ │ └── CapturedMultipartContent.cs
│ ├── HttpRequestMessageExtensions.cs
│ ├── IHttpSettingsContainer.cs
│ ├── FlurlHttp.cs
│ ├── HttpStatusRangeParser.cs
│ ├── FileUtil.cs
│ ├── MultipartExtensions.cs
│ ├── Flurl.Http.csproj
│ ├── HttpCall.cs
│ ├── CookieExtensions.cs
│ ├── DownloadExtensions.cs
│ ├── HeaderExtensions.cs
│ ├── FlurlHttpException.cs
│ ├── HttpResponseMessageExtensions.cs
│ ├── SettingsExtensions.cs
│ ├── FlurlClient.cs
│ └── UrlBuilderExtensions.cs
├── Flurl.Http.CodeGen
│ ├── Flurl.Http.CodeGen.csproj
│ ├── CodeWriter.cs
│ ├── HttpExtensionMethod.cs
│ ├── UrlExtensionMethod.cs
│ └── Program.cs
└── Flurl
│ ├── NullValueHandling.cs
│ ├── Flurl.csproj
│ ├── QueryParameter.cs
│ ├── QueryParamCollection.cs
│ ├── Util
│ └── CommonExtensions.cs
│ └── StringExtensions.cs
├── .gitignore
├── PackageTesters
├── PackageTester.NET45
│ ├── App.config
│ ├── Program.cs
│ ├── packages.config
│ └── PackageTester.NET45.csproj
├── PackageTester.NET461
│ ├── App.config
│ ├── Program.cs
│ ├── packages.config
│ └── PackageTester.NET461.csproj
├── PackageTester.NETCore
│ ├── Program.cs
│ └── PackageTester.NETCore.csproj
└── PackageTester.Shared
│ ├── PackageTester.Shared.projitems
│ ├── PackageTester.Shared.shproj
│ └── Tester.cs
├── .gitattributes
├── appveyor.yml
├── Test
└── Flurl.Test
│ ├── Http
│ ├── HttpTestFixtureBase.cs
│ ├── DefaultUrlEncodedSerializerTests.cs
│ ├── FlurlHttpExceptionTests.cs
│ ├── HttpStatusRangeParserTests.cs
│ ├── MultipartTests.cs
│ ├── PostTests.cs
│ ├── HttpMethodTests.cs
│ ├── FlurlClientTests.cs
│ ├── GetTests.cs
│ └── SettingsExtensionsTests.cs
│ ├── Flurl.Test.csproj
│ ├── ReflectionHelper.cs
│ └── CommonExtensionsTests.cs
├── NuGet.Config
├── LICENSE
├── README.md
└── Flurl.sln
/Build/test.cmd:
--------------------------------------------------------------------------------
1 | @cd ..\test\Flurl.Test\
2 | @call dotnet test -c Release
3 | @cd ..\..\Build\
--------------------------------------------------------------------------------
/Build/test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 |
4 | SCRIPT_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
5 |
6 | dotnet test -c Release "${SCRIPT_ROOT}/../Test/Flurl.Test/"
--------------------------------------------------------------------------------
/src/Flurl.Http/Testing/TimeoutResponseMessage.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http;
2 |
3 | namespace Flurl.Http.Testing
4 | {
5 | internal class TimeoutResponseMessage : HttpResponseMessage
6 | {
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bin
2 | obj
3 | packages
4 | *.suo
5 | *.nupkg
6 | *.DotSettings.user
7 | *.xproj.user
8 | .vs
9 | *.lock.json
10 | *.log
11 | .vscode
12 | publish
13 | TestResult.xml
14 | *.bak
15 | .idea
16 |
--------------------------------------------------------------------------------
/PackageTesters/PackageTester.NET45/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/Flurl.Http.CodeGen/Flurl.Http.CodeGen.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp2.0
6 |
7 |
8 |
--------------------------------------------------------------------------------
/PackageTesters/PackageTester.NET461/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
--------------------------------------------------------------------------------
/PackageTesters/PackageTester.NETCore/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace PackageTester
4 | {
5 | public class Program
6 | {
7 | public static void Main(string[] args) {
8 | new Tester().DoTestsAsync().Wait();
9 | Console.ReadLine();
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/PackageTesters/PackageTester.NET45/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace PackageTester.NET45
4 | {
5 | public class Program
6 | {
7 | public static void Main(string[] args) {
8 | new Tester().DoTestsAsync().Wait();
9 | Console.ReadLine();
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/PackageTesters/PackageTester.NET461/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace PackageTester.NET461
4 | {
5 | public class Program
6 | {
7 | public static void Main(string[] args) {
8 | new Tester().DoTestsAsync().Wait();
9 | Console.ReadLine();
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/PackageTesters/PackageTester.NET45/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/PackageTesters/PackageTester.NET461/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: 1.0.0-{branch}-{build}
2 |
3 | image: Visual Studio 2017
4 |
5 | init:
6 | - dotnet --version
7 |
8 | pull_requests:
9 | do_not_increment_build_number: true
10 |
11 | build_script:
12 | - cmd: call cmd /C "cd .\build & build.cmd"
13 |
14 | test_script:
15 | - cmd: call cmd /C "cd .\build & test.cmd"
16 |
17 | artifacts:
18 | - path: '**\*.nupkg'
--------------------------------------------------------------------------------
/PackageTesters/PackageTester.NETCore/PackageTester.NETCore.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp2.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Test/Flurl.Test/Http/HttpTestFixtureBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using Flurl.Http.Testing;
5 | using NUnit.Framework;
6 |
7 | namespace Flurl.Test.Http
8 | {
9 | public abstract class HttpTestFixtureBase
10 | {
11 | protected HttpTest HttpTest { get; private set; }
12 |
13 | [SetUp]
14 | public void CreateHttpTest() {
15 | HttpTest = new HttpTest();
16 | }
17 |
18 | [TearDown]
19 | public void DisposeHttpTest() {
20 | HttpTest.Dispose();
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Flurl.Http/Configuration/IFlurlClientFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Flurl.Http.Configuration
4 | {
5 | ///
6 | /// Interface for defining a strategy for creating, caching, and reusing IFlurlClient instances and,
7 | /// by proxy, their underlying HttpClient instances.
8 | ///
9 | public interface IFlurlClientFactory : IDisposable
10 | {
11 | ///
12 | /// Strategy to create a FlurlClient or reuse an exisitng one, based on URL being called.
13 | ///
14 | /// The URL being called.
15 | ///
16 | IFlurlClient Get(Url url);
17 | }
18 | }
--------------------------------------------------------------------------------
/src/Flurl.Http/Testing/TestHttpClientFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Http;
3 | using Flurl.Http.Configuration;
4 |
5 | namespace Flurl.Http.Testing
6 | {
7 | ///
8 | /// IHttpClientFactory implementation used to fake and record calls in tests.
9 | ///
10 | public class TestHttpClientFactory : DefaultHttpClientFactory
11 | {
12 | ///
13 | /// Creates an instance of FakeHttpMessageHander, which prevents actual HTTP calls from being made.
14 | ///
15 | ///
16 | public override HttpMessageHandler CreateMessageHandler() {
17 | return new FakeHttpMessageHandler();
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/src/Flurl/NullValueHandling.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Flurl
6 | {
7 | ///
8 | /// Describes how to handle null values in query parameters.
9 | ///
10 | public enum NullValueHandling
11 | {
12 | ///
13 | /// Set as name without value in query string.
14 | ///
15 | NameOnly,
16 | ///
17 | /// Don't add to query string, remove any existing value.
18 | ///
19 | Remove,
20 | ///
21 | /// Don't add to query string, but leave any existing value unchanged.
22 | ///
23 | Ignore
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Flurl.Http/Content/CapturedJsonContent.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 |
3 | namespace Flurl.Http.Content
4 | {
5 | ///
6 | /// Provides HTTP content based on a serialized JSON object, with the JSON string captured to a property
7 | /// so it can be read without affecting the read-once content stream.
8 | ///
9 | public class CapturedJsonContent : CapturedStringContent
10 | {
11 | ///
12 | /// Initializes a new instance of the class.
13 | ///
14 | /// The json.
15 | public CapturedJsonContent(string json) : base(json, Encoding.UTF8, "application/json") { }
16 | }
17 | }
--------------------------------------------------------------------------------
/PackageTesters/PackageTester.Shared/PackageTester.Shared.projitems:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
5 | true
6 | d4717aa7-5549-4bad-81c5-406844a12990
7 |
8 |
9 | PackageTester.Shared
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Test/Flurl.Test/Http/DefaultUrlEncodedSerializerTests.cs:
--------------------------------------------------------------------------------
1 | using Flurl.Http.Configuration;
2 | using NUnit.Framework;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace Flurl.Test.Http
10 | {
11 | [TestFixture, Parallelizable]
12 | public class DefaultUrlEncodedSerializerTests
13 | {
14 | [Test]
15 | public void can_serialize_object() {
16 | var vals = new {
17 | a = "foo",
18 | b = 333,
19 | c = (string)null, // exlude
20 | d = ""
21 | };
22 |
23 | var serialized = new DefaultUrlEncodedSerializer().Serialize(vals);
24 | Assert.AreEqual("a=foo&b=333&d=", serialized);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Flurl.Http/Configuration/ISerializer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 |
6 | namespace Flurl.Http.Configuration
7 | {
8 | ///
9 | /// Contract for serializing and deserializing objects.
10 | ///
11 | public interface ISerializer
12 | {
13 | ///
14 | /// Serializes an object to a string representation.
15 | ///
16 | string Serialize(object obj);
17 | ///
18 | /// Deserializes an object from a string representation.
19 | ///
20 | T Deserialize(string s);
21 | ///
22 | /// Deserializes an object from a stream representation.
23 | ///
24 | T Deserialize(Stream stream);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Build/build.cmd:
--------------------------------------------------------------------------------
1 | @call dotnet --info
2 |
3 | @call dotnet restore -v m ../
4 |
5 | @if ERRORLEVEL 1 (
6 | echo Error! Restoring dependicies failed.
7 | exit /b 1
8 | ) else (
9 | echo Restoring dependicies was successful.
10 | )
11 |
12 | @set project=..\src\Flurl.Http.CodeGen\Flurl.Http.CodeGen.csproj
13 |
14 | @call dotnet run -c Release -p %project% ..\src\Flurl.Http\GeneratedExtensions.cs
15 | @if ERRORLEVEL 1 (
16 | echo Error! Generation cs file failed.
17 | exit /b 1
18 | )
19 |
20 | @set project=..\src\Flurl\
21 |
22 | @call dotnet build -c Release %project%
23 |
24 | @if ERRORLEVEL 1 (
25 | echo Error! Build Flurl failed.
26 | exit /b 1
27 | )
28 |
29 | @set project=..\src\Flurl.Http\
30 |
31 | @call dotnet build -c Release %project%
32 |
33 | @if ERRORLEVEL 1 (
34 | echo Error! Build Flurl.Http failed.
35 | exit /b 1
36 | )
--------------------------------------------------------------------------------
/src/Flurl.Http/Content/CapturedUrlEncodedContent.cs:
--------------------------------------------------------------------------------
1 | namespace Flurl.Http.Content
2 | {
3 | ///
4 | /// Provides HTTP content based on an object serialized to URL-encoded name-value pairs.
5 | /// Useful in simulating an HTML form POST. Serialized content is captured to Content property
6 | /// so it can be read without affecting the read-once content stream.
7 | ///
8 | public class CapturedUrlEncodedContent : CapturedStringContent
9 | {
10 | ///
11 | /// Initializes a new instance of the class.
12 | ///
13 | /// Content represented as a (typically anonymous) object, which will be parsed into name/value pairs.
14 | public CapturedUrlEncodedContent(string data) : base(data, null, "application/x-www-form-urlencoded") { }
15 | }
16 | }
--------------------------------------------------------------------------------
/Build/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 |
4 | SCRIPT_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
5 |
6 | NETSTANDARD_SLN="${SCRIPT_ROOT}/Flurl.netstandard.sln"
7 |
8 | command -v dotnet >/dev/null 2>&1 || {
9 | echo >&2 "This script requires the dotnet core sdk tooling to be installed"
10 | exit 1
11 | }
12 |
13 | echo "!!WARNING!! This script only builds netstandard and netcoreapp targets"
14 | echo "!!WARNING!! Do not publish nupkgs generated from this script"
15 |
16 | dotnet --info
17 |
18 | dotnet restore -v m "${NETSTANDARD_SLN}"
19 |
20 | dotnet run -c Release -p "${SCRIPT_ROOT}/../src/Flurl.Http.CodeGen/Flurl.Http.CodeGen.csproj" "${SCRIPT_ROOT}/../src/Flurl.Http/GeneratedExtensions.cs"
21 |
22 | dotnet build -c Release "${SCRIPT_ROOT}/../src/Flurl/"
23 | dotnet build -c Release "${SCRIPT_ROOT}/../src/Flurl.Http/"
24 |
--------------------------------------------------------------------------------
/Test/Flurl.Test/Flurl.Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net45;netcoreapp2.0;
5 | netcoreapp2.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/Flurl.Http/HttpRequestMessageExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net.Http;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace Flurl.Http
9 | {
10 | internal static class HttpRequestMessageExtensions
11 | {
12 | ///
13 | /// Associate an HttpCall object with this request
14 | ///
15 | internal static void SetHttpCall(this HttpRequestMessage request, HttpCall call) {
16 | if (request?.Properties != null)
17 | request.Properties["FlurlHttpCall"] = call;
18 | }
19 |
20 | ///
21 | /// Get the HttpCall assocaited with this request, if any.
22 | ///
23 | internal static HttpCall GetHttpCall(this HttpRequestMessage request) {
24 | if (request?.Properties != null && request.Properties.TryGetValue("FlurlHttpCall", out var obj) && obj is HttpCall)
25 | return (HttpCall)obj;
26 | return null;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Flurl.Http/IHttpSettingsContainer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net;
5 | using System.Text;
6 | using Flurl.Http.Configuration;
7 | using Flurl.Util;
8 |
9 | namespace Flurl.Http
10 | {
11 | ///
12 | /// Defines stateful aspects (headers, cookies, etc) common to both IFlurlClient and IFlurlRequest
13 | ///
14 | public interface IHttpSettingsContainer
15 | {
16 | ///
17 | /// Gets or sets the FlurlHttpSettings object used by this client.
18 | ///
19 | FlurlHttpSettings Settings { get; set; }
20 |
21 | ///
22 | /// Collection of headers sent on all requests using this client.
23 | ///
24 | IDictionary Headers { get; }
25 |
26 | ///
27 | /// Collection of HttpCookies sent and received with all requests using this client.
28 | ///
29 | IDictionary Cookies { get; }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Flurl.Http/Configuration/PerHostFlurlClientFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Flurl.Http.Configuration
8 | {
9 | ///
10 | /// An IFlurlClientFactory implementation that caches and reuses the same one instance of
11 | /// FlurlClient per host being called. Maximizes reuse of underlying HttpClient/Handler
12 | /// while allowing things like cookies to be host-specific. This is the default
13 | /// implementation used when calls are made fluently off Urls/strings.
14 | ///
15 | public class PerHostFlurlClientFactory : FlurlClientFactoryBase
16 | {
17 | ///
18 | /// Returns the host part of the URL (i.e. www.api.com) so that all calls to the same
19 | /// host use the same FlurlClient (and HttpClient/HttpMessageHandler) instance.
20 | ///
21 | /// The URL.
22 | /// The cache key
23 | protected override string GetCacheKey(Url url) => new Uri(url).Host;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Flurl.Http/Content/CapturedStringContent.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http;
2 | using System.Text;
3 |
4 | namespace Flurl.Http.Content
5 | {
6 | ///
7 | /// Provides HTTP content based on a string, with the string itself captured to a property
8 | /// so it can be read without affecting the read-once content stream.
9 | ///
10 | public class CapturedStringContent : StringContent
11 | {
12 | ///
13 | /// The content body captured as a string. Can be read multiple times (unlike the content stream).
14 | ///
15 | public string Content { get; }
16 |
17 | ///
18 | /// Initializes a new instance of the class.
19 | ///
20 | /// The content.
21 | /// The encoding.
22 | /// Type of the media.
23 | public CapturedStringContent(string content, Encoding encoding = null, string mediaType = null) :
24 | base(content, encoding, mediaType)
25 | {
26 | Content = content;
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/PackageTesters/PackageTester.Shared/PackageTester.Shared.shproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | d4717aa7-5549-4bad-81c5-406844a12990
5 | 14.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Todd Menier
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/Flurl.Http/Testing/FakeHttpMessageHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Http;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace Flurl.Http.Testing
7 | {
8 | ///
9 | /// An HTTP message handler that prevents actual HTTP calls from being made and instead returns
10 | /// responses from a provided response factory.
11 | ///
12 | public class FakeHttpMessageHandler : HttpMessageHandler
13 | {
14 | ///
15 | /// Sends the request asynchronous.
16 | ///
17 | /// The request.
18 | /// The cancellation token.
19 | protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
20 | HttpTest.Current?.CallLog.Add(request.GetHttpCall());
21 | var tcs = new TaskCompletionSource();
22 | var resp = HttpTest.Current?.GetNextResponse() ?? new HttpResponseMessage();
23 | if (resp is TimeoutResponseMessage)
24 | tcs.SetCanceled();
25 | else
26 | tcs.SetResult(resp);
27 | return tcs.Task;
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/src/Flurl.Http/Configuration/IHttpClientFactory.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http;
2 |
3 | namespace Flurl.Http.Configuration
4 | {
5 | ///
6 | /// Interface defining creation of HttpClient and HttpMessageHandler used in all Flurl HTTP calls.
7 | /// Implementation can be added via FlurlHttp.Configure. However, in order not to lose much of
8 | /// Flurl.Http's functionality, it's almost always best to inherit DefaultHttpClientFactory and
9 | /// extend the base implementations, rather than implementing this interface directly.
10 | ///
11 | public interface IHttpClientFactory
12 | {
13 | ///
14 | /// Defines how HttpClient should be instantiated and configured by default. Do NOT attempt
15 | /// to cache/reuse HttpClient instances here - that should be done at the FlurlClient level
16 | /// via a custom FlurlClientFactory that gets registered globally.
17 | ///
18 | /// The HttpMessageHandler used to construct the HttpClient.
19 | ///
20 | HttpClient CreateHttpClient(HttpMessageHandler handler);
21 |
22 | ///
23 | /// Defines how the
24 | ///
25 | ///
26 | HttpMessageHandler CreateMessageHandler();
27 | }
28 | }
--------------------------------------------------------------------------------
/src/Flurl.Http/Configuration/PerBaseUrlFlurlClientFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Flurl.Http.Configuration
8 | {
9 | ///
10 | /// An IFlurlClientFactory implementation that caches and reuses the same IFlurlClient instance
11 | /// per URL requested, which it assumes is a "base" URL, and sets the IFlurlClient.BaseUrl property
12 | /// to that value. Ideal for use with IoC containers - register as a singleton, inject into a service
13 | /// that wraps some web service, and use to set a private IFlurlClient field in the constructor.
14 | ///
15 | public class PerBaseUrlFlurlClientFactory : FlurlClientFactoryBase
16 | {
17 | ///
18 | /// Returns the entire URL, which is assumed to be some "base" URL for a service.
19 | ///
20 | /// The URL.
21 | /// The cache key
22 | protected override string GetCacheKey(Url url) => url.ToString();
23 |
24 | ///
25 | /// Returns a new new FlurlClient with BaseUrl set to the URL passed.
26 | ///
27 | /// The URL
28 | ///
29 | protected override IFlurlClient Create(Url url) => new FlurlClient(url);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Flurl.Http/Testing/HttpCallAssertException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace Flurl.Http.Testing
6 | {
7 | ///
8 | /// An exception thrown by HttpTest's assertion methods to indicate that the assertion failed.
9 | ///
10 | public class HttpCallAssertException : Exception
11 | {
12 | ///
13 | /// Initializes a new instance of the class.
14 | ///
15 | /// The expected call conditions.
16 | /// The expected number of calls.
17 | /// The actual number calls.
18 | public HttpCallAssertException(IList conditions, int? expectedCalls, int actualCalls) : base(BuildMessage(conditions, expectedCalls, actualCalls)) { }
19 |
20 | private static string BuildMessage(IList conditions, int? expectedCalls, int actualCalls) {
21 | var expected =
22 | (expectedCalls == null) ? "any calls to be made" :
23 | (expectedCalls == 0) ? "no calls to be made" :
24 | (expectedCalls == 1) ? "1 call to be made" :
25 | expectedCalls + " calls to be made";
26 | var actual =
27 | (actualCalls == 0) ? "no matching calls were made" :
28 | (actualCalls == 1) ? "1 matching call was made" :
29 | actualCalls + " matching calls were made";
30 | if (conditions.Any())
31 | expected += " with " + string.Join(" and ", conditions);
32 | else
33 | actual = actual.Replace(" matching", "");
34 | return $"Expected {expected}, but {actual}.";
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/src/Flurl.Http/Configuration/DefaultHttpClientFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Net.Http;
4 |
5 | namespace Flurl.Http.Configuration
6 | {
7 | ///
8 | /// Default implementation of IHttpClientFactory used by FlurlHttp. The created HttpClient includes hooks
9 | /// that enable FlurlHttp's testing features and respect its configuration settings. Therefore, custom factories
10 | /// should inherit from this class, rather than implementing IHttpClientFactory directly.
11 | ///
12 | public class DefaultHttpClientFactory : IHttpClientFactory
13 | {
14 | ///
15 | /// Override in custom factory to customize the creation of HttpClient used in all Flurl HTTP calls.
16 | /// In order not to lose Flurl.Http functionality, it is recommended to call base.CreateClient and
17 | /// customize the result.
18 | ///
19 | public virtual HttpClient CreateHttpClient(HttpMessageHandler handler) {
20 | return new HttpClient(handler) {
21 | // Timeouts handled per request via FlurlHttpSettings.Timeout
22 | Timeout = System.Threading.Timeout.InfiniteTimeSpan
23 | };
24 | }
25 |
26 | ///
27 | /// Override in custom factory to customize the creation of HttpClientHandler used in all Flurl HTTP calls.
28 | /// In order not to lose Flurl.Http functionality, it is recommended to call base.CreateMessageHandler and
29 | /// customize the result.
30 | ///
31 | public virtual HttpMessageHandler CreateMessageHandler() {
32 | return new HttpClientHandler {
33 | // #266
34 | AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
35 | };
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/src/Flurl/Flurl.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net40;netstandard1.0;netstandard1.3;netstandard2.0;
5 | netstandard1.0;netstandard1.3;netstandard2.0;
6 | True
7 | Flurl
8 | 2.7.1
9 | Todd Menier
10 | A fluent, portable URL builder. To make HTTP calls off the fluent chain, check out Flurl.Http.
11 | http://tmenier.github.io/Flurl
12 | https://pbs.twimg.com/profile_images/534024476296376320/IuPGZ_bX_400x400.png
13 | https://raw.githubusercontent.com/tmenier/Flurl/master/LICENSE
14 | https://github.com/tmenier/Flurl.git
15 | git
16 | fluent url uri querystring builder
17 | https://github.com/tmenier/Flurl/releases
18 | false
19 |
20 |
21 |
22 | True
23 |
24 |
25 |
26 | bin\Release\Flurl.xml
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/Flurl.Http/Content/FileContent.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Net;
3 | using System.Net.Http;
4 | using System.Threading.Tasks;
5 |
6 | namespace Flurl.Http.Content
7 | {
8 | ///
9 | /// Represents HTTP content based on a local file. Typically used with PostMultipartAsync for uploading files.
10 | ///
11 | public class FileContent : HttpContent
12 | {
13 | ///
14 | /// The local file path.
15 | ///
16 | public string Path { get; }
17 |
18 | private readonly int _bufferSize;
19 |
20 | ///
21 | /// Initializes a new instance of the class.
22 | ///
23 | /// The local file path.
24 | /// The buffer size of the stream upload in bytes. Defaults to 4096.
25 | public FileContent(string path, int bufferSize = 4096) {
26 | Path = path;
27 | _bufferSize = bufferSize;
28 | }
29 |
30 | ///
31 | /// Serializes to stream asynchronous.
32 | ///
33 | /// The stream.
34 | /// The context.
35 | ///
36 | protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context) {
37 | using (var source = await FileUtil.OpenReadAsync(Path, _bufferSize).ConfigureAwait(false)) {
38 | await source.CopyToAsync(stream, _bufferSize).ConfigureAwait(false);
39 | }
40 | }
41 |
42 | ///
43 | /// Tries the length of the compute.
44 | ///
45 | /// The length.
46 | ///
47 | protected override bool TryComputeLength(out long length) {
48 | length = -1;
49 | return false;
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Flurl
2 |
3 | [](https://ci.appveyor.com/project/kroniak/flurl/branch/master)
4 | [](https://www.nuget.org/packages/Flurl/)
5 | [](https://www.nuget.org/packages/Flurl.Http/)
6 |
7 | Flurl is a modern, fluent, asynchronous, testable, portable, buzzword-laden URL builder and HTTP client library.
8 |
9 | ````c#
10 | var result = await "https://api.mysite.com"
11 | .AppendPathSegment("person")
12 | .SetQueryParams(new { api_key = "xyz" })
13 | .WithOAuthBearerToken("my_oauth_token")
14 | .PostJsonAsync(new { first_name = firstName, last_name = lastName })
15 | .ReceiveJson();
16 |
17 | [Test]
18 | public void Can_Create_Person() {
19 | // fake & record all http calls in the test subject
20 | using (var httpTest = new HttpTest()) {
21 | // arrange
22 | httpTest.RespondWith(200, "OK");
23 |
24 | // act
25 | await sut.CreatePersonAsync("Frank", "Underwood");
26 |
27 | // assert
28 | httpTest.ShouldHaveCalled("http://api.mysite.com/*")
29 | .WithVerb(HttpMethod.Post)
30 | .WithContentType("application/json");
31 | }
32 | }
33 | ````
34 |
35 | Get it on NuGet:
36 |
37 | `PM> Install-Package Flurl.Http`
38 |
39 | Or get just the stand-alone URL builder without the HTTP features:
40 |
41 | `PM> Install-Package Flurl`
42 |
43 | For updates and announcements, [follow @FlurlHttp on Twitter](https://twitter.com/intent/user?screen_name=FlurlHttp).
44 |
45 | For detailed documentation, please visit the [main site](https://flurl.io).
46 |
--------------------------------------------------------------------------------
/src/Flurl.Http/Configuration/DefaultUrlEncodedSerializer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using Flurl.Util;
4 |
5 | namespace Flurl.Http.Configuration
6 | {
7 | ///
8 | /// ISerializer implementation that converts an object representing name/value pairs to a URL-encoded string.
9 | /// Default serializer used in calls to PostUrlEncodedAsync, etc.
10 | ///
11 | public class DefaultUrlEncodedSerializer : ISerializer
12 | {
13 | ///
14 | /// Serializes the specified object.
15 | ///
16 | /// The object.
17 | public string Serialize(object obj) {
18 | if (obj == null)
19 | return null;
20 |
21 | var qp = new QueryParamCollection();
22 | foreach (var kv in obj.ToKeyValuePairs())
23 | qp.Merge(kv.Key, kv.Value, false, NullValueHandling.Ignore);
24 |
25 | return qp.ToString(true);
26 | }
27 |
28 | ///
29 | /// Deserializes the specified s.
30 | ///
31 | ///
32 | /// The s.
33 | /// Deserializing to UrlEncoded not supported.
34 | public T Deserialize(string s) {
35 | throw new NotImplementedException("Deserializing to UrlEncoded is not supported.");
36 | }
37 |
38 | ///
39 | /// Deserializes the specified stream.
40 | ///
41 | ///
42 | /// The stream.
43 | /// Deserializing to UrlEncoded not supported.
44 | public T Deserialize(Stream stream) {
45 | throw new NotImplementedException("Deserializing to UrlEncoded is not supported.");
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/src/Flurl.Http/Configuration/NewtonsoftJsonSerializer.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using Newtonsoft.Json;
3 |
4 | namespace Flurl.Http.Configuration
5 | {
6 | ///
7 | /// ISerializer implementation that uses Newtonsoft Json.NET.
8 | /// Default serializer used in calls to GetJsonAsync, PostJsonAsync, etc.
9 | ///
10 | public class NewtonsoftJsonSerializer : ISerializer
11 | {
12 | private readonly JsonSerializerSettings _settings;
13 |
14 | ///
15 | /// Initializes a new instance of the class.
16 | ///
17 | /// The settings.
18 | public NewtonsoftJsonSerializer(JsonSerializerSettings settings) {
19 | _settings = settings;
20 | }
21 |
22 | ///
23 | /// Serializes the specified object.
24 | ///
25 | /// The object.
26 | ///
27 | public string Serialize(object obj) {
28 | return JsonConvert.SerializeObject(obj, _settings);
29 | }
30 |
31 | ///
32 | /// Deserializes the specified s.
33 | ///
34 | ///
35 | /// The s.
36 | ///
37 | public T Deserialize(string s) {
38 | return JsonConvert.DeserializeObject(s, _settings);
39 | }
40 |
41 | ///
42 | /// Deserializes the specified stream.
43 | ///
44 | ///
45 | /// The stream.
46 | ///
47 | public T Deserialize(Stream stream) {
48 | // http://james.newtonking.com/json/help/index.html?topic=html/Performance.htm
49 | using (var sr = new StreamReader(stream))
50 | using (var jr = new JsonTextReader(sr)) {
51 | return JsonSerializer.CreateDefault(_settings).Deserialize(jr);
52 | }
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/src/Flurl.Http/FlurlHttp.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Flurl.Http.Configuration;
3 |
4 | namespace Flurl.Http
5 | {
6 | ///
7 | /// A static container for global configuration settings affecting Flurl.Http behavior.
8 | ///
9 | public static class FlurlHttp
10 | {
11 | private static readonly object _configLock = new object();
12 |
13 | private static Lazy _settings =
14 | new Lazy(() => new GlobalFlurlHttpSettings());
15 |
16 | ///
17 | /// Globally configured Flurl.Http settings. Should normally be written to by calling FlurlHttp.Configure once application at startup.
18 | ///
19 | public static GlobalFlurlHttpSettings GlobalSettings => _settings.Value;
20 |
21 | ///
22 | /// Provides thread-safe access to Flurl.Http's global configuration settings. Should only be called once at application startup.
23 | ///
24 | /// the action to perform against the GlobalSettings
25 | public static void Configure(Action configAction) {
26 | lock (_configLock) {
27 | configAction(GlobalSettings);
28 | }
29 | }
30 |
31 | ///
32 | /// Provides thread-safe access to a specific IFlurlClient, typically to configure settings and default headers.
33 | /// 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.
34 | ///
35 | /// the URL used to find the IFlurlClient
36 | /// the action to perform against the IFlurlClient
37 | public static void ConfigureClient(string url, Action configAction) {
38 | var client = GlobalSettings.FlurlClientFactory.Get(url);
39 | lock (_configLock) {
40 | configAction(client);
41 | }
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/src/Flurl/QueryParameter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using Flurl.Util;
7 |
8 | namespace Flurl
9 | {
10 | ///
11 | /// Represents an individual name/value pair within a URL query.
12 | ///
13 | public class QueryParameter
14 | {
15 | private object _value;
16 | private string _encodedValue;
17 |
18 | ///
19 | /// Creates a new instance of a query parameter. Allows specifying whether string value provided has
20 | /// already been URL-encoded.
21 | ///
22 | public QueryParameter(string name, object value, bool isEncoded = false) {
23 | Name = name;
24 | if (isEncoded && value != null) {
25 | _encodedValue = value as string;
26 | _value = Url.Decode(_encodedValue, true);
27 | }
28 | else {
29 | Value = value;
30 | }
31 | }
32 |
33 | ///
34 | /// The name (left side) of the query parameter.
35 | ///
36 | public string Name { get; set; }
37 |
38 | ///
39 | /// The value (right side) of the query parameter.
40 | ///
41 | public object Value {
42 | get => _value;
43 | set {
44 | _value = value;
45 | _encodedValue = null;
46 | }
47 | }
48 |
49 | ///
50 | /// Returns the string ("name=value") representation of the query parameter.
51 | ///
52 | /// Indicates whether to encode space characters with "+" instead of "%20".
53 | ///
54 | public string ToString(bool encodeSpaceAsPlus) {
55 | var name = Url.EncodeIllegalCharacters(Name, encodeSpaceAsPlus);
56 | var value =
57 | (_encodedValue != null) ? _encodedValue :
58 | (Value != null) ? Url.Encode(Value.ToInvariantString(), encodeSpaceAsPlus) :
59 | null;
60 |
61 | return (value == null) ? name : $"{name}={value}";
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Flurl.Http/HttpStatusRangeParser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Net;
4 | using System.Text.RegularExpressions;
5 |
6 | namespace Flurl.Http
7 | {
8 | ///
9 | /// The status range parser class.
10 | ///
11 | public static class HttpStatusRangeParser
12 | {
13 | ///
14 | /// Determines whether the specified pattern is match.
15 | ///
16 | /// The pattern.
17 | /// The value.
18 | /// pattern is invalid.
19 | public static bool IsMatch(string pattern, HttpStatusCode value) {
20 | return IsMatch(pattern, (int)value);
21 | }
22 |
23 | ///
24 | /// Determines whether the specified pattern is match.
25 | ///
26 | /// The pattern.
27 | /// The value.
28 | /// is invalid.
29 | public static bool IsMatch(string pattern, int value) {
30 | if (pattern == null)
31 | return false;
32 |
33 | foreach (var range in pattern.Split(',').Select(p => p.Trim())) {
34 | if (range == "")
35 | continue;
36 |
37 | if (range == "*")
38 | return true; // special case - allow everything
39 |
40 | var bounds = range.Split('-');
41 | int lower = 0, upper = 0;
42 |
43 | var valid =
44 | bounds.Length <= 2 &&
45 | int.TryParse(Regex.Replace(bounds.First().Trim(), "[*xX]", "0"), out lower) &&
46 | int.TryParse(Regex.Replace(bounds.Last().Trim(), "[*xX]", "9"), out upper);
47 |
48 | if (!valid) {
49 | throw new ArgumentException(
50 | $"Invalid range pattern: \"{pattern}\". Examples of allowed patterns: \"400\", \"4xx\", \"300,400-403\", \"*\".");
51 | }
52 |
53 | if (value >= lower && value <= upper)
54 | return true;
55 | }
56 | return false;
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/Test/Flurl.Test/Http/FlurlHttpExceptionTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Flurl.Http;
7 | using NUnit.Framework;
8 |
9 | namespace Flurl.Test.Http
10 | {
11 | [TestFixture, Parallelizable]
12 | class FlurlHttpExceptionTests : HttpTestFixtureBase
13 | {
14 | [Test]
15 | public async Task exception_message_is_nice() {
16 | HttpTest.RespondWithJson(new { message = "bad data!" }, 400);
17 |
18 | try {
19 | await "http://myapi.com".PostJsonAsync(new { data = "bad" });
20 | Assert.Fail("should have thrown 400.");
21 | }
22 | catch (FlurlHttpException ex) {
23 | Assert.AreEqual("Call failed with status code 400 (Bad Request): POST http://myapi.com", ex.Message);
24 | }
25 | }
26 |
27 | [Test]
28 | public async Task exception_message_excludes_request_response_labels_when_body_empty() {
29 | HttpTest.RespondWith("", 400);
30 |
31 | try {
32 | await "http://myapi.com".GetAsync();
33 | Assert.Fail("should have thrown 400.");
34 | }
35 | catch (FlurlHttpException ex) {
36 | // no "Request body:", "Response body:", or line breaks
37 | Assert.AreEqual("Call failed with status code 400 (Bad Request): GET http://myapi.com", ex.Message);
38 | }
39 | }
40 |
41 | [Test]
42 | public async Task can_catch_parsing_error() {
43 | HttpTest.RespondWith("I'm not JSON!");
44 |
45 | try {
46 | await "http://myapi.com".GetJsonAsync();
47 | Assert.Fail("should have failed to parse response.");
48 | }
49 | catch (FlurlParsingException ex) {
50 | Assert.AreEqual("Response could not be deserialized to JSON: GET http://myapi.com", ex.Message);
51 | Assert.AreEqual("I'm not JSON!", await ex.GetResponseStringAsync());
52 | // will differ if you're using a different serializer (which you probably aren't):
53 | Assert.IsInstanceOf(ex.InnerException);
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Flurl.Http/FileUtil.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | #if NETSTANDARD1_1
3 | using System.Linq;
4 | #endif
5 | using System.Threading.Tasks;
6 |
7 | namespace Flurl.Http
8 | {
9 | internal static class FileUtil
10 | {
11 | #if NETSTANDARD1_1
12 | internal static string GetFileName(string path) {
13 | return path?.Split(PCLStorage.PortablePath.DirectorySeparatorChar).Last();
14 | }
15 |
16 | internal static string CombinePath(params string[] paths) {
17 | return PCLStorage.PortablePath.Combine(paths);
18 | }
19 |
20 | internal static async Task OpenReadAsync(string path, int bufferSize) {
21 | var file = await PCLStorage.FileSystem.Current.GetFileFromPathAsync(path).ConfigureAwait(false);
22 | return await file.OpenAsync(PCLStorage.FileAccess.Read).ConfigureAwait(false);
23 | }
24 |
25 | internal static async Task OpenWriteAsync(string folderPath, string fileName, int bufferSize) {
26 | var folder = await PCLStorage.FileSystem.Current.LocalStorage.CreateFolderAsync(folderPath, PCLStorage.CreationCollisionOption.OpenIfExists).ConfigureAwait(false);
27 | var file = await folder.CreateFileAsync(fileName, PCLStorage.CreationCollisionOption.ReplaceExisting).ConfigureAwait(false);
28 | return await file.OpenAsync(PCLStorage.FileAccess.ReadAndWrite).ConfigureAwait(false);
29 | }
30 | #else
31 | internal static string GetFileName(string path) {
32 | return Path.GetFileName(path);
33 | }
34 |
35 | internal static string CombinePath(params string[] paths) {
36 | return Path.Combine(paths);
37 | }
38 |
39 | internal static Task OpenReadAsync(string path, int bufferSize) {
40 | return Task.FromResult(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, useAsync: true));
41 | }
42 |
43 | internal static Task OpenWriteAsync(string folderPath, string fileName, int bufferSize) {
44 | Directory.CreateDirectory(folderPath); // checks existence
45 | var filePath = Path.Combine(folderPath, fileName);
46 | return Task.FromResult(new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize, useAsync: true));
47 | }
48 | #endif
49 | }
50 | }
--------------------------------------------------------------------------------
/src/Flurl.Http/Configuration/FlurlClientFactoryBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 |
4 | namespace Flurl.Http.Configuration
5 | {
6 | ///
7 | /// Encapsulates a creation/caching strategy for IFlurlClient instances. Custom factories looking to extend
8 | /// Flurl's behavior should inherit from this class, rather than implementing IFlurlClientFactory directly.
9 | ///
10 | public abstract class FlurlClientFactoryBase : IFlurlClientFactory
11 | {
12 | private readonly ConcurrentDictionary _clients = new ConcurrentDictionary();
13 |
14 | ///
15 | /// By defaykt, uses a caching strategy of one FlurlClient per host. This maximizes reuse of
16 | /// underlying HttpClient/Handler while allowing things like cookies to be host-specific.
17 | ///
18 | /// The URL.
19 | /// The FlurlClient instance.
20 | public virtual IFlurlClient Get(Url url) {
21 | if (url == null)
22 | throw new ArgumentNullException(nameof(url));
23 |
24 | return _clients.AddOrUpdate(
25 | GetCacheKey(url),
26 | u => Create(u),
27 | (u, client) => client.IsDisposed ? Create(u) : client);
28 | }
29 |
30 | ///
31 | /// Defines a strategy for getting a cache key based on a Url. Default implementation
32 | /// returns the host part (i.e www.api.com) so that all calls to the same host use the
33 | /// same FlurlClient (and HttpClient/HttpMessageHandler) instance.
34 | ///
35 | /// The URL.
36 | /// The cache key
37 | protected abstract string GetCacheKey(Url url);
38 |
39 | ///
40 | /// Creates a new FlurlClient
41 | ///
42 | /// The URL (not used)
43 | ///
44 | protected virtual IFlurlClient Create(Url url) => new FlurlClient();
45 |
46 | ///
47 | /// Disposes all cached IFlurlClient instances and clears the cache.
48 | ///
49 | public void Dispose() {
50 | foreach (var kv in _clients) {
51 | if (!kv.Value.IsDisposed)
52 | kv.Value.Dispose();
53 | }
54 | _clients.Clear();
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Flurl.Http.CodeGen/CodeWriter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace Flurl.Http.CodeGen
5 | {
6 | ///
7 | /// Wraps a StreamWriter. Mainly just keeps track of indentation.
8 | ///
9 | public class CodeWriter : IDisposable
10 | {
11 | private readonly StreamWriter _sw;
12 | private int _indent;
13 | private bool _wrapping;
14 |
15 | public CodeWriter(string filePath)
16 | {
17 | _sw = new StreamWriter(File.OpenWrite(filePath));
18 | }
19 |
20 | ///
21 | /// use @0, @1, @2, etc for tokens. ({0} would be a pain because you'd alway need to escape "{" and "}")
22 | ///
23 | public CodeWriter WriteLine(string line, params object[] args)
24 | {
25 | line = line.Trim();
26 |
27 | for (int i = 0; i < args.Length; i++)
28 | {
29 | var val = (args[i] == null) ? "" : args[i].ToString();
30 | line = line.Replace("@" + i, val);
31 | }
32 |
33 | if (line == "}" || line == "{")
34 | {
35 | _indent--;
36 | }
37 |
38 | _sw.Write(new String('\t', _indent));
39 | _sw.WriteLine(line);
40 |
41 | if (line == "" || line.StartsWith("//") || line.EndsWith("]"))
42 | {
43 | _wrapping = false;
44 | }
45 | else if (line.EndsWith(";") || line.EndsWith("}"))
46 | {
47 | if (_wrapping)
48 | _indent--;
49 | _wrapping = false;
50 | }
51 | else if (line.EndsWith("{"))
52 | {
53 | _indent++;
54 | _wrapping = false;
55 | }
56 | else
57 | {
58 | if (!_wrapping)
59 | _indent++;
60 | _wrapping = true;
61 | }
62 |
63 | return this; // fluent!
64 | }
65 |
66 | public CodeWriter WriteLine()
67 | {
68 | _sw.WriteLine();
69 | return this;
70 | }
71 |
72 | public void Dispose()
73 | {
74 | _sw.Dispose();
75 | }
76 | }
77 | }
--------------------------------------------------------------------------------
/Test/Flurl.Test/Http/HttpStatusRangeParserTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Flurl.Http;
3 | using NUnit.Framework;
4 |
5 | namespace Flurl.Test.Http
6 | {
7 | [TestFixture, Parallelizable]
8 | public class HttpStatusRangeParserTests
9 | {
10 | [TestCase("4**", 399, ExpectedResult = false)]
11 | [TestCase("4**", 400, ExpectedResult = true)]
12 | [TestCase("4**", 499, ExpectedResult = true)]
13 | [TestCase("4**", 500, ExpectedResult = false)]
14 |
15 | [TestCase("4xx", 399, ExpectedResult = false)]
16 | [TestCase("4xx", 400, ExpectedResult = true)]
17 | [TestCase("4xx", 499, ExpectedResult = true)]
18 | [TestCase("4xx", 500, ExpectedResult = false)]
19 |
20 | [TestCase("4XX", 399, ExpectedResult = false)]
21 | [TestCase("4XX", 400, ExpectedResult = true)]
22 | [TestCase("4XX", 499, ExpectedResult = true)]
23 | [TestCase("4XX", 500, ExpectedResult = false)]
24 |
25 | [TestCase("400-499", 399, ExpectedResult = false)]
26 | [TestCase("400-499", 400, ExpectedResult = true)]
27 | [TestCase("400-499", 499, ExpectedResult = true)]
28 | [TestCase("400-499", 500, ExpectedResult = false)]
29 |
30 | [TestCase("100,3xx,600", 100, ExpectedResult = true)]
31 | [TestCase("100,3xx,600", 101, ExpectedResult = false)]
32 | [TestCase("100,3xx,600", 300, ExpectedResult = true)]
33 | [TestCase("100,3xx,600", 399, ExpectedResult = true)]
34 | [TestCase("100,3xx,600", 400, ExpectedResult = false)]
35 | [TestCase("100,3xx,600", 600, ExpectedResult = true)]
36 |
37 | [TestCase("400-409,490-499", 399, ExpectedResult = false)]
38 | [TestCase("400-409,490-499", 405, ExpectedResult = true)]
39 | [TestCase("400-409,490-499", 450, ExpectedResult = false)]
40 | [TestCase("400-409,490-499", 495, ExpectedResult = true)]
41 | [TestCase("400-409,490-499", 500, ExpectedResult = false)]
42 |
43 | [TestCase("*", 0, ExpectedResult = true)]
44 | [TestCase(",,,*", 9999, ExpectedResult = true)]
45 |
46 | [TestCase("", 0, ExpectedResult = false)]
47 | [TestCase(",,,", 9999, ExpectedResult = false)]
48 | public bool parser_works(string pattern, int value) {
49 | return HttpStatusRangeParser.IsMatch(pattern, value);
50 | }
51 |
52 | [TestCase("-100")]
53 | [TestCase("100-")]
54 | [TestCase("1yy")]
55 | public void parser_throws_on_invalid_pattern(string pattern) {
56 | Assert.Throws(() => HttpStatusRangeParser.IsMatch(pattern, 100));
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/PackageTesters/PackageTester.Shared/Tester.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using System.IO;
5 | using Flurl;
6 | using Flurl.Http;
7 | using Flurl.Http.Testing;
8 |
9 | namespace PackageTester
10 | {
11 | public class Tester
12 | {
13 | private int _pass;
14 | private int _fail;
15 |
16 | public async Task DoTestsAsync() {
17 | _pass = 0;
18 | _fail = 0;
19 |
20 | await Test("Testing real request to google.com...", async () => {
21 | var real = await "http://www.google.com".GetStringAsync();
22 | Assert(real.Trim().StartsWith("<"), $"Response from google.com doesn't look right: {real}");
23 | });
24 |
25 | await Test("Testing fake request with HttpTest...", async () => {
26 | using (var test = new HttpTest()) {
27 | test.RespondWith("fake response");
28 | var fake = await "http://www.google.com".GetStringAsync();
29 | Assert(fake == "fake response", $"Fake response doesn't look right: {fake}");
30 | }
31 | });
32 |
33 | await Test("Testing file download...", async () => {
34 | var path = "c:\\google.txt";
35 | if (File.Exists(path)) File.Delete(path);
36 | var result = await "http://www.google.com".DownloadFileAsync("c:\\", "google.txt");
37 | Assert(result == path, $"Download result {result} doesn't match {path}");
38 | Assert(File.Exists(path), $"File didn't appear to download to {path}");
39 | if (File.Exists(path)) File.Delete(path);
40 | });
41 |
42 | if (_fail > 0) {
43 | Console.ForegroundColor = ConsoleColor.Red;
44 | Console.WriteLine($"{_pass} passed, {_fail} failed");
45 | }
46 | else {
47 | Console.ForegroundColor = ConsoleColor.Green;
48 | Console.WriteLine("Everything looks good");
49 | }
50 | Console.ResetColor();
51 | }
52 |
53 | private async Task Test(string msg, Func act) {
54 | Console.WriteLine(msg);
55 | try {
56 | await act();
57 | Console.WriteLine("pass.");
58 | _pass++;
59 | }
60 | catch (Exception ex) {
61 | Console.ForegroundColor = ConsoleColor.Red;
62 | Console.WriteLine($"Fail! {ex.Message}");
63 | Console.WriteLine(ex.StackTrace);
64 | _fail++;
65 | }
66 | finally {
67 | Console.ResetColor();
68 | }
69 | }
70 |
71 | private void Assert(bool check, string msg) {
72 | if (!check) throw new Exception(msg);
73 | }
74 | }
75 |
76 | internal class TestResponse
77 | {
78 | public string TestString { get; set; }
79 | }
80 | }
--------------------------------------------------------------------------------
/Test/Flurl.Test/Http/MultipartTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Net.Http;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using Flurl.Http;
9 | using Flurl.Http.Content;
10 | using NUnit.Framework;
11 |
12 | namespace Flurl.Test.Http
13 | {
14 | [TestFixture, Parallelizable]
15 | public class MultipartTests
16 | {
17 | [Test]
18 | public void can_build_multipart_content() {
19 | var content = new CapturedMultipartContent()
20 | .AddString("string", "foo")
21 | .AddStringParts(new { part1 = 1, part2 = 2, part3 = (string)null }) // part3 should be excluded
22 | .AddFile("file", Path.Combine("path", "to", "image.jpg"), "image/jpeg")
23 | .AddJson("json", new { foo = "bar" })
24 | .AddUrlEncoded("urlEnc", new { fizz = "buzz" });
25 |
26 | Assert.AreEqual(6, content.Parts.Length);
27 |
28 | Assert.AreEqual("string", content.Parts[0].Headers.ContentDisposition.Name);
29 | Assert.IsInstanceOf(content.Parts[0]);
30 | Assert.AreEqual("foo", (content.Parts[0] as CapturedStringContent).Content);
31 |
32 | Assert.AreEqual("part1", content.Parts[1].Headers.ContentDisposition.Name);
33 | Assert.IsInstanceOf(content.Parts[1]);
34 | Assert.AreEqual("1", (content.Parts[1] as CapturedStringContent).Content);
35 |
36 | Assert.AreEqual("part2", content.Parts[2].Headers.ContentDisposition.Name);
37 | Assert.IsInstanceOf(content.Parts[2]);
38 | Assert.AreEqual("2", (content.Parts[2] as CapturedStringContent).Content);
39 |
40 | Assert.AreEqual("file", content.Parts[3].Headers.ContentDisposition.Name);
41 | Assert.AreEqual("image.jpg", content.Parts[3].Headers.ContentDisposition.FileName);
42 | Assert.IsInstanceOf(content.Parts[3]);
43 |
44 | Assert.AreEqual("json", content.Parts[4].Headers.ContentDisposition.Name);
45 | Assert.IsInstanceOf(content.Parts[4]);
46 | Assert.AreEqual("{\"foo\":\"bar\"}", (content.Parts[4] as CapturedJsonContent).Content);
47 |
48 | Assert.AreEqual("urlEnc", content.Parts[5].Headers.ContentDisposition.Name);
49 | Assert.IsInstanceOf(content.Parts[5]);
50 | Assert.AreEqual("fizz=buzz", (content.Parts[5] as CapturedUrlEncodedContent).Content);
51 | }
52 |
53 | [Test]
54 | public void must_provide_required_args_to_builder() {
55 | var content = new CapturedMultipartContent();
56 | Assert.Throws(() => content.AddStringParts(null));
57 | Assert.Throws(() => content.AddString("other", null));
58 | Assert.Throws(() => content.AddString(null, "hello!"));
59 | Assert.Throws(() => content.AddFile(" ", "path"));
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Flurl.Http/MultipartExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Http;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Flurl.Http.Content;
6 |
7 | namespace Flurl.Http
8 | {
9 | ///
10 | /// Fluent extension menthods for sending multipart/form-data requests.
11 | ///
12 | public static class MultipartExtensions
13 | {
14 | ///
15 | /// Sends an asynchronous multipart/form-data POST request.
16 | ///
17 | /// A delegate for building the content parts.
18 | /// The IFlurlRequest.
19 | /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
20 | /// A Task whose result is the received HttpResponseMessage.
21 | public static Task PostMultipartAsync(this IFlurlRequest request, Action buildContent, CancellationToken cancellationToken = default(CancellationToken)) {
22 | var cmc = new CapturedMultipartContent(request.Settings);
23 | buildContent(cmc);
24 | return request.SendAsync(HttpMethod.Post, cmc, cancellationToken);
25 | }
26 |
27 | ///
28 | /// Creates a FlurlRequest from the URL and sends an asynchronous multipart/form-data POST request.
29 | ///
30 | /// A delegate for building the content parts.
31 | /// The URL.
32 | /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
33 | /// A Task whose result is the received HttpResponseMessage.
34 | public static Task PostMultipartAsync(this Url url, Action buildContent, CancellationToken cancellationToken = default(CancellationToken)) {
35 | return new FlurlRequest(url).PostMultipartAsync(buildContent, cancellationToken);
36 | }
37 |
38 | ///
39 | /// Creates a FlurlRequest from the URL and sends an asynchronous multipart/form-data POST request.
40 | ///
41 | /// A delegate for building the content parts.
42 | /// The URL.
43 | /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
44 | /// A Task whose result is the received HttpResponseMessage.
45 | public static Task PostMultipartAsync(this string url, Action buildContent, CancellationToken cancellationToken = default(CancellationToken)) {
46 | return new FlurlRequest(url).PostMultipartAsync(buildContent, cancellationToken);
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Flurl.Http/Flurl.Http.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net45;netstandard1.1;netstandard1.3;netstandard2.0;
5 | netstandard1.1;netstandard1.3;netstandard2.0;
6 | True
7 | Flurl.Http
8 | 2.3.1
9 | Todd Menier
10 | A fluent, portable, testable HTTP client library.
11 | http://tmenier.github.io/Flurl
12 | https://pbs.twimg.com/profile_images/534024476296376320/IuPGZ_bX_400x400.png
13 | https://raw.githubusercontent.com/tmenier/Flurl/master/LICENSE
14 | https://github.com/tmenier/Flurl.git
15 | git
16 | httpclient rest json http fluent url uri tdd assert async
17 | https://github.com/tmenier/Flurl/releases
18 | false
19 |
20 |
21 |
22 | True
23 |
24 |
25 |
26 | bin\Release\Flurl.Http.xml
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | portable-net45+win8+wp8
49 |
50 |
51 |
52 | bin\Debug\net45\Flurl.Http.xml
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/src/Flurl.Http/HttpCall.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Net.Http;
4 | using Flurl.Http.Content;
5 |
6 | namespace Flurl.Http
7 | {
8 | ///
9 | /// Encapsulates request, response, and other details associated with an HTTP call. Useful for diagnostics and available in
10 | /// global event handlers and FlurlHttpException.Call.
11 | ///
12 | public class HttpCall
13 | {
14 | ///
15 | /// The IFlurlRequest associated with this call.
16 | ///
17 | public IFlurlRequest FlurlRequest { get; set; }
18 |
19 | ///
20 | /// The HttpRequestMessage associated with this call.
21 | ///
22 | public HttpRequestMessage Request { get; set; }
23 |
24 | ///
25 | /// Captured request body. Available ONLY if Request.Content is a Flurl.Http.Content.CapturedStringContent.
26 | ///
27 | public string RequestBody => (Request.Content as CapturedStringContent)?.Content;
28 |
29 | ///
30 | /// HttpResponseMessage associated with the call if the call completed, otherwise null.
31 | ///
32 | public HttpResponseMessage Response { get; set; }
33 |
34 | ///
35 | /// Exception that occurred while sending the HttpRequestMessage.
36 | ///
37 | public Exception Exception { get; set; }
38 |
39 | ///
40 | /// User code should set this to true inside global event handlers (OnError, etc) to indicate
41 | /// that the exception was handled and should not be propagated further.
42 | ///
43 | public bool ExceptionHandled { get; set; }
44 |
45 | ///
46 | /// DateTime the moment the request was sent.
47 | ///
48 | public DateTime StartedUtc { get; set; }
49 |
50 | ///
51 | /// DateTime the moment a response was received.
52 | ///
53 | public DateTime? EndedUtc { get; set; }
54 |
55 | ///
56 | /// Total duration of the call if it completed, otherwise null.
57 | ///
58 | public TimeSpan? Duration => EndedUtc - StartedUtc;
59 |
60 | ///
61 | /// True if a response was received, regardless of whether it is an error status.
62 | ///
63 | public bool Completed => Response != null;
64 |
65 | ///
66 | /// True if a response with a successful HTTP status was received.
67 | ///
68 | public bool Succeeded => Completed &&
69 | (Response.IsSuccessStatusCode || HttpStatusRangeParser.IsMatch(FlurlRequest.Settings.AllowedHttpStatusRange, Response.StatusCode));
70 |
71 | ///
72 | /// HttpStatusCode of the response if the call completed, otherwise null.
73 | ///
74 | public HttpStatusCode? HttpStatus => Completed ? (HttpStatusCode?)Response.StatusCode : null;
75 |
76 | ///
77 | /// Returns the verb and absolute URI associated with this call.
78 | ///
79 | ///
80 | public override string ToString() {
81 | return $"{Request.Method:U} {FlurlRequest.Url}";
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/Flurl.Http.CodeGen/HttpExtensionMethod.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace Flurl.Http.CodeGen
5 | {
6 | public class HttpExtensionMethod
7 | {
8 | public static IEnumerable GetAll() {
9 | return
10 | from httpVerb in new[] { null, "Get", "Post", "Head", "Put", "Delete", "Patch", "Options" }
11 | from bodyType in new[] { null, "Json", /*"Xml",*/ "String", "UrlEncoded" }
12 | from extensionType in new[] { "IFlurlRequest", "Url", "string" }
13 | where SupportedCombo(httpVerb, bodyType, extensionType)
14 | from deserializeType in new[] { null, "Json", "JsonList", /*"Xml",*/ "String", "Stream", "Bytes" }
15 | where httpVerb == "Get" || deserializeType == null
16 | from isGeneric in new[] { true, false }
17 | where AllowDeserializeToGeneric(deserializeType) || !isGeneric
18 | select new HttpExtensionMethod {
19 | HttpVerb = httpVerb,
20 | BodyType = bodyType,
21 | ExtentionOfType = extensionType,
22 | DeserializeToType = deserializeType,
23 | IsGeneric = isGeneric
24 | };
25 | }
26 |
27 | private static bool SupportedCombo(string verb, string bodyType, string extensionType) {
28 | switch (verb) {
29 | case null: // Send
30 | return bodyType != null || extensionType != "IFlurlRequest";
31 | case "Post":
32 | return true;
33 | case "Put":
34 | case "Patch":
35 | return bodyType != "UrlEncoded";
36 | default: // Get, Head, Delete, Options
37 | return bodyType == null;
38 | }
39 | }
40 |
41 | private static bool AllowDeserializeToGeneric(string deserializeType) {
42 | switch (deserializeType) {
43 | case "Json":
44 | return true;
45 | default:
46 | return false;
47 | }
48 | }
49 |
50 | public string HttpVerb { get; set; }
51 | public string BodyType { get; set; }
52 | public string ExtentionOfType { get; set; }
53 | public string DeserializeToType { get; set; }
54 | public bool IsGeneric { get; set; }
55 |
56 | public string Name => $"{HttpVerb ?? "Send"}{BodyType ?? DeserializeToType}Async";
57 |
58 | public string TaskArg {
59 | get {
60 | switch (DeserializeToType) {
61 | case "Json": return IsGeneric ? "T" : "dynamic";
62 | case "JsonList": return "IList";
63 | //case "Xml": return ?;
64 | case "String": return "string";
65 | case "Stream": return "Stream";
66 | case "Bytes": return "byte[]";
67 | default: return "HttpResponseMessage";
68 | }
69 | }
70 | }
71 |
72 | public string ReturnTypeDescription {
73 | get {
74 | //var response = (xm.DeserializeToType == null) ? "" : "" + xm.TaskArg;
75 | switch (DeserializeToType) {
76 | case "Json": return "the JSON response body deserialized to " + (IsGeneric ? "an object of type T" : "a dynamic");
77 | case "JsonList": return "the JSON response body deserialized to a list of dynamics";
78 | //case "Xml": return ?;
79 | case "String": return "the response body as a string";
80 | case "Stream": return "the response body as a Stream";
81 | case "Bytes": return "the response body as a byte array";
82 | default: return "the received HttpResponseMessage";
83 | }
84 | }
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/PackageTesters/PackageTester.NET45/PackageTester.NET45.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {AA8792B6-E0FA-46BA-BA03-C7971745F577}
8 | Exe
9 | PackageTester.NET45
10 | PackageTester.NET45
11 | v4.5
12 | 512
13 |
14 |
15 | AnyCPU
16 | true
17 | full
18 | false
19 | bin\Debug\
20 | DEBUG;TRACE
21 | prompt
22 | 4
23 |
24 |
25 | AnyCPU
26 | pdbonly
27 | true
28 | bin\Release\
29 | TRACE
30 | prompt
31 | 4
32 |
33 |
34 |
35 | ..\..\packages\Flurl.2.7.0\lib\net40\Flurl.dll
36 |
37 |
38 | ..\..\packages\Flurl.Http.2.2.1\lib\net45\Flurl.Http.dll
39 |
40 |
41 | ..\..\packages\Newtonsoft.Json.11.0.1\lib\net45\Newtonsoft.Json.dll
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/Flurl.Http/CookieExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using Flurl.Util;
8 |
9 | namespace Flurl.Http
10 | {
11 | ///
12 | /// Fluent extension methods for working with HTTP cookies.
13 | ///
14 | public static class CookieExtensions
15 | {
16 | ///
17 | /// Allows cookies to be sent and received. Not necessary to call when setting cookies via WithCookie/WithCookies.
18 | ///
19 | /// The IFlurlClient or IFlurlRequest.
20 | /// This IFlurlClient.
21 | public static T EnableCookies(this T clientOrRequest) where T : IHttpSettingsContainer {
22 | clientOrRequest.Settings.CookiesEnabled = true;
23 | return clientOrRequest;
24 | }
25 |
26 | ///
27 | /// Sets an HTTP cookie to be sent with this IFlurlRequest or all requests made with this IFlurlClient.
28 | ///
29 | /// The IFlurlClient or IFlurlRequest.
30 | /// The cookie to set.
31 | /// This IFlurlClient.
32 | public static T WithCookie(this T clientOrRequest, Cookie cookie) where T : IHttpSettingsContainer {
33 | clientOrRequest.Settings.CookiesEnabled = true;
34 | clientOrRequest.Cookies[cookie.Name] = cookie;
35 | return clientOrRequest;
36 | }
37 |
38 | ///
39 | /// Sets an HTTP cookie to be sent with this IFlurlRequest or all requests made with this IFlurlClient.
40 | ///
41 | /// The IFlurlClient or IFlurlRequest.
42 | /// The cookie name.
43 | /// The cookie value.
44 | /// The cookie expiration (optional). If excluded, cookie only lives for duration of session.
45 | /// This IFlurlClient.
46 | public static T WithCookie(this T clientOrRequest, string name, object value, DateTime? expires = null) where T : IHttpSettingsContainer {
47 | var cookie = new Cookie(name, value?.ToInvariantString()) { Expires = expires ?? DateTime.MinValue };
48 | return clientOrRequest.WithCookie(cookie);
49 | }
50 |
51 | ///
52 | /// 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.
53 | ///
54 | /// The IFlurlClient or IFlurlRequest.
55 | /// Names/values of HTTP cookies to set. Typically an anonymous object or IDictionary.
56 | /// Expiration for all cookies (optional). If excluded, cookies only live for duration of session.
57 | /// This IFlurlClient.
58 | public static T WithCookies(this T clientOrRequest, object cookies, DateTime? expires = null) where T : IHttpSettingsContainer {
59 | if (cookies == null)
60 | return clientOrRequest;
61 |
62 | foreach (var kv in cookies.ToKeyValuePairs())
63 | clientOrRequest.WithCookie(kv.Key, kv.Value, expires);
64 |
65 | return clientOrRequest;
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/PackageTesters/PackageTester.NET461/PackageTester.NET461.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {84FB572A-8B77-4B09-B825-2A240BCE1B7A}
8 | Exe
9 | PackageTester.NET461
10 | PackageTester.NET461
11 | v4.6.1
12 | 512
13 | true
14 |
15 |
16 |
17 | AnyCPU
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | AnyCPU
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 |
35 |
36 |
37 | ..\..\packages\Flurl.2.7.0\lib\net40\Flurl.dll
38 |
39 |
40 | ..\..\packages\Flurl.Http.2.2.1\lib\net45\Flurl.Http.dll
41 |
42 |
43 | ..\..\packages\Newtonsoft.Json.11.0.1\lib\net45\Newtonsoft.Json.dll
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | Designer
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/Flurl.Http/DownloadExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Net.Http;
3 | using System.Threading.Tasks;
4 | using Flurl.Util;
5 |
6 | namespace Flurl.Http
7 | {
8 | ///
9 | /// Fluent extension methods for downloading a file.
10 | ///
11 | public static class DownloadExtensions
12 | {
13 | ///
14 | /// Asynchronously downloads a file at the specified URL.
15 | ///
16 | /// The flurl request.
17 | /// Path of local folder where file is to be downloaded.
18 | /// Name of local file. If not specified, the source filename (from Content-Dispostion header, or last segment of the URL) is used.
19 | /// Buffer size in bytes. Default is 4096.
20 | /// A Task whose result is the local path of the downloaded file.
21 | public static async Task DownloadFileAsync(this IFlurlRequest request, string localFolderPath, string localFileName = null, int bufferSize = 4096) {
22 | var response = await request.SendAsync(HttpMethod.Get, completionOption: HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
23 |
24 | localFileName =
25 | localFileName ??
26 | response.Content?.Headers.ContentDisposition?.FileName?.StripQuotes() ??
27 | request.Url.Path.Split('/').Last();
28 |
29 | // http://codereview.stackexchange.com/a/18679
30 | using (var httpStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
31 | using (var filestream = await FileUtil.OpenWriteAsync(localFolderPath, localFileName, bufferSize).ConfigureAwait(false)) {
32 | await httpStream.CopyToAsync(filestream, bufferSize).ConfigureAwait(false);
33 | }
34 |
35 | return FileUtil.CombinePath(localFolderPath, localFileName);
36 | }
37 |
38 | ///
39 | /// Asynchronously downloads a file at the specified URL.
40 | ///
41 | /// The Url.
42 | /// Path of local folder where file is to be downloaded.
43 | /// Name of local file. If not specified, the source filename (last segment of the URL) is used.
44 | /// Buffer size in bytes. Default is 4096.
45 | /// A Task whose result is the local path of the downloaded file.
46 | public static Task DownloadFileAsync(this string url, string localFolderPath, string localFileName = null, int bufferSize = 4096) {
47 | return new FlurlRequest(url).DownloadFileAsync(localFolderPath, localFileName, bufferSize);
48 | }
49 |
50 | ///
51 | /// Asynchronously downloads a file at the specified URL.
52 | ///
53 | /// The Url.
54 | /// Path of local folder where file is to be downloaded.
55 | /// Name of local file. If not specified, the source filename (last segment of the URL) is used.
56 | /// Buffer size in bytes. Default is 4096.
57 | /// A Task whose result is the local path of the downloaded file.
58 | public static Task DownloadFileAsync(this Url url, string localFolderPath, string localFileName = null, int bufferSize = 4096) {
59 | return new FlurlRequest(url).DownloadFileAsync(localFolderPath, localFileName, bufferSize);
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/Test/Flurl.Test/Http/PostTests.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http;
2 | using System.Threading.Tasks;
3 | using Flurl.Http;
4 | using NUnit.Framework;
5 |
6 | namespace Flurl.Test.Http
7 | {
8 | [TestFixture, Parallelizable]
9 | public class PostTests : HttpMethodTests
10 | {
11 | public PostTests() : base(HttpMethod.Post) { }
12 |
13 | protected override Task CallOnString(string url) => url.PostAsync(null);
14 | protected override Task CallOnUrl(Url url) => url.PostAsync(null);
15 | protected override Task CallOnFlurlRequest(IFlurlRequest req) => req.PostAsync(null);
16 |
17 | [Test]
18 | public async Task can_post_string() {
19 | var expectedEndpoint = "http://some-api.com";
20 | var expectedBody = "abc123";
21 | await expectedEndpoint.PostStringAsync(expectedBody);
22 | HttpTest.ShouldHaveCalled(expectedEndpoint)
23 | .WithVerb(HttpMethod.Post)
24 | .WithRequestBody(expectedBody)
25 | .Times(1);
26 | }
27 |
28 | [Test]
29 | public async Task can_post_object_as_json() {
30 | var expectedEndpoint = "http://some-api.com";
31 | var expectedBody = new {a = 1, b = 2};
32 | await expectedEndpoint.PostJsonAsync(expectedBody);
33 | HttpTest.ShouldHaveCalled(expectedEndpoint)
34 | .WithVerb(HttpMethod.Post)
35 | .WithContentType("application/json")
36 | .WithRequestJson(expectedBody)
37 | .Times(1);
38 | }
39 |
40 | [Test]
41 | public async Task can_post_url_encoded() {
42 | await "http://some-api.com".PostUrlEncodedAsync(new { a = 1, b = 2, c = "hi there", d = new[] { 1, 2, 3 } });
43 | HttpTest.ShouldHaveCalled("http://some-api.com")
44 | .WithVerb(HttpMethod.Post)
45 | .WithContentType("application/x-www-form-urlencoded")
46 | .WithRequestBody("a=1&b=2&c=hi+there&d=1&d=2&d=3")
47 | .Times(1);
48 | }
49 |
50 | [Test]
51 | public async Task can_receive_json() {
52 | HttpTest.RespondWithJson(new TestData { id = 1, name = "Frank" });
53 |
54 | var data = await "http://some-api.com".PostJsonAsync(new { a = 1, b = 2 }).ReceiveJson();
55 |
56 | Assert.AreEqual(1, data.id);
57 | Assert.AreEqual("Frank", data.name);
58 | }
59 |
60 | [Test]
61 | public async Task can_receive_json_dynamic() {
62 | HttpTest.RespondWithJson(new { id = 1, name = "Frank" });
63 |
64 | var data = await "http://some-api.com".PostJsonAsync(new { a = 1, b = 2 }).ReceiveJson();
65 |
66 | Assert.AreEqual(1, data.id);
67 | Assert.AreEqual("Frank", data.name);
68 | }
69 |
70 | [Test]
71 | public async Task can_receive_json_dynamic_list() {
72 | HttpTest.RespondWithJson(new[] {
73 | new { id = 1, name = "Frank" },
74 | new { id = 2, name = "Claire" }
75 | });
76 |
77 | var data = await "http://some-api.com".PostJsonAsync(new { a = 1, b = 2 }).ReceiveJsonList();
78 |
79 | Assert.AreEqual(1, data[0].id);
80 | Assert.AreEqual("Frank", data[0].name);
81 | Assert.AreEqual(2, data[1].id);
82 | Assert.AreEqual("Claire", data[1].name);
83 | }
84 |
85 | [Test]
86 | public async Task can_receive_string() {
87 | HttpTest.RespondWith("good job");
88 |
89 | var data = await "http://some-api.com".PostJsonAsync(new { a = 1, b = 2 }).ReceiveString();
90 |
91 | Assert.AreEqual("good job", data);
92 | }
93 |
94 | private class TestData
95 | {
96 | public int id { get; set; }
97 | public string name { get; set; }
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/Test/Flurl.Test/Http/HttpMethodTests.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Net.Http;
3 | using System.Threading.Tasks;
4 | using Flurl.Http;
5 | using NUnit.Framework;
6 |
7 | namespace Flurl.Test.Http
8 | {
9 | ///
10 | /// Each HTTP method with first-class support in Flurl (via PostAsync, GetAsync, etc.) should
11 | /// have a test fixture that inherits from this base class.
12 | ///
13 | public abstract class HttpMethodTests : HttpTestFixtureBase
14 | {
15 | private readonly HttpMethod _verb;
16 |
17 | protected HttpMethodTests(HttpMethod verb) {
18 | _verb = verb;
19 | }
20 |
21 | protected abstract Task CallOnString(string url);
22 | protected abstract Task CallOnUrl(Url url);
23 | protected abstract Task CallOnFlurlRequest(IFlurlRequest req);
24 |
25 | [Test]
26 | public async Task can_call_on_FlurlClient() {
27 | await CallOnFlurlRequest(new FlurlRequest("http://www.api.com"));
28 | HttpTest.ShouldHaveCalled("http://www.api.com").WithVerb(_verb).Times(1);
29 | }
30 |
31 | [Test]
32 | public async Task can_call_on_string() {
33 | await CallOnString("http://www.api.com");
34 | HttpTest.ShouldHaveCalled("http://www.api.com").WithVerb(_verb).Times(1);
35 | }
36 |
37 | [Test]
38 | public async Task can_call_on_url() {
39 | await CallOnUrl(new Url("http://www.api.com"));
40 | HttpTest.ShouldHaveCalled("http://www.api.com").WithVerb(_verb).Times(1);
41 | }
42 | }
43 |
44 | [TestFixture, Parallelizable]
45 | public class PutTests : HttpMethodTests
46 | {
47 | public PutTests() : base(HttpMethod.Put) { }
48 | protected override Task CallOnString(string url) => url.PutAsync(null);
49 | protected override Task CallOnUrl(Url url) => url.PutAsync(null);
50 | protected override Task CallOnFlurlRequest(IFlurlRequest req) => req.PutAsync(null);
51 | }
52 |
53 | [TestFixture, Parallelizable]
54 | public class PatchTests : HttpMethodTests
55 | {
56 | public PatchTests() : base(new HttpMethod("PATCH")) { }
57 | protected override Task CallOnString(string url) => url.PatchAsync(null);
58 | protected override Task CallOnUrl(Url url) => url.PatchAsync(null);
59 | protected override Task CallOnFlurlRequest(IFlurlRequest req) => req.PatchAsync(null);
60 | }
61 |
62 | [TestFixture, Parallelizable]
63 | public class DeleteTests : HttpMethodTests
64 | {
65 | public DeleteTests() : base(HttpMethod.Delete) { }
66 | protected override Task CallOnString(string url) => url.DeleteAsync();
67 | protected override Task CallOnUrl(Url url) => url.DeleteAsync();
68 | protected override Task CallOnFlurlRequest(IFlurlRequest req) => req.DeleteAsync();
69 | }
70 |
71 | [TestFixture, Parallelizable]
72 | public class HeadTests : HttpMethodTests
73 | {
74 | public HeadTests() : base(HttpMethod.Head) { }
75 | protected override Task CallOnString(string url) => url.HeadAsync();
76 | protected override Task CallOnUrl(Url url) => url.HeadAsync();
77 | protected override Task CallOnFlurlRequest(IFlurlRequest req) => req.HeadAsync();
78 | }
79 |
80 | [TestFixture, Parallelizable]
81 | public class OptionsTests : HttpMethodTests
82 | {
83 | public OptionsTests() : base(HttpMethod.Options) { }
84 | protected override Task CallOnString(string url) => url.OptionsAsync();
85 | protected override Task CallOnUrl(Url url) => url.OptionsAsync();
86 | protected override Task CallOnFlurlRequest(IFlurlRequest req) => req.OptionsAsync();
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/Test/Flurl.Test/ReflectionHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Reflection;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace Flurl.Test
7 | {
8 | public static class ReflectionHelper
9 | {
10 | public static MethodInfo[] GetAllExtensionMethods(Assembly asm) {
11 | // http://stackoverflow.com/a/299526/62600
12 | return (
13 | from type in asm.GetTypes()
14 | where type.GetTypeInfo().IsSealed && !type.GetTypeInfo().IsGenericType && !type.GetTypeInfo().IsNested
15 | from method in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
16 | where method.IsDefined(typeof(ExtensionAttribute), false)
17 | where method.GetParameters()[0].ParameterType == typeof(T)
18 | select method).ToArray();
19 | }
20 |
21 | public static bool AreSameMethodSignatures(MethodInfo method1, MethodInfo method2) {
22 | if (method1.Name != method2.Name)
23 | return false;
24 |
25 | if (!AreSameType(method1.ReturnType, method2.ReturnType))
26 | return false;
27 |
28 | var genArgs1 = method1.GetGenericArguments();
29 | var genArgs2 = method2.GetGenericArguments();
30 |
31 | if (genArgs1.Length != genArgs2.Length)
32 | return false;
33 |
34 | for (int i = 0; i < genArgs1.Length; i++) {
35 | if (!AreSameType(genArgs1[i], genArgs2[i]))
36 | return false;
37 | }
38 |
39 | var args1 = method1.GetParameters().Skip(IsExtensionMethod(method1) ? 1 : 0).ToArray();
40 | var args2 = method2.GetParameters().Skip(IsExtensionMethod(method2) ? 1 : 0).ToArray();
41 |
42 | if (args1.Length != args2.Length)
43 | return false;
44 |
45 | for (int i = 0; i < args1.Length; i++) {
46 | if (args1[i].Name != args2[i].Name) return false;
47 | if (!AreSameType(args1[i].ParameterType, args2[i].ParameterType)) return false;
48 | if (args1[i].IsOptional != args2[i].IsOptional) return false;
49 | if (!AreSameValue(args1[i].DefaultValue, args2[i].DefaultValue)) return false;
50 | if (args1[i].IsIn != args2[i].IsIn) return false;
51 | }
52 | return true;
53 | }
54 |
55 | public static bool IsExtensionMethod(MethodInfo method) {
56 | var type = method.DeclaringType;
57 | return
58 | type.GetTypeInfo().IsSealed &&
59 | !type.GetTypeInfo().IsGenericType &&
60 | !type.GetTypeInfo().IsNested &&
61 | method.IsStatic &&
62 | method.IsDefined(typeof(ExtensionAttribute), false);
63 | }
64 |
65 | public static bool AreSameValue(object a, object b) {
66 | if (a == null && b == null)
67 | return true;
68 | if (a == null ^ b == null)
69 | return false;
70 | // ok, neither is null
71 | return a.Equals(b);
72 | }
73 |
74 | public static bool AreSameType(Type a, Type b) {
75 | if (a.IsGenericParameter && b.IsGenericParameter) {
76 |
77 | var constraintsA = a.GetTypeInfo().GetGenericParameterConstraints();
78 | var constraintsB = b.GetTypeInfo().GetGenericParameterConstraints();
79 |
80 | if (constraintsA.Length != constraintsB.Length)
81 | return false;
82 |
83 | for (int i = 0; i < constraintsA.Length; i++) {
84 | if (!AreSameType(constraintsA[i], constraintsB[i]))
85 | return false;
86 | }
87 | return true;
88 | }
89 |
90 | if (a.GetTypeInfo().IsGenericType && b.GetTypeInfo().IsGenericType) {
91 |
92 | if (a.GetGenericTypeDefinition() != b.GetGenericTypeDefinition())
93 | return false;
94 |
95 | var genArgsA = a.GetGenericArguments();
96 | var genArgsB = b.GetGenericArguments();
97 |
98 | if (genArgsA.Length != genArgsB.Length)
99 | return false;
100 |
101 | for (int i = 0; i < genArgsA.Length; i++) {
102 | if (!AreSameType(genArgsA[i], genArgsB[i]))
103 | return false;
104 | }
105 |
106 | return true;
107 | }
108 |
109 | return a == b;
110 | }
111 | }
112 | }
--------------------------------------------------------------------------------
/Test/Flurl.Test/CommonExtensionsTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using NUnit.Framework;
5 | using Flurl.Util;
6 |
7 | namespace Flurl.Test
8 | {
9 | [TestFixture, Parallelizable]
10 | public class CommonExtensionsTests
11 | {
12 | [Test]
13 | public void can_parse_object_to_kv()
14 | {
15 | var kv = new {
16 | one = 1,
17 | two = "foo",
18 | three = (string)null
19 | }.ToKeyValuePairs();
20 |
21 | CollectionAssert.AreEquivalent(new Dictionary {
22 | { "one", 1 },
23 | { "two", "foo" },
24 | { "three", null }
25 | }, kv);
26 | }
27 |
28 | [Test]
29 | public void can_parse_dictionary_to_kv()
30 | {
31 | var kv = new Dictionary {
32 | { "one", 1 },
33 | { "two", "foo" },
34 | { "three", null }
35 | }.ToKeyValuePairs();
36 |
37 | CollectionAssert.AreEquivalent(new Dictionary {
38 | { "one", 1 },
39 | { "two", "foo" },
40 | { "three", null }
41 | }, kv);
42 | }
43 |
44 | [Test]
45 | public void can_parse_collection_of_kvp_to_kv()
46 | {
47 | var kv = new[] {
48 | new KeyValuePair