转发原文 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但不推荐。
明确前后端的边界,更有利于相关人员专注于自己的职责,分工明确是为了更好的协作,提高工作效率,而不是用来踢皮球。
分离的标准
所谓客户端似乎只有原生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一样全程渲染
关于前后端分离不再多说,更多的参考:
JS前端渲染VS服务端渲染
前后端分离的思考与实践
Web系统开发构架再思考-前后端的完全分离
起源于2000年,简单、高效、扩展性强的REST架构风格是当下的主流,各大互联网公司无论是公开的/私有的API大都使用这种架构风格。
通过URI定义资源,配合HTTP方法使用:GET来获取资源,POST新建资源,PUT更新资源,DELETE删除资源。
这样的方式非常便于客户端开发人员的理解,更详细的介绍见:
REST入门介绍
深入浅出REST
理解本真的REST架构风格
最佳实践:更好的设计你的 REST API
用好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框架,它们功能特点大致上都差不多,以下将对使用到的功能进行简要说明。
内容协商
内容协商,即对于同一个资源,可以有多种展现形式。
客户端通过在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有现成的解决方案WebApiThrottle
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注释(///
.Net文档生成工具ADB,借助HTML Help Workshop生成CHM文档,有提供源码(有些bug需要自行修改)
DocFX,已在GitHub开源,生成HTML格式文档
前期在使用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