using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.Http.Headers;
using NewLife.Collections;
using NewLife.Data;
using NewLife.Http;
using NewLife.Log;
using NewLife.Reflection;
using NewLife.Serialization;
namespace NewLife.Remoting;
/// <summary>Api助手</summary>
public static class ApiHelper
{
#region 远程调用
/// <summary>性能跟踪器</summary>
public static ITracer? Tracer { get; set; } = DefaultTracer.Instance;
/// <summary>Http过滤器</summary>
public static IHttpFilter? Filter { get; set; }
/// <summary>异æ¥è°ƒç”¨ï¼Œç‰å¾…返回结果</summary>
/// <typeparam name="TResult">å“应类型,优先原始å—节数æ®ï¼Œå—典返回整体,Object返回data,没找到data时返回整体å—典,其它对dataååºåˆ—化</typeparam>
/// <param name="client">Http客户端</param>
/// <param name="action">æœåŠ¡æ“作</param>
/// <param name="args">傿•°</param>
/// <param name="cancellationToken">å–æ¶ˆé€šçŸ¥</param>
/// <returns></returns>
public static Task<TResult?> GetAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TResult>(this HttpClient client, String action, Object? args = null, CancellationToken cancellationToken = default) => client.InvokeAsync<TResult>(HttpMethod.Get, action, args, null, "data", cancellationToken);
/// <summary>åŒæ¥èŽ·å–ï¼Œå‚æ•°æž„é€ åœ¨Url</summary>
/// <typeparam name="TResult">å“应类型,优先原始å—节数æ®ï¼Œå—典返回整体,Object返回data,没找到data时返回整体å—典,其它对dataååºåˆ—化</typeparam>
/// <param name="client">Http客户端</param>
/// <param name="action">æœåŠ¡æ“作</param>
/// <param name="args">傿•°</param>
/// <returns></returns>
public static TResult? Get<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TResult>(this HttpClient client, String action, Object? args = null) => GetAsync<TResult>(client, action, args).ConfigureAwait(false).GetAwaiter().GetResult();
/// <summary>异æ¥è°ƒç”¨ï¼Œç‰å¾…返回结果</summary>
/// <typeparam name="TResult">å“应类型,优先原始å—节数æ®ï¼Œå—典返回整体,Object返回data,没找到data时返回整体å—典,其它对dataååºåˆ—化</typeparam>
/// <param name="client">Http客户端</param>
/// <param name="action">æœåŠ¡æ“作</param>
/// <param name="args">傿•°</param>
/// <param name="cancellationToken">å–æ¶ˆé€šçŸ¥</param>
/// <returns></returns>
public static Task<TResult?> PostAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TResult>(this HttpClient client, String action, Object? args = null, CancellationToken cancellationToken = default) => client.InvokeAsync<TResult>(HttpMethod.Post, action, args, null, "data", cancellationToken);
/// <summary>åŒæ¥æäº¤ï¼Œå‚æ•°Json打包在Body</summary>
/// <typeparam name="TResult">å“应类型,优先原始å—节数æ®ï¼Œå—典返回整体,Object返回data,没找到data时返回整体å—典,其它对dataååºåˆ—化</typeparam>
/// <param name="client">Http客户端</param>
/// <param name="action">æœåŠ¡æ“作</param>
/// <param name="args">傿•°</param>
/// <returns></returns>
public static TResult? Post<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TResult>(this HttpClient client, String action, Object? args = null) => PostAsync<TResult>(client, action, args).ConfigureAwait(false).GetAwaiter().GetResult();
/// <summary>异æ¥ä¸Šä¼ ,ç‰å¾…返回结果</summary>
/// <typeparam name="TResult">å“应类型,优先原始å—节数æ®ï¼Œå—典返回整体,Object返回data,没找到data时返回整体å—典,其它对dataååºåˆ—化</typeparam>
/// <param name="client">Http客户端</param>
/// <param name="action">æœåŠ¡æ“作</param>
/// <param name="args">傿•°</param>
/// <param name="cancellationToken">å–æ¶ˆé€šçŸ¥</param>
/// <returns></returns>
public static Task<TResult?> PutAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TResult>(this HttpClient client, String action, Object? args = null, CancellationToken cancellationToken = default) => client.InvokeAsync<TResult>(HttpMethod.Put, action, args, null, "data", cancellationToken);
/// <summary>异æ¥åˆ 除,ç‰å¾…返回结果</summary>
/// <typeparam name="TResult">å“应类型,优先原始å—节数æ®ï¼Œå—典返回整体,Object返回data,没找到data时返回整体å—典,其它对dataååºåˆ—化</typeparam>
/// <param name="client">Http客户端</param>
/// <param name="action">æœåŠ¡æ“作</param>
/// <param name="args">傿•°</param>
/// <param name="cancellationToken">å–æ¶ˆé€šçŸ¥</param>
/// <returns></returns>
public static Task<TResult?> DeleteAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TResult>(this HttpClient client, String action, Object? args = null, CancellationToken cancellationToken = default) => client.InvokeAsync<TResult>(HttpMethod.Delete, action, args, null, "data", cancellationToken);
/// <summary>异æ¥è°ƒç”¨ï¼Œç‰å¾…返回结果</summary>
/// <typeparam name="TResult">å“应类型,优先原始å—节数æ®ï¼Œå—典返回整体,Object返回data,没找到data时返回整体å—典,其它对dataååºåˆ—化</typeparam>
/// <param name="client">Http客户端</param>
/// <param name="method">请求方法</param>
/// <param name="action">æœåŠ¡æ“作</param>
/// <param name="args">傿•°</param>
/// <param name="onRequest">请求头回调</param>
/// <param name="dataName">æ•°æ®å—段å称,默认data。åŒä¸€å¥—rpc体系ä¸åŒæŽ¥å£çš„code/message一致,但dataå¯èƒ½ä¸åŒ</param>
/// <param name="cancellationToken">å–æ¶ˆé€šçŸ¥</param>
/// <returns></returns>
public static async Task<TResult?> InvokeAsync<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TResult>(this HttpClient client, HttpMethod method, String action, Object? args = null, Action<HttpRequestMessage>? onRequest = null, String dataName = "data", CancellationToken cancellationToken = default)
{
//if (client?.BaseAddress == null) throw new ArgumentNullException(nameof(client.BaseAddress));
var returnType = typeof(TResult);
// 构建请求。请求对象为本次调用独å ,å‘é€å®ŒæˆåŽå³å¯é‡Šæ”¾ï¼ˆè¯·æ±‚释放会连带释放其内容)
using var request = BuildRequest(method, action, args);
// 指定返回类型
if (returnType == typeof(Byte[]) || returnType == typeof(IPacket))
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/octet-stream"));
else
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// å¯èƒ½é™„åŠ å¤´éƒ¨
onRequest?.Invoke(request);
// 开始跟踪,注入TraceId
using var span = Tracer?.NewSpan(request);
var filter = Filter;
try
{
// å‘起请求
if (filter != null) await filter.OnRequest(client, request, null, cancellationToken).ConfigureAwait(false);
var response = await client.SendAsync(request, cancellationToken).ConfigureAwait(false);
try
{
if (filter != null) await filter.OnResponse(client, response, request, cancellationToken).ConfigureAwait(false);
return await ProcessResponse<TResult>(response, null, dataName).ConfigureAwait(false);
}
finally
{
// 调用方索å–原始å“应时所有æƒç§»äº¤ï¼Œä¸åœ¨æ¤é”€æ¯ï¼›å…¶ä½™ç»“果读å–åŽç«‹å³é‡Šæ”¾ï¼Œè¿žæŽ¥åŠæ—¶å½’还
if (typeof(TResult) != typeof(HttpResponseMessage)) response.Dispose();
}
}
catch (Exception ex)
{
// 跟踪异常
span?.SetError(ex, args);
if (filter != null) await filter.OnError(client, ex, request, cancellationToken).ConfigureAwait(false);
throw;
}
}
#endregion
#region 远程辅助
/// <summary>建立请求,action写到url里é¢</summary>
/// <param name="method">请求方法</param>
/// <param name="action">动作</param>
/// <param name="args">傿•°</param>
/// <param name="jsonHost">Jsonåºåˆ—化主机</param>
/// <returns></returns>
public static HttpRequestMessage BuildRequest(HttpMethod method, String action, Object? args, IJsonHost? jsonHost = null)
{
// åºåˆ—åŒ–å‚æ•°ï¼Œå†³å®šGET/POST
var request = new HttpRequestMessage(method, action);
if (args is HttpContent content)
request.Content = content;
else if (method == HttpMethod.Get || method == HttpMethod.Delete)
{
if (args is IPacket pk)
{
request.RequestUri = new Uri(AppendUrl(action, pk.ToArray().ToUrlBase64()), UriKind.RelativeOrAbsolute);
}
else if (args is Byte[] buf)
{
request.RequestUri = new Uri(AppendUrl(action, buf.ToUrlBase64()), UriKind.RelativeOrAbsolute);
}
else if (args != null)
{
var type = args.GetType();
if (type.IsBaseType())
{
// 基础类型没有命å属性,直接转为å—符串放到 Url 尾部
request.RequestUri = new Uri(AppendUrl(action, Uri.EscapeDataString(args + "")), UriKind.RelativeOrAbsolute);
}
else if (type.IsArray || args is IList)
{
// 数组/列表没有命å属性,JSON åºåˆ—åŒ–åŽæ”¾åˆ° Url 尾部
jsonHost ??= JsonHelper.Default;
request.RequestUri = new Uri(AppendUrl(action, Uri.EscapeDataString(jsonHost.Write(args, jsonOptions: null))), UriKind.RelativeOrAbsolute);
}
else
{
var ps = args.ToDictionary();
var url = GetUrl(action, ps);
request.RequestUri = new Uri(url, UriKind.RelativeOrAbsolute);
}
}
}
else if (method == HttpMethod.Post || method == HttpMethod.Put || method.Method == "PATCH")
{
if (args is IPacket pk)
{
request.Content = BuildContent(pk);
}
else if (args is Byte[] buf)
{
if (buf != null) request.Content = BuildContent((ArrayPacket)buf);
}
else if (args != null)
{
jsonHost ??= JsonHelper.Default;
content = new ByteArrayContent(jsonHost.Write(args, jsonOptions: null).GetBytes());
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
request.Content = content;
}
}
return request;
}
/// <summary>为二进制数æ®ç”Ÿæˆè¯·æ±‚体内容。对超长内容进行压缩</summary>
/// <param name="data"></param>
/// <returns></returns>
public static HttpContent BuildContent(IPacket data)
{
var gzip = NewLife.Net.SocketSetting.Current.AutoGZip;
if (gzip > 0 && data.Total >= gzip)
{
var buf = data.ReadBytes();
buf = buf.CompressGZip();
var content = new ByteArrayContent(buf);
content.Headers.ContentType = new MediaTypeHeaderValue("application/x-gzip");
return content;
}
else
{
var content = data.Next == null && data.TryGetArray(out var segment) ?
new ByteArrayContent(segment.Array!, segment.Offset, segment.Count) :
new ByteArrayContent(data.ReadBytes());
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
return content;
}
}
/// <summary>结果代ç å称。默认 code/errcode</summary>
public static IList<String> CodeNames { get; } = ["code", "errcode", "status"];
/// <summary>结果消æ¯å称。默认 message/msg/errmsg</summary>
public static IList<String> MessageNames { get; } = ["message", "msg", "errmsg", "error"];
/// <summary>处ç†å“应。统一识别code/message</summary>
/// <typeparam name="TResult">å“应类型,优先原始å—节数æ®ï¼Œå—典返回整体,Object返回data,没找到data时返回整体å—典,其它对dataååºåˆ—化</typeparam>
/// <param name="response">Httpå“应消æ¯</param>
/// <param name="dataName">æ•°æ®å—段å称,默认data。åŒä¸€å¥—rpc体系ä¸åŒæŽ¥å£çš„code/message一致,但dataå¯èƒ½ä¸åŒ</param>
/// <returns></returns>
public static Task<TResult?> ProcessResponse<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TResult>(HttpResponseMessage response, String dataName = "data") => ProcessResponse<TResult>(response, null, dataName);
/// <summary>处ç†å“应。统一识别code/message</summary>
/// <remarks>返回 HttpResponseMessage(原始å“应)时ä¸åšè§£æžã€åŽŸæ ·é€ä¼ ,所有æƒéšä¹‹ç§»äº¤è°ƒç”¨æ–¹ï¼›å…¶ä½™ç±»åž‹è¯»å–内容åŽç”±è°ƒç”¨æ–¹è´Ÿè´£é‡Šæ”¾å“应。</remarks>
/// <typeparam name="TResult">å“应类型,优先原始å—节数æ®ï¼Œå—典返回整体,Object返回data,没找到data时返回整体å—典,其它对dataååºåˆ—化</typeparam>
/// <param name="codeName">状æ€ç å—æ®µå</param>
/// <param name="response">Httpå“应消æ¯</param>
/// <param name="dataName">æ•°æ®å—段å称,默认data。åŒä¸€å¥—rpc体系ä¸åŒæŽ¥å£çš„code/message一致,但dataå¯èƒ½ä¸åŒ</param>
/// <returns></returns>
public static Task<TResult?> ProcessResponse<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TResult>(HttpResponseMessage response, String? codeName, String? dataName) => ProcessResponse<TResult>(response, codeName, dataName, JsonHelper.Default);
/// <summary>处ç†å“应。统一识别code/message</summary>
/// <typeparam name="TResult">å“应类型,优先原始å—节数æ®ï¼Œå—典返回整体,Object返回data,没找到data时返回整体å—典,其它对dataååºåˆ—化</typeparam>
/// <param name="codeName">状æ€ç å—æ®µå</param>
/// <param name="response">Httpå“应消æ¯</param>
/// <param name="dataName">æ•°æ®å—段å称,默认data。åŒä¸€å¥—rpc体系ä¸åŒæŽ¥å£çš„code/message一致,但dataå¯èƒ½ä¸åŒ</param>
/// <param name="jsonHost">Jsonåºåˆ—化主机</param>
/// <returns></returns>
public static async Task<TResult?> ProcessResponse<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TResult>(HttpResponseMessage response, String? codeName, String? dataName, IJsonHost jsonHost)
{
var rtype = typeof(TResult);
if (rtype == typeof(HttpResponseMessage)) return (TResult)(Object)response;
var buf = response.Content == null ? null : (await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false));
// 异常处ç†
if (response.StatusCode >= HttpStatusCode.BadRequest)
{
var msg = buf?.ToStr().Trim('\"');
// 400å“应å¯èƒ½åŒ…å«é”™è¯¯ä¿¡æ¯
if (!msg.IsNullOrEmpty() && msg.StartsWith("{") && msg.EndsWith("}"))
{
var dic = jsonHost.Decode(msg);
if (dic != null)
{
var msg2 = "";
if (dic.TryGetValue("title", out var v)) msg2 = v + "";
if (dic.TryGetValue("errors", out v) && v != null) msg2 += jsonHost.Write(v, jsonOptions: null);
if (!msg2.IsNullOrEmpty()) msg = msg2.Trim();
}
}
if (msg.IsNullOrEmpty()) msg = response.ReasonPhrase;
if (msg.IsNullOrEmpty()) msg = response.StatusCode + "";
//throw new ApiException((Int32)response.StatusCode, msg);
// Http异常å“应状æ€ï¼Œä¸åº”该抛出ApiExceptionå¼‚å¸¸ï¼Œå› ä¸ºåŽè€…会被认为是业务异常
#if NET5_0_OR_GREATER
throw new HttpRequestException(msg, null, response.StatusCode);
#else
throw new HttpRequestException(msg);
#endif
}
if (buf == null || buf.Length == 0) return default;
// 原始数æ®
if (rtype == typeof(Byte[])) return (TResult)(Object)buf;
if (rtype == typeof(IPacket)) return (TResult)(Object)new ArrayPacket(buf);
var str = buf.ToStr().Trim();
return ProcessResponse<TResult>(str, codeName, dataName ?? "data", jsonHost);
}
/// <summary>处ç†å“应。</summary>
/// <typeparam name="TResult">å“应类型,å—典返回整体,Object返回data,没找到data时返回整体å—典,其它对dataååºåˆ—化</typeparam>
/// <param name="response">文本å“应消æ¯</param>
/// <param name="codeName">状æ€ç å—æ®µå</param>
/// <param name="dataName">æ•°æ®å—段å称,默认data。åŒä¸€å¥—rpc体系ä¸åŒæŽ¥å£çš„code/message一致,但dataå¯èƒ½ä¸åŒ</param>
/// <param name="jsonHost">Jsonåºåˆ—化主机</param>
/// <returns></returns>
public static TResult? ProcessResponse<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TResult>(String? response, String? codeName, String dataName, IJsonHost? jsonHost = null)
{
if (response.IsNullOrEmpty()) return default;
var rtype = typeof(TResult);
jsonHost ??= JsonHelper.Default;
IDictionary<String, Object?>? dic = null;
if (response.StartsWith("<") && response.EndsWith(">"))
{
// XMLååºåˆ—化
dic = XmlParser.Decode(response);
}
else
{
// Jsonååºåˆ—化,å¯èƒ½æ˜¯å—典或列表
var obj = jsonHost.Parse(response);
// 如果没有data部分。å¯èƒ½æ˜¯ IDictionary<String, Object?> 或 IList<Object?> ,或其它
if (dataName.IsNullOrEmpty() && obj is TResult result2) return result2;
dic = obj as IDictionary<String, Object?>;
}
//if (dic == null) return default;
if (dic == null) throw new InvalidCastException($"Unable to convert to type [{typeof(TResult)}]! {response.Cut(64)}");
var nodata = dataName.IsNullOrEmpty() || !dic.ContainsKey(dataName);
// 未指定有效数æ®å时,整体返回
if (nodata && dic is TResult result3) return result3;
// 如果没有指定数æ®å,或者结果ä¸ä¸åŒ…嫿•°æ®å,则整个å—典作为结果数æ®
var data = nodata ? dic : dic[dataName];
var code = 0;
if (!codeName.IsNullOrEmpty())
{
if (dic.TryGetValue(codeName, out var v))
{
if (v is Boolean b)
code = b ? 0 : -1;
else
code = v.ToInt();
}
}
else
{
foreach (var item in CodeNames)
{
if (dic.TryGetValue(item, out var v))
{
if (v is Boolean b)
code = b ? 0 : -1;
else
code = v.ToInt();
break;
}
}
}
if (code is not ApiCode.Ok and not ApiCode.Ok200)
{
var message = "";
foreach (var item in MessageNames)
{
if (dic.TryGetValue(item, out var v))
{
message = v as String;
break;
}
}
//var message = js["message"] + "";
//if (message.IsNullOrEmpty()) message = js["msg"] + "";
if (message.IsNullOrEmpty()) message = data + "";
throw new ApiException(code, message);
}
// 简å•类型
if (data is TResult result) return result;
if (rtype == typeof(Object)) return (TResult?)data;
if (data == null && rtype.IsNullable()) return (TResult?)(Object?)null;
if (rtype.IsBaseType()) return data.ChangeType<TResult>();
// ååºåˆ—化
if (data == null) return default;
if (data is not IDictionary<String, Object> and not IList<Object>)
throw new InvalidDataException($"Unrecognized response data [{(data as String)?.Cut(64)}] for [{typeof(TResult).Name}]");
return jsonHost.Convert<TResult>(data);
}
/// <summary>æ ¹æ®åŠ¨ä½œå’Œå‚æ•°æž„é€ Url</summary>
/// <param name="action"></param>
/// <param name="ps"></param>
/// <returns></returns>
public static String GetUrl(String action, IDictionary<String, Object?>? ps)
{
var url = action;
if (ps != null && ps.Count > 0)
{
var sb = Pool.StringBuilder.Get();
sb.Append(action);
if (action.Contains('?'))
sb.Append('&');
else
sb.Append('?');
var first = true;
foreach (var item in ps)
{
if (!first) sb.Append('&');
first = false;
var v = item.Value is DateTime dt ? dt.ToFullString() : (item.Value + "");
sb.AppendFormat("{0}={1}", Encode(item.Key), Encode(v));
}
url = sb.Return(true);
}
return url;
}
/// <summary>è¿½åŠ å‚æ•°åˆ°Url。自动识别是å¦éœ€è¦æ·»åŠ ? 或 &</summary>
private static String AppendUrl(String action, String parameter) =>
action + (action.Contains('?') ? "&" : "?") + parameter;
private static String Encode(String data)
{
if (String.IsNullOrEmpty(data)) return String.Empty;
return Uri.EscapeDataString(data).Replace("%20", "+");
}
#endregion
}
|