Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Let's Talk HTTP in .NET Core

Let's Talk HTTP in .NET Core

Avatar for Steve Gordon

Steve Gordon

June 07, 2019
Tweet

More Decks by Steve Gordon

Other Decks in Technology

Transcript

  1. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 What we’ll cover • Why do we

    need IHttpClientFactory? • How to use IHttpClientFactory • Outgoing middleware • Handling transient errors with Polly • Patterns and recommendations • Other HTTP improvements in .NET Core 2.1 • Coming in .NET Core 3.0 NOTE: I'm generally speaking about .NET Core today.
  2. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { [HttpGet]

    public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my-user-agent"); var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } } }
  3. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { [HttpGet]

    public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my-user-agent"); var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } } }
  4. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class Startup { public Startup(IConfiguration configuration)

    { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); } }
  5. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class Startup { public Startup(IConfiguration configuration)

    { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); } }
  6. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class Startup { public Startup(IConfiguration configuration)

    { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddSingleton<HttpClient>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); } }
  7. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { [HttpGet]

    public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my-user-agent"); var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await client.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } } }
  8. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly HttpClient _httpClient; public GitHubController(HttpClient httpClient) => _httpClient = httpClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my-user-agent"); var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await client.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } } }
  9. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly HttpClient _httpClient; public GitHubController(HttpClient httpClient) => _httpClient = httpClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { using (var client = new HttpClient()) { client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my-user-agent"); var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await client.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } } }
  10. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly HttpClient _httpClient; public GitHubController(HttpClient httpClient) => _httpClient = httpClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my-user-agent"); var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); var response = await client.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  11. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly HttpClient _httpClient; public GitHubController(HttpClient httpClient) => _httpClient = httpClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("Accept", "application/vnd.github.v3+json"); request.Headers.Add("User-Agent", "my-user-agent"); var response = await client.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  12. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly HttpClient _httpClient; public GitHubController(HttpClient httpClient) => _httpClient = httpClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("Accept", "application/vnd.github.v3+json"); request.Headers.Add("User-Agent", "my-user-agent"); var response = await client.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  13. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly HttpClient _httpClient; public GitHubController(HttpClient httpClient) => _httpClient = httpClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("Accept", "application/vnd.github.v3+json"); request.Headers.Add("User-Agent", "my-user-agent"); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  14. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 IHttpClientFactory • Manages the lifetime of HttpMessageHandlers

    • Provides a central location for naming and configuring logical HttpClients • Codifies the concept of outgoing middleware via delegating handlers • Integrates with Polly for transient-fault handling • Improved diagnostics and logging
  15. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class Startup { public Startup(IConfiguration configuration)

    { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddSingleton<HttpClient>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); } }
  16. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class Startup { public Startup(IConfiguration configuration)

    { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddSingleton<HttpClient>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); } }
  17. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class Startup { public Startup(IConfiguration configuration)

    { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddHttpClient(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); } }
  18. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly HttpClient _httpClient; public GitHubController(HttpClient httpClient) => _httpClient = httpClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("Accept", "application/vnd.github.v3+json"); request.Headers.Add("User-Agent", "my-user-agent"); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  19. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly HttpClient _httpClient; public GitHubController(HttpClient httpClient) => _httpClient = httpClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("Accept", "application/vnd.github.v3+json"); request.Headers.Add("User-Agent", "my-user-agent"); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  20. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("Accept", "application/vnd.github.v3+json"); request.Headers.Add("User-Agent", "my-user-agent"); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  21. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("Accept", "application/vnd.github.v3+json"); request.Headers.Add("User-Agent", "my-user-agent"); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  22. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("Accept", "application/vnd.github.v3+json"); request.Headers.Add("User-Agent", "my-user-agent"); var httpClient = _factory.CreateClient(); var response = await httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  23. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class Startup { ... public void

    ConfigureServices(IServiceCollection services) { services.AddHttpClient(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  24. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class Startup { ... public void

    ConfigureServices(IServiceCollection services) { services.AddHttpClient("github", client => { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my-user-agent"); }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  25. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class Startup { ... public void

    ConfigureServices(IServiceCollection services) { services.AddHttpClient("github", client => { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my-user-agent"); }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  26. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class Startup { ... public void

    ConfigureServices(IServiceCollection services) { services.AddHttpClient("github", client => { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my-user-agent"); }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  27. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var url = "https://api.github.com/orgs/aspnet/repos"; var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("Accept", "application/vnd.github.v3+json"); request.Headers.Add("User-Agent", "my-user-agent"); var httpClient = _factory.CreateClient(); var response = await httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  28. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); request.Headers.Add("Accept", "application/vnd.github.v3+json"); request.Headers.Add("User-Agent", "my-user-agent"); var httpClient = _factory.CreateClient(); var response = await httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  29. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); request.Headers.Add("Accept", "application/vnd.github.v3+json"); request.Headers.Add("User-Agent", "my-user-agent"); var httpClient = _factory.CreateClient(); var response = await httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  30. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var httpClient = _factory.CreateClient(); var response = await httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  31. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var httpClient = _factory.CreateClient(); var response = await httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  32. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var httpClient = _factory.CreateClient("github"); var response = await httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  33. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubClient : IGitHubClient { private

    readonly HttpClient _httpClient; public GitHubClient(HttpClient client) { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my_user_agent"); _httpClient = client; } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data); } }
  34. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubClient : IGitHubClient { private

    readonly HttpClient _httpClient; public GitHubClient(HttpClient client) { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my_user_agent"); _httpClient = client; } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data); } }
  35. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubClient : IGitHubClient { private

    readonly HttpClient _httpClient; public GitHubClient(HttpClient client) { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my_user_agent"); _httpClient = client; } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data); } }
  36. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubClient : IGitHubClient { private

    readonly HttpClient _httpClient; public GitHubClient(HttpClient client) { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my_user_agent"); _httpClient = client; } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data); } }
  37. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class Startup { ... public void

    ConfigureServices(IServiceCollection services) { services.AddHttpClient("github", client => { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my-user-agent"); }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  38. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class Startup { ... public void

    ConfigureServices(IServiceCollection services) { services.AddHttpClient<IGitHubClient, GitHubClient>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  39. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly IHttpClientFactory _factory; public GitHubController(IHttpClientFactory factory) => _factory = factory; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var httpClient = _factory.CreateClient("github"); var response = await httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  40. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly IGitHubClient _gitHubClient; public GitHubController(IGitHubClient gitHubClient) => _gitHubClient = gitHubClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var httpClient = _factory.CreateClient("github"); var response = await httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  41. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly IGitHubClient _gitHubClient; public GitHubController(IGitHubClient gitHubClient) => _gitHubClient = gitHubClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var httpClient = _factory.CreateClient("github"); var response = await httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return Ok(JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data)); } }
  42. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly IGitHubClient _gitHubClient; public GitHubController(IGitHubClient gitHubClient) => _gitHubClient = gitHubClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var data = await _gitHubClient.GetAspNetReposAsync(); return Ok(data); } }
  43. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 Outgoing "middleware" Outer Handler Middle Handler Inner

    Handler HttpClientHandler SocketsHttpHandler https://api.github.com Our Application HttpClient H t t p R e q u e s t M e s s a g e H t t p R e s p o n s e M e s s a g e
  44. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class StatusCodeMetricHandler : DelegatingHandler { private

    readonly IMonitoringService _monitoringService; public StatusCodeMetricHandler(IMonitoringService monitoringService) => _monitoringService = monitoringService; protected override async Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken ct) { // note: we could do things with the request before passing it along var response = await base.SendAsync(request, ct); _monitoringService.RecordStatusCodeMetric((int)response.StatusCode); return response; } }
  45. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class StatusCodeMetricHandler : DelegatingHandler { private

    readonly IMonitoringService _monitoringService; public StatusCodeMetricHandler(IMonitoringService monitoringService) => _monitoringService = monitoringService; protected override async Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken ct) { // note: we could do things with the request before passing it along var response = await base.SendAsync(request, ct); _monitoringService.RecordStatusCodeMetric((int)response.StatusCode); return response; } }
  46. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class StatusCodeMetricHandler : DelegatingHandler { private

    readonly IMonitoringService _monitoringService; public StatusCodeMetricHandler(IMonitoringService monitoringService) => _monitoringService = monitoringService; protected override async Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken ct) { // note: we could do things with the request before passing it along var response = await base.SendAsync(request, ct); _monitoringService.RecordStatusCodeMetric((int)response.StatusCode); return response; } }
  47. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class StatusCodeMetricHandler : DelegatingHandler { private

    readonly IMonitoringService _monitoringService; public StatusCodeMetricHandler(IMonitoringService monitoringService) => _monitoringService = monitoringService; protected override async Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken ct) { // note: we could do things with the request before passing it along var response = await base.SendAsync(request, ct); _monitoringService.RecordStatusCodeMetric((int)response.StatusCode); return response; } }
  48. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class StatusCodeMetricHandler : DelegatingHandler { private

    readonly IMonitoringService _monitoringService; public StatusCodeMetricHandler(IMonitoringService monitoringService) => _monitoringService = monitoringService; protected override async Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken ct) { // note: we could do things with the request before passing it along var response = await base.SendAsync(request, ct); _monitoringService.RecordStatusCodeMetric((int)response.StatusCode); return response; } }
  49. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class StatusCodeMetricHandler : DelegatingHandler { private

    readonly IMonitoringService _monitoringService; public StatusCodeMetricHandler(IMonitoringService monitoringService) => _monitoringService = monitoringService; protected override async Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken ct) { // note: we could do things with the request before passing it along var response = await base.SendAsync(request, ct); _monitoringService.RecordStatusCodeMetric((int)response.StatusCode); return response; } }
  50. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class Startup { ... public void

    ConfigureServices(IServiceCollection services) { services.AddHttpClient<IGitHubClient, GitHubClient>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  51. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class Startup { ... public void

    ConfigureServices(IServiceCollection services) { services.AddTransient<IMonitoringService, MonitoringService>(); services.AddTransient<StatusCodeMetricHandler>(); services.AddHttpClient<IGitHubClient, GitHubClient>() .AddHttpMessageHandler<StatusCodeMetricHandler>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  52. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class Startup { ... public void

    ConfigureServices(IServiceCollection services) { services.AddTransient<IMonitoringService, MonitoringService>(); services.AddTransient<StatusCodeMetricHandler>(); services.AddHttpClient<IGitHubClient, GitHubClient>() .AddHttpMessageHandler<StatusCodeMetricHandler>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  53. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 Polly • Polly is a .NET resilience

    and transient-fault-handling library • Integrates easily with IHttpClientFactory • Retry • Circuit-breaker • Timeout • Bulkhead isolation • Cache • Fallback • PolicyWrap
  54. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class Startup { ... public void

    ConfigureServices(IServiceCollection services) { services.AddTransient<IMonitoringService, MonitoringService>(); services.AddTransient<StatusCodeMetricHandler>(); services.AddHttpClient<IGitHubClient, GitHubClient>() .AddHttpMessageHandler<StatusCodeMetricHandler>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  55. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class Startup { ... public void

    ConfigureServices(IServiceCollection services) { services.AddTransient<IMonitoringService, MonitoringService>(); services.AddTransient<StatusCodeMetricHandler>(); services.AddHttpClient<IGitHubClient, GitHubClient>() .AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(3, retryCount => TimeSpan.FromSeconds(Math.Pow(2, retryCount)))) .AddHttpMessageHandler<StatusCodeMetricHandler>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { … } }
  56. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubClient : IGitHubClient { private

    readonly HttpClient _httpClient; public GitHubClient(HttpClient client) { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my_user_agent"); _httpClient = client; } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var response = await _httpClient.SendAsync(request); var data = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<IEnumerable<GitHubRepo>>(data); } }
  57. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubClient : IGitHubClient { private

    readonly HttpClient _httpClient; public GitHubClient(HttpClient client) { client.BaseAddress = new Uri("https://api.github.com/"); client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); client.DefaultRequestHeaders.Add("User-Agent", "my_user_agent"); _httpClient = client; } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var response = await _httpClient.SendAsync(request); return await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(); } }
  58. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubClient : IGitHubClient { private

    readonly HttpClient _httpClient; public GitHubClient(HttpClient client) { // hidden for brevity ... } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var response = await _httpClient.SendAsync(request); return await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(); } }
  59. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubClient : IGitHubClient { private

    readonly HttpClient _httpClient; public GitHubClient(HttpClient client) { // hidden for brevity ... } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var response = await _httpClient.SendAsync(request); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(); } }
  60. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubClient : IGitHubClient { private

    readonly HttpClient _httpClient; public GitHubClient(HttpClient client) { // hidden for brevity ... } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var response = await _httpClient.SendAsync(request); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>() : Array.Empty<GitHubRepo>(); } }
  61. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubClient : IGitHubClient { private

    readonly HttpClient _httpClient; public GitHubClient(HttpClient client) { // hidden for brevity ... } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync() { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var response = await _httpClient.SendAsync(request); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>() : Array.Empty<GitHubRepo>(); } }
  62. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubClient : IGitHubClient { private

    readonly HttpClient _httpClient; public GitHubClient(HttpClient client) { // hidden for brevity ... } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); var response = await _httpClient.SendAsync(request); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>() : Array.Empty<GitHubRepo>(); } }
  63. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubClient : IGitHubClient { private

    readonly HttpClient _httpClient; public GitHubClient(HttpClient client) { // hidden for brevity ... } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); ct.ThrowIfCancellationRequested(); var response = await _httpClient.SendAsync(request); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>() : Array.Empty<GitHubRepo>(); } }
  64. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubClient : IGitHubClient { private

    readonly HttpClient _httpClient; public GitHubClient(HttpClient client) { // hidden for brevity ... } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); ct.ThrowIfCancellationRequested(); var response = await _httpClient.SendAsync(request, ct); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>() : Array.Empty<GitHubRepo>(); } }
  65. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubClient : IGitHubClient { private

    readonly HttpClient _httpClient; public GitHubClient(HttpClient client) { // hidden for brevity ... } public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); ct.ThrowIfCancellationRequested(); var response = await _httpClient.SendAsync(request, ct); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(ct) : Array.Empty<GitHubRepo>(); } }
  66. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly IGitHubClient _gitHubClient; public GitHubController(IGitHubClient gitHubClient) => _gitHubClient = gitHubClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos() { var data = await _gitHubClient.GetAspNetReposAsync(); return Ok(data); } }
  67. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly IGitHubClient _gitHubClient; public GitHubController(IGitHubClient gitHubClient) => _gitHubClient = gitHubClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos(CancellationToken ct) { var data = await _gitHubClient.GetAspNetReposAsync(ct); return Ok(data); } }
  68. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubController : ControllerBase { private

    readonly IGitHubClient _gitHubClient; public GitHubController(IGitHubClient gitHubClient) => _gitHubClient = gitHubClient; [HttpGet] public async Task<ActionResult<IEnumerable<GitHubRepo>>> GetAspNetRepos(CancellationToken ct) { var data = await _gitHubClient.GetAspNetReposAsync(ct); return Ok(data); } }
  69. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 When Should I Dispose? • HttpClient (using

    IHttpClientFactory) – No effect • HttpClient (without IHttpClientFactory) – Generally never • HttpRequestMessage – Only has an effect if sending StreamContent. • HttpResponseMessage – Safest is always to dispose after handling response. No effect in most cases.
  70. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubClient : IGitHubClient { //

    ctor - hidden for brevity ... public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); ct.ThrowIfCancellationRequested(); var response = await _httpClient.SendAsync(request, ct); return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(ct) : Array.Empty<GitHubRepo>(); } } }
  71. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubClient : IGitHubClient { //

    ctor - hidden for brevity ... public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); ct.ThrowIfCancellationRequested(); var response = await _httpClient.SendAsync(request, ct); try { return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(ct) : Array.Empty<GitHubRepo>(); } finally { response.Dispose(); } } }
  72. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 public class GitHubClient : IGitHubClient { //

    ctor - hidden for brevity ... public async Task<IEnumerable<GitHubRepo>> GetAspNetReposAsync(CancellationToken ct = default) { var request = new HttpRequestMessage(HttpMethod.Get, "orgs/aspnet/repos"); ct.ThrowIfCancellationRequested(); var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, ct); try { return response.IsSuccessStatusCode ? await response.Content.ReadAsAsync<IEnumerable<GitHubRepo>>(ct) : Array.Empty<GitHubRepo>(); } finally { response.Dispose(); } } }
  73. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 SocketsHttpHandler • Sockets are the basis of

    both outgoing and incoming networking communication • SocketsHttpHandler is a brand-new managed HttpMessageHandler and is the default implementation for HttpClient • Consistent behaviour across platforms and platform/dependency versions. Built on top of System.Net.Sockets • Elimination of platform dependencies on libcurl (for Linux) and WinHTTP (for Windows) – simplifies both development, deployment and servicing • Enabled by default in .NET Core 2.1+ (Does not support HTTP/2)
  74. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 Coming in .NET Core 3.0 • HTTP/2

    is supported (primarily for gRPC scenarios) although not enabled by default • HttpClient now includes a Version property which applies to all messages sent via that client (HTTP/1.1 by default) • TLS 1.3 and OpenSSL 1.1.1 on Linux reduces connection times and improves security – Windows and macOS when OS support it
  75. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 In Summary • Use IHttpClientFactory in .NET

    Core 2.1+ • Use Polly for transient-fault handling • Beware content.ReadAsStringAsync and client.GetStringAsync • Expect and handle errors • Pass cancellation tokens • Go enjoy talking HTTP! ☺
  76. @stevejgordon www.stevejgordon.co.uk https://bit.ly/letstalkhttp40 Resources 1. https://bit.ly/letstalkhttp40 2. https://www.stevejgordon.co.uk/httpclient-creation-and-disposal-internals-should-i-dispose-of- httpclient 3.

    https://www.stevejgordon.co.uk/demystifying-httpclient-internals-httprequestmessage 4. https://www.stevejgordon.co.uk/demystifying-httpclient-internals-httpresponsemessage 5. https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/ 6. https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore- 2.1 7. https://github.com/App-vNext/Polly