├── .appveyor.yml
├── .gitattributes
├── .gitignore
├── .travis.yml
├── .vsts-pipelines
└── builds
│ ├── ci-internal.yml
│ └── ci-public.yml
├── CONTRIBUTING.md
├── Directory.Build.props
├── Directory.Build.targets
├── HttpClientFactory.sln
├── LICENSE.txt
├── NuGet.config
├── NuGetPackageVerifier.json
├── README.md
├── benchmarks
└── Microsoft.Extensions.Http.Performance
│ ├── Configs
│ └── CoreConfig.cs
│ ├── CreationOverheadBenchmark.cs
│ ├── FakeClientHandler.cs
│ ├── FakeLoggerProvider.cs
│ ├── LoggingOverheadBenchmark.cs
│ └── Microsoft.Extensions.Http.Performance.csproj
├── build.cmd
├── build.sh
├── build
├── Key.snk
├── dependencies.props
├── repo.props
└── sources.props
├── korebuild-lock.txt
├── korebuild.json
├── run.cmd
├── run.ps1
├── run.sh
├── samples
└── HttpClientFactorySample
│ ├── HttpClientFactorySample.csproj
│ └── Program.cs
├── src
├── Directory.Build.props
├── Microsoft.Extensions.Http.Polly
│ ├── DependencyInjection
│ │ ├── PollyHttpClientBuilderExtensions.cs
│ │ └── PollyServiceCollectionExtensions.cs
│ ├── HttpRequestMessageExtensions.cs
│ ├── Microsoft.Extensions.Http.Polly.csproj
│ ├── PolicyHttpMessageHandler.cs
│ ├── Properties
│ │ ├── AssemblyInfo.cs
│ │ └── Resources.Designer.cs
│ ├── Resources.resx
│ └── baseline.netcore.json
└── Microsoft.Extensions.Http
│ ├── ActiveHandlerTrackingEntry.cs
│ ├── DefaultHttpClientFactory.cs
│ ├── DefaultHttpMessageHandlerBuilder.cs
│ ├── DefaultTypedHttpClientFactory.cs
│ ├── DependencyInjection
│ ├── DefaultHttpClientBuilder.cs
│ ├── HttpClientBuilderExtensions.cs
│ ├── HttpClientFactoryServiceCollectionExtensions.cs
│ └── IHttpClientBuilder.cs
│ ├── ExpiredHandlerTrackingEntry.cs
│ ├── HttpClientFactoryExtensions.cs
│ ├── HttpClientFactoryOptions.cs
│ ├── HttpMessageHandlerBuilder.cs
│ ├── HttpMessageHandlerFactoryExtensions.cs
│ ├── IHttpClientFactory.cs
│ ├── IHttpMessageHandlerBuilderFilter.cs
│ ├── IHttpMessageHandlerFactory.cs
│ ├── ITypedHttpClientFactory.cs
│ ├── LifetimeTrackingHttpMessageHandler.cs
│ ├── Logging
│ ├── HttpHeadersLogValue.cs
│ ├── LoggingHttpMessageHandler.cs
│ ├── LoggingHttpMessageHandlerBuilderFilter.cs
│ └── LoggingScopeHttpMessageHandler.cs
│ ├── Microsoft.Extensions.Http.csproj
│ ├── Properties
│ ├── AssemblyInfo.cs
│ └── Resources.Designer.cs
│ ├── Resources.resx
│ └── baseline.netcore.json
├── test
├── Directory.Build.props
├── Microsoft.Extensions.Http.Polly.Test
│ ├── DependencyInjection
│ │ └── PollyHttpClientBuilderExtensionsTest.cs
│ ├── HttpRequestMessageExtensionsTest.cs
│ ├── Microsoft.Extensions.Http.Polly.Test.csproj
│ └── PolicyHttpMessageHandlerTest.cs
└── Microsoft.Extensions.Http.Test
│ ├── DefaultHttpClientFactoryTest.cs
│ ├── DefaultHttpMessageHandlerBuilderTest.cs
│ ├── DependencyInjection
│ ├── HttpClientFactoryServiceCollectionExtensionsTest.cs
│ └── OtherTestOptions.cs
│ ├── HttpMessageHandlerBuilderTest.cs
│ ├── ITestTypedClient.cs
│ ├── Microsoft.Extensions.Http.Test.csproj
│ └── TestTypedClient.cs
└── version.props
/.appveyor.yml:
--------------------------------------------------------------------------------
1 | init:
2 | - git config --global core.autocrlf true
3 | branches:
4 | only:
5 | - master
6 | - /^release\/.*$/
7 | - /^(.*\/)?ci-.*$/
8 | build_script:
9 | - ps: .\run.ps1 default-build
10 | clone_depth: 1
11 | environment:
12 | global:
13 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
14 | DOTNET_CLI_TELEMETRY_OPTOUT: 1
15 | test: 'off'
16 | deploy: 'off'
17 | os: Visual Studio 2017
18 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.doc diff=astextplain
2 | *.DOC diff=astextplain
3 | *.docx diff=astextplain
4 | *.DOCX diff=astextplain
5 | *.dot diff=astextplain
6 | *.DOT diff=astextplain
7 | *.pdf diff=astextplain
8 | *.PDF diff=astextplain
9 | *.rtf diff=astextplain
10 | *.RTF diff=astextplain
11 |
12 | *.jpg binary
13 | *.png binary
14 | *.gif binary
15 |
16 | *.cs text=auto diff=csharp
17 | *.vb text=auto
18 | *.resx text=auto
19 | *.c text=auto
20 | *.cpp text=auto
21 | *.cxx text=auto
22 | *.h text=auto
23 | *.hxx text=auto
24 | *.py text=auto
25 | *.rb text=auto
26 | *.java text=auto
27 | *.html text=auto
28 | *.htm text=auto
29 | *.css text=auto
30 | *.scss text=auto
31 | *.sass text=auto
32 | *.less text=auto
33 | *.js text=auto
34 | *.lisp text=auto
35 | *.clj text=auto
36 | *.sql text=auto
37 | *.php text=auto
38 | *.lua text=auto
39 | *.m text=auto
40 | *.asm text=auto
41 | *.erl text=auto
42 | *.fs text=auto
43 | *.fsx text=auto
44 | *.hs text=auto
45 |
46 | *.csproj text=auto
47 | *.vbproj text=auto
48 | *.fsproj text=auto
49 | *.dbproj text=auto
50 | *.sln text=auto eol=crlf
51 |
52 | *.sh eol=lf
53 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | [Oo]bj/
2 | [Bb]in/
3 | TestResults/
4 | .nuget/
5 | *.sln.ide/
6 | _ReSharper.*/
7 | packages/
8 | artifacts/
9 | PublishProfiles/
10 | .vs/
11 | .vscode/
12 | .build/
13 | .idea/
14 | .testPublish/
15 | bower_components/
16 | node_modules/
17 | **/wwwroot/lib/
18 | debugSettings.json
19 | project.lock.json
20 | *.user
21 | *.suo
22 | *.cache
23 | *.docstates
24 | _ReSharper.*
25 | nuget.exe
26 | *net45.csproj
27 | *net451.csproj
28 | *k10.csproj
29 | *.psess
30 | *.vsp
31 | *.pidb
32 | *.userprefs
33 | *DS_Store
34 | *.ncrunchsolution
35 | *.*sdf
36 | *.ipch
37 | .settings
38 | *.sln.ide
39 | node_modules
40 | **/[Cc]ompiler/[Rr]esources/**/*.js
41 | *launchSettings.json
42 | global.json
43 | BenchmarkDotNet.Artifacts/
44 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: csharp
2 | sudo: false
3 | dist: trusty
4 | env:
5 | global:
6 | - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
7 | - DOTNET_CLI_TELEMETRY_OPTOUT: 1
8 | mono: none
9 | os:
10 | - linux
11 | - osx
12 | osx_image: xcode8.2
13 | addons:
14 | apt:
15 | packages:
16 | - libunwind8
17 | branches:
18 | only:
19 | - master
20 | - /^release\/.*$/
21 | - /^(.*\/)?ci-.*$/
22 | before_install:
23 | - if test "$TRAVIS_OS_NAME" == "osx"; then brew update; brew install openssl; ln -s
24 | /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib
25 | /usr/local/lib/; fi
26 | script:
27 | - ./build.sh
28 |
--------------------------------------------------------------------------------
/.vsts-pipelines/builds/ci-internal.yml:
--------------------------------------------------------------------------------
1 | trigger:
2 | - master
3 | - release/*
4 |
5 | resources:
6 | repositories:
7 | - repository: buildtools
8 | type: git
9 | name: aspnet-BuildTools
10 | ref: refs/heads/master
11 |
12 | phases:
13 | - template: .vsts-pipelines/templates/project-ci.yml@buildtools
14 |
--------------------------------------------------------------------------------
/.vsts-pipelines/builds/ci-public.yml:
--------------------------------------------------------------------------------
1 | trigger:
2 | - master
3 | - release/*
4 |
5 | # See https://github.com/aspnet/BuildTools
6 | resources:
7 | repositories:
8 | - repository: buildtools
9 | type: github
10 | endpoint: DotNet-Bot GitHub Connection
11 | name: aspnet/BuildTools
12 | ref: refs/heads/master
13 |
14 | phases:
15 | - template: .vsts-pipelines/templates/project-ci.yml@buildtools
16 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributing
2 | ======
3 |
4 | Information on contributing to this repo is in the [Contributing Guide](https://github.com/aspnet/Home/blob/master/CONTRIBUTING.md) in the Home repo.
5 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Microsoft ASP.NET Core
12 | https://github.com/aspnet/HttpClientFactory
13 | git
14 | $(MSBuildThisFileDirectory)
15 | $(MSBuildThisFileDirectory)build\Key.snk
16 | true
17 | true
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 | $(MicrosoftNETCoreApp20PackageVersion)
4 | $(MicrosoftNETCoreApp21PackageVersion)
5 | $(MicrosoftNETCoreApp22PackageVersion)
6 | $(NETStandardLibrary20PackageVersion)
7 |
8 | 99.9
9 |
10 |
11 |
--------------------------------------------------------------------------------
/HttpClientFactory.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27025.1
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0ACA47C2-6B67-46B8-A661-C564E4450DE4}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{A7C2238C-5C0F-4D33-BE66-4015985CE962}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Http", "src\Microsoft.Extensions.Http\Microsoft.Extensions.Http.csproj", "{99E3492F-87BB-4506-8D2B-D1C7101655DC}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Http.Test", "test\Microsoft.Extensions.Http.Test\Microsoft.Extensions.Http.Test.csproj", "{752F6163-AE48-46D3-8A6D-695BBF3629CB}"
13 | EndProject
14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{FF6B150F-C423-41BB-9563-55A0DFEAE21C}"
15 | EndProject
16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpClientFactorySample", "samples\HttpClientFactorySample\HttpClientFactorySample.csproj", "{42C81623-6316-4C15-9E41-84AF48608C47}"
17 | EndProject
18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{BAD8867C-73F7-4DB8-8E79-70287E67987A}"
19 | EndProject
20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Http.Performance", "benchmarks\Microsoft.Extensions.Http.Performance\Microsoft.Extensions.Http.Performance.csproj", "{0A0C4C1A-3A07-43C1-8A74-4DE193D50939}"
21 | EndProject
22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{EBF25046-7C9B-4F84-ACEA-E836012F4459}"
23 | ProjectSection(SolutionItems) = preProject
24 | build\dependencies.props = build\dependencies.props
25 | build\repo.props = build\repo.props
26 | EndProjectSection
27 | EndProject
28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Http.Polly", "src\Microsoft.Extensions.Http.Polly\Microsoft.Extensions.Http.Polly.csproj", "{69D013E2-AC8C-4BE4-87AD-FCE0826FC1C5}"
29 | EndProject
30 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Extensions.Http.Polly.Test", "test\Microsoft.Extensions.Http.Polly.Test\Microsoft.Extensions.Http.Polly.Test.csproj", "{6C61E727-6E0B-402D-8E6E-2FE4E7590822}"
31 | EndProject
32 | Global
33 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
34 | Debug|Any CPU = Debug|Any CPU
35 | Release|Any CPU = Release|Any CPU
36 | EndGlobalSection
37 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
38 | {99E3492F-87BB-4506-8D2B-D1C7101655DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39 | {99E3492F-87BB-4506-8D2B-D1C7101655DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
40 | {99E3492F-87BB-4506-8D2B-D1C7101655DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
41 | {99E3492F-87BB-4506-8D2B-D1C7101655DC}.Release|Any CPU.Build.0 = Release|Any CPU
42 | {752F6163-AE48-46D3-8A6D-695BBF3629CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
43 | {752F6163-AE48-46D3-8A6D-695BBF3629CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
44 | {752F6163-AE48-46D3-8A6D-695BBF3629CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
45 | {752F6163-AE48-46D3-8A6D-695BBF3629CB}.Release|Any CPU.Build.0 = Release|Any CPU
46 | {42C81623-6316-4C15-9E41-84AF48608C47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
47 | {42C81623-6316-4C15-9E41-84AF48608C47}.Debug|Any CPU.Build.0 = Debug|Any CPU
48 | {42C81623-6316-4C15-9E41-84AF48608C47}.Release|Any CPU.ActiveCfg = Release|Any CPU
49 | {42C81623-6316-4C15-9E41-84AF48608C47}.Release|Any CPU.Build.0 = Release|Any CPU
50 | {0A0C4C1A-3A07-43C1-8A74-4DE193D50939}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
51 | {0A0C4C1A-3A07-43C1-8A74-4DE193D50939}.Debug|Any CPU.Build.0 = Debug|Any CPU
52 | {0A0C4C1A-3A07-43C1-8A74-4DE193D50939}.Release|Any CPU.ActiveCfg = Release|Any CPU
53 | {0A0C4C1A-3A07-43C1-8A74-4DE193D50939}.Release|Any CPU.Build.0 = Release|Any CPU
54 | {69D013E2-AC8C-4BE4-87AD-FCE0826FC1C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
55 | {69D013E2-AC8C-4BE4-87AD-FCE0826FC1C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
56 | {69D013E2-AC8C-4BE4-87AD-FCE0826FC1C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
57 | {69D013E2-AC8C-4BE4-87AD-FCE0826FC1C5}.Release|Any CPU.Build.0 = Release|Any CPU
58 | {6C61E727-6E0B-402D-8E6E-2FE4E7590822}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
59 | {6C61E727-6E0B-402D-8E6E-2FE4E7590822}.Debug|Any CPU.Build.0 = Debug|Any CPU
60 | {6C61E727-6E0B-402D-8E6E-2FE4E7590822}.Release|Any CPU.ActiveCfg = Release|Any CPU
61 | {6C61E727-6E0B-402D-8E6E-2FE4E7590822}.Release|Any CPU.Build.0 = Release|Any CPU
62 | EndGlobalSection
63 | GlobalSection(SolutionProperties) = preSolution
64 | HideSolutionNode = FALSE
65 | EndGlobalSection
66 | GlobalSection(NestedProjects) = preSolution
67 | {99E3492F-87BB-4506-8D2B-D1C7101655DC} = {0ACA47C2-6B67-46B8-A661-C564E4450DE4}
68 | {752F6163-AE48-46D3-8A6D-695BBF3629CB} = {A7C2238C-5C0F-4D33-BE66-4015985CE962}
69 | {42C81623-6316-4C15-9E41-84AF48608C47} = {FF6B150F-C423-41BB-9563-55A0DFEAE21C}
70 | {0A0C4C1A-3A07-43C1-8A74-4DE193D50939} = {BAD8867C-73F7-4DB8-8E79-70287E67987A}
71 | {69D013E2-AC8C-4BE4-87AD-FCE0826FC1C5} = {0ACA47C2-6B67-46B8-A661-C564E4450DE4}
72 | {6C61E727-6E0B-402D-8E6E-2FE4E7590822} = {A7C2238C-5C0F-4D33-BE66-4015985CE962}
73 | EndGlobalSection
74 | GlobalSection(ExtensibilityGlobals) = postSolution
75 | SolutionGuid = {002C7A25-D737-4B87-AFBB-B6E0FB2DB0D3}
76 | EndGlobalSection
77 | EndGlobal
78 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright (c) .NET Foundation and Contributors
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/NuGet.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/NuGetPackageVerifier.json:
--------------------------------------------------------------------------------
1 | {
2 | "Default": {
3 | "rules": [
4 | "DefaultCompositeRule"
5 | ]
6 | }
7 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | HttpClient Factory [Archived]
2 | =============================
3 |
4 | **This GitHub project has been archived.** Ongoing development on this project can be found in .
5 |
6 | Contains an opinionated factory for creating HttpClient instances.
7 |
8 | This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [AspNetCore](https://github.com/aspnet/AspNetCore) repo.
9 |
--------------------------------------------------------------------------------
/benchmarks/Microsoft.Extensions.Http.Performance/Configs/CoreConfig.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using BenchmarkDotNet.Columns;
5 | using BenchmarkDotNet.Configs;
6 | using BenchmarkDotNet.Diagnosers;
7 | using BenchmarkDotNet.Engines;
8 | using BenchmarkDotNet.Jobs;
9 | using BenchmarkDotNet.Validators;
10 |
11 | namespace Microsoft.Extensions.Http.Performance
12 | {
13 | public class CoreConfig : ManualConfig
14 | {
15 | public CoreConfig()
16 | : this(Job.Core
17 | .WithRemoveOutliers(false)
18 | .With(new GcMode() { Server = true })
19 | .With(RunStrategy.Throughput)
20 | .WithLaunchCount(3)
21 | .WithWarmupCount(5)
22 | .WithTargetCount(10))
23 | {
24 | Add(JitOptimizationsValidator.FailOnError);
25 | }
26 |
27 | public CoreConfig(Job job)
28 | {
29 | Add(DefaultConfig.Instance);
30 |
31 | Add(MemoryDiagnoser.Default);
32 | Add(StatisticColumn.OperationsPerSecond);
33 |
34 | Add(job);
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/benchmarks/Microsoft.Extensions.Http.Performance/CreationOverheadBenchmark.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Net.Http;
6 | using System.Net.Http.Headers;
7 | using System.Threading.Tasks;
8 | using BenchmarkDotNet.Attributes;
9 | using Microsoft.Extensions.DependencyInjection;
10 |
11 | namespace Microsoft.Extensions.Http.Performance
12 | {
13 | [ParameterizedJobConfig(typeof(CoreConfig))]
14 | public class CreationOverheadBenchmark
15 | {
16 | private const int Iterations = 100;
17 |
18 | public CreationOverheadBenchmark()
19 | {
20 | Handler = new FakeClientHandler();
21 |
22 | var serviceCollection = new ServiceCollection();
23 | serviceCollection.AddHttpClient("example", c =>
24 | {
25 | c.BaseAddress = new Uri("http://example.com/");
26 | c.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
27 | })
28 | .ConfigurePrimaryHttpMessageHandler(() => Handler);
29 |
30 | var services = serviceCollection.BuildServiceProvider();
31 | Factory = services.GetRequiredService();
32 | }
33 |
34 | public IHttpClientFactory Factory { get; }
35 |
36 | public HttpMessageHandler Handler { get; }
37 |
38 | [Benchmark(
39 | Description = "use IHttpClientFactory",
40 | OperationsPerInvoke = Iterations)]
41 | public async Task CreateClient()
42 | {
43 | for (var i = 0; i < Iterations; i++)
44 | {
45 | var client = Factory.CreateClient("example");
46 |
47 | var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "api/Products"));
48 | response.EnsureSuccessStatusCode();
49 | }
50 | }
51 |
52 | [Benchmark(
53 | Description = "new HttpClient",
54 | Baseline = true,
55 | OperationsPerInvoke = Iterations)]
56 | public async Task Baseline()
57 | {
58 | for (var i = 0; i < Iterations; i++)
59 | {
60 | var client = new HttpClient(Handler, disposeHandler: false)
61 | {
62 | BaseAddress = new Uri("http://example.com/"),
63 | DefaultRequestHeaders =
64 | {
65 | Accept =
66 | {
67 | new MediaTypeWithQualityHeaderValue("application/json"),
68 | }
69 | },
70 | };
71 |
72 | var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "api/Products"));
73 | response.EnsureSuccessStatusCode();
74 | }
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/benchmarks/Microsoft.Extensions.Http.Performance/FakeClientHandler.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Net;
6 | using System.Net.Http;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | namespace Microsoft.Extensions.Http.Performance
11 | {
12 | internal class FakeClientHandler : HttpMessageHandler
13 | {
14 | public TimeSpan Latency { get; set; } = TimeSpan.FromMilliseconds(10);
15 |
16 | protected async override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
17 | {
18 | await Task.Yield();
19 |
20 | var response = new HttpResponseMessage(HttpStatusCode.OK);
21 | response.RequestMessage = request;
22 | return response;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/benchmarks/Microsoft.Extensions.Http.Performance/FakeLoggerProvider.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using Microsoft.Extensions.Logging;
6 |
7 | namespace Microsoft.Extensions.Http.Performance
8 | {
9 | internal class FakeLoggerProvider : ILoggerProvider
10 | {
11 | public bool IsEnabled { get; set; }
12 |
13 | public ILogger CreateLogger(string categoryName)
14 | {
15 | return new Logger(this);
16 | }
17 |
18 | public void Dispose()
19 | {
20 | }
21 |
22 | private class Logger : ILogger
23 | {
24 | private FakeLoggerProvider _provider;
25 |
26 | public Logger(FakeLoggerProvider provider)
27 | {
28 | _provider = provider;
29 | }
30 |
31 | public IDisposable BeginScope(TState state)
32 | {
33 | return null;
34 | }
35 |
36 | public bool IsEnabled(LogLevel logLevel)
37 | {
38 | return _provider.IsEnabled;
39 | }
40 |
41 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)
42 | {
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/benchmarks/Microsoft.Extensions.Http.Performance/LoggingOverheadBenchmark.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Net.Http;
6 | using System.Net.Http.Headers;
7 | using System.Threading.Tasks;
8 | using BenchmarkDotNet.Attributes;
9 | using Microsoft.Extensions.DependencyInjection;
10 | using Microsoft.Extensions.Logging;
11 |
12 | namespace Microsoft.Extensions.Http.Performance
13 | {
14 | [ParameterizedJobConfig(typeof(CoreConfig))]
15 | public class LoggingOverheadBenchmark
16 | {
17 | private const int Iterations = 100;
18 |
19 | public LoggingOverheadBenchmark()
20 | {
21 | Handler = new FakeClientHandler();
22 | LoggerProvider = new FakeLoggerProvider();
23 |
24 | var serviceCollection = new ServiceCollection();
25 | serviceCollection.AddLogging(b => b.AddProvider(LoggerProvider));
26 | serviceCollection.AddHttpClient("example", c =>
27 | {
28 | c.BaseAddress = new Uri("http://example.com/");
29 | c.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
30 | })
31 | .ConfigurePrimaryHttpMessageHandler(() => Handler);
32 |
33 | var services = serviceCollection.BuildServiceProvider();
34 | Factory = services.GetRequiredService();
35 | }
36 |
37 | private IHttpClientFactory Factory { get; }
38 |
39 | private HttpMessageHandler Handler { get; }
40 |
41 | private FakeLoggerProvider LoggerProvider { get; }
42 |
43 | [Benchmark(
44 | Description = "logging on",
45 | OperationsPerInvoke = Iterations)]
46 | public async Task LoggingOn()
47 | {
48 | LoggerProvider.IsEnabled = true;
49 |
50 | for (var i = 0; i < Iterations; i++)
51 | {
52 | var client = Factory.CreateClient("example");
53 |
54 | var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "api/Products"));
55 | response.EnsureSuccessStatusCode();
56 | }
57 | }
58 |
59 | [Benchmark(
60 | Description = "logging off",
61 | OperationsPerInvoke = Iterations)]
62 | public async Task LoggingOff()
63 | {
64 | LoggerProvider.IsEnabled = false;
65 |
66 | for (var i = 0; i < Iterations; i++)
67 | {
68 | var client = Factory.CreateClient("example");
69 |
70 | var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "api/Products"));
71 | response.EnsureSuccessStatusCode();
72 | }
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/benchmarks/Microsoft.Extensions.Http.Performance/Microsoft.Extensions.Http.Performance.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.0;net461
5 | netcoreapp2.0
6 | Exe
7 | true
8 | true
9 | false
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/build.cmd:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 | PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' default-build %*; exit $LASTEXITCODE"
3 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -euo pipefail
4 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
5 |
6 | # Call "sync" between "chmod" and execution to prevent "text file busy" error in Docker (aufs)
7 | chmod +x "$DIR/run.sh"; sync
8 | "$DIR/run.sh" default-build "$@"
9 |
--------------------------------------------------------------------------------
/build/Key.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aspnet/HttpClientFactory/7c4f9479b4753a37c8b0669a883002e5e448cc94/build/Key.snk
--------------------------------------------------------------------------------
/build/dependencies.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
4 |
5 |
6 | 0.10.13
7 | 3.0.0-alpha1-20181004.7
8 | 3.0.0-alpha1-10584
9 | 3.0.0-alpha1-10584
10 | 5.2.6
11 | 3.0.0-alpha1-10584
12 | 3.0.0-alpha1-10584
13 | 3.0.0-alpha1-10584
14 | 3.0.0-alpha1-10584
15 | 3.0.0-alpha1-10584
16 | 3.0.0-alpha1-10584
17 | 3.0.0-alpha1-10584
18 | 3.0.0-alpha1-10584
19 | 2.0.9
20 | 2.1.3
21 | 2.2.0-preview2-26905-02
22 | 15.6.1
23 | 4.9.0
24 | 2.0.3
25 | 2.0.1
26 | 6.0.1
27 | 0.10.0
28 | 2.3.1
29 | 2.4.0
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/build/repo.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Internal.AspNetCore.Universe.Lineup
7 | https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/build/sources.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | $(DotNetRestoreSources)
6 |
7 | $(RestoreSources);
8 | https://dotnet.myget.org/F/dotnet-core/api/v3/index.json;
9 | https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json;
10 | https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json;
11 |
12 |
13 | $(RestoreSources);
14 | https://api.nuget.org/v3/index.json;
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/korebuild-lock.txt:
--------------------------------------------------------------------------------
1 | version:3.0.0-alpha1-20181004.7
2 | commithash:27fabdaf2b1d4753c3d2749581694ca65d78f7f2
3 |
--------------------------------------------------------------------------------
/korebuild.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json",
3 | "channel": "master"
4 | }
5 |
--------------------------------------------------------------------------------
/run.cmd:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 | PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' %*; exit $LASTEXITCODE"
3 |
--------------------------------------------------------------------------------
/run.ps1:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env powershell
2 | #requires -version 4
3 |
4 | <#
5 | .SYNOPSIS
6 | Executes KoreBuild commands.
7 |
8 | .DESCRIPTION
9 | Downloads korebuild if required. Then executes the KoreBuild command. To see available commands, execute with `-Command help`.
10 |
11 | .PARAMETER Command
12 | The KoreBuild command to run.
13 |
14 | .PARAMETER Path
15 | The folder to build. Defaults to the folder containing this script.
16 |
17 | .PARAMETER Channel
18 | The channel of KoreBuild to download. Overrides the value from the config file.
19 |
20 | .PARAMETER DotNetHome
21 | The directory where .NET Core tools will be stored.
22 |
23 | .PARAMETER ToolsSource
24 | The base url where build tools can be downloaded. Overrides the value from the config file.
25 |
26 | .PARAMETER Update
27 | Updates KoreBuild to the latest version even if a lock file is present.
28 |
29 | .PARAMETER Reinstall
30 | Re-installs KoreBuild
31 |
32 | .PARAMETER ConfigFile
33 | The path to the configuration file that stores values. Defaults to korebuild.json.
34 |
35 | .PARAMETER ToolsSourceSuffix
36 | The Suffix to append to the end of the ToolsSource. Useful for query strings in blob stores.
37 |
38 | .PARAMETER CI
39 | Sets up CI specific settings and variables.
40 |
41 | .PARAMETER Arguments
42 | Arguments to be passed to the command
43 |
44 | .NOTES
45 | This function will create a file $PSScriptRoot/korebuild-lock.txt. This lock file can be committed to source, but does not have to be.
46 | When the lockfile is not present, KoreBuild will create one using latest available version from $Channel.
47 |
48 | The $ConfigFile is expected to be an JSON file. It is optional, and the configuration values in it are optional as well. Any options set
49 | in the file are overridden by command line parameters.
50 |
51 | .EXAMPLE
52 | Example config file:
53 | ```json
54 | {
55 | "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json",
56 | "channel": "master",
57 | "toolsSource": "https://aspnetcore.blob.core.windows.net/buildtools"
58 | }
59 | ```
60 | #>
61 | [CmdletBinding(PositionalBinding = $false)]
62 | param(
63 | [Parameter(Mandatory = $true, Position = 0)]
64 | [string]$Command,
65 | [string]$Path = $PSScriptRoot,
66 | [Alias('c')]
67 | [string]$Channel,
68 | [Alias('d')]
69 | [string]$DotNetHome,
70 | [Alias('s')]
71 | [string]$ToolsSource,
72 | [Alias('u')]
73 | [switch]$Update,
74 | [switch]$Reinstall,
75 | [string]$ToolsSourceSuffix,
76 | [string]$ConfigFile = $null,
77 | [switch]$CI,
78 | [Parameter(ValueFromRemainingArguments = $true)]
79 | [string[]]$Arguments
80 | )
81 |
82 | Set-StrictMode -Version 2
83 | $ErrorActionPreference = 'Stop'
84 |
85 | #
86 | # Functions
87 | #
88 |
89 | function Get-KoreBuild {
90 |
91 | $lockFile = Join-Path $Path 'korebuild-lock.txt'
92 |
93 | if (!(Test-Path $lockFile) -or $Update) {
94 | Get-RemoteFile "$ToolsSource/korebuild/channels/$Channel/latest.txt" $lockFile $ToolsSourceSuffix
95 | }
96 |
97 | $version = Get-Content $lockFile | Where-Object { $_ -like 'version:*' } | Select-Object -first 1
98 | if (!$version) {
99 | Write-Error "Failed to parse version from $lockFile. Expected a line that begins with 'version:'"
100 | }
101 | $version = $version.TrimStart('version:').Trim()
102 | $korebuildPath = Join-Paths $DotNetHome ('buildtools', 'korebuild', $version)
103 |
104 | if ($Reinstall -and (Test-Path $korebuildPath)) {
105 | Remove-Item -Force -Recurse $korebuildPath
106 | }
107 |
108 | if (!(Test-Path $korebuildPath)) {
109 | Write-Host -ForegroundColor Magenta "Downloading KoreBuild $version"
110 | New-Item -ItemType Directory -Path $korebuildPath | Out-Null
111 | $remotePath = "$ToolsSource/korebuild/artifacts/$version/korebuild.$version.zip"
112 |
113 | try {
114 | $tmpfile = Join-Path ([IO.Path]::GetTempPath()) "KoreBuild-$([guid]::NewGuid()).zip"
115 | Get-RemoteFile $remotePath $tmpfile $ToolsSourceSuffix
116 | if (Get-Command -Name 'Microsoft.PowerShell.Archive\Expand-Archive' -ErrorAction Ignore) {
117 | # Use built-in commands where possible as they are cross-plat compatible
118 | Microsoft.PowerShell.Archive\Expand-Archive -Path $tmpfile -DestinationPath $korebuildPath
119 | }
120 | else {
121 | # Fallback to old approach for old installations of PowerShell
122 | Add-Type -AssemblyName System.IO.Compression.FileSystem
123 | [System.IO.Compression.ZipFile]::ExtractToDirectory($tmpfile, $korebuildPath)
124 | }
125 | }
126 | catch {
127 | Remove-Item -Recurse -Force $korebuildPath -ErrorAction Ignore
128 | throw
129 | }
130 | finally {
131 | Remove-Item $tmpfile -ErrorAction Ignore
132 | }
133 | }
134 |
135 | return $korebuildPath
136 | }
137 |
138 | function Join-Paths([string]$path, [string[]]$childPaths) {
139 | $childPaths | ForEach-Object { $path = Join-Path $path $_ }
140 | return $path
141 | }
142 |
143 | function Get-RemoteFile([string]$RemotePath, [string]$LocalPath, [string]$RemoteSuffix) {
144 | if ($RemotePath -notlike 'http*') {
145 | Copy-Item $RemotePath $LocalPath
146 | return
147 | }
148 |
149 | $retries = 10
150 | while ($retries -gt 0) {
151 | $retries -= 1
152 | try {
153 | Invoke-WebRequest -UseBasicParsing -Uri $($RemotePath + $RemoteSuffix) -OutFile $LocalPath
154 | return
155 | }
156 | catch {
157 | Write-Verbose "Request failed. $retries retries remaining"
158 | }
159 | }
160 |
161 | Write-Error "Download failed: '$RemotePath'."
162 | }
163 |
164 | #
165 | # Main
166 | #
167 |
168 | # Load configuration or set defaults
169 |
170 | $Path = Resolve-Path $Path
171 | if (!$ConfigFile) { $ConfigFile = Join-Path $Path 'korebuild.json' }
172 |
173 | if (Test-Path $ConfigFile) {
174 | try {
175 | $config = Get-Content -Raw -Encoding UTF8 -Path $ConfigFile | ConvertFrom-Json
176 | if ($config) {
177 | if (!($Channel) -and (Get-Member -Name 'channel' -InputObject $config)) { [string] $Channel = $config.channel }
178 | if (!($ToolsSource) -and (Get-Member -Name 'toolsSource' -InputObject $config)) { [string] $ToolsSource = $config.toolsSource}
179 | }
180 | }
181 | catch {
182 | Write-Host -ForegroundColor Red $Error[0]
183 | Write-Error "$ConfigFile contains invalid JSON."
184 | exit 1
185 | }
186 | }
187 |
188 | if (!$DotNetHome) {
189 | $DotNetHome = if ($env:DOTNET_HOME) { $env:DOTNET_HOME } `
190 | elseif ($env:USERPROFILE) { Join-Path $env:USERPROFILE '.dotnet'} `
191 | elseif ($env:HOME) {Join-Path $env:HOME '.dotnet'}`
192 | else { Join-Path $PSScriptRoot '.dotnet'}
193 | }
194 |
195 | if (!$Channel) { $Channel = 'master' }
196 | if (!$ToolsSource) { $ToolsSource = 'https://aspnetcore.blob.core.windows.net/buildtools' }
197 |
198 | # Execute
199 |
200 | $korebuildPath = Get-KoreBuild
201 | Import-Module -Force -Scope Local (Join-Path $korebuildPath 'KoreBuild.psd1')
202 |
203 | try {
204 | Set-KoreBuildSettings -ToolsSource $ToolsSource -DotNetHome $DotNetHome -RepoPath $Path -ConfigFile $ConfigFile -CI:$CI
205 | Invoke-KoreBuildCommand $Command @Arguments
206 | }
207 | finally {
208 | Remove-Module 'KoreBuild' -ErrorAction Ignore
209 | }
210 |
--------------------------------------------------------------------------------
/run.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -euo pipefail
4 |
5 | #
6 | # variables
7 | #
8 |
9 | RESET="\033[0m"
10 | RED="\033[0;31m"
11 | YELLOW="\033[0;33m"
12 | MAGENTA="\033[0;95m"
13 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
14 | [ -z "${DOTNET_HOME:-}" ] && DOTNET_HOME="$HOME/.dotnet"
15 | verbose=false
16 | update=false
17 | reinstall=false
18 | repo_path="$DIR"
19 | channel=''
20 | tools_source=''
21 | tools_source_suffix=''
22 | ci=false
23 |
24 | #
25 | # Functions
26 | #
27 | __usage() {
28 | echo "Usage: $(basename "${BASH_SOURCE[0]}") command [options] [[--] ...]"
29 | echo ""
30 | echo "Arguments:"
31 | echo " command The command to be run."
32 | echo " ... Arguments passed to the command. Variable number of arguments allowed."
33 | echo ""
34 | echo "Options:"
35 | echo " --verbose Show verbose output."
36 | echo " -c|--channel The channel of KoreBuild to download. Overrides the value from the config file.."
37 | echo " --config-file The path to the configuration file that stores values. Defaults to korebuild.json."
38 | echo " -d|--dotnet-home The directory where .NET Core tools will be stored. Defaults to '\$DOTNET_HOME' or '\$HOME/.dotnet."
39 | echo " --path The directory to build. Defaults to the directory containing the script."
40 | echo " -s|--tools-source|-ToolsSource The base url where build tools can be downloaded. Overrides the value from the config file."
41 | echo " --tools-source-suffix|-ToolsSourceSuffix The suffix to append to tools-source. Useful for query strings."
42 | echo " -u|--update Update to the latest KoreBuild even if the lock file is present."
43 | echo " --reinstall Reinstall KoreBuild."
44 | echo " --ci Apply CI specific settings and environment variables."
45 | echo ""
46 | echo "Description:"
47 | echo " This function will create a file \$DIR/korebuild-lock.txt. This lock file can be committed to source, but does not have to be."
48 | echo " When the lockfile is not present, KoreBuild will create one using latest available version from \$channel."
49 |
50 | if [[ "${1:-}" != '--no-exit' ]]; then
51 | exit 2
52 | fi
53 | }
54 |
55 | get_korebuild() {
56 | local version
57 | local lock_file="$repo_path/korebuild-lock.txt"
58 | if [ ! -f "$lock_file" ] || [ "$update" = true ]; then
59 | __get_remote_file "$tools_source/korebuild/channels/$channel/latest.txt" "$lock_file" "$tools_source_suffix"
60 | fi
61 | version="$(grep 'version:*' -m 1 "$lock_file")"
62 | if [[ "$version" == '' ]]; then
63 | __error "Failed to parse version from $lock_file. Expected a line that begins with 'version:'"
64 | return 1
65 | fi
66 | version="$(echo "${version#version:}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
67 | local korebuild_path="$DOTNET_HOME/buildtools/korebuild/$version"
68 |
69 | if [ "$reinstall" = true ] && [ -d "$korebuild_path" ]; then
70 | rm -rf "$korebuild_path"
71 | fi
72 |
73 | {
74 | if [ ! -d "$korebuild_path" ]; then
75 | mkdir -p "$korebuild_path"
76 | local remote_path="$tools_source/korebuild/artifacts/$version/korebuild.$version.zip"
77 | tmpfile="$(mktemp)"
78 | echo -e "${MAGENTA}Downloading KoreBuild ${version}${RESET}"
79 | if __get_remote_file "$remote_path" "$tmpfile" "$tools_source_suffix"; then
80 | unzip -q -d "$korebuild_path" "$tmpfile"
81 | fi
82 | rm "$tmpfile" || true
83 | fi
84 |
85 | source "$korebuild_path/KoreBuild.sh"
86 | } || {
87 | if [ -d "$korebuild_path" ]; then
88 | echo "Cleaning up after failed installation"
89 | rm -rf "$korebuild_path" || true
90 | fi
91 | return 1
92 | }
93 | }
94 |
95 | __error() {
96 | echo -e "${RED}error: $*${RESET}" 1>&2
97 | }
98 |
99 | __warn() {
100 | echo -e "${YELLOW}warning: $*${RESET}"
101 | }
102 |
103 | __machine_has() {
104 | hash "$1" > /dev/null 2>&1
105 | return $?
106 | }
107 |
108 | __get_remote_file() {
109 | local remote_path=$1
110 | local local_path=$2
111 | local remote_path_suffix=$3
112 |
113 | if [[ "$remote_path" != 'http'* ]]; then
114 | cp "$remote_path" "$local_path"
115 | return 0
116 | fi
117 |
118 | local failed=false
119 | if __machine_has wget; then
120 | wget --tries 10 --quiet -O "$local_path" "${remote_path}${remote_path_suffix}" || failed=true
121 | else
122 | failed=true
123 | fi
124 |
125 | if [ "$failed" = true ] && __machine_has curl; then
126 | failed=false
127 | curl --retry 10 -sSL -f --create-dirs -o "$local_path" "${remote_path}${remote_path_suffix}" || failed=true
128 | fi
129 |
130 | if [ "$failed" = true ]; then
131 | __error "Download failed: $remote_path" 1>&2
132 | return 1
133 | fi
134 | }
135 |
136 | #
137 | # main
138 | #
139 |
140 | command="${1:-}"
141 | shift
142 |
143 | while [[ $# -gt 0 ]]; do
144 | case $1 in
145 | -\?|-h|--help)
146 | __usage --no-exit
147 | exit 0
148 | ;;
149 | -c|--channel|-Channel)
150 | shift
151 | channel="${1:-}"
152 | [ -z "$channel" ] && __usage
153 | ;;
154 | --config-file|-ConfigFile)
155 | shift
156 | config_file="${1:-}"
157 | [ -z "$config_file" ] && __usage
158 | if [ ! -f "$config_file" ]; then
159 | __error "Invalid value for --config-file. $config_file does not exist."
160 | exit 1
161 | fi
162 | ;;
163 | -d|--dotnet-home|-DotNetHome)
164 | shift
165 | DOTNET_HOME="${1:-}"
166 | [ -z "$DOTNET_HOME" ] && __usage
167 | ;;
168 | --path|-Path)
169 | shift
170 | repo_path="${1:-}"
171 | [ -z "$repo_path" ] && __usage
172 | ;;
173 | -s|--tools-source|-ToolsSource)
174 | shift
175 | tools_source="${1:-}"
176 | [ -z "$tools_source" ] && __usage
177 | ;;
178 | --tools-source-suffix|-ToolsSourceSuffix)
179 | shift
180 | tools_source_suffix="${1:-}"
181 | [ -z "$tools_source_suffix" ] && __usage
182 | ;;
183 | -u|--update|-Update)
184 | update=true
185 | ;;
186 | --reinstall|-[Rr]einstall)
187 | reinstall=true
188 | ;;
189 | --ci|-[Cc][Ii])
190 | ci=true
191 | ;;
192 | --verbose|-Verbose)
193 | verbose=true
194 | ;;
195 | --)
196 | shift
197 | break
198 | ;;
199 | *)
200 | break
201 | ;;
202 | esac
203 | shift
204 | done
205 |
206 | if ! __machine_has unzip; then
207 | __error 'Missing required command: unzip'
208 | exit 1
209 | fi
210 |
211 | if ! __machine_has curl && ! __machine_has wget; then
212 | __error 'Missing required command. Either wget or curl is required.'
213 | exit 1
214 | fi
215 |
216 | [ -z "${config_file:-}" ] && config_file="$repo_path/korebuild.json"
217 | if [ -f "$config_file" ]; then
218 | if __machine_has jq ; then
219 | if jq '.' "$config_file" >/dev/null ; then
220 | config_channel="$(jq -r 'select(.channel!=null) | .channel' "$config_file")"
221 | config_tools_source="$(jq -r 'select(.toolsSource!=null) | .toolsSource' "$config_file")"
222 | else
223 | __error "$config_file contains invalid JSON."
224 | exit 1
225 | fi
226 | elif __machine_has python ; then
227 | if python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then
228 | config_channel="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")"
229 | config_tools_source="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")"
230 | else
231 | __error "$config_file contains invalid JSON."
232 | exit 1
233 | fi
234 | elif __machine_has python3 ; then
235 | if python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then
236 | config_channel="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")"
237 | config_tools_source="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")"
238 | else
239 | __error "$config_file contains invalid JSON."
240 | exit 1
241 | fi
242 | else
243 | __error 'Missing required command: jq or python. Could not parse the JSON file.'
244 | exit 1
245 | fi
246 |
247 | [ ! -z "${config_channel:-}" ] && channel="$config_channel"
248 | [ ! -z "${config_tools_source:-}" ] && tools_source="$config_tools_source"
249 | fi
250 |
251 | [ -z "$channel" ] && channel='master'
252 | [ -z "$tools_source" ] && tools_source='https://aspnetcore.blob.core.windows.net/buildtools'
253 |
254 | get_korebuild
255 | set_korebuildsettings "$tools_source" "$DOTNET_HOME" "$repo_path" "$config_file" "$ci"
256 | invoke_korebuild_command "$command" "$@"
257 |
--------------------------------------------------------------------------------
/samples/HttpClientFactorySample/HttpClientFactorySample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.2;net461
5 | portable
6 | Exe
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/samples/HttpClientFactorySample/Program.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Net.Http;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 | using Microsoft.Extensions.DependencyInjection;
9 | using Microsoft.Extensions.Logging;
10 | using Newtonsoft.Json.Linq;
11 | using Polly;
12 |
13 | namespace HttpClientFactorySample
14 | {
15 | public class Program
16 | {
17 | public static void Main(string[] args) => Run().GetAwaiter().GetResult();
18 |
19 | public static async Task Run()
20 | {
21 | var serviceCollection = new ServiceCollection();
22 | serviceCollection.AddLogging(b =>
23 | {
24 | b.AddFilter((category, level) => true); // Spam the world with logs.
25 |
26 | // Add console logger so we can see all the logging produced by the client by default.
27 | b.AddConsole(c => c.IncludeScopes = true);
28 | });
29 |
30 | Configure(serviceCollection);
31 |
32 | var services = serviceCollection.BuildServiceProvider();
33 |
34 | Console.WriteLine("Creating a client...");
35 | var github = services.GetRequiredService();
36 |
37 | Console.WriteLine("Sending a request...");
38 | var response = await github.GetJson();
39 |
40 | var data = await response.Content.ReadAsAsync();
41 | Console.WriteLine("Response data:");
42 | Console.WriteLine(data);
43 |
44 | Console.WriteLine("Press the ANY key to exit...");
45 | Console.ReadKey();
46 | }
47 |
48 | public static void Configure(IServiceCollection services)
49 | {
50 | var registry = services.AddPolicyRegistry();
51 |
52 | var timeout = Policy.TimeoutAsync(TimeSpan.FromSeconds(10));
53 | var longTimeout = Policy.TimeoutAsync(TimeSpan.FromSeconds(30));
54 |
55 | registry.Add("regular", timeout);
56 | registry.Add("long", longTimeout);
57 |
58 | services.AddHttpClient("github", c =>
59 | {
60 | c.BaseAddress = new Uri("https://api.github.com/");
61 |
62 | c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); // GitHub API versioning
63 | c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample"); // GitHub requires a user-agent
64 | })
65 |
66 | // Build a totally custom policy using any criteria
67 | .AddPolicyHandler(Policy.TimeoutAsync(TimeSpan.FromSeconds(10)))
68 |
69 | // Use a specific named policy from the registry. Simplest way, policy is cached for the
70 | // lifetime of the handler.
71 | .AddPolicyHandlerFromRegistry("regular")
72 |
73 | // Run some code to select a policy based on the request
74 | .AddPolicyHandler((request) =>
75 | {
76 | return request.Method == HttpMethod.Get ? timeout : longTimeout;
77 | })
78 |
79 | // Run some code to select a policy from the registry based on the request
80 | .AddPolicyHandlerFromRegistry((reg, request) =>
81 | {
82 | return request.Method == HttpMethod.Get ?
83 | reg.Get>("regular") :
84 | reg.Get>("long");
85 | })
86 |
87 | // Build a policy that will handle exceptions, 408s, and 500s from the remote server
88 | .AddTransientHttpErrorPolicy(p => p.RetryAsync())
89 |
90 | .AddHttpMessageHandler(() => new RetryHandler()) // Retry requests to github using our retry handler
91 | .AddTypedClient();
92 | }
93 |
94 | private class GitHubClient
95 | {
96 | public GitHubClient(HttpClient httpClient)
97 | {
98 | HttpClient = httpClient;
99 | }
100 |
101 | public HttpClient HttpClient { get; }
102 |
103 | // Gets the list of services on github.
104 | public async Task GetJson()
105 | {
106 | var request = new HttpRequestMessage(HttpMethod.Get, "/");
107 |
108 | var response = await HttpClient.SendAsync(request).ConfigureAwait(false);
109 | response.EnsureSuccessStatusCode();
110 |
111 | return response;
112 | }
113 | }
114 |
115 | private class RetryHandler : DelegatingHandler
116 | {
117 | public int RetryCount { get; set; } = 5;
118 |
119 | protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
120 | {
121 | for (var i = 0; i < RetryCount; i++)
122 | {
123 | try
124 | {
125 | return await base.SendAsync(request, cancellationToken);
126 | }
127 | catch (HttpRequestException) when (i == RetryCount - 1)
128 | {
129 | throw;
130 | }
131 | catch (HttpRequestException)
132 | {
133 | // Retry
134 | await Task.Delay(TimeSpan.FromMilliseconds(50));
135 | }
136 | }
137 |
138 | // Unreachable.
139 | throw null;
140 | }
141 | }
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http.Polly/DependencyInjection/PollyHttpClientBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Net.Http;
6 | using Microsoft.Extensions.Http;
7 | using Polly;
8 | using Polly.Extensions.Http;
9 | using Polly.Registry;
10 |
11 | namespace Microsoft.Extensions.DependencyInjection
12 | {
13 | ///
14 | /// Extensions methods for configuring message handlers as part of
15 | /// and message handler pipeline.
16 | ///
17 | public static class PollyHttpClientBuilderExtensions
18 | {
19 | ///
20 | /// Adds a which will surround request execution with the provided
21 | /// .
22 | ///
23 | /// The .
24 | /// The .
25 | /// An that can be used to configure the client.
26 | ///
27 | ///
28 | /// See the remarks on for guidance on configuring policies.
29 | ///
30 | ///
31 | public static IHttpClientBuilder AddPolicyHandler(this IHttpClientBuilder builder, IAsyncPolicy policy)
32 | {
33 | if (builder == null)
34 | {
35 | throw new ArgumentNullException(nameof(builder));
36 | }
37 |
38 | if (policy == null)
39 | {
40 | throw new ArgumentNullException(nameof(policy));
41 | }
42 |
43 | builder.AddHttpMessageHandler(() => new PolicyHttpMessageHandler(policy));
44 | return builder;
45 | }
46 |
47 | ///
48 | /// Adds a which will surround request execution with a policy returned
49 | /// by the .
50 | ///
51 | /// The .
52 | ///
53 | /// Selects an to apply to the current request.
54 | ///
55 | /// An that can be used to configure the client.
56 | ///
57 | ///
58 | /// See the remarks on for guidance on configuring policies.
59 | ///
60 | ///
61 | public static IHttpClientBuilder AddPolicyHandler(
62 | this IHttpClientBuilder builder,
63 | Func> policySelector)
64 | {
65 | if (builder == null)
66 | {
67 | throw new ArgumentNullException(nameof(builder));
68 | }
69 |
70 | if (policySelector == null)
71 | {
72 | throw new ArgumentNullException(nameof(policySelector));
73 | }
74 |
75 | builder.AddHttpMessageHandler(() => new PolicyHttpMessageHandler(policySelector));
76 | return builder;
77 | }
78 |
79 | ///
80 | /// Adds a which will surround request execution with a policy returned
81 | /// by the .
82 | ///
83 | /// The .
84 | ///
85 | /// Selects an to apply to the current request.
86 | ///
87 | /// An that can be used to configure the client.
88 | ///
89 | ///
90 | /// See the remarks on for guidance on configuring policies.
91 | ///
92 | ///
93 | public static IHttpClientBuilder AddPolicyHandler(
94 | this IHttpClientBuilder builder,
95 | Func> policySelector)
96 | {
97 | if (builder == null)
98 | {
99 | throw new ArgumentNullException(nameof(builder));
100 | }
101 |
102 | if (policySelector == null)
103 | {
104 | throw new ArgumentNullException(nameof(policySelector));
105 | }
106 |
107 | builder.AddHttpMessageHandler((services) =>
108 | {
109 | return new PolicyHttpMessageHandler((request) => policySelector(services, request));
110 | });
111 | return builder;
112 | }
113 |
114 | ///
115 | /// Adds a which will surround request execution with a policy returned
116 | /// by the .
117 | ///
118 | /// The .
119 | ///
120 | /// The key used to resolve a policy from the .
121 | ///
122 | /// An that can be used to configure the client.
123 | ///
124 | ///
125 | /// See the remarks on for guidance on configuring policies.
126 | ///
127 | ///
128 | public static IHttpClientBuilder AddPolicyHandlerFromRegistry(this IHttpClientBuilder builder, string policyKey)
129 | {
130 | if (builder == null)
131 | {
132 | throw new ArgumentNullException(nameof(builder));
133 | }
134 |
135 | if (policyKey == null)
136 | {
137 | throw new ArgumentNullException(nameof(policyKey));
138 | }
139 |
140 | builder.AddHttpMessageHandler((services) =>
141 | {
142 | var registry = services.GetRequiredService>();
143 |
144 | var policy = registry.Get>(policyKey);
145 |
146 | return new PolicyHttpMessageHandler(policy);
147 | });
148 | return builder;
149 | }
150 |
151 | ///
152 | /// Adds a which will surround request execution with a policy returned
153 | /// by the .
154 | ///
155 | /// The .
156 | ///
157 | /// Selects an to apply to the current request.
158 | ///
159 | /// An that can be used to configure the client.
160 | ///
161 | ///
162 | /// See the remarks on for guidance on configuring policies.
163 | ///
164 | ///
165 | public static IHttpClientBuilder AddPolicyHandlerFromRegistry(
166 | this IHttpClientBuilder builder,
167 | Func, HttpRequestMessage, IAsyncPolicy> policySelector)
168 | {
169 | if (builder == null)
170 | {
171 | throw new ArgumentNullException(nameof(builder));
172 | }
173 |
174 | if (policySelector == null)
175 | {
176 | throw new ArgumentNullException(nameof(policySelector));
177 | }
178 |
179 | builder.AddHttpMessageHandler((services) =>
180 | {
181 | var registry = services.GetRequiredService>();
182 | return new PolicyHttpMessageHandler((request) => policySelector(registry, request));
183 | });
184 | return builder;
185 | }
186 |
187 | ///
188 | /// Adds a which will surround request execution with a
189 | /// created by executing the provided configuration delegate. The policy builder will be preconfigured to trigger
190 | /// application of the policy for requests that fail with conditions that indicate a transient failure.
191 | ///
192 | /// The .
193 | /// A delegate used to create a .
194 | /// An that can be used to configure the client.
195 | ///
196 | ///
197 | /// See the remarks on for guidance on configuring policies.
198 | ///
199 | ///
200 | /// The provided to has been
201 | /// preconfigured errors to handle errors in the following categories:
202 | ///
203 | /// - Network failures (as )
204 | /// - HTTP 5XX status codes (server errors)
205 | /// - HTTP 408 status code (request timeout)
206 | ///
207 | ///
208 | ///
209 | /// The policy created by will be cached indefinitely per named client. Policies
210 | /// are generally designed to act as singletons, and can be shared when appropriate. To share a policy across multiple
211 | /// named clients, first create the policy and then pass it to multiple calls to
212 | /// as desired.
213 | ///
214 | ///
215 | public static IHttpClientBuilder AddTransientHttpErrorPolicy(
216 | this IHttpClientBuilder builder,
217 | Func, IAsyncPolicy> configurePolicy)
218 | {
219 | if (builder == null)
220 | {
221 | throw new ArgumentNullException(nameof(builder));
222 | }
223 |
224 | if (configurePolicy == null)
225 | {
226 | throw new ArgumentNullException(nameof(configurePolicy));
227 | }
228 |
229 | var policyBuilder = HttpPolicyExtensions.HandleTransientHttpError();
230 |
231 | // Important - cache policy instances so that they are singletons per handler.
232 | var policy = configurePolicy(policyBuilder);
233 |
234 | builder.AddHttpMessageHandler(() => new PolicyHttpMessageHandler(policy));
235 | return builder;
236 | }
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http.Polly/DependencyInjection/PollyServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using Polly.Registry;
6 |
7 | namespace Microsoft.Extensions.DependencyInjection
8 | {
9 | ///
10 | /// Provides convenience extension methods to register and
11 | /// in the service collection.
12 | ///
13 | public static class PollyServiceCollectionExtensions
14 | {
15 | ///
16 | /// Registers an empty in the service collection with service types
17 | /// , and and returns
18 | /// the newly created registry.
19 | ///
20 | /// The .
21 | /// The newly created .
22 | public static IPolicyRegistry AddPolicyRegistry(this IServiceCollection services)
23 | {
24 | if (services == null)
25 | {
26 | throw new ArgumentNullException(nameof(services));
27 | }
28 |
29 | // Create an empty registry, register and return it as an instance. This is the best way to get a
30 | // single instance registered using both interfaces.
31 | var registry = new PolicyRegistry();
32 | services.AddSingleton>(registry);
33 | services.AddSingleton>(registry);
34 |
35 | return registry;
36 | }
37 |
38 | ///
39 | /// Registers the provided in the service collection with service types
40 | /// , and and returns
41 | /// the provided registry.
42 | ///
43 | /// The .
44 | /// The .
45 | /// The provided .
46 | public static IPolicyRegistry AddPolicyRegistry(this IServiceCollection services, IPolicyRegistry registry)
47 | {
48 | if (services == null)
49 | {
50 | throw new ArgumentNullException(nameof(services));
51 | }
52 |
53 | if (registry == null)
54 | {
55 | throw new ArgumentNullException(nameof(registry));
56 | }
57 |
58 | services.AddSingleton>(registry);
59 | services.AddSingleton>(registry);
60 |
61 | return registry;
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http.Polly/HttpRequestMessageExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using Microsoft.Extensions.Http;
5 | using System;
6 | using System.Net.Http;
7 |
8 | namespace Polly
9 | {
10 | ///
11 | /// Extension methods for Polly integration.
12 | ///
13 | public static class HttpRequestMessageExtensions
14 | {
15 | internal static readonly string PolicyExecutionContextKey = "PolicyExecutionContext";
16 |
17 | ///
18 | /// Gets the associated with the provided .
19 | ///
20 | /// The .
21 | /// The if set, otherwise null.
22 | ///
23 | /// The will attach a context to the prior
24 | /// to executing a , if one does not already exist. The will be provided
25 | /// to the policy for use inside the and in other message handlers.
26 | ///
27 | public static Context GetPolicyExecutionContext(this HttpRequestMessage request)
28 | {
29 | if (request == null)
30 | {
31 | throw new ArgumentNullException(nameof(request));
32 | }
33 |
34 | request.Properties.TryGetValue(PolicyExecutionContextKey, out var context);
35 | return context as Context;
36 | }
37 |
38 | ///
39 | /// Sets the associated with the provided .
40 | ///
41 | /// The .
42 | /// The , may be null.
43 | ///
44 | /// The will attach a context to the prior
45 | /// to executing a , if one does not already exist. The will be provided
46 | /// to the policy for use inside the and in other message handlers.
47 | ///
48 | public static void SetPolicyExecutionContext(this HttpRequestMessage request, Context context)
49 | {
50 | if (request == null)
51 | {
52 | throw new ArgumentNullException(nameof(request));
53 | }
54 |
55 | request.Properties[PolicyExecutionContextKey] = context;
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http.Polly/Microsoft.Extensions.Http.Polly.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | The HttpClient factory is a pattern for configuring and retrieving named HttpClients in a composable way. This package integrates IHttpClientFactory with the Polly library, to add transient-fault-handling and resiliency through fluent policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback.
6 |
7 | netstandard2.0
8 | $(NoWarn);CS1591
9 | true
10 | aspnetcore;httpclient
11 |
12 |
13 | Microsoft.Extensions.Http
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http.Polly/PolicyHttpMessageHandler.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Net.Http;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 | using Microsoft.Extensions.DependencyInjection;
9 | using Polly;
10 |
11 | namespace Microsoft.Extensions.Http
12 | {
13 | ///
14 | /// A implementation that executes request processing surrounded by a .
15 | ///
16 | ///
17 | ///
18 | /// This message handler implementation supports the use of policies provided by the Polly library for
19 | /// transient-fault-handling and resiliency.
20 | ///
21 | ///
22 | /// The documentation provided here is focused guidance for using Polly together with the .
23 | /// See the Polly project and its documentation (https://github.com/app-vnext/Polly) for authoritative information on Polly.
24 | ///
25 | ///
26 | /// The extension methods on are designed as a convenient and correct
27 | /// way to create a .
28 | ///
29 | ///
30 | /// The
31 | /// method supports the creation of a for any kind of policy. This includes
32 | /// non-reactive policies, such as Timeout or Cache, which don't require the underlying request to fail first.
33 | ///
34 | ///
35 | /// and the convenience methods
36 | /// only accept the generic . Generic policy instances can be created
37 | /// by using the generic methods on such as .
38 | ///
39 | ///
40 | /// To adapt an existing non-generic , use code like the following:
41 | ///
42 | /// Converting a non-generic IAsyncPolicy policy
to .
43 | ///
44 | /// policy.AsAsyncPolicy<HttpResponseMessage>()
45 | ///
46 | ///
47 | ///
48 | ///
49 | /// The
50 | /// method is an opinionated convenience method that supports the application of a policy for requests that fail due
51 | /// to a connection failure or server error (5XX HTTP status code). This kind of method supports only reactive policies
52 | /// such as Retry, Circuit-Breaker or Fallback. This method is only provided for convenience; we recommend creating
53 | /// your own policies as needed if this does not meet your requirements.
54 | ///
55 | ///
56 | /// Take care when using policies such as Retry or Timeout together as HttpClient provides its own timeout via
57 | /// . When combining Retry and Timeout, will act as a
58 | /// timeout across all tries; a Polly Timeout policy can be configured after a Retry policy in the configuration sequence,
59 | /// to provide a timeout-per-try.
60 | ///
61 | ///
62 | /// All policies provided by Polly are designed to be efficient when used in a long-lived way. Certain policies such as the
63 | /// Bulkhead and Circuit-Breaker maintain state and should be scoped across calls you wish to share the Bulkhead or Circuit-Breaker state.
64 | /// Take care to ensure the correct lifetimes when using policies and message handlers together in custom scenarios. The extension
65 | /// methods provided by are designed to assign a long lifetime to policies
66 | /// and ensure that they can be used when the handler rotation feature is active.
67 | ///
68 | ///
69 | /// The will attach a context to the prior
70 | /// to executing a , if one does not already exist. The will be provided
71 | /// to the policy for use inside the and in other message handlers.
72 | ///
73 | ///
74 | public class PolicyHttpMessageHandler : DelegatingHandler
75 | {
76 | private readonly IAsyncPolicy _policy;
77 | private readonly Func> _policySelector;
78 |
79 | ///
80 | /// Creates a new .
81 | ///
82 | /// The policy.
83 | public PolicyHttpMessageHandler(IAsyncPolicy policy)
84 | {
85 | if (policy == null)
86 | {
87 | throw new ArgumentNullException(nameof(policy));
88 | }
89 |
90 | _policy = policy;
91 | }
92 |
93 | ///
94 | /// Creates a new .
95 | ///
96 | /// A function which can select the desired policy for a given .
97 | public PolicyHttpMessageHandler(Func> policySelector)
98 | {
99 | if (policySelector == null)
100 | {
101 | throw new ArgumentNullException(nameof(policySelector));
102 | }
103 |
104 | _policySelector = policySelector;
105 | }
106 |
107 | ///
108 | protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
109 | {
110 | if (request == null)
111 | {
112 | throw new ArgumentNullException(nameof(request));
113 | }
114 |
115 | // Guarantee the existence of a context for every policy execution, but only create a new one if needed. This
116 | // allows later handlers to flow state if desired.
117 | var cleanUpContext = false;
118 | var context = request.GetPolicyExecutionContext();
119 | if (context == null)
120 | {
121 | context = new Context();
122 | request.SetPolicyExecutionContext(context);
123 | cleanUpContext = true;
124 | }
125 |
126 | HttpResponseMessage response;
127 | try
128 | {
129 | var policy = _policy ?? SelectPolicy(request);
130 | response = await policy.ExecuteAsync((c, ct) => SendCoreAsync(request, c, ct), context, cancellationToken);
131 | }
132 | finally
133 | {
134 | if (cleanUpContext)
135 | {
136 | request.SetPolicyExecutionContext(null);
137 | }
138 | }
139 |
140 | return response;
141 | }
142 |
143 | ///
144 | /// Called inside the execution of the to perform request processing.
145 | ///
146 | /// The .
147 | /// The .
148 | /// The .
149 | /// Returns a that will yield a response when completed.
150 | protected virtual Task SendCoreAsync(HttpRequestMessage request, Context context, CancellationToken cancellationToken)
151 | {
152 | if (request == null)
153 | {
154 | throw new ArgumentNullException(nameof(request));
155 | }
156 |
157 | if (context == null)
158 | {
159 | throw new ArgumentNullException(nameof(context));
160 | }
161 |
162 | return base.SendAsync(request, cancellationToken);
163 | }
164 |
165 | private IAsyncPolicy SelectPolicy(HttpRequestMessage request)
166 | {
167 | var policy = _policySelector(request);
168 | if (policy == null)
169 | {
170 | var message = Resources.FormatPolicyHttpMessageHandler_PolicySelector_ReturnedNull(
171 | "policySelector",
172 | "Policy.NoOpAsync()");
173 | throw new InvalidOperationException(message);
174 | }
175 |
176 | return policy;
177 | }
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http.Polly/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System.Runtime.CompilerServices;
5 |
6 | [assembly: InternalsVisibleTo("Microsoft.Extensions.Http.Polly.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
7 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http.Polly/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //
2 | namespace Microsoft.Extensions.Http
3 | {
4 | using System.Globalization;
5 | using System.Reflection;
6 | using System.Resources;
7 |
8 | internal static class Resources
9 | {
10 | private static readonly ResourceManager _resourceManager
11 | = new ResourceManager("Microsoft.Extensions.Http.Resources", typeof(Resources).GetTypeInfo().Assembly);
12 |
13 | ///
14 | /// The '{0}' function must return a non-null policy instance. To create a policy that takes no action, use '{1}'.
15 | ///
16 | internal static string PolicyHttpMessageHandler_PolicySelector_ReturnedNull
17 | {
18 | get => GetString("PolicyHttpMessageHandler_PolicySelector_ReturnedNull");
19 | }
20 |
21 | ///
22 | /// The '{0}' function must return a non-null policy instance. To create a policy that takes no action, use '{1}'.
23 | ///
24 | internal static string FormatPolicyHttpMessageHandler_PolicySelector_ReturnedNull(object p0, object p1)
25 | => string.Format(CultureInfo.CurrentCulture, GetString("PolicyHttpMessageHandler_PolicySelector_ReturnedNull"), p0, p1);
26 |
27 | private static string GetString(string name, params string[] formatterNames)
28 | {
29 | var value = _resourceManager.GetString(name);
30 |
31 | System.Diagnostics.Debug.Assert(value != null);
32 |
33 | if (formatterNames != null)
34 | {
35 | for (var i = 0; i < formatterNames.Length; i++)
36 | {
37 | value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
38 | }
39 | }
40 |
41 | return value;
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http.Polly/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | The '{0}' function must return a non-null policy instance. To create a policy that takes no action, use '{1}'.
122 |
123 |
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http.Polly/baseline.netcore.json:
--------------------------------------------------------------------------------
1 | {
2 | "AssemblyIdentity": "Microsoft.Extensions.Http.Polly, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
3 | "Types": [
4 | {
5 | "Name": "Polly.HttpRequestMessageExtensions",
6 | "Visibility": "Public",
7 | "Kind": "Class",
8 | "Abstract": true,
9 | "Static": true,
10 | "Sealed": true,
11 | "ImplementedInterfaces": [],
12 | "Members": [
13 | {
14 | "Kind": "Method",
15 | "Name": "GetPolicyExecutionContext",
16 | "Parameters": [
17 | {
18 | "Name": "request",
19 | "Type": "System.Net.Http.HttpRequestMessage"
20 | }
21 | ],
22 | "ReturnType": "Polly.Context",
23 | "Static": true,
24 | "Extension": true,
25 | "Visibility": "Public",
26 | "GenericParameter": []
27 | },
28 | {
29 | "Kind": "Method",
30 | "Name": "SetPolicyExecutionContext",
31 | "Parameters": [
32 | {
33 | "Name": "request",
34 | "Type": "System.Net.Http.HttpRequestMessage"
35 | },
36 | {
37 | "Name": "context",
38 | "Type": "Polly.Context"
39 | }
40 | ],
41 | "ReturnType": "System.Void",
42 | "Static": true,
43 | "Extension": true,
44 | "Visibility": "Public",
45 | "GenericParameter": []
46 | }
47 | ],
48 | "GenericParameters": []
49 | },
50 | {
51 | "Name": "Microsoft.Extensions.Http.PolicyHttpMessageHandler",
52 | "Visibility": "Public",
53 | "Kind": "Class",
54 | "BaseType": "System.Net.Http.DelegatingHandler",
55 | "ImplementedInterfaces": [],
56 | "Members": [
57 | {
58 | "Kind": "Method",
59 | "Name": "SendAsync",
60 | "Parameters": [
61 | {
62 | "Name": "request",
63 | "Type": "System.Net.Http.HttpRequestMessage"
64 | },
65 | {
66 | "Name": "cancellationToken",
67 | "Type": "System.Threading.CancellationToken"
68 | }
69 | ],
70 | "ReturnType": "System.Threading.Tasks.Task",
71 | "Virtual": true,
72 | "Override": true,
73 | "Visibility": "Protected",
74 | "GenericParameter": []
75 | },
76 | {
77 | "Kind": "Method",
78 | "Name": "SendCoreAsync",
79 | "Parameters": [
80 | {
81 | "Name": "request",
82 | "Type": "System.Net.Http.HttpRequestMessage"
83 | },
84 | {
85 | "Name": "context",
86 | "Type": "Polly.Context"
87 | },
88 | {
89 | "Name": "cancellationToken",
90 | "Type": "System.Threading.CancellationToken"
91 | }
92 | ],
93 | "ReturnType": "System.Threading.Tasks.Task",
94 | "Virtual": true,
95 | "Visibility": "Protected",
96 | "GenericParameter": []
97 | },
98 | {
99 | "Kind": "Constructor",
100 | "Name": ".ctor",
101 | "Parameters": [
102 | {
103 | "Name": "policy",
104 | "Type": "Polly.IAsyncPolicy"
105 | }
106 | ],
107 | "Visibility": "Public",
108 | "GenericParameter": []
109 | },
110 | {
111 | "Kind": "Constructor",
112 | "Name": ".ctor",
113 | "Parameters": [
114 | {
115 | "Name": "policySelector",
116 | "Type": "System.Func>"
117 | }
118 | ],
119 | "Visibility": "Public",
120 | "GenericParameter": []
121 | }
122 | ],
123 | "GenericParameters": []
124 | },
125 | {
126 | "Name": "Microsoft.Extensions.DependencyInjection.PollyHttpClientBuilderExtensions",
127 | "Visibility": "Public",
128 | "Kind": "Class",
129 | "Abstract": true,
130 | "Static": true,
131 | "Sealed": true,
132 | "ImplementedInterfaces": [],
133 | "Members": [
134 | {
135 | "Kind": "Method",
136 | "Name": "AddPolicyHandler",
137 | "Parameters": [
138 | {
139 | "Name": "builder",
140 | "Type": "Microsoft.Extensions.DependencyInjection.IHttpClientBuilder"
141 | },
142 | {
143 | "Name": "policy",
144 | "Type": "Polly.IAsyncPolicy"
145 | }
146 | ],
147 | "ReturnType": "Microsoft.Extensions.DependencyInjection.IHttpClientBuilder",
148 | "Static": true,
149 | "Extension": true,
150 | "Visibility": "Public",
151 | "GenericParameter": []
152 | },
153 | {
154 | "Kind": "Method",
155 | "Name": "AddPolicyHandler",
156 | "Parameters": [
157 | {
158 | "Name": "builder",
159 | "Type": "Microsoft.Extensions.DependencyInjection.IHttpClientBuilder"
160 | },
161 | {
162 | "Name": "policySelector",
163 | "Type": "System.Func>"
164 | }
165 | ],
166 | "ReturnType": "Microsoft.Extensions.DependencyInjection.IHttpClientBuilder",
167 | "Static": true,
168 | "Extension": true,
169 | "Visibility": "Public",
170 | "GenericParameter": []
171 | },
172 | {
173 | "Kind": "Method",
174 | "Name": "AddPolicyHandler",
175 | "Parameters": [
176 | {
177 | "Name": "builder",
178 | "Type": "Microsoft.Extensions.DependencyInjection.IHttpClientBuilder"
179 | },
180 | {
181 | "Name": "policySelector",
182 | "Type": "System.Func>"
183 | }
184 | ],
185 | "ReturnType": "Microsoft.Extensions.DependencyInjection.IHttpClientBuilder",
186 | "Static": true,
187 | "Extension": true,
188 | "Visibility": "Public",
189 | "GenericParameter": []
190 | },
191 | {
192 | "Kind": "Method",
193 | "Name": "AddPolicyHandlerFromRegistry",
194 | "Parameters": [
195 | {
196 | "Name": "builder",
197 | "Type": "Microsoft.Extensions.DependencyInjection.IHttpClientBuilder"
198 | },
199 | {
200 | "Name": "policyKey",
201 | "Type": "System.String"
202 | }
203 | ],
204 | "ReturnType": "Microsoft.Extensions.DependencyInjection.IHttpClientBuilder",
205 | "Static": true,
206 | "Extension": true,
207 | "Visibility": "Public",
208 | "GenericParameter": []
209 | },
210 | {
211 | "Kind": "Method",
212 | "Name": "AddPolicyHandlerFromRegistry",
213 | "Parameters": [
214 | {
215 | "Name": "builder",
216 | "Type": "Microsoft.Extensions.DependencyInjection.IHttpClientBuilder"
217 | },
218 | {
219 | "Name": "policySelector",
220 | "Type": "System.Func, System.Net.Http.HttpRequestMessage, Polly.IAsyncPolicy>"
221 | }
222 | ],
223 | "ReturnType": "Microsoft.Extensions.DependencyInjection.IHttpClientBuilder",
224 | "Static": true,
225 | "Extension": true,
226 | "Visibility": "Public",
227 | "GenericParameter": []
228 | },
229 | {
230 | "Kind": "Method",
231 | "Name": "AddTransientHttpErrorPolicy",
232 | "Parameters": [
233 | {
234 | "Name": "builder",
235 | "Type": "Microsoft.Extensions.DependencyInjection.IHttpClientBuilder"
236 | },
237 | {
238 | "Name": "configurePolicy",
239 | "Type": "System.Func, Polly.IAsyncPolicy>"
240 | }
241 | ],
242 | "ReturnType": "Microsoft.Extensions.DependencyInjection.IHttpClientBuilder",
243 | "Static": true,
244 | "Extension": true,
245 | "Visibility": "Public",
246 | "GenericParameter": []
247 | }
248 | ],
249 | "GenericParameters": []
250 | },
251 | {
252 | "Name": "Microsoft.Extensions.DependencyInjection.PollyServiceCollectionExtensions",
253 | "Visibility": "Public",
254 | "Kind": "Class",
255 | "Abstract": true,
256 | "Static": true,
257 | "Sealed": true,
258 | "ImplementedInterfaces": [],
259 | "Members": [
260 | {
261 | "Kind": "Method",
262 | "Name": "AddPolicyRegistry",
263 | "Parameters": [
264 | {
265 | "Name": "services",
266 | "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
267 | }
268 | ],
269 | "ReturnType": "Polly.Registry.IPolicyRegistry",
270 | "Static": true,
271 | "Extension": true,
272 | "Visibility": "Public",
273 | "GenericParameter": []
274 | },
275 | {
276 | "Kind": "Method",
277 | "Name": "AddPolicyRegistry",
278 | "Parameters": [
279 | {
280 | "Name": "services",
281 | "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
282 | },
283 | {
284 | "Name": "registry",
285 | "Type": "Polly.Registry.IPolicyRegistry"
286 | }
287 | ],
288 | "ReturnType": "Polly.Registry.IPolicyRegistry",
289 | "Static": true,
290 | "Extension": true,
291 | "Visibility": "Public",
292 | "GenericParameter": []
293 | }
294 | ],
295 | "GenericParameters": []
296 | }
297 | ]
298 | }
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http/ActiveHandlerTrackingEntry.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Diagnostics;
6 | using System.Threading;
7 | using Microsoft.Extensions.DependencyInjection;
8 | using Microsoft.Extensions.Internal;
9 |
10 | namespace Microsoft.Extensions.Http
11 | {
12 | // Thread-safety: We treat this class as immutable except for the timer. Creating a new object
13 | // for the 'expiry' pool simplifies the threading requirements significantly.
14 | internal class ActiveHandlerTrackingEntry
15 | {
16 | private static readonly TimerCallback _timerCallback = (s) => ((ActiveHandlerTrackingEntry)s).Timer_Tick();
17 | private readonly object _lock;
18 | private bool _timerInitialized;
19 | private Timer _timer;
20 | private TimerCallback _callback;
21 |
22 | public ActiveHandlerTrackingEntry(
23 | string name,
24 | LifetimeTrackingHttpMessageHandler handler,
25 | IServiceScope scope,
26 | TimeSpan lifetime)
27 | {
28 | Name = name;
29 | Handler = handler;
30 | Scope = scope;
31 | Lifetime = lifetime;
32 |
33 | _lock = new object();
34 | }
35 |
36 | public LifetimeTrackingHttpMessageHandler Handler { get; private set; }
37 |
38 | public TimeSpan Lifetime { get; }
39 |
40 | public string Name { get; }
41 |
42 | public IServiceScope Scope { get; }
43 |
44 | public void StartExpiryTimer(TimerCallback callback)
45 | {
46 | if (Lifetime == Timeout.InfiniteTimeSpan)
47 | {
48 | return; // never expires.
49 | }
50 |
51 | if (Volatile.Read(ref _timerInitialized))
52 | {
53 | return;
54 | }
55 |
56 | StartExpiryTimerSlow(callback);
57 | }
58 |
59 | private void StartExpiryTimerSlow(TimerCallback callback)
60 | {
61 | Debug.Assert(Lifetime != Timeout.InfiniteTimeSpan);
62 |
63 | lock (_lock)
64 | {
65 | if (Volatile.Read(ref _timerInitialized))
66 | {
67 | return;
68 | }
69 |
70 | _callback = callback;
71 | _timer = NonCapturingTimer.Create(_timerCallback, this, Lifetime, Timeout.InfiniteTimeSpan);
72 | }
73 | }
74 |
75 | private void Timer_Tick()
76 | {
77 | Debug.Assert(_callback != null);
78 | Debug.Assert(_timer != null);
79 |
80 | lock (_lock)
81 | {
82 | _timer.Dispose();
83 | _timer = null;
84 |
85 | _callback(this);
86 | }
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http/DefaultHttpMessageHandlerBuilder.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Net.Http;
7 |
8 | namespace Microsoft.Extensions.Http
9 | {
10 | internal class DefaultHttpMessageHandlerBuilder : HttpMessageHandlerBuilder
11 | {
12 | public DefaultHttpMessageHandlerBuilder(IServiceProvider services)
13 | {
14 | Services = services;
15 | }
16 |
17 | private string _name;
18 |
19 | public override string Name
20 | {
21 | get => _name;
22 | set
23 | {
24 | if (value == null)
25 | {
26 | throw new ArgumentNullException(nameof(value));
27 | }
28 |
29 | _name = value;
30 | }
31 | }
32 |
33 | public override HttpMessageHandler PrimaryHandler { get; set; } = new HttpClientHandler();
34 |
35 | public override IList AdditionalHandlers { get; } = new List();
36 |
37 | public override IServiceProvider Services { get; }
38 |
39 | public override HttpMessageHandler Build()
40 | {
41 | if (PrimaryHandler == null)
42 | {
43 | var message = Resources.FormatHttpMessageHandlerBuilder_PrimaryHandlerIsNull(nameof(PrimaryHandler));
44 | throw new InvalidOperationException(message);
45 | }
46 |
47 | return CreateHandlerPipeline(PrimaryHandler, AdditionalHandlers);
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http/DefaultTypedHttpClientFactory.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Net.Http;
6 | using System.Threading;
7 | using Microsoft.Extensions.DependencyInjection;
8 |
9 | namespace Microsoft.Extensions.Http
10 | {
11 | internal class DefaultTypedHttpClientFactory : ITypedHttpClientFactory
12 | {
13 | private readonly Cache _cache;
14 | private readonly IServiceProvider _services;
15 |
16 | public DefaultTypedHttpClientFactory(Cache cache, IServiceProvider services)
17 | {
18 | if (cache == null)
19 | {
20 | throw new ArgumentNullException(nameof(cache));
21 | }
22 |
23 | if (services == null)
24 | {
25 | throw new ArgumentNullException(nameof(services));
26 | }
27 |
28 | _cache = cache;
29 | _services = services;
30 | }
31 |
32 | public TClient CreateClient(HttpClient httpClient)
33 | {
34 | if (httpClient == null)
35 | {
36 | throw new ArgumentNullException(nameof(httpClient));
37 | }
38 |
39 | return (TClient)_cache.Activator(_services, new object[] { httpClient });
40 | }
41 |
42 | // The Cache should be registered as a singleton, so it that it can
43 | // act as a cache for the Activator. This allows the outer class to be registered
44 | // as a transient, so that it doesn't close over the application root service provider.
45 | public class Cache
46 | {
47 | private readonly static Func _createActivator = () => ActivatorUtilities.CreateFactory(typeof(TClient), new Type[] { typeof(HttpClient), });
48 |
49 | private ObjectFactory _activator;
50 | private bool _initialized;
51 | private object _lock;
52 |
53 | public ObjectFactory Activator => LazyInitializer.EnsureInitialized(
54 | ref _activator,
55 | ref _initialized,
56 | ref _lock,
57 | _createActivator);
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http/DependencyInjection/DefaultHttpClientBuilder.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | namespace Microsoft.Extensions.DependencyInjection
5 | {
6 | internal class DefaultHttpClientBuilder : IHttpClientBuilder
7 | {
8 | public DefaultHttpClientBuilder(IServiceCollection services, string name)
9 | {
10 | Services = services;
11 | Name = name;
12 | }
13 |
14 | public string Name { get; }
15 |
16 | public IServiceCollection Services { get; }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http/DependencyInjection/IHttpClientBuilder.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System.Net.Http;
5 |
6 | namespace Microsoft.Extensions.DependencyInjection
7 | {
8 | ///
9 | /// A builder for configuring named instances returned by .
10 | ///
11 | public interface IHttpClientBuilder
12 | {
13 | ///
14 | /// Gets the name of the client configured by this builder.
15 | ///
16 | string Name { get; }
17 |
18 | ///
19 | /// Gets the application service collection.
20 | ///
21 | IServiceCollection Services { get; }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http/ExpiredHandlerTrackingEntry.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Net.Http;
6 | using Microsoft.Extensions.DependencyInjection;
7 |
8 | namespace Microsoft.Extensions.Http
9 | {
10 | // Thread-safety: This class is immutable
11 | internal class ExpiredHandlerTrackingEntry
12 | {
13 | private readonly WeakReference _livenessTracker;
14 |
15 | // IMPORTANT: don't cache a reference to `other` or `other.Handler` here.
16 | // We need to allow it to be GC'ed.
17 | public ExpiredHandlerTrackingEntry(ActiveHandlerTrackingEntry other)
18 | {
19 | Name = other.Name;
20 | Scope = other.Scope;
21 |
22 | _livenessTracker = new WeakReference(other.Handler);
23 | InnerHandler = other.Handler.InnerHandler;
24 | }
25 |
26 | public bool CanDispose => !_livenessTracker.IsAlive;
27 |
28 | public HttpMessageHandler InnerHandler { get; }
29 |
30 | public string Name { get; }
31 |
32 | public IServiceScope Scope { get; }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http/HttpClientFactoryExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using Microsoft.Extensions.Options;
5 |
6 | namespace System.Net.Http
7 | {
8 | ///
9 | /// Extensions methods for .
10 | ///
11 | public static class HttpClientFactoryExtensions
12 | {
13 | ///
14 | /// Creates a new using the default configuration.
15 | ///
16 | /// The .
17 | /// An configured using the default configuration.
18 | public static HttpClient CreateClient(this IHttpClientFactory factory)
19 | {
20 | if (factory == null)
21 | {
22 | throw new ArgumentNullException(nameof(factory));
23 | }
24 |
25 | return factory.CreateClient(Options.DefaultName);
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http/HttpClientFactoryOptions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Net.Http;
7 | using System.Threading;
8 | using Microsoft.Extensions.DependencyInjection;
9 |
10 | namespace Microsoft.Extensions.Http
11 | {
12 | ///
13 | /// An options class for configuring the default .
14 | ///
15 | public class HttpClientFactoryOptions
16 | {
17 | // Establishing a minimum lifetime helps us avoid some possible destructive cases.
18 | //
19 | // IMPORTANT: This is used in a resource string. Update the resource if this changes.
20 | internal readonly static TimeSpan MinimumHandlerLifetime = TimeSpan.FromSeconds(1);
21 |
22 | private TimeSpan _handlerLifetime = TimeSpan.FromMinutes(2);
23 |
24 | ///
25 | /// Gets a list of operations used to configure an .
26 | ///
27 | public IList> HttpMessageHandlerBuilderActions { get; } = new List>();
28 |
29 | ///
30 | /// Gets a list of operations used to configure an .
31 | ///
32 | public IList> HttpClientActions { get; } = new List>();
33 |
34 | ///
35 | /// Gets or sets the length of time that a instance can be reused. Each named
36 | /// client can have its own configured handler lifetime value. The default value of this property is two minutes.
37 | /// Set the lifetime to to disable handler expiry.
38 | ///
39 | ///
40 | ///
41 | /// The default implementation of will pool the
42 | /// instances created by the factory to reduce resource consumption. This setting configures the amount of time
43 | /// a handler can be pooled before it is scheduled for removal from the pool and disposal.
44 | ///
45 | ///
46 | /// Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections; creating
47 | /// more handlers than necessary can result in connection delays. Some handlers also keep connections open indefinitely
48 | /// which can prevent the handler from reacting to DNS changes. The value of should be
49 | /// chosen with an understanding of the application's requirement to respond to changes in the network environment.
50 | ///
51 | ///
52 | /// Expiry of a handler will not immediately dispose the handler. An expired handler is placed in a separate pool
53 | /// which is processed at intervals to dispose handlers only when they become unreachable. Using long-lived
54 | /// instances will prevent the underlying from being
55 | /// disposed until all references are garbage-collected.
56 | ///
57 | ///
58 | public TimeSpan HandlerLifetime
59 | {
60 | get => _handlerLifetime;
61 | set
62 | {
63 | if (value != Timeout.InfiniteTimeSpan && value < MinimumHandlerLifetime)
64 | {
65 | throw new ArgumentException(Resources.HandlerLifetime_InvalidValue, nameof(value));
66 | }
67 |
68 | _handlerLifetime = value;
69 | }
70 | }
71 |
72 | ///
73 | ///
74 | /// Gets or sets a value that determines whether the will
75 | /// create a dependency injection scope when building an .
76 | /// If false (default), a scope will be created, otherwise a scope will not be created.
77 | ///
78 | ///
79 | /// This option is provided for compatibility with existing applications. It is recommended
80 | /// to use the default setting for new applications.
81 | ///
82 | ///
83 | ///
84 | ///
85 | /// The will (by default) create a dependency injection scope
86 | /// each time it creates an . The created scope has the same
87 | /// lifetime as the message handler, and will be disposed when the message handler is disposed.
88 | ///
89 | ///
90 | /// When operations that are part of are executed
91 | /// they will be provided with the scoped via
92 | /// . This includes retrieving a message handler
93 | /// from dependency injection, such as one registered using
94 | /// .
95 | ///
96 | ///
97 | public bool SuppressHandlerScope { get; set; }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http/HttpMessageHandlerBuilder.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Net.Http;
8 |
9 | namespace Microsoft.Extensions.Http
10 | {
11 | ///
12 | /// A builder abstraction for configuring instances.
13 | ///
14 | ///
15 | /// The is registered in the service collection as
16 | /// a transient service. Callers should retrieve a new instance for each to
17 | /// be created. Implementors should expect each instance to be used a single time.
18 | ///
19 | public abstract class HttpMessageHandlerBuilder
20 | {
21 | ///
22 | /// Gets or sets the name of the being created.
23 | ///
24 | ///
25 | /// The is set by the infrastructure
26 | /// and is public for unit testing purposes only. Setting the outside of
27 | /// testing scenarios may have unpredictable results.
28 | ///
29 | public abstract string Name { get; set; }
30 |
31 | ///
32 | /// Gets or sets the primary .
33 | ///
34 | public abstract HttpMessageHandler PrimaryHandler { get; set; }
35 |
36 | ///
37 | /// Gets a list of additional instances used to configure an
38 | /// pipeline.
39 | ///
40 | public abstract IList AdditionalHandlers { get; }
41 |
42 | ///
43 | /// Gets an which can be used to resolve services
44 | /// from the dependency injection container.
45 | ///
46 | ///
47 | /// This property is sensitive to the value of
48 | /// . If true this
49 | /// property will be a reference to the application's root service provider. If false
50 | /// (default) this will be a reference to a scoped service provider that has the same
51 | /// lifetime as the handler being created.
52 | ///
53 | public virtual IServiceProvider Services { get; }
54 |
55 | ///
56 | /// Creates an .
57 | ///
58 | ///
59 | /// An built from the and
60 | /// .
61 | ///
62 | public abstract HttpMessageHandler Build();
63 |
64 | protected internal static HttpMessageHandler CreateHandlerPipeline(HttpMessageHandler primaryHandler, IEnumerable additionalHandlers)
65 | {
66 | // This is similar to https://github.com/aspnet/AspNetWebStack/blob/master/src/System.Net.Http.Formatting/HttpClientFactory.cs#L58
67 | // but we don't want to take that package as a dependency.
68 |
69 | if (primaryHandler == null)
70 | {
71 | throw new ArgumentNullException(nameof(primaryHandler));
72 | }
73 |
74 | if (additionalHandlers == null)
75 | {
76 | throw new ArgumentNullException(nameof(additionalHandlers));
77 | }
78 |
79 | var additionalHandlersList = additionalHandlers as IReadOnlyList ?? additionalHandlers.ToArray();
80 |
81 | var next = primaryHandler;
82 | for (var i = additionalHandlersList.Count - 1; i >= 0; i--)
83 | {
84 | var handler = additionalHandlersList[i];
85 | if (handler == null)
86 | {
87 | var message = Resources.FormatHttpMessageHandlerBuilder_AdditionalHandlerIsNull(nameof(additionalHandlers));
88 | throw new InvalidOperationException(message);
89 | }
90 |
91 | // Checking for this allows us to catch cases where someone has tried to re-use a handler. That really won't
92 | // work the way you want and it can be tricky for callers to figure out.
93 | if (handler.InnerHandler != null)
94 | {
95 | var message = Resources.FormatHttpMessageHandlerBuilder_AdditionHandlerIsInvalid(
96 | nameof(DelegatingHandler.InnerHandler),
97 | nameof(DelegatingHandler),
98 | nameof(HttpMessageHandlerBuilder),
99 | Environment.NewLine,
100 | handler);
101 | throw new InvalidOperationException(message);
102 | }
103 |
104 | handler.InnerHandler = next;
105 | next = handler;
106 | }
107 |
108 | return next;
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http/HttpMessageHandlerFactoryExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using Microsoft.Extensions.Options;
5 |
6 | namespace System.Net.Http
7 | {
8 | ///
9 | /// Extensions methods for .
10 | ///
11 | public static class HttpMessageHandlerFactoryExtensions
12 | {
13 | ///
14 | /// Creates a new using the default configuration.
15 | ///
16 | /// The .
17 | /// An configured using the default configuration.
18 | public static HttpMessageHandler CreateHandler(this IHttpMessageHandlerFactory factory)
19 | {
20 | if (factory == null)
21 | {
22 | throw new ArgumentNullException(nameof(factory));
23 | }
24 |
25 | return factory.CreateHandler(Options.DefaultName);
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http/IHttpClientFactory.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using Microsoft.Extensions.DependencyInjection;
5 |
6 | namespace System.Net.Http
7 | {
8 | ///
9 | /// A factory abstraction for a component that can create instances with custom
10 | /// configuration for a given logical name.
11 | ///
12 | ///
13 | /// A default can be registered in an
14 | /// by calling .
15 | /// The default will be registered in the service collection as a singleton.
16 | ///
17 | public interface IHttpClientFactory
18 | {
19 | ///
20 | /// Creates and configures an instance using the configuration that corresponds
21 | /// to the logical name specified by .
22 | ///
23 | /// The logical name of the client to create.
24 | /// A new instance.
25 | ///
26 | ///
27 | /// Each call to is guaranteed to return a new
28 | /// instance. Callers may cache the returned instance indefinitely or surround
29 | /// its use in a using block to dispose it when desired.
30 | ///
31 | ///
32 | /// The default implementation may cache the underlying
33 | /// instances to improve performance.
34 | ///
35 | ///
36 | /// Callers are also free to mutate the returned instance's public properties
37 | /// as desired.
38 | ///
39 | ///
40 | HttpClient CreateClient(string name);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http/IHttpMessageHandlerBuilderFilter.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 |
6 | namespace Microsoft.Extensions.Http
7 | {
8 | ///
9 | /// Used by the to apply additional initialization to the configure the
10 | /// immediately before
11 | /// is called.
12 | ///
13 | public interface IHttpMessageHandlerBuilderFilter
14 | {
15 | ///
16 | /// Applies additional initialization to the
17 | ///
18 | /// A delegate which will run the next .
19 | Action Configure(Action next);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http/IHttpMessageHandlerFactory.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using Microsoft.Extensions.DependencyInjection;
5 |
6 | namespace System.Net.Http
7 | {
8 | ///
9 | /// A factory abstraction for a component that can create instances with custom
10 | /// configuration for a given logical name.
11 | ///
12 | ///
13 | /// A default can be registered in an
14 | /// by calling .
15 | /// The default will be registered in the service collection as a singleton.
16 | ///
17 | public interface IHttpMessageHandlerFactory
18 | {
19 | ///
20 | /// Creates and configures an instance using the configuration that corresponds
21 | /// to the logical name specified by .
22 | ///
23 | /// The logical name of the message handler to create.
24 | /// A new instance.
25 | ///
26 | ///
27 | /// The default implementation may cache the underlying
28 | /// instances to improve performance.
29 | ///
30 | ///
31 | /// The default implementation also manages the lifetime of the
32 | /// handler created, so disposing of the returned by this method may
33 | /// have no effect.
34 | ///
35 | ///
36 | HttpMessageHandler CreateHandler(string name);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http/ITypedHttpClientFactory.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Net.Http;
6 | using Microsoft.Extensions.DependencyInjection;
7 |
8 | namespace Microsoft.Extensions.Http
9 | {
10 | ///
11 | /// A factory abstraction for a component that can create typed client instances with custom
12 | /// configuration for a given logical name.
13 | ///
14 | /// The type of typed client to create.
15 | ///
16 | ///
17 | /// The is infrastructure that supports the
18 | /// and
19 | /// functionality. This type
20 | /// should rarely be used directly in application code, use instead
21 | /// to retrieve typed clients.
22 | ///
23 | ///
24 | /// A default can be registered in an
25 | /// by calling .
26 | /// The default will be registered in the service collection as a singleton
27 | /// open-generic service.
28 | ///
29 | ///
30 | /// The default uses type activation to create typed client instances. Typed
31 | /// client types are not retrieved directly from the . See
32 | /// for details.
33 | ///
34 | ///
35 | ///
36 | /// This sample shows the basic pattern for defining a typed client class.
37 | ///
38 | /// class ExampleClient
39 | /// {
40 | /// private readonly HttpClient _httpClient;
41 | /// private readonly ILogger _logger;
42 | ///
43 | /// // typed clients can use constructor injection to access additional services
44 | /// public ExampleClient(HttpClient httpClient, ILogger<ExampleClient> logger)
45 | /// {
46 | /// _httpClient = httpClient;
47 | /// _logger = logger;
48 | /// }
49 | ///
50 | /// // typed clients can expose the HttpClient for application code to call directly
51 | /// public HttpClient HttpClient => _httpClient;
52 | ///
53 | /// // typed clients can also define methods that abstract usage of the HttpClient
54 | /// public async Task SendHelloRequest()
55 | /// {
56 | /// var response = await _httpClient.GetAsync("/helloworld");
57 | /// response.EnsureSuccessStatusCode();
58 | /// }
59 | /// }
60 | ///
61 | ///
62 | ///
63 | /// This sample shows how to consume a typed client from an ASP.NET Core middleware.
64 | ///
65 | /// // in Startup.cs
66 | /// public void Configure(IApplicationBuilder app, ExampleClient exampleClient)
67 | /// {
68 | /// app.Run(async (context) =>
69 | /// {
70 | /// var response = await _exampleClient.GetAsync("/helloworld");
71 | /// await context.Response.WriteAsync("Remote server said: ");
72 | /// await response.Content.CopyToAsync(context.Response.Body);
73 | /// });
74 | /// }
75 | ///
76 | ///
77 | ///
78 | /// This sample shows how to consume a typed client from an ASP.NET Core MVC Controller.
79 | ///
80 | /// // in Controllers/HomeController.cs
81 | /// public class HomeController : ControllerBase(IApplicationBuilder app, ExampleClient exampleClient)
82 | /// {
83 | /// private readonly ExampleClient _exampleClient;
84 | ///
85 | /// public HomeController(ExampleClient exampleClient)
86 | /// {
87 | /// _exampleClient = exampleClient;
88 | /// }
89 | ///
90 | /// public async Task<IActionResult> Index()
91 | /// {
92 | /// var response = await _exampleClient.GetAsync("/helloworld");
93 | /// var text = await response.Content.ReadAsStringAsync();
94 | /// return Content("Remote server said: " + text, "text/plain");
95 | /// };
96 | /// }
97 | ///
98 | ///
99 | public interface ITypedHttpClientFactory
100 | {
101 | ///
102 | /// Creates a typed client given an associated .
103 | ///
104 | ///
105 | /// An created by the for the named client
106 | /// associated with .
107 | ///
108 | /// An instance of .
109 | TClient CreateClient(HttpClient httpClient);
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http/LifetimeTrackingHttpMessageHandler.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System.Net.Http;
5 |
6 | namespace Microsoft.Extensions.Http
7 | {
8 | // This a marker used to check if the underlying handler should be disposed. HttpClients
9 | // share a reference to an instance of this class, and when it goes out of scope the inner handler
10 | // is eligible to be disposed.
11 | internal class LifetimeTrackingHttpMessageHandler : DelegatingHandler
12 | {
13 | public LifetimeTrackingHttpMessageHandler(HttpMessageHandler innerHandler)
14 | : base(innerHandler)
15 | {
16 | }
17 |
18 | protected override void Dispose(bool disposing)
19 | {
20 | // The lifetime of this is tracked separately by ActiveHandlerTrackingEntry
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Microsoft.Extensions.Http/Logging/HttpHeadersLogValue.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) .NET Foundation. All rights reserved.
2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Collections;
6 | using System.Collections.Generic;
7 | using System.Net.Http.Headers;
8 | using System.Text;
9 |
10 | namespace Microsoft.Extensions.Http.Logging
11 | {
12 | internal class HttpHeadersLogValue : IReadOnlyList>
13 | {
14 | private readonly Kind _kind;
15 |
16 | private string _formatted;
17 | private List> _values;
18 |
19 | public HttpHeadersLogValue(Kind kind, HttpHeaders headers, HttpHeaders contentHeaders)
20 | {
21 | _kind = kind;
22 |
23 | Headers = headers;
24 | ContentHeaders = contentHeaders;
25 | }
26 |
27 | public HttpHeaders Headers { get; }
28 |
29 | public HttpHeaders ContentHeaders { get; }
30 |
31 | private List> Values
32 | {
33 | get
34 | {
35 | if (_values == null)
36 | {
37 | var values = new List>();
38 |
39 | foreach (var kvp in Headers)
40 | {
41 | values.Add(new KeyValuePair(kvp.Key, kvp.Value));
42 | }
43 |
44 | if (ContentHeaders != null)
45 | {
46 | foreach (var kvp in ContentHeaders)
47 | {
48 | values.Add(new KeyValuePair(kvp.Key, kvp.Value));
49 | }
50 | }
51 |
52 | _values = values;
53 | }
54 |
55 | return _values;
56 | }
57 | }
58 |
59 | public KeyValuePair this[int index]
60 | {
61 | get
62 | {
63 | if (index < 0 || index >= Count)
64 | {
65 | throw new IndexOutOfRangeException(nameof(index));
66 | }
67 |
68 | return Values[index];
69 | }
70 | }
71 |
72 | public int Count => Values.Count;
73 |
74 | public IEnumerator> GetEnumerator()
75 | {
76 | return Values.GetEnumerator();
77 | }
78 |
79 | IEnumerator IEnumerable.GetEnumerator()
80 | {
81 | return Values.GetEnumerator();
82 | }
83 |
84 | public override string ToString()
85 | {
86 | if (_formatted == null)
87 | {
88 | var builder = new StringBuilder();
89 | builder.AppendLine(_kind == Kind.Request ? "Request Headers:" : "Response Headers:");
90 |
91 | for (var i = 0; i < Values.Count; i++)
92 | {
93 | var kvp = Values[i];
94 | builder.Append(kvp.Key);
95 | builder.Append(": ");
96 |
97 | foreach (var value in (IEnumerable