我来给.Net设计一款HttpClient

发表于:2017-11-01 11:09

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:jiulang    来源:51Testing软件测试网采编

  1、前言
  时间飞快,转眼半年,碌碌无为,眼下就要三十而立,回想三年前的今天,我将NetworkSocket库开放到github,一直在更新与学习,不求有这个库能有多好,只求自己在过程能成长,将领悟到一些思想应用到库里面去。今天,我来给大家介绍半年前在github开放的WebApiClient这个库,正如NetworkSocket一样,它正在渐渐从渺小变得强大,从简单变得抽象、易用、可高度扩展,它将带你进入不一个和以往完全不同风格的调用http接口的世界。 
  2、编程风格
  2.1传统调用风格
  一般的,我们需要new 一个HttpClient实例,然后准备请求url、请求body的HttpContent,然后发送,等待接收,解析回复内容.....
  这些都是需要一行一行代码来实现,代码里不仅表现了“做什么(What)”,而且更多表现出“如何(How)”完成工作这样的实现细节,大概想想代码像下面:
  /// <summary>
  /// 更新用户信息
  /// 使用application/json提交
  /// </summary>
  /// <param name="user"></param>
  /// <returns></returns>
  async static Task<UserInfo> UpdateAsync(UserInfo user)
  {
      var httpClient = new HttpClient();
      var serializer = new JavaScriptSerializer();
      var json = serializer.Serialize(user);
      var content = new StringContent(json, Encoding.UTF8, "application/json");
      var url = "http://localhost:9999/webapi/user/updateWithJson";
      var response = await httpClient.PostAsync(url, content);
      var resJson = await response.Content.ReadAsStringAsync();
      var resUser = serializer.Deserialize<UserInfo>(resJson);
      return resUser;
  }
  服务端任何接口的小变化,都直接影响到我们接口调用的某行具体代码,如果有100个接口,这代码的维护也不小。
  2.2WebApiClient风格
  WebApiClient的设计可以解放使用者的劳动力,只需要使用者根据http接口来定义一份.Net的interface,在接口里描述你想要什么(what i wan),但不需要实现这份interface(how it do),大概如下:
  [HttpHost("http://localhost:9999")]
  public interface UserApi
  {
      [HttpPost("/webapi/user/UpdateWithJson")]
      Task<UserInfo> UpdateWithJsonAsync([JsonContent] UserInfo user);
  }
  那么,接口的实现者给谁来完成呢?给WebApiClient来完成,它很聪明,知道你想要什么,这个正像我们写一条sql:select * from table一样,只有what,没有how。  
  3、使用层设计
  3.1接口与服务端设计一致
  使用者编写的interface,可以与服务端接近完全一致,在编写接口或文档对照方面相当容易。
  3.2使用Attribute标记描述“干什么”
  上面的[HttpPost]和[JsonContent],用来标记是干什么(不是怎么干)
  3.3没有了,只剩下调用接口了
  var userApi = new HttpApiClient().Implement<UserApi>();
  var resUser = await userApi.UpdateWithJsonAsync(user);
  4、架构层设计
  使用Castle来动态实现interface的实例,并获得实例方法调用的拦截,在拦截层,一一调用与方法相关标记的Atribute,Attribute是真正的逻辑实现者,每个Attribute只关注自己应该做什么。
  4.1、ApiActionContext
  ApiActionContext用于描述接口的详细信息以及接口周边的其它信息,在拦截interface的实例某方法之后,都生成一份ApiActionContext实例,但实例的很多属性是缓存中获取的,任何特性在执行的时候,都可以访问和修改这个ApiActionContext。
  4.2、Attribute标记分类
  1、IHttpActionAttribute (ApiActionContext)         // 与Api方法相关
  2、IApiParameterAttribute (ApiActionContext)    // 与 api参数相关
  3、IApiActionFilterAttribute (ApiActionContext)   // 与Api请求前后有关
  4、IApiReturnAttribute (ApiActionContext)          // 与api返回值相关
  这4个Attribute接口有着各自的职责,前三者一个共同的目标:构造和影响一个请求内容对象HttpRequestMessage,第4个的目标是:从回复的HttpResponseMessage中得到接口的返回值
  4.3 Attribute的执行
  在拦截器里,按照IApiActionAttribute > IApiParameterAttribute > IApiActionFilterAttribute > IApiReturnAttribute的顺序,将与方法的所有特性都执行,就可以完成接口的调用,大概实现如下:
  /// <summary>
  /// 异步执行api
  /// </summary>
  /// <param name="context">上下文</param>
  /// <returns></returns>
  private async Task<object> ExecuteInternalAsync(ApiActionContext context)
  {
      var apiAction = context.ApiActionDescriptor;
      foreach (var actionAttribute in apiAction.Attributes)
      {
          await actionAttribute.BeforeRequestAsync(context);
      }
      foreach (var parameter in apiAction.Parameters)
      {
          foreach (var parameterAttribute in parameter.Attributes)
          {
              await parameterAttribute.BeforeRequestAsync(context, parameter);
          }
      }
      foreach (var filter in apiAction.Filters)
      {
          await filter.OnBeginRequestAsync(context);
      }
      // 执行Http请求,获取回复对象
      var httpClient = context.HttpClientContext.HttpClient;
      context.ResponseMessage = await httpClient.SendAsync(context.RequestMessage);
      foreach (var filter in apiAction.Filters)
      {
          await filter.OnEndRequestAsync(context);
      }
      return await apiAction.Return.Attribute.GetTaskResult(context);
  } 
  5、支持的功能特性
  5.1方法或接口级特性
  绝对主机域名:[HttpHost]
  请求方式与路径:[HttpGet]、[HttpPost]、[HttpDelete]、[HttpPut]、[HttpHead]和[HttpOptions]
  代理:[Proxy]
  请求头:[Header]
  返回值:[AutoReturn]、[JsonReturn]、[XmlReturn]
  使用者可以自己扩充更多特性。
  5.2 参数级特性
  路径或query:[PathQuery]、[Url]
  请求头:[Header]
  请求Body:[HttpContent]、[JsonContent]、[XmlContent]、[FormContent]、[MulitpartConten]
  使用者可以自己扩充更多特性。
  5.3特殊参数类型
  MulitpartFile类(表单文件)、Url类(请求地址)、Proxy类 (请求代理)
  这些特殊参数类型在参数里,可以是本类型或本类型的集合,都会被执行
  使用者可以自己扩充更多的特殊参数类型。
  6、扩展能力
  6.1 扩展特性
  任何只要实现了IHttpActionAttribute、IApiParameterAttribute 、IApiActionFilterAttribute 、IApiReturnAttribute 之一或以上的特性,只要打在接口或参数上,就会得到调用,在调用里实现处理逻辑。
  6.2 特殊参数扩展
  任何实现了IApiParameterable接口的参数值,也会得到调用。
  6.3 自定义xml/json序列化
  HttpApiClient.Config.UseXmlFormatter(your formatter)
  HttpApiClient.Config.UseJsonFormatter(your formatter)
  6.4 自定义HttpClient上下文提供者
  HttpApiClient.Config.UseHttpClientContextProvider(your provider)
  你可以自己控制HttpClient的配置与生命周期
  6.5 自定义过滤器
  继承ApiActionFilterAttribute,可以实现自己的拦截器,作日志、授权什么的都可以;
  在子类修改AllowMultiple属性与OrderIndex属性,可以实现特性的排序与是否在接口和方法上重复使用。
  7、项目地址
  https://github.com/xljiulang/WebApiClient
  里面有一个demo,借助networksocket,http服务端与客户端都是同一个进程,调试过程非常方便,数据流向一目了然。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号