RESTful – 移动互联网时代的高效API架构风格

转发原文 http://blog.jd-in.com/1009.html

移动互联网时代,如果没有那么一两款应用能适配手持设备,都不好意思跟人说是互联网公司。
传统的web在手持设备上无法带来良好的体验,随着技术的发展,解决方案随之而来:

  • 兼容性较强的H5

  • 优化层次更深的各平台NativeApp

  • 混合H5及NativeApp的Hybrid

不管是选择哪几种技术的组合,出现多种客户端是无法避免的。
为这些客户端提供数据,以及与服务端之间进行数据交互,需要一个统一的接入方式,而我们选择了.Net平台下RESTful的实现Asp.Net WebApi。

客户端分类

明确客户端种类,了解各个客户端的特点,才能更好的为它们提供服务。

web客户端

通过ajax访问服务端API:

  • 传统web

  • H5

天生对json友好,但是对其他数据格式存在解析效率问题。
在与API分开部署情况下,需要考虑ajax请求跨域的问题。
更倾向于使用cookie来保存用户身份,如果是H5/SPA(Single Page Application)的话,也可以使用Token(保存在cookie或本地存储里)。

NativeApp客户端

通过原生语言编写的http库访问服务端API:

  • iOS

  • Android

  • UWP(Universal Windows App)

  • 其他平台

即可以使用冗余的xml,也可以用灵活的json, 甚至可以用更精简、传输效率更高的protocol buffer/Thrift之类的格式(只要有对应原生平台语言的实现),可以根据实际情况选择效率最佳的格式。
更倾向使用Token保存用户身份,虽然也可以使用cookie但不推荐。

web前后端是否需要分离

明确前后端的边界,更有利于相关人员专注于自己的职责,分工明确是为了更好的协作,提高工作效率,而不是用来踢皮球。

分离的标准

所谓客户端似乎只有原生App与web两种,那么web到底有没有必要与原生App并列,有没有必要与服务端分离呢?
这个得根据应用的复杂度来决定:

  • 简单展示/没什么交互的站点,不需要分离

  • SPA/交互复杂的H5更加需要与服务端分离

  • 有NativeApp的应用,对应的web/H5站点肯定也复杂,同样需要分离

其他非技术上的因素:

  • 后端人手不足忙成狗,前端工作不饱和

  • 对于立志成为[前端工程师的男人]的切图仔,一定要成全他啊

个人认为前后端更应该分离的理由:

  • 专门维护一套包含视图的服务端(如Asp.Net MVC)成本远远高于只提供数据的Web Api

  • 不再纠结前端,后端可以专注于业务,以及API接口,更有精力做后端优化

  • 能更进一步做动静态分离,API服务器只用于数据请求,吞吐量更大、性能更好

  • 如果前端仅仅只提供模板,后端绑定数据过程难免会出现一些渲染问题,造成前后端人员一起在这些问题上浪费双倍时间

  • 前后端都负责view,虽然是不同阶段,但也会导致前后端职责划分不清(特别是出现问题时),沟通成本高

  • 当前web的已经足够复杂,原生App能做到的功能web前端基本也能做到,web可以和原生App共用一套服务端API,节省服务端开发成本、降低维护成本

  • 前端业务功能代码复用,假如同时存在传统web、h5、spa等两个以上的话

  • 近几年移动端爆发式增长,前端技术更新非常迅速,新技术层出不穷,用日新月异形容也不过分,很多后端工程师掌握的那点远古前端技术很难再驾驭好前端

  • BAT等大企业都在推行大前端概念,而且前端工程化程度已经非常高,各种成熟的解决方案那么多(不信?打开京东、淘宝等各大网站看看,打开微信钱包看看“应用号”),此时不用更待何时

  • 平台无关性,不管后端使用哪种技术,甚至中途更换后端技术都没有问题,只要能按照定下的规范提供API

  • 由前端负责视图渲染/交互,与app客户端一样深度参与到业务功能开发中来,更高的参与度,更清楚优化点在哪,必将带来更好的体验

  • 更明确的职责分工,有利于以后前后端团队的扩大,以及更好的协作

分离优缺点

本文所说的前后端分离,是指服务端只负责提供数据,view/controller的交给处理,是否分离决定了采用服务端渲染还是客户端渲染。

服务端渲染的缺点:

  • 每次都返回完整的html,传输量大

  • 界面跳转时容易出现白屏,响应慢

客户端渲染的优点:

  • 传输的内容更小、更节省带宽,除了第一次需要下载html模板,后面只需要传输json数据。特别是流量敏感的手机网络浏览H5时,节省流量更加重要

  • 传输内容小使得响应更快,体验更好

  • 与服务端分开部署,静态文件能更有效利用缓存/cdn,特别是强缓存,把前端优化做到极致,参考:

客户端渲染的缺点:

  • SEO难题

  • 首屏加载慢

对应的解决方案是首屏渲染,大致是这样的流程:

其中REST API服务端和首屏渲染服务端是两个不同的服务端:

  • REST API服务端只负责提供数据

  • 首屏渲染服务端负责从REST API获取数据,然后返回渲染的结果

    • 比如React在.Net平台就有对应的这种服务端渲染方案React.Net

  • 首屏渲染服务端只做首屏渲染,用于弥补部分客户端渲染的不足,而不是像传统Asp.Net MVC一样全程渲染

关于前后端分离不再多说,更多的参考:

REST架构风格

起源于2000年,简单、高效、扩展性强的REST架构风格是当下的主流,各大互联网公司无论是公开的/私有的API大都使用这种架构风格。
通过URI定义资源,配合HTTP方法使用:GET来获取资源,POST新建资源,PUT更新资源,DELETE删除资源。
这样的方式非常便于客户端开发人员的理解,更详细的介绍见:

用好RESTful API,一定要理解这种REST架构风格。要针对资源定义URI,不要给一个URI定义无数资源,也不要给资源定义无数种行为。

为什么选择Asp.Net WebApi

服务端采用的是.Net平台的技术,对应的有如下解决方案:

  • HttpHandler

    • 性能高

    • 没有现成的框架/集成解决方案,开发效率太低

  • WebService

    • 基于soap、xml的古董,性能不佳

    • 无法支持多种格式输出需要

    • 需要生成代理类,不方便调用,而且服务端接口调整会影响客户端调用

  • WCF

    • 支持REST风格架构

    • 定义契约太过繁琐

    • 太重量级,接口调整会影响客户端调用

    • 用于做互联网公开API不太合适

  • Asp.Net MVC

    • 带有View,同样也比较重

    • 而且不好的习惯导致一个Controller里面无数的Action,会给客户端开发人员造成很大的困扰

  • Asp.Net WebApi

    • RESTful实现,简单高效,易于扩展

    • 抛弃view显得轻量级

    • 支持Owin承载

  • ServiceStack

  • 可以用于替代WCF/MVC/WebApi等技术

  • 收费的解决方案(不完全开源)

  • 可运行于Win/Linux/OSX等系统上

  • Nancy (快速入门

    • RESTful实现,简单优雅的微型框架,良好的扩展性,已在GitHub开源

    • 完全不依赖System.Web,灵活性更好,真正的轻量级(和它一比WebApi也显得笨重了)

    • 有自己的视图引擎来实现服务端渲染(虽然实际上并不需要)

    • 不仅可以运行于Windows的.Net平台上,还可以通过Mono运行在Linux/OSX等平台上

    • 支持很多扩展,支持Owin等方式来承载

  • 自定义Owin框架:比如博客园的闪电轻量型Owin框架

    • 轻量级、高性能,可以选择实现REST架构

    • 可运行于win/linux/osx上

    • 自行开发成本较大

Owin是.Net Web程序与Web服务器之间的桥梁,是.Net平台发展的趋势,支持Owin承载的框架可以直接使用Owin的一些现有基础设施。
不管是WebApi2还是Nancy,都非常容易上手,特别是使用过Asp.Net MVC的,几乎不用怎么学习,只要理解RESTful,就能立即使用它们来开发。
原本Nancy是最佳选择,但考虑到原有业务代码与Asp.Net MVC耦合较重、难以拆开,以及一些使用习惯上的问题,所以WebApi2就成了当下兼容最好的解决方案。
再次强调:千万不要像使用Asp.Net MVC一样使用WebApi,一个Controller里面无数个Action,那样的风格根本不是RESTful,从现在开始戒掉Asp.Net MVC吧。(虽然实际上是违反了不少REST设计原则(*  ̄︿ ̄))

RESTful框架功能要点

不管使用了哪个RESTful框架,它们功能特点大致上都差不多,以下将对使用到的功能进行简要说明。

内容协商

内容协商,即对于同一个资源,可以有多种展现形式。
客户端通过在Accept头或者URL等方式,通知服务端需要返回什么格式的数据,服务端根据请求返回对应格式的结果。
WebApi和Nancy都内置了JSON/XML/Text等格式,而且支持自行扩展其他格式:

  • WebApi通过向HttpConfiguration.Formatters加入自定义的MediaTypeFormatter

  • Nancy通过自定义IResponseProcessor来实现

通过这些方法,可以轻易支持protocol buffer格式的输出(实际上Nancy有现成的Nancy.Serialization.ProtBuf)。

ModelBinding

ModelBinding即模型绑定,把HTTP请求数据绑定model(更确切的来说是具体Action方法的输入参数),这样就可以避免手工转换(parameter->model),大大提高开发效率,实在是非常方便的一个方法。
除了常规的Form Body Request,客户端还可以向服务器发送Payload Request,WebApi2和Nancy内置了JSON/XML的解析,也可以扩展其他格式的解析:

  • WebApi通过自定义TypeConverter,或者实现IModelBinder接口并加入到HttpConfiguration.Services集合

  • Nancy通过实现IBodyDeserializer接口,Bootstrapper会自动注册

这样就可以支持发送经过protocol buffer序列化的内容,甚至是经过特定算法加密的内容。

依赖注入

WebApi和Nancy都支持依赖注入,但默认的注入容器只支持构造函数注入(而且不是特别健壮),但同样可以扩展:

  • WebApi通过实现IDependencyResolver接口,并替换HttpConfiguration.DependencyResolver

  • Nancy通过自定义Bootstrapper来实现,Nancy会优先加载自定义的Bootstrapper

这样就可以使用性能最佳的Autofac,Nancy也已经有现成的Nancy.Bootstrappers.Autofac。
然而由于业务与底层中大量使用了MEF,所以WebApi也只能选择MEF。

需要注意:实现IDependencyResolver接口时,一定要写释放容器的代码,否则会造成内存无法被释放,从而使IIS进程内存占用过高而且降不下来。
有如下方式来实现容器的资源释放:

  • IDependencyResolver接口继承了IDisposable,所以只需实现Dispose方法并释放容器。

  • IDependencyResolver.BeginScope创建子容器时,要使用当前的容器作为Root/Parent容器

  • 子容器实现IDependencyScope时,同样需要实现Dispose方法来释放当前的子容器

  • 如果以上操作都没做的话,那么还有一个办法,在创建依赖容器之后,通过HttpContext的DisposeOnPipelineCompleted(IDisposable)实例方法通知当前HTTP上下文,在请求完成之后释放这个容器

请求过滤

请求过滤的用途有很多,只罗列用到的以及可能用到的:

  • 性能计数器,在action开始和结束分别记录时间并进行计算,得出一个action处理时间

    • 开源的解决方案Metrics.Net就可以做到,并且支持WebApi/Nancy/Owin

  • 客户端校验/筛选,对于非法客户端直接4xx状态码,具体实现要看业务

  • 请求限流,防止API被频繁调用,特别是要防止大并发量请求的攻击

WebApi通过Filter实现,Nancy通过pipeline的BeforeRequest/AfterRequest来实现。

异常处理

异常处理主要是用于捕获运行过程中出现的异常,并记录到日志中,以便将来的版本去修复它。
WebApi通过实现IExceptionFilter/IExceptionHandler接口来捕获异常,Nancy通过pipeline的OnError来捕获异常。

身份认证及安全

要同时支持NativeApp以及Web,有如下方案:

  • OAuth2,Owin有对应的实现,各大厂商也在用

  • JSON Web Token,业界评价似乎也挺高,开源的解决方案有JSON Web Token For .Net

  • Cookie,对移动端不是很友好,但兼容web可能需要

最终选择的是Owin的中间件OAuth2 Bearer Token(理由不解释),不管是WebApi2还是Nancy都支持(虽然Nancy也有自己的Token机制),可以自行实现加密算法保障安全性。
另外,为了避免Token泄露造成问题:

  • Token的过期时间应尽可能短(但不能短到影响用户体验)

  • Token里面可以写入一些设备/时间戳等信息,可以使Token强制失效来保障安全

  • 避免私钥泄露

为兼容web可能需要cookie,为保证安全,cookie需要设置成httponly避免xss攻击。
需要注意:一定要使用https保障安全,一定要使用https保障安全,一定要使用https保障安全,重要的事情重复3遍。
更多可参考:理解OAuth 2.0使用json web token基于Token的认证和基于声明的标识

文档化

API最终是给客户端调用的,所以需要统一的规范,并且明确的让客户端开发人员知道这些API:

  • 名称及用途

  • 地址及调用方法(HttpMethod)

  • 输入参数,需要传递哪些参数

  • 输出内容,返回的结果是什么数据结构

  • 可能还有更详细的说明

因此,文档化是必不可少的。
而随着需求的变更,以及功能的调整,API可能也会经常性的变动。
为确保客户端代码能够与最新的API匹配,API文档与代码同步也是必要的。
传统的办法是使用word之类的软件手动写文档,每次代码变更就去更新word文档; 但这样不仅工作量巨大、浪费时间,而且很难保证不会出现遗漏。
现代的办法就是自动生成,在代码中写好XML注释(///<summary>格式),然后使用工具生成,使用过两个工具:

前期在使用ADB生成CHM文档,基本能看但格式一般,而且需要手动打开exe来选择dll生成,使用起来有点麻烦。
之后选择了DocFX,安装NuGet包“docfx.msbuild”到需要生成文档的Project,直接生成项目即可出现文档,非常方便。除此之外,它还支持自定义模板、markdown(GFM)格式、跨平台的dotnet core,通过docfx.json和toc.yml进行配置,具体使用请看官网手册。

客户端与服务端交互

与REST Api交互,各个客户端都有现成的库:

  • iOS的AFNetworking、RestKit

  • Android的OKHttp

  • UWP的RestClient、HttpClient

  • Web使用ajax,各个库都有对应的封装:jquery的ajax、angular的$resource

这些库都能很好的支持REST请求,使用简单、易于扩展,而且开源的哦(虽然没几个人会去看源码)。

Token的获取

获取Token的过程如下:

客户端得到Token之后,持久化到一个容器中,但为了安全性考虑,最好保存在一个安全的位置:

  • iOS/Android放在keychain里

  • web不管是放在localStorage还是cookie,都无法保障安全,折衷办法是加密保存,或者干脆不用Token直接用cookie认证

Token有一个过期时间,客户端要注意刷新Token(为了更好的体验,这个过程应该是不被用户感知的):

  • 在初次载入App时要检查token是否过期,及时刷新token

  • 在运行过程中检查token是否过期,在token即将过期之前刷新token

  • 如果以上两步都未奏效,而token还是过期了,可以在接收到4xx拒绝授权响应之后,重新请求token,在重新得到token之后再一次放弃刚刚失败的请求

为了使用简单,目前并未使用OAuth2的refreshToken方法,不管是获取还是刷新token都是使用上面的流程。

请求REST API

客户端请求API过程如下:

总结

最后,总结一下本文要点:

  • REST架构风格及WebApi功能要点

  • 客户端渲染、前后端分离

最后不得不吐槽一下这个模板以及小书匠,调个样式调半天,默认的样式真是丑到没朋友。

over

猜你喜欢

转载自blog.csdn.net/huijunma2010/article/details/85335772