net core天马行空系列:移植Feign,结合Polly,实现回退,熔断,重试,超时,做最好用的声明式http服务调用端
系列目录
1.net core天马行空系列:原生DI+AOP实现spring boot注解式编程
2.net core天马行空系列: 泛型仓储和声明式事物实现最优雅的crud操作
3.net core天马行空系列: 一个接口多个实现类,利用mixin技术通过自定义服务名,实现精准属性注入
4.net core天马行空系列:SummerBoot,将SpringBoot的先进理念与C#的简洁优雅合二为一
正文开始
hi,大家好,我就是高产似母猪的三合。距离上一篇文章,好像有点久了,我也不知道我都在干嘛,啊哈哈哈哈,今天这篇文章,从几个问题开始。
Feign是什么?
Feign它是一个声明式http服务调用端,做一个类比,如果把httpClient比作ado.net,那么feign就相当于ef/dapper。他是更顶层的封装,提供了更简单更友好的调用方式,更适合面向接口的编程习惯,通过接口+注解,就能实现http访问。因为微软爸爸认为网络请求是IO操作,用异步更为合适,所以feign只支持异步返回,即task<int>这种形式。
Feign用法
首先,注册服务的时候添加以下红线框出来的服务。
然后定义接口如下
最后注入使用
已经有webApiClient、refit了,为什么还要移植feign呢?
什么是回退、熔断、重试、超时?
回退:通俗来说就是备胎,如果方法调用报错了,比如调用的服务挂了,调用超时了等,就返回备胎里面的内容。定义回退类QueryEmployeeFallBack,该类也要实现IQueryEmployee接口
namespace Example.Feign
{
public class QueryEmployeeFallBack : IQueryEmployee
{
public async Task<Employee> FindAsync([Param("")] string id, [Body] Employee user)
{
await Task.Delay();
return new Employee() {Name = "fallBack"};
} public async Task<List<Employee>> GetEmployeeAsync(string url, int ab)
{
return await Task.FromResult(new List<Employee>(){new Employee(){Name = "fallBack"}});
} public Task<int> GetEmployeeCountAsync()
{
return Task.FromResult();
}
}
}
重试:字面意思,就是方法调用报错了,则进行重试
超时:即给方法的执行限定时间,如果超出了这个时间,就默认为方法执行失败。
断路:如果执行方法出现错误,执行多次还是错误的话,就认为这个方法可能挂了,在设定的时间内就不会去调用这个方法,而是直接返回错误,实际场景比如调用的api服务可能因为访问量太大,快挂了,这时候我们试了3次还是报错后,在10s内就不会真正去访问这个api接口,而是直接报错,就可以避免给api接口造成更多压力。
移植的feign属于summerBoot项目的一部分,基于MIT协议开源,欢迎star,感兴趣的可以加Q群799648362
github地址:https://github.com/TripleView/SummerBoot
nuget搜索:SummerBoot
效果图
定义一个employee类用来测试
先新建一个webApi项目,写几个接口供http访问。
测试1.get请求,返回int类型,符合预期
测试2,带参数的get请求,返回List<Employee>,api接口正常接收到请求参数,调用返回正常,符合预期。
测试3.发送post请求,在body里添加内容,同时利用param注解改变参数名称,发送接收都正常,符合预期。
测试4,同时访问3个api接口,返回正常,符合预期
测试5,超时+回退,在feign客户端里定义超时时间为2000毫秒,即2s,api接口那边添加一行让线程停3s的代码,如果没超时应该返回1,但是超时了,日志里也显示超时,这时候就会进入回退,返回QueryEmployeeFallBack类对应方法的值0。
测试6,超时+重试+回退。定义重试3次,每次间隔1s,其余和测试5一致,日志显示,重试3次后进入回退,返回0,符合预期。
测试7,超时+重试+回退+熔断。关闭api接口,模拟服务挂了,定义重试3次,每次间隔1s,开启断路。日志提示重试3次,之间有进入熔断,返回0,符合预期。
源码解析,授人以渔
1.通过IL代码动态生成接口的实现类
核心类FeignProxyBuilder,这里要特别感谢苏州的黑洞视界同学,在IL代码方面给了我很大的帮助。这个类的核心思想,就是动态生成接口的实现类,生成的实现类只是一个空壳,他里面只有3个字段,第一个,List<object> 类型,用来存放调用方法时的实际参数,第二个,IServiceProvider,由构造函数传入,第三个,httpService类,这个类就是真正执行网络请求的类,也是由构造函数传入,这个实现类的作用就是当调用接口里的方法时,利用IL代码把传递进来的参数收集起来存放到list<object>的字段里,然后调用httpSerivce的方法,把参数集合,方法体信息methodInfo,还有IServiceProvider一起传入到httpService的代理方法里,httpService处理完毕后获得结果,利用IL代码清除掉list<object>字段里存放的参数,保证每次调用方法时,list<object>里都是空的,避免上次调用方法的参数没清空,影响到本次调用,最后返回方法调用结果。具体如何动态生成这个实现类,代码里都有详细注解了,同时IL处理时有个要义,调用实例前,一定都要加载类本身,即Ldarg_0。
public interface IProxyBuilder
{
Object Build(Type interfaceType,params object[] constructor);
T Build<T>(params object[] constructor);
}
namespace SummerBoot.Feign
{
public interface IFeignProxyBuilder:IProxyBuilder
{
}
}
public class FeignProxyBuilder : IFeignProxyBuilder
{
public static Dictionary<string, MethodInfo> MethodsCache { get; set; } = new Dictionary<string, MethodInfo>(); private Type targetType; private static ConcurrentDictionary<string, Type> TargetTypeCache { set; get; } =
new ConcurrentDictionary<string, Type>(); public object Build(Type interfaceType, params object[] constructor)
{
var cacheKey = interfaceType.FullName;
var resultType = TargetTypeCache.GetOrAdd(cacheKey, (s)=> BuildTargetType(interfaceType, constructor));
var result = Activator.CreateInstance(resultType, args: constructor);
return result;
} /// <summary>
/// 动态生成接口的实现类
/// </summary>
/// <param name="interfaceType"></param>
/// <param name="constructor"></param>
/// <returns></returns>
private Type BuildTargetType(Type interfaceType, params object[] constructor)
{
targetType = interfaceType;
string assemblyName = targetType.Name + "ProxyAssembly";
string moduleName = targetType.Name + "ProxyModule";
string typeName = targetType.Name + "Proxy"; AssemblyName assyName = new AssemblyName(assemblyName);
AssemblyBuilder assyBuilder = AssemblyBuilder.DefineDynamicAssembly(assyName, AssemblyBuilderAccess.Run);
ModuleBuilder modBuilder = assyBuilder.DefineDynamicModule(moduleName); //新类型的属性
TypeAttributes newTypeAttribute = TypeAttributes.Class | TypeAttributes.Public;
//父类型
Type parentType;
//要实现的接口
Type[] interfaceTypes; if (targetType.IsInterface)
{
parentType = typeof(object);
interfaceTypes = new Type[] { targetType };
}
else
{
parentType = targetType;
interfaceTypes = Type.EmptyTypes;
}
//得到类型生成器
TypeBuilder typeBuilder = modBuilder.DefineType(typeName, newTypeAttribute, parentType, interfaceTypes); //定义一个字段存放httpService
var httpType = typeof(HttpService);
FieldBuilder httpServiceField = typeBuilder.DefineField("httpService",
httpType, FieldAttributes.Public); //定义一个字段存放IServiceProvider
var iServiceProviderType = typeof(IServiceProvider);
FieldBuilder serviceProviderField = typeBuilder.DefineField("iServiceProvider",
iServiceProviderType, FieldAttributes.Public); //定义一个集合存放参数集合
FieldBuilder paramterArrField = typeBuilder.DefineField("paramterArr",
typeof(List<object>), FieldAttributes.Public);
//创建构造函数
ConstructorBuilder constructorBuilder =
typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[] { httpType, iServiceProviderType }); //il创建构造函数,对httpService和IServiceProvider两个字段进行赋值,同时初始化存放参数的集合
ILGenerator ilgCtor = constructorBuilder.GetILGenerator();
ilgCtor.Emit(OpCodes.Ldarg_0); //加载当前类
ilgCtor.Emit(OpCodes.Ldarg_1);
ilgCtor.Emit(OpCodes.Stfld, httpServiceField); ilgCtor.Emit(OpCodes.Ldarg_0); //加载当前类
ilgCtor.Emit(OpCodes.Ldarg_2);
ilgCtor.Emit(OpCodes.Stfld, serviceProviderField); ilgCtor.Emit(OpCodes.Ldarg_0); //加载当前类
ilgCtor.Emit(OpCodes.Newobj, typeof(List<object>).GetConstructors().First());
ilgCtor.Emit(OpCodes.Stfld, paramterArrField);
ilgCtor.Emit(OpCodes.Ret); //返回 MethodInfo[] targetMethods = targetType.GetMethods(); foreach (MethodInfo targetMethod in targetMethods)
{
//只挑出virtual的方法
if (targetMethod.IsVirtual)
{
//缓存接口的方法体,便于后续将方法体传递给httpService
string methodKey = Guid.NewGuid().ToString();
MethodsCache[methodKey] = targetMethod; //得到方法的各个参数的类型和参数
var paramInfo = targetMethod.GetParameters();
var parameterType = paramInfo.Select(it => it.ParameterType).ToArray();
var returnType = targetMethod.ReturnType;
//方法返回值只能是task,即只支持异步,因为http操作是io操作
if (!typeof(Task).IsAssignableFrom(returnType)) throw new Exception("return type must be task<>");
var underType = returnType.IsGenericType ? returnType.GetGenericArguments().First() : returnType; //通过emit生成方法体
MethodBuilder methodBuilder = typeBuilder.DefineMethod(targetMethod.Name, MethodAttributes.Public | MethodAttributes.Virtual, targetMethod.ReturnType, parameterType);
ILGenerator ilGen = methodBuilder.GetILGenerator(); MethodInfo executeMethod = null;
var methodTmp = httpType.GetMethod("ExecuteAsync"); if (methodTmp == null) throw new Exception("找不到执行方法");
executeMethod = methodTmp.IsGenericMethod ? methodTmp.MakeGenericMethod(underType) : methodTmp; // 栈底放这玩意,加载字段前要加载类实例,即Ldarg_0
ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Ldfld, httpServiceField); //把所有参数都放到list<object>里
ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Ldfld, paramterArrField);
for (int i = ; i < parameterType.Length; i++)
{
ilGen.Emit(OpCodes.Dup);
ilGen.Emit(OpCodes.Ldarg_S, i + );
if (parameterType[i].IsValueType)
{
ilGen.Emit(OpCodes.Box, parameterType[i]);
}
ilGen.Emit(OpCodes.Callvirt, typeof(List<object>).GetMethod("Add"));
} // 当前栈[httpServiceField paramterArrField]
//从缓存里取出方法体
ilGen.Emit(OpCodes.Call,
typeof(FeignProxyBuilder).GetMethod("get_MethodsCache", BindingFlags.Static | BindingFlags.Public));
ilGen.Emit(OpCodes.Ldstr, methodKey);
ilGen.Emit(OpCodes.Call, typeof(Dictionary<string, MethodInfo>).GetMethod("get_Item")); ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Ldfld, serviceProviderField); ilGen.Emit(OpCodes.Callvirt, executeMethod);
//清空list里的参数
ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Ldfld, paramterArrField);
ilGen.Emit(OpCodes.Callvirt, typeof(List<object>).GetMethod("Clear"));
// pop the stack if return void
if (targetMethod.ReturnType == typeof(void))
{
ilGen.Emit(OpCodes.Pop);
} // complete
ilGen.Emit(OpCodes.Ret);
typeBuilder.DefineMethodOverride(methodBuilder, targetMethod);
}
} var resultType = typeBuilder.CreateTypeInfo().AsType();
return resultType;
} public T Build<T>(params object[] constructor)
{
return (T)this.Build(typeof(T), constructor);
}
}
2.完全面向接口设计的feign源码
feign中的接口
1.IClient接口,这个接口代表的就是实际执行网络请求的类,他的默认实现类DefaultFeignClient 基于httpClient,也可以自己实现这个接口,换成其他网络请求客户端。在feign框架调用iclient之前,会把所有请求信息封装到requestTemplate里,iclient执行完毕后,也需要把所有返回信息封装到responseTemplate里,利用这两个类,解耦了feign框架与具体iclient实现类。
namespace SummerBoot.Feign
{
/// <summary>
/// 实际执行http请求的客户端
/// </summary>
public interface IClient
{
Task<ResponseTemplate> ExecuteAsync(RequestTemplate requestTemplate, CancellationToken cancellationToken);
/// <summary>
/// 默认的IClient类,内部采用httpClient
/// </summary>
public class DefaultFeignClient : IClient
{
private IHttpClientFactory HttpClientFactory { get; }
public DefaultFeignClient(IHttpClientFactory iHttpClientFactory)
{
HttpClientFactory = iHttpClientFactory;
} public async Task<ResponseTemplate> ExecuteAsync(RequestTemplate requestTemplate, CancellationToken cancellationToken)
{
var httpClient = HttpClientFactory.CreateClient(); var httpRequest = new HttpRequestMessage(requestTemplate.HttpMethod, requestTemplate.Url); if (requestTemplate.HttpMethod == HttpMethod.Post)
{
httpRequest.Content = new StringContent(requestTemplate.Body);
} //处理header
foreach (var requestTemplateHeader in requestTemplate.Headers)
{
var uppperKey = requestTemplateHeader.Key.ToUpper(); var key = uppperKey.Replace("-", "");
//判断普通标头
if (HttpHeaderSupport.RequestHeaders.Contains(key))
{
httpRequest.Headers.Remove(requestTemplateHeader.Key);
httpRequest.Headers.Add(requestTemplateHeader.Key, requestTemplateHeader.Value);
}
//判断body标头
else if (HttpHeaderSupport.ContentHeaders.Contains(key))
{
httpRequest.Content.Headers.Remove(requestTemplateHeader.Key);
httpRequest.Content.Headers.Add(requestTemplateHeader.Key, requestTemplateHeader.Value);
}
//自定义标头
else
{
httpRequest.Headers.TryAddWithoutValidation(requestTemplateHeader.Key,
requestTemplateHeader.Value);
}
} var httpResponse = await httpClient.SendAsync(httpRequest, cancellationToken); //把httpResponseMessage转化为responseTemplate
var result = await ConvertResponseAsync(httpResponse); return result;
} /// <summary>
/// 把httpResponseMessage转化为responseTemplate
/// </summary>
/// <param name="responseMessage"></param>
/// <returns></returns>
private async Task<ResponseTemplate> ConvertResponseAsync(HttpResponseMessage responseMessage)
{
var responseTemplate = new ResponseTemplate
{
HttpStatusCode = responseMessage.StatusCode
}; var headers = responseMessage.Headers;
foreach (var httpResponseHeader in headers)
{
responseTemplate.Headers.Add(httpResponseHeader);
} var stream = new MemoryStream();
await responseMessage.Content.CopyToAsync(stream);
stream.Seek(, SeekOrigin.Begin);
responseTemplate.Body = stream;
return responseTemplate;
}
}
}
}
2.IFeignEncoder接口,这个接口的作用是定义序列化器,比如在post请求中,定义如何序列化参数然后放到body里,默认实现类DefaultEncoder 基于json,也可以实现该接口,换成其他种序列化方式。
namespace SummerBoot.Feign
{
/// <summary>
/// 序列化接口
/// </summary>
public interface IFeignEncoder
{
void Encoder(object obj, RequestTemplate requestTemplate);
/// <summary>
/// 默认的序列化器
/// </summary>
public class DefaultEncoder : IFeignEncoder
{
public void Encoder(object obj, RequestTemplate requestTemplate)
{
var objStr = JsonConvert.SerializeObject(obj);
requestTemplate.Body = objStr;
}
}
}
}
3.IFeignDecoder接口,这个接口的作用是定义反序列化器,作用是将网络请求成功后收到的信息反序列化成结果,默认实现类DefaultDecoder基于json,也可以实现该接口,换成其他种反序列化方式
namespace SummerBoot.Feign
{
/// <summary>
/// 反序列化接口
/// </summary>
public interface IFeignDecoder
{
object Decoder(ResponseTemplate responseTemplate, Type type); T Decoder<T>(ResponseTemplate responseTemplate); /// <summary>
/// 默认的反序列化器
/// </summary>
public class DefaultDecoder : IFeignDecoder
{
public object Decoder(ResponseTemplate responseTemplate, Type type)
{
if (responseTemplate.HttpStatusCode == HttpStatusCode.NotFound ||
responseTemplate.HttpStatusCode == HttpStatusCode.NoContent)
{
return type.GetDefaultValue();
} if (responseTemplate.Body == null) return null;
var body = responseTemplate.Body;
var str = body.ConvertToString();
return JsonConvert.DeserializeObject(str, type);
} public T Decoder<T>(ResponseTemplate responseTemplate)
{
return (T)this.Decoder(responseTemplate, typeof(T));
}
}
}
}
IRequestInterceptor接口,这个接口的作用是在网络请求前,拦截该请求,可以做一些自定义操作,比如添加授权,该接口没有默认实现类,如果有拦截需求可以实现该接口并注册到DI容器即可拦截。
namespace SummerBoot.Feign
{
public interface IRequestInterceptor
{
void Apply(RequestTemplate requestTemplate);
}
}
feign在添加服务的时候都是采用尝试注册,所以如果有自定义的接口,在添加feign服务之前注册到DI容器里,即可替换feign的默认实现。
类似这样
feign中的注解
1.FeignClientAttribute,这个注解只能用在接口上,添加了这个注解的接口都会被当成feign客户端注册到DI容器里,主要有服务名称,url地址,回退类,路径等参数。
namespace SummerBoot.Feign
{
[AttributeUsage(AttributeTargets.Interface)]
public class FeignClientAttribute:Attribute
{
/// <summary>
/// 服务名称
/// </summary>
public string Name { get; }
/// <summary>
/// url地址
/// </summary>
public string Url { get; }
/// <summary>
/// 回退类
/// </summary>
public Type FallBack { get; }
public Type Configuration { get; }
public bool Decode404 { get; }
public string Qualifier { get; }
/// <summary>
/// 路径
/// </summary>
public string Path { get; }
/// <summary>
///
/// </summary>
/// <param name="name">服务名称</param>
/// <param name="url">url地址</param>
/// <param name="fallBack">回退类</param>
/// <param name="configuration"></param>
/// <param name="decode404"></param>
/// <param name="qualifier"></param>
/// <param name="path">路径</param>
public FeignClientAttribute(string name,string url="",Type fallBack=null,Type configuration=null,bool decode404=false,string qualifier="",string path="")
{
Name = name;
Url = url;
FallBack = fallBack;
Configuration = configuration;
Decode404 = decode404;
Qualifier = qualifier;
Path = path;
}
}
}
2.HeadersAttribute注解,用来添加请求头,params类型,可以添加N个请求头
namespace SummerBoot.Feign
{
[AttributeUsage(AttributeTargets.Method)]
public class HeadersAttribute:Attribute
{
public string[] Param { get; }
public HeadersAttribute(params string[] param)
{
Param = param;
}
}
}
3.GetMappingAttribute和PostMappingAttribute,get请求和post请求的注解,只有一个value的参数,代表路径。
public class PostMappingAttribute : HttpMappingAttribute
{
public PostMappingAttribute(string value):base(value)
{
}
}
namespace SummerBoot.Feign
{
public class GetMappingAttribute:HttpMappingAttribute
{
public GetMappingAttribute(string value):base(value)
{ }
}
}
4.BodyAttribute,在post请求中,给class类型的参数添加,即代表将该参数添加到请求的body里。
namespace SummerBoot.Feign
{
[AttributeUsage(AttributeTargets.Parameter)]
public class BodyAttribute:Attribute
{ }
}
5.ParamAttribute,参数注解,主要是用来实现重命名参数的效果,比如接口方法中这个参数名叫employee,但是url中是user,那么利用这个注解即可重命名为user,[param("user")]即可。
namespace SummerBoot.Feign
{
[AttributeUsage(AttributeTargets.Parameter)]
public class ParamAttribute : Attribute
{
public string Value { get; } public ParamAttribute(string value = "")
{
Value = value;
}
}
}
6.PollyAttribute注解,主要定义了重试,超时,断路的一些策略。
namespace SummerBoot.Core
{
[AttributeUsage(AttributeTargets.Interface)]
public class PollyAttribute:Attribute
{
public int Retry { get; }
public int RetryInterval { get; }
public bool OpenCircuitBreaker { get; }
public int ExceptionsAllowedBeforeBreaking { get; } public int DurationOfBreak { get; }
public int Timeout { get; }
/// <summary>
///
/// </summary>
/// <param name="retry">重试次数</param>
/// <param name="retryInterval">重试间隔,单位毫秒</param>
/// <param name="openCircuitBreaker">是否开启断路</param>
/// <param name="exceptionsAllowedBeforeBreaking">错误几次进入断路</param>
/// <param name="durationOfBreak">断路时间</param>
/// <param name="timeout">超时时间,单位毫秒</param>
public PollyAttribute(int retry=,int retryInterval=,bool openCircuitBreaker=false,int exceptionsAllowedBeforeBreaking = , int durationOfBreak = , int timeout=)
{
Retry = retry;
RetryInterval = retryInterval;
OpenCircuitBreaker = openCircuitBreaker;
ExceptionsAllowedBeforeBreaking = exceptionsAllowedBeforeBreaking;
DurationOfBreak = durationOfBreak;
Timeout = timeout;
}
}
}
核心类FeignAspectSupport,这个类就是一个胶水类,负责解析注解,获得各种接口实现类,然后进行方法调用,获取最后结果,主要地方都有注释。
namespace SummerBoot.Feign
{
public class FeignAspectSupport
{
private IServiceProvider _serviceProvider; public async Task<T> BaseExecuteAsync<T>(MethodInfo method, object[] args, IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
//获得具体的client客户端
var feignClient = serviceProvider.GetService<IClient>();
//序列化器与反序列化器
var encoder = serviceProvider.GetService<IFeignEncoder>();
var decoder = serviceProvider.GetService<IFeignDecoder>(); //读取feignClientAttribute里的信息;
//接口类型
var interfaceType = method.DeclaringType;
if (interfaceType == null) throw new Exception(nameof(interfaceType));
var feignClientAttribute = interfaceType.GetCustomAttribute<FeignClientAttribute>();
var url = feignClientAttribute.Url;
var path = feignClientAttribute.Path;
var path2 = string.Empty;
var clientName = feignClientAttribute.Name;
var requestPath = url + path;
var requestTemplate = new RequestTemplate(); //获得请求拦截器
var requestInterceptor = serviceProvider.GetService<IRequestInterceptor>(); //处理请求头逻辑
ProcessHeaders(method, requestTemplate); //处理get逻辑
var getMappingAttribute = method.GetCustomAttribute<GetMappingAttribute>();
if (getMappingAttribute != null)
{
path2 = getMappingAttribute.Value;
requestTemplate.HttpMethod = HttpMethod.Get;
} //处理post逻辑
var postMappingAttribute = method.GetCustomAttribute<PostMappingAttribute>();
if (postMappingAttribute != null)
{
path2 = postMappingAttribute.Value;
requestTemplate.HttpMethod = HttpMethod.Post;
} var urlTemp = (requestPath + path2).ToLower(); requestTemplate.Url = GetUrl(urlTemp); //处理参数,因为有些参数需要拼接到url里,所以要在url处理完毕后才能处理参数
ProcessParameter(method, args, requestTemplate, encoder); //如果存在拦截器,则进行拦截
if(requestInterceptor!=null) requestInterceptor.Apply(requestTemplate); var responseTemplate = await feignClient.ExecuteAsync(requestTemplate, new CancellationToken()); //判断方法返回值是否为异步类型
var isAsyncReturnType = method.ReturnType.IsAsyncType();
//返回类型
var returnType = isAsyncReturnType ? method.ReturnType.GenericTypeArguments.First() : method.ReturnType; var resultTmp = (T)decoder.Decoder(responseTemplate, returnType); return resultTmp;
} private string GetUrl(string urlTemp)
{
Func<string,string> func = (string s) =>
{
s = s.Replace("//", "/");
s = s.Replace("///", "/");
s = "http://" + s;
return s;
}; if (urlTemp.Length < )
{
return func(urlTemp);
} var isHttp = urlTemp.Substring(, ) == "http://";
var isHttps = urlTemp.Substring(, ) == "https://";
if (!isHttp && !isHttps)
{
return func(urlTemp);
} if (isHttp)
{
urlTemp = urlTemp.Substring(, urlTemp.Length - );
return func(urlTemp);
} if (isHttps)
{
urlTemp=urlTemp.Substring(, urlTemp.Length - );
urlTemp = urlTemp.Replace("//", "/");
urlTemp = urlTemp.Replace("///", "/");
urlTemp = "https://" + urlTemp;
} return urlTemp;
} /// <summary>
/// 处理请求头逻辑
/// </summary>
/// <param name="method"></param>
/// <param name="requestTemplate"></param>
private void ProcessHeaders(MethodInfo method, RequestTemplate requestTemplate)
{
var headersAttribute = method.GetCustomAttribute<HeadersAttribute>();
if (headersAttribute != null)
{
var headerParams = headersAttribute.Param;
foreach (var headerParam in headerParams)
{
if (headerParam.HasIndexOf(':'))
{
var headerParamArr = headerParam.Split(":");
var key = headerParamArr[].Trim();
var keyValue = headerParamArr[];
var hasHeaderKey = requestTemplate.Headers.TryGetValue(key, out var keyList);
if (!hasHeaderKey) keyList = new List<string>();
keyList.Add(keyValue.Trim());
if (!hasHeaderKey) requestTemplate.Headers.Add(key, keyList);
}
}
}
} /// <summary>
/// 处理参数
/// </summary>
private void ProcessParameter(MethodInfo method, object[] args, RequestTemplate requestTemplate, IFeignEncoder encoder)
{
var parameterInfos = method.GetParameters();
//所有参数里,只能有一个body的注解
var hasBodyAttribute = false;
//参数集合
var parameters = new Dictionary<string, string>();
//url
var url = requestTemplate.Url; for (var i = ; i < parameterInfos.Length; i++)
{
var arg = args[i];
var parameterInfo = parameterInfos[i];
var parameterType = parameterInfos[i].ParameterType;
var paramAttribute = parameterInfo.GetCustomAttribute<ParamAttribute>();
var bodyAttribute = parameterInfo.GetCustomAttribute<BodyAttribute>();
var parameterName = parameterInfos[i].Name; if (paramAttribute != null && bodyAttribute != null)
{
throw new Exception(parameterType.Name + "can not accept parameterAttrite and bodyAttribute");
} if (hasBodyAttribute) throw new Exception("bodyAttribute just only one");
if (bodyAttribute != null) hasBodyAttribute = true; var parameterTypeIsString = parameterType.IsString(); //处理param类型
if ((parameterTypeIsString || parameterType.IsValueType) && bodyAttribute == null)
{
parameterName = paramAttribute != null? paramAttribute.Value.GetValueOrDefault(parameterName):parameterName;
parameters.Add(parameterName, arg.ToString());
} //处理body类型
if (!parameterTypeIsString && parameterType.IsClass && bodyAttribute != null)
{
encoder.Encoder(args[i], requestTemplate);
}
} var strParam = string.Join("&", parameters.Select(o => o.Key + "=" + o.Value));
if (strParam.HasText()) url = string.Concat(url, '?', strParam);
requestTemplate.Url = url;
}
}
}
核心类httpService,这个类是FeignAspectSupport的子类,主要是负责解析polly的各种策略,然后组合成polly的policy,最后调用FeignAspectSupport的方法, 实现重试,断路,超时等
namespace SummerBoot.Feign
{
public class HttpService : FeignAspectSupport
{ public async Task<T> ExecuteAsync<T>(List<object> originArgs, MethodInfo method, IServiceProvider serviceProvider)
{
var args = new List<object>();
originArgs.ForEach(it => args.Add(it)); var interfaceType = method.DeclaringType;
if (interfaceType == null) throw new Exception(nameof(interfaceType));
var feignClientAttribute = interfaceType.GetCustomAttribute<FeignClientAttribute>();
var name = feignClientAttribute.Name;
var fallBack = feignClientAttribute.FallBack;
object interfaceTarget;
//处理熔断重试超时逻辑
IAsyncPolicy<T> policy = Policy.NoOpAsync<T>();
var logFactory = serviceProvider.GetService<ILoggerFactory>();
var log = logFactory.CreateLogger<HttpService>(); if (fallBack != null)
{
policy = policy.WrapAsync(Policy<T>.Handle<Exception>()
.FallbackAsync<T>(async (x) =>
{
interfaceTarget = serviceProvider.GetServiceByName(interfaceType.Name + "FallBack", interfaceType);
var fallBackMethod = interfaceTarget.GetType().GetMethods().First(it => it.ReturnType == method.ReturnType && it.Name == method.Name);
var fallBackTask = fallBackMethod.Invoke(interfaceTarget, args.ToArray()) as Task<T>;
if (fallBackTask == null) throw new Exception("fallBack method ReturnValue error");
return await fallBackTask;
}));
} var pollyAttribute = interfaceType.GetCustomAttribute<PollyAttribute>(); if (pollyAttribute != null)
{
if (pollyAttribute.Retry > )
{
policy = policy.WrapAsync(Policy.Handle<Exception>().WaitAndRetryAsync(pollyAttribute.Retry, i => TimeSpan.FromMilliseconds(pollyAttribute.RetryInterval), (
(exception, retryCount, context) =>
{
log.LogError($"feign客户端{name}:开始第 " + retryCount + "次重试,当前时间:" + DateTime.Now);
})));
} if (pollyAttribute.Timeout > )
{
policy = policy.WrapAsync(Policy.TimeoutAsync(() => TimeSpan.FromMilliseconds(pollyAttribute.Timeout), Polly.Timeout.TimeoutStrategy.Pessimistic, (
(context, span, arg3, arg4) =>
{
log.LogError($"feign客户端{name}:超时提醒,当前时间:" + DateTime.Now);
return Task.CompletedTask;
})));
} if (pollyAttribute.OpenCircuitBreaker)
{
policy = policy.WrapAsync(Policy.Handle<Exception>().CircuitBreakerAsync(pollyAttribute.ExceptionsAllowedBeforeBreaking, TimeSpan.FromMilliseconds(pollyAttribute.DurationOfBreak), (
//policy = policy.WrapAsync(Policy.Handle<Exception>().CircuitBreakerAsync(1, TimeSpan.FromMilliseconds(1500), (
(exception, span, arg3) =>
{
log.LogError($"feign客户端{name}:熔断: {span.TotalMilliseconds } ms, 异常: " + exception.Message + DateTime.Now);
}), (context =>
{
log.LogError("feign客户端{name}:熔断器关闭了" + DateTime.Now);
}),
(() => { log.LogError("feign客户端{name}:熔断时间到,进入半开状态" + DateTime.Now); })));
} return await policy.ExecuteAsync(async () => await base.BaseExecuteAsync<T>(method, args.ToArray(), serviceProvider));
} return await base.BaseExecuteAsync<T>(method, args.ToArray(), serviceProvider); }
}
}
写在最后
他山之石,可以攻玉。如果觉得这篇文章不错,不妨点个赞咯。如果有好的想法,不管新人,还是大神,都欢迎贡献代码,sb项目已经有3个小伙伴了。
net core天马行空系列:移植Feign,结合Polly,实现回退,熔断,重试,超时,做最好用的声明式http服务调用端的更多相关文章
- net core天马行空系列:SummerBoot,将SpringBoot的先进理念与C#的简洁优雅合二为一
系列目录 1.net core天马行空系列:原生DI+AOP实现spring boot注解式编程 2.net core天马行空系列: 泛型仓储和声明式事物实现最优雅的crud操作 3.net core ...
- net core天马行空系列: 泛型仓储和声明式事物实现最优雅的crud操作
系列目录 1.net core天马行空系列:原生DI+AOP实现spring boot注解式编程 哈哈哈哈,大家好,我就是那个高产似母猪的三合,长久以来,我一直在思考,如何才能实现高效而简洁的仓储模式 ...
- net core天马行空系列: 一个接口多个实现类,利用mixin技术通过自定义服务名,实现精准属性注入
系列目录 1.net core天马行空系列:原生DI+AOP实现spring boot注解式编程 2.net core天马行空系列: 泛型仓储和声明式事物实现最优雅的crud操作 哈哈哈哈,大家好,我 ...
- net core天马行空系列-微服务篇:全声明式http客户端feign快速接入微服务中心nacos
1.前言 hi,大家好,我是三合,距离上一篇博客已经过去了整整两年,这两年里,博主通关了<人生>这个游戏里的两大关卡,买房和结婚.最近闲了下来,那么当然要继续写博客了,今天这篇博客的主要内 ...
- net core天马行空系列:原生DI+AOP实现spring boot注解式编程
写过spring boot之后,那种无处不在的注解让我非常喜欢,比如属性注入@autowire,配置值注入@value,声明式事物@Transactional等,都非常简洁优雅,那么我就在想,这些在n ...
- net core天马行空系列:降低net core门槛,数据库操作和http访问仅需写接口,实现类由框架动态生成
引文 hi,大家好,我是三合.不知各位有没有想过,如果能把数据库操作和http访问都统一封装成接口(interface)的形式, 然后接口对应的实现类由框架去自动生成,那么必然能大大降低工作量,因 ...
- net core天马行空系列-可用于依赖注入的,数据库表和c#实体类互相转换的接口实现
1.前言 hi,大家好,我是三合.作为一名程序猿,日常开发中,我们在接到需求以后,一般都会先构思一个模型,然后根据模型写实体类,写完实体类后在数据库里建表,接着进行增删改查, 也有第二种情况,就是有些 ...
- net core天马行空系列-各大数据库快速批量插入数据方法汇总
1.前言 hi,大家好,我是三合.我是怎么想起写一篇关于数据库快速批量插入的博客的呢?事情起源于我们工作中的一个需求,简单来说,就是有一个定时任务,从数据库里获取大量数据,在应用层面经过处理后再把结果 ...
- Spring Cloud 声明式服务调用 Feign
一.简介 在上一篇中,我们介绍注册中心Eureka,但是没有服务注册和服务调用,服务注册和服务调用本来应该在上一章就应该给出例子的,但是我觉得还是和Feign一起讲比较好,因为在实际项目中,都是使用声 ...
随机推荐
- OpenCVSharp对图像进行颜色分割
使用OpencvSharp的InRange函数对图像进行RGB颜色的分割. using System; using OpenCvSharp; using OpenCvSharp.Extensions; ...
- (转)假如没有OI By Vani
假如没有OI ...
- 思科WLC5508上传定制Portal展示模版
1. 登录Cisco设备,获取模板样例登录cisco WLC设备后点击help,打开帮助文档Wireless Tab-->Web Login Page-->External Web Aut ...
- 基于Dockerfile制作tomcat镜像
Docker 概述: 在前面的例子中,我们从下载镜像,启动容器,在容器中输入命令来运行程序,这些命令都是手工一条条往里输入的,无法重复利用,而且效率很低.所以就需要一种文件或脚本,我们把想执行的操 ...
- zookeeper伪分布式集群搭建
zookeeper集群搭建注意点: 配置数据文件myid1/2/3对应server.1/2/3 通过zkCli.sh -server [ip]:[port]检测集群是否 ...
- 吴裕雄--天生自然 python数据分析:健康指标聚集分析(健康分析)
# This Python 3 environment comes with many helpful analytics libraries installed # It is defined by ...
- 中国的规模优势,有望帮助AI芯片后来者居上?
芯片一直是个神奇的东西,表面上看是电脑.笔记本.智能手机改变了世界,其实,真正改变世界的硬件内核是芯片,芯片相关的技术才是科技界最实用.最浪漫的基础技术,也正因如此,谁掌握了芯片基础技术,谁就能立于 ...
- 携程酒店DevOps测试实践
作者简介 王幸福,携程酒店研发部高级测试经理,负责无线自动化测试相关工作.在测试框架和平台研发.移动测试.DevOps等领域有着丰富的经验. 如今很多大型互联网公司.创新型企业都在积极地进行DevOp ...
- OpenStack入门
云计算优势 降低成本,安全稳定,易扩展. 云计算三种服务模式 IaaS:基础设施即服务 IaaS(Infrastructure-as-a- Service):基础设施即服务.消费者通过Internet ...
- Bugku的一道注入
继续补sqli的题 这道题与之前的题的区别是在第二部分中加了一道waf,所以需要特殊的手段来进行注入. 题目来源:http://123.206.87.240:9004/1ndex.php?id=1 第 ...