C# 关于HttpClient的使用

发布时间 2023-08-22 11:24:59作者: XiongHui23

当我们在C#中调用RestApi通常有三种方式

HttpWebRequest 是一种相对底层的处理 Http request/response 的方式 已过时
WebClient 提供了对 HttpWebRequest 的高层封装,来简化使用者的调用 已过时
HttpClient 是一种新的处理 Http request/response 工具包,具有更高的性能 推荐使用

为什么推荐使用HttpClient

WebRequest

.NET Framework 中第一个用来处理 Http 请求的类,非常灵活。可以用来存取 headers, cookies, protocols 和 timeouts 等等。但灵活的同时也导致使用难度加大,各个开发都有自己的写法。

HttpWebRequest http =  (HttpWebRequest)WebRequest.Create("http://localhost:8081/api/default");
WebResponse response = http.GetResponse();
Stream memoryStream = response.GetResponseStream();
StreamReader streamReader = new StreamReader(memoryStream);
string data = streamReader.ReadToEnd();
WebClient

基于HttpWebRequest的封装,提供了使用便利性,但舍弃了部分性能。在只做简单api调用时优势很大。不太适用bgy各系统之间调用的千奇百怪鉴权。

using (var webClient = new WebClient()) { 
    var data = webClient.DownloadString("http://localhost:8081/api/default"); 
}
HttpClient

HttpClient 作为后来之物,它吸取了 HttpWebRequest 的灵活性及 WebClient 的便捷性,所以说 ? 和 ? 可兼得。

//单例注入HttpClient client
HttpResponseMessage response = await client.GetAsync(uri);
if (response.IsSuccessStatusCode)
{
    var data = await response.Content.ReadAsStringAsync();
}

基于bgy接口调用行情的HttpClient封装

url 请求地址 鉴于get请求,参数都直接拼接在url后面
data json字符串 多数都是json交互,少量会有Form
headers 请求头 多数对接都是请求头附加token用于网关鉴权,使用字典类型声明方便快捷
contentType 请求类型 默认json,可选Form
tryCount 重试次数 目前是即时重试,后续可拓展EventBus做延迟重试功能
writeLog 是否写日志 建议接口调用都保留日志,避免甩锅?‍
Get请求
private async Task<string> GetHandle(string url, Dictionary<string, string> headers = null, int tryCount = 0, bool writeLog = true)
{
	HttpRequestMessage message = new HttpRequestMessage(System.Net.Http.HttpMethod.Get, url);
	if (headers?.ContainsKey("Authorization") == true)
	{
		headers.TryGetValue("Authorization", out string val);
		headers.Remove("Authorization");
		message.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", val);
	}
	if (message.Content != null)
		message.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(JsonDataType);
	headers?.ForEach(item =>
	{
		message.Headers.Add(item.Key, item.Value);
	});
	if (writeLog)
	{
		var apiLog = new SystemApiLog
		{
			RequestUrl = message.RequestUri.AbsoluteUri
		};
		DateTime dtSpan = DateTime.Now;
		apiLog.Method = "HttpGet";
		apiLog.ResponseIp = localIp;
		apiLog.ApiPath = GetApiPath();
		try
		{
			var result = await SendAsync(message);
			var html = await result.Content.ReadAsStringAsync();
			apiLog.ResponseData = html;
			apiLog.ElapsedTime = (DateTime.Now - dtSpan).TotalSeconds;
			BusHelper.ApiLog(apiLog);
			return html;
		}
		catch (Exception ex)
		{
			apiLog.ResponseData = ex.ToString();
			apiLog.ElapsedTime = (DateTime.Now - dtSpan).TotalSeconds;
			BusHelper.ApiLog(apiLog); //写日志换成自己的表!
			if (tryCount > 0)
				return await GetHandle(url, headers, --tryCount);
			return null;
		}
	}
	else
	{
		var result = await SendAsync(message);
		var html = await result.Content.ReadAsStringAsync();
		return html;
	}
}

Post请求
private async Task<string> PostHandle(string url, string data, Dictionary<string, string> headers = null, ContentType contentType = ContentType.Json, int tryCount = 0, bool writeLog = true)
{
	HttpRequestMessage message = new HttpRequestMessage(System.Net.Http.HttpMethod.Post, url)
	{
		Content = new StringContent(data, Encoding.UTF8)
	};
	if (headers?.ContainsKey("Authorization") == true)
	{
		headers.TryGetValue("Authorization", out string val);
		headers.Remove("Authorization");
		message.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", val);
	}
	message.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType == ContentType.Form ? FromDataType : JsonDataType);
	headers?.ForEach(item =>
	{
		message.Headers.Add(item.Key, item.Value);
	});
	//请求日志
	if (writeLog)
	{
		var apiLog = new SystemApiLog
		{
			RequestUrl = message.RequestUri.AbsoluteUri
		};
		DateTime dtSpan = DateTime.Now;
		apiLog.Method = "HttpPost";
		apiLog.ResponseIp = localIp;
		apiLog.RequestData = data;
		apiLog.ApiPath = GetApiPath();
		try
		{
			var result = await SendAsync(message);
			var html = await result.Content.ReadAsStringAsync();
			apiLog.ResponseData = html;
			apiLog.ElapsedTime = (DateTime.Now - dtSpan).TotalSeconds;
			BusHelper.ApiLog(apiLog);//写日志换成自己的表!
			return html;
		}
		//异常重试及异常日志
		catch (Exception ex)
		{
			apiLog.ResponseData = ex.ToString();
			apiLog.ElapsedTime = (DateTime.Now - dtSpan).TotalSeconds;
			BusHelper.ApiLog(apiLog);
			if (tryCount > 0)
				return await PostHandle(url, data, headers, contentType, --tryCount);
			return null;
		}
	}
	else
	{
		var result = await SendAsync(message);
		var html = await result.Content.ReadAsStringAsync();
		return html;
	}
}
依赖的常量or方法
private const string FromDataType = "application/x-www-form-urlencoded";
private const string JsonDataType = "application/json";
private static readonly string localIp = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()
                .Select(p => p.GetIPProperties())
                .SelectMany(p => p.UnicastAddresses) 
                .FirstOrDefault(p => p.Address.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(p.Address))?.Address.ToString();

/// <summary>
/// 反射请求方法获取apipath
/// </summary>
private string GetApiPath()
{
	try
	{
		var st = new StackTrace(true);
		//index:0为本身的方法;1为调用方法;2为其上上层,依次类推
		MethodBase mb = st.GetFrame(4).GetMethod();
		mb = NewMethod(st, mb, 4);
		return $"{mb.Name}";
	}
	catch (Exception)
	{
		//catch 避免因反射层数不够导致的异常 影响接口请求
		return null;
	}
	static MethodBase NewMethod(StackTrace st, MethodBase mb, int index)
	{
		if (mb.DeclaringType.Name == nameof(HttpApi) || mb.DeclaringType.Name == "AsyncMethodBuilderCore")
		{
			mb = st.GetFrame(index + 1).GetMethod();
			return NewMethod(st, mb, index + 1);
		}
		return mb;
	}
}