(精华)2020年9月13日 C#基础知识点 网络编程HttpClient详解

tech2024-12-16  6

一、HttpClient用法

HttpClient 提供的方法:

GetAsync(String) //以异步操作将GET请求发送给指定的URI GetAsync(URI) //以异步操作将GET请求发送给指定的URI GetAsync(String, HttpCompletionOption) //以异步操作的HTTP完成选项发送GET请求到指定的URI GetAsync(String, CancellationToken) //以异步操作的取消标记发送GET请求到指定URI GetAsync(Uri, HttpCompletionOption) //以异步操作的HTTP完成选项发送GET请求到指定的URI GetAsync(Uri, HttpCompletionOption, CancellationToken) //以异步操作的HTTP完成选项和取消标记发送DELETE请求到指定的URI GetAsync(Uri, HttpCompletionOption, CancellationToken) //以异步操作的HTTP完成选项和取消标记发送DELETE请求到指定的URI GetByteArrayAsync(String) //将GET请求发送到指定URI并在异步操作中以字节数组的形式返回响应正文 GetByteArrayAsync(Uri) //将GET请求发送到指定URI并在一异步操作中以字节数组形式返回响应正文 GetHashCode //用作特定类型的哈希函数,继承自Object GetStreamAsync(String) //将GET请求发送到指定URI并在异步操作中以流的形式返回响应正文 GetStreamAsync(Uri) //将GET请求发送到指定URI并在异步操作以流的形式返回响应正文 GetStreamAsync(String) //将GET请求发送到指定URI并在异步操作中以字符串的形式返回响应正文 GetStringAsync(Uri) //将GET请求发送到指定URI并在异步操作中以字符串形式返回响应正文 using(var httpClient = new HttpClient()) { //other codes }

以上用法是不推荐的,HttpClient 这个对象有点特殊,虽然继承了 IDisposable 接口,但它是可以被共享的(或者说可以被复用),且线程安全。从项目经验来看,推荐在整个应用的生命周期内复用 HttpClient 实例,而不是每次RPC请求的时候就实例化一个,在高并发的情况下,会造成Socket资源的耗尽。

1.1:基本的使用

public class Program { private static readonly HttpClient _httpClient = new HttpClient(); static void Main(string[] args) { HttpAsync(); Console.WriteLine("Hello World!"); Console.Read(); } public static async void HttpAsync() { for (int i = 0; i < 10; i++) { var result = await _httpClient.GetAsync("http://www.baidu.com"); if (result .IsSuccessStatusCode) { Console.WriteLine($"响应状态码: {(int)response.StatusCode} {response.ReasonPhrase}"); var responseBody = await response.Content.ReadAsStringAsync(); Console.WriteLine($"响应内容:{responseBody}"); } } } }

1.2:自定义SendAsync和响应头的使用

public static async Task HttpAsync() { try { using var client = new HttpClient(new SampleMessageHandler("error")); var response = await client.GetAsync(Url); response.EnsureSuccessStatusCode(); ShowHeaders("响应头:", response.Headers); Console.WriteLine($"响应状态码: {(int)response.StatusCode} {response.ReasonPhrase}"); var responseBody = await response.Content.ReadAsStringAsync(); Console.WriteLine($"响应内容:{responseBody}"); } catch (Exception ex) { Console.WriteLine($"{ex.Message}"); } } public static void ShowHeaders(string title, HttpHeaders headers) { Console.WriteLine(title); foreach (var header in headers) { var value = string.Join(" ", header.Value); Console.WriteLine($"Header: {header.Key} Value: {value}"); } Console.WriteLine(); } public class SampleMessageHandler : HttpClientHandler { private readonly string _displayMessage; public SampleMessageHandler(string message) { _displayMessage = message; } protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { Console.WriteLine($"来自SampleMessageHandler的消息: {_displayMessage}"); if (_displayMessage != "error") return base.SendAsync(request, cancellationToken); var response = new HttpResponseMessage(HttpStatusCode.BadRequest); return Task.FromResult(response); } }

1.3:HttpRequestMessage实现请求

public static async Task HttpAsync() { using var client = new HttpClient(); var request = new HttpRequestMessage(HttpMethod.Get, Url); var response = await client.SendAsync(request); if (response.IsSuccessStatusCode) { Console.WriteLine($"响应状态码: {(int)response.StatusCode} {response.ReasonPhrase}"); var responseBody = await response.Content.ReadAsStringAsync(); Console.WriteLine($"响应内容 {responseBody}"); } }

1.4:TCP实现SendAsync数据发送

public static async Task<string> HttpAsync() { const int readBufferSize = 1024; const string hostname = "127.0.0.1"; try { using var client = new TcpClient(); await client.ConnectAsync(hostname, 80); var stream = client.GetStream(); var header = "GET / HTTP/1.1\r\n" + $"Host: {hostname}:80\r\n" + "Connection: close\r\n" + "\r\n"; var buffer = Encoding.UTF8.GetBytes(header); await stream.WriteAsync(buffer, 0, buffer.Length); await stream.FlushAsync(); var ms = new MemoryStream(); buffer = new byte[readBufferSize]; var read = 0; do { read = await stream.ReadAsync(buffer, 0, readBufferSize); ms.Write(buffer, 0, read); Array.Clear(buffer, 0, buffer.Length); } while (read > 0); ms.Seek(0, SeekOrigin.Begin); using var reader = new StreamReader(ms); return reader.ReadToEnd(); } catch (SocketException ex) { Console.WriteLine(ex.Message); return null; } }

HttpClient到底层的执行过程图

二、HttpClient高级用法

public async Task<string> GetAccessTokenAsync() { string uri = "你的URL"; HttpClientHandler handler = new HttpClientHandler { //设置是否发送凭证信息,有的服务器需要验证身份,不是所有服务器需要 UseDefaultCredentials = false }; HttpClient httpClient = new HttpClient(handler); HttpResponseMessage response = await httpClient.GetAsync(uri); response.EnsureSuccessStatusCode(); //回复结果直接读成字符串 string resp = await response.Content.ReadAsStringAsync(); JObject json = (JObject)JsonConvert.DeserializeObject(resp); string accessToken = json["access_token"].ToString(); //采用流读数据 //using (Stream streamResponse = await response.Content.ReadAsStreamAsync()) //{ // StreamReader reader = new StreamReader(streamResponse); // string responseFromServer = reader.ReadToEnd(); // JObject res = (JObject)JsonConvert.DeserializeObject(responseFromServer); // accessToken = res["access_token"].ToString(); // reader.Close(); //} //获得许可证凭证 PostMailAsync(accessToken); //关闭响应 return "success"; }

优化:帮HttpClient预热 我们采用一种预热方式,在正式发post请求之前,先发一个head请求:

_httpClient.SendAsync(new HttpRequestMessage { Method = new HttpMethod("HEAD"), RequestUri = new Uri(BASE_ADDRESS + "/") }) .Result.EnsureSuccessStatusCode();

经测试,通过这种热身方法,可以将第一次请求的耗时由2s左右降到1s以内(测试结果是700多ms)。

存在问题 复用 HttpClient 后,依然存在一些问题:

因为是复用的 HttpClient ,那么一些公共的设置就没办法灵活的调整了,如请求头的自定义。因为 HttpClient 请求每个 url 时,会缓存该 url 对应的主机 ip ,从而会导致 DNS 更新失效( TTL 失效了) 那么有没有办法解决HttpClient的这些个问题?直到 HttpClientFactory 的出现,这些坑 “完美” 规避掉了。

三、HttpClientFactory

HttpClientFacotry 很高效,可以最大程度上节省系统 socket 。(“JUST USE IT AND FXXK SHUT UP”😛)Factory,顾名思义 HttpClientFactory 就是 HttpClient 的工厂,内部已经帮我们处理好了对 HttpClient 的管理,不需要我们人工进行对象释放,同时,支持自定义请求头,支持DNS更新等等等。从微软源码分析,HttpClient 继承自 HttpMessageInvoker ,而 HttpMessageInvoker 实质就是 HttpClientHandler 。HttpClientFactory 创建的 HttpClient ,也即是 HttpClientHandler ,这些个 HttpClient 被放到了“池子”中,工厂每次在 create 的时候会自动判断是新建还是复用。(默认生命周期为 2 min)

3.1.1在 Startup.cs 中进行注册

public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { //other codes services.AddHttpClient("client_1", config => //这里指定的 name=client_1 ,可以方便我们后期复用该实例 { config.BaseAddress = new Uri("http://client_1.com"); config.DefaultRequestHeaders.Add("header_1", "header_1"); }); services.AddHttpClient("client_2", config => { config.BaseAddress = new Uri("http://client_2.com"); config.DefaultRequestHeaders.Add("header_2", "header_2"); }); services.AddHttpClient(); //other codes services.AddMvc().AddFluentValidation(); } }

3.1.2使用,这里直接以 controller 为例,其他地方自行 DI

public class TestController : ControllerBase { private readonly IHttpClientFactory _httpClient; public TestController(IHttpClientFactory httpClient) { _httpClient = httpClient; } public async Task<ActionResult> Test() { var client = _httpClient.CreateClient("client_1"); //复用在 Startup 中定义的 client_1 的 httpclient var result = await client.GetStringAsync("/page1.html"); var client2 = _httpClient.CreateClient(); //新建一个 HttpClient var result2 = await client.GetStringAsync("http://www.site.com/XXX.html"); return null; } }

3.2.1:使用自定义类执行 HttpClientFactory 请求 自定义 HttpClientFactory 请求类

public class SampleClient { public HttpClient Client { get; private set; } public SampleClient(HttpClient httpClient) { httpClient.BaseAddress = new Uri("https://api.SampleClient.com/"); httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); httpClient.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample"); Client = httpClient; } }

3.2.2在 Startup.cs 中 ConfigureService 方法中注册 SampleClient

services.AddHttpClient<ISampleClient, SampleClient>();

3.3.3调用:

public class ValuesController : Controller { private readonly ISampleClient _sampleClient; public ValuesController(ISampleClient sampleClient) { _sampleClient = sampleClient; } [HttpGet] public async Task<ActionResult> Get() { string result = await _sampleClient.GetData(); return Ok(result); } }
最新回复(0)