一个好的RESTfulAPI,具备特征
一个好的RESTfulAPI,应该具备以下特征:
这个API应该是对浏览器友好的,能够很好地融入Web,而不是与Web格格不入。
1浏览器是最常见和最通用的REST客户端。好的RESTfulAPI应该能够使用浏览器HTML完成所有的测试(不需要使用编程语言)。这样的API还可以很方便地使用各种自动化的Web功能测试、性能测试工具来做测试。Web前端应用(基于浏览器的RIA应用、移动App等等)也可以很方便地将多个RESTfulAPI的功能组合起来,建造Mashup类的应用。
这个API中所包含的资源和对于资源的操作,应该是直观和容易理解的,并且符合HTTP协议的要求。
REST开发又被称作“面向资源的开发”,这说明对于资源的抽象,是设计RESTfulAPI的核心内容。RESTfulAPI建模的过程与面向对象建模类似,是以名词为核心的。这些名词就是资源,任何可命名的抽象概念都可以定义为一个资源。而HTTP协议并不是一种传输协议,它实际提供了一个操作资源的统一接口。对于资源的任何操作,都应该映射到HTTP的几个有限的方法(常用的有GET/POST/PUT/DELETE四个方法,还有不常用的PATCH/HEAD/OPTIONS方法)上面。所以RESTfulAPI建模的过程,可以看作是具有统一接口约束的面向对象建模过程。
按照HTTP协议的规定,GET方法是安全且幂等的,POST方法是既不安全也不幂等的(可以用来作为所有写操作的通配方法),PUT、DELETE方法都是不安全但幂等的。将对资源的操作合理映射到这四个方法上面,既不过度使用某个方法(例如过度使用GET方法或POST方法),也不添加过多的操作以至于HTTP的四个方法不够用。
2如果发现资源上的操作过多,以至于HTTP的方法不够用,应该考虑设计出更多的资源。设计出更多资源(以及相应的URI)对于RESTfulAPI来说并没有什么害处。
这个API应该是松耦合的。
RESTfulAPI的设计包括了三个循序渐进、由低到高的层次:资源抽象、统一接口、超文本驱动。正是这三个层次确保了RESTfulAPI的松耦合性。
3当设计面向互联网的API时,松耦合变成了一种“必须有”的强需求。紧耦合的API非常脆弱,一旦公布出去,服务器端和客户端都无法持续进化。尤其是服务器端,公布出去的接口根本不敢改,改了之后,几乎所有客户端应用立即无法正常工作。REST这种架构风格就是紧耦合API的解毒剂,这个话题可以谈的很深,这里就不展开了。感兴趣的读者可以参考《REST实战》。
这个API中所使用的表述格式应该是常见的通用格式
在RESTfulAPI中,对于资源的操作,是通过在服务器端-客户端之间传递资源的表述来间接完成的。资源的表述可以有很多种格式,并且在响应和请求中的资源表述格式也会有所不同。GET/POST响应中的资源表述格式,常见的有HTML、XML、JSON;POST/PUT请求中的资源表述格式,常见的有标准的HTML表单参数、XML、JSON。
4这些常见表述格式,处理起来非常容易,有大量的框架和库提供支持。所以除非有很合理的要求,通常不需要使用自定义的私有格式。
使用HTTP响应状态代码来表达各种出错情况
HTTP响应状态代码,是HTTP协议这个统一接口中用来表达出错情况的标准机制。响应状态代码分成两部分:statuscode和reasonphase。两部分都是可定制的,也可以使用标准的statuscode,只定制reasonphase。
5如果一个所谓的“RESTfulAPI”对于任何请求都返回200OK响应,在响应的消息体中返回出错情况信息,这种做法显然不符合“确保操作语义的可见性”这个REST架构风格的基本要求。
这个API应该对于HTTP缓存是友好的
6充分利用好HTTP缓存是RESTfulAPI可伸缩性的根本。HTTP协议是一个分层的架构,从两端的useragent到originserver之间,可以插入很多中间组件。而在整个HTTP通信链条的很多位置,都可以设置缓存。HTTP协议内建有很好的缓存机制,可以分成过期模型和验证模型两套缓存机制。如果API设计者完全没有考虑过如何利用HTTP缓存,那么这个API的可伸缩性会有很多问题。
简单理解一
就是用URL定位资源,用HTTP描述操作。
简单理解二
URL定位资源,用HTTP动词(GET,POST,DELETE,DETC)描述操作。
官方定义
一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。
以web开发举例
在设计web接口的时候,REST主要是用于定义接口名,接口名一般是用名次写,不用动词,那怎么表达“获取”或者“删除”或者“更新”这样的操作呢——用请求类型来区分。
比如,我们有一个students接口,对于“学生”我们有增删改查四种操作,怎么定义REST接口?
增加一个学生,uri: http://testcodecom/school/students 接口类型:POST
删除一个朋友,uri: http://testcodecom/school/students 接口类型:DELETE
修改一个朋友,uri: http://testcodecom/school/students 接口类型:PUT
查找朋友,uri: http://testcodecom/school/students 接口类型:GET
上面我们定义的四个接口就是符合REST协议的,请注意,这几个接口都没有动词,只有名词students,都是通过Http请求的接口类型来判断是什么业务操作。
举个反例
uri: http://testcodecom/school/addStudents 该接口用来表示增加学生,这就是不符合REST协议的接口。
建议
用HTTP Status Code传递Server的状态信息。比如最常用的 200 表示成功,500 表示Server内部错误,403表示Bad Request等。(反例:传统web开发返回的状态码一律都是200,其实不可取。)
REST风格接口意义
前后端分离。前端拿到数据只负责展示和渲染,不对数据做任何处理。后端处理数据并以JSON格式传输出去,定义这样一套统一的接口,在web,ios,android三端都可以用相同的接口,节约开发成本以及便于同一调试。
由于REST可以降低开发的复杂度,提高系统的可伸缩性,增强系统的可扩展性,简化应用系统之间的集成,因而得到了广大开发人员的喜爱,同时得到了业界广泛的支持。比如IBM,Google等公司的很多产品都提供了REST API给开发人员;与此同时,大量的开源项目和云计算服务都提供了REST API接口。
而在最近,一些新产品的开发甚至已经几乎完全抛弃了传统的类似JSP的技术, 转而大量使用REST风格的构架设计, 即在服务器端所有商业逻辑都以REST API的方式暴露给客户端, 所有浏览器用户界面使用widget、Ajax、HTML5 等技术,用HTTP的方式与后台直接交互。
那么, 在REST API爆炸式增长的今天, 我们应该如何更好的设计我们的接口, 来提高我们的API的可用性,易用性,可维护性与可扩展性呢?本文将从以下方面与您探讨REST API设计方面的最佳实践:
如何规划资源标识结构与URI模式
如何根据应用场景提供内容协商
如何正确的使用HTTP响应代码
如何处理缓存和并发请求
如何利用数据冗余和链接元素
先决条件
如果您具有如下知识与经验,将有助于您阅读和理解本文章的内容 。
REST相关的基本知识;
HTTP协议的基本知识;
一定的Web开发经验。
REST简介
REST是英文 State Transfer的缩写,是近年来迅速兴起的,一种基于HTTP、URI以及XML这些现有协议与标准的,针对网络应用的设计和开发方式。它可以降低开发的复杂度,提高系统的可伸缩性。
REST的核心是可编辑的资源及其集合,用符合Atom文档标准的Feed和Entry表示。每个资源或者集合有一个惟一的URI。系统以资源为中心,构建并提供一系列的Web服务。REST的基本概念和原则包括:系统上的所有事物都被抽象为资源;每个资源对应唯一的资源标识;对资源的操作不会改变资源标识本身;所有的操作都是无状态的;等等。
在REST中,开发人员显式地使用HTTP方法,对系统资源进行创建、读取、更新和删除的操作:
使用POST方法在服务器上创建资源
使用GET方法从服务器检索某个资源或者资源集合
使用PUT方法对服务器的现有资源进行更新
使用DELETE方法删除服务器的某个资源
图 1 用 HTTP 方法操作相册系统资源的简单范例
更好的规划你的资源标识结构与URI模式
REST 中,最基本的莫过于资源标识结构和URI模式了。更好的规划他们,是我们的API设计取得成功的最关键的一步。
首先,我们来看看基本资源类型
上文中提到,在REST构架的设计中,系统中的所有事物都被抽象为资源。
在一个文档系统中,文档、目录、注释、草稿等等,是组成系统的资源。
在一个银行系统中,客户信息、理财产品、利率信息、网点信息等等,是组成系统的资源。
在一个航空客票系统中,旅客信息、机票订单、航班信息、机场信息等等,是组成系统的资源。
这些资源,通常在系统中以“Entry”的形式出现。下面的代码样例向您展示了一个常见的“Entry”资源。
清单 1 一个简单的Entry资源样例
以下是引用片段:
REST API 请求:
GET example/航班信息/CA827/entry
CA827
CA8272010-08-24T02:35:40937Z
2010-08-24T02:35:40937Z
Beijing
Changsha
09:30:00
AB330
我们会注意到,这些资源,在描述了某种事物的同时,还有可能存在一定的层次结构关系。比如,文档从属于某个目录,注释从属于文档;旅客信息可以从属于机票订单,也可以从属于某个航班。
当我们的资源有这种层次关系的时候,我们不妨在URI模式的设计中,用复合的URI来帮助开发者更好的理解和设计资源。
比如, 针对一个文档的评论, 他的URI模式可以设计成如下:/文件夹/[文件夹名]/文件/[文件名]/评论/[ 评论唯一标示 ]。 这样,在构造和解析URI的过程中, 可以帮助开发者更好的理解系统,设计程序。
其次,我们来看看集合资源类型
资源,除了作为独立个体可以被访问,还可以由多个个体组合成一个集合,在系统中,通常以“feed”的形式存在。资源的集合, 可以是处于相同层次上,有相同从属关系的一组资源,比如一个文件夹下的所有文件; 也可以是根据某种条件查询出来的查询结果的资源集合,比如所有30岁以上40岁以下,拥有100万资产以上客户的名单。
下面,我们来讨论一下设计集合类型资源的REST API时需要考虑的问题。
使用过滤条件来帮助用户更准确地获取数据
我们要返回的资源集合,无论是否有相同从属关系,大部分时候都需要进行必要的过滤,提供足够的过滤参数,查询参数, 能够帮助开发者高效的,准确地获取所需要的数据。 在服务器端过滤数据通常比客户端高效,并且减少了不必要的数据传输,可以大大减少网络开销,提高执行效率。
最常见的过滤条件,是通过URL参数实现,比如/环境工程系/学生籍贯=北京&性别=女。
很多时候,我们需要制定更加复杂的过滤条件,那么我们可以有两种选择:
首先,我们可以使用正则表达式或者服务器可以理解的语法,比如/环境工程系/学生filter=age between (15, 18)
其次,我们还可以使用POST方法,携带一个文件来描述复杂的查询条件,文件的格式与语法通常需要在服务器端有相应的设计与定义。不过通常POST方法没有缓存机制,因此不是查询数据的首选。
使用排序来帮助客户端更好的展现数据
虽然进行客户端排序对于开发者来说是件轻而易举的事情,但是直接得到已经排序的返回结果,仍然是大部分开发者所期望的。尤其是很多时候,我们在浏览器,使用 Widget 展示结果,不适宜在客户端存储大量数据进行内存排序。
排序, 通常有2个参数,一个是用来排序的字段,一个是排序的升序降续方式。比如我们可以用支持这样的参数组合的手段,提供基本的排序能力:sortOrder=asc&sortField=age
使用分页来帮助客户端处理大量数据
由于返回的结果可能有几百几千条记录,将这些记录一次性的返回给客户端是不现实的,巨大的网络流量开销和客户端数据区的内存开销,都是我们在应用开发的时候不希望看到的,因此,如果你的集合资源有可能有大量的数据返回,请务必提供分页的功能支持。
我们通常用一个以上参数来制定一个返回结果的区域,比较常见的有下面两种:
一种常见于用固定行数的表格来展示数据,用当前处于第几页和每页返回多少行数据来确定需要的数据, 比如/所有学生page=5&pagesize=50
另外一种常见于用更加灵活的界面展示数据,用从第几行开始,一共返回多少行数据来确定需要的数据, 比如/所有学生startIndex=27&count=22
下面是一个来自IBM 的API样例,尝试请求该API,你可以看到该集合很好的支持了结果的分页与排序。同时我们从返回的信息中可以看到,每个文档Entry的URI都按照/社区库/[社区库ID]/文档/[文档ID]的复合URI的模式设计的。
清单 2 IBM 的某个社区文件库的集合资源的API
以下是引用片段:
REST API 请求:
GETIBM 的文件服务标签云的API
以下是引用片段:
REST API请求,要求返回XML格式数据:
GETibm//
/files/form/anonymous/api/tags/feedformat=json
&scope=document&pageSize=30&sK=cloud&sO=dsc
使用Aept头进行内容协商
使用URL参数,简单灵活,但是也由此带来了设计上的随意和不标准。并且,过多的参数会导致URL的可读性变差,更有甚者,可能会导致URL过长,超出规范,API请求无法执行。
更为标准的内容协商方式是使用HTTP头。我们通常使用Aept来设置我们接受的返回结果的内容格式,用Aept-Charset来设置字符集,用Aept-Encoding来设置数据传输格式,用Aept-Language来设置语言。
使用URI模式进行内容协商
还有一种模式,就是将协商设置直接作为URI的一部分,将不同的返回视为不同的资源,比如/航班号/json来返回JSON格式的结果,用/航班号/atom来返回ATOM格式的结果。
正确的使用HTTP响应代码
作为API的设计者,正确的将API执行结果和失败原因用清晰简洁的方式传达给客户程序是十分关键的一步。 我们确实可以在HTTP的相应内容中描述是否成功,如果出错是因为什么, 然而, 这就意味着用户需要进行内容解析,才知道执行结果和错误原因。因此,HTTP响应代码可以保证客户端在第一时间用最高效的方式获知API运行结果,并采取相应动作。
转载,仅供参考,祝你愉快,。
企业提供特定的服务service,其他用户通过internet即web访问这些服务。换句话说,WebService是两个计算机之间通讯(交谈)的技术,网络中一台计算机想要调用另一台计算机的方法时,此时可以需要WebService。很火的SOA、云计算在技术层面上都是WebService
用程序员的观点就是:企业提供实现某功能的函数,其他用过通过在线访问这些函数。
webservice两个重要文件:aar:服务包文件,提供服务,wsdl:web服务接口定义语言文件,供客户端使用。
webservice关键技术
1、XML:可扩展的标记语言(XML)是Web service平台中表示数据的基本格式。除了易于建立和易于分析外,XML主要的优点在于它既是平台无关的,又是厂商无关的。
2、SOAP:SOAP是web service的标准通信协议,SOAP为simple object access protocoll的缩写,简单对象访问协议它是一种标准化的传输消息的XML消息格式。即XML文件的消息格式,由这个协议来决定。SOAP简单的理解,就是这样的一个开放协议SOAP=RPC+HTTP+XML:采用HTTP作为底层通讯协议;RPC作为一致性的调用途径,xml作为数据传送的格式。
3、Axis2:Axis2是实现Web Service的一种技术框架,是新一代的SOAP引擎,即通过这个架构很方便地实现webservice即在服务端通过这个技术支持,很方便地发布webservice服务,使开发只关注具体的商业实现,而由这个框架直接发布,节省了开发者的时间。在客户端同样只关注调用。为了使用这个框架,在3个地方需要部署Axis2的库:web服务器tomcat上、eclipse服务端开发axis2库、eclipse客户端开发ksoap2库。
4、WSDL:WSDL的全称是web service Description Language,是一种基于XML格式的关于web服务的描述语言。其主要目的在于web service的提供者将自己的web服务的所有相关内容,如所提供的服务的传输方式,服务方法接口,接口参数,服务路径等,生成相应的完全文档,发布给使用者。使用者可以通过这个WSDL文档,创建相应的SOAP请求消息,通过HTTP传递给webservice提供者;web服务在完成服务请求后,将SOAP返回消息传回请求者,服务请求者再根据WSDL文档将SOAP返回消息解析成自己能够理解的内容。
5、UDDI:UDDI 是一种目录服务,企业可以使用它对 Web services 进行注册和搜索。UDDI,英文为 "Universal Description, Discovery and Integration",可译为“通用描述、发现与集成服务”。UDDI是一种创建注册表服务的规范,以便大家将自己的web service进行注册发布供使用者查找然而当服务提供者想将自己的web service向全世界公布,以便外部找到其服务时,那么服务提供者可以将自己的web service注册到相应的UDDI商用注册网站,目前全球有IBM等4家UDDI商用注册网站。因为WSDL文件中已经给定了web service的地址URI,外部可以直接通过WSDL提供的URI进行相应的web service调用。所以UDDI并不是一个必需的web service组件,服务方完全可以不进行UDDI的注册。
webservice服务端的实现
1、tomcat服务器部署:部署Axis2到tomcat:将Axis2war解压到tomcat的webapps目录下即可,如果有数据库连接,需把数据库连接jar包添加到tomcat的lib目录下,如:oracle的ojdbc14jar,和mysql的 mysql-connector-java-3111-binjar。部署成功后,启动tomcat,可通过http://localhost:8080/axis2/访问,查看是否部署成功。webservice的axis服务存档aar发布文件就发布到tomcat\webapps\axis2\WEB-INF\services这个目录下
2、安装axis2插件到eclipse:解压插件,直接拷贝到eclipse的插件目录plugins,两个插件为:orgapacheaxis2eclipsecodegenplugin_160jar、orgapacheaxis2eclipseserviceplugin_160jar。打开Eclipse,选择File/New/Other菜单项,看到如下界面表明安装成功:
安装这两个插件的目的是:方便生成Axis2的服务包(aar文件)和生成Axis2客户端
3、打包生成axis2服务包:
选择服务程序类文件所在的目录,不包括包,选中Include,表示在生成的服务包中只包括类文件,不包括其他文件。
选择跳过WSDL文件,WSDL文件会在部署这个服务到Tomcat后,有Axis2自动生成,通过http://localhost:8080/axis2/services/newWswsdl可以查看文件内容。
注意:WSDL是web服务定义语言,通过XML的方式对该服务类进行描述,客户端访问服务时要用到这个文件,可以生成这个文件给客户端,也可以上面的在线获取这个文件。
点击“next”,进入添加服务需要的库文件界面:
需要的库文件直接部署到tomcat中,此处不需要添加,进入下一界面:
选择自动生成servicexml文件。点击进入下一界面:
输入服务名和类名,点击load即可找出该服务程序的全部方法。
输入发布路径,和文件名。生成newWsaar服务包文件。
部署完成后,输入http://localhost:8080/axis2/services/listServices,即可看到已部署的全部服务。
eclipse中webservice不能发布的原因:
1、界面问题,把发布界面最大化或拖拽,是界面刷新,就可以看到输入界面了。
2、发布界面最后一步,load时,没有任何反映,是这个类的代码有问题,例如有加载动态库的代码,动态库找不到,则会没有反映。
3、代码没改变的情况下好像只能生成一次,想在生成的话,需要改代码,或需要重启eclipse
webservie服务端:java调用C++动态库的实现
1、在eclipse中编写一个java类文件,应用中的其他类可以调用这个类中的函数:
[cpp] view plaincopyprint
01package util;
02
03public class InvokeDll
04{
05 //测试函数
06 public native int testFunc(int a, int b);
07
08 //获取最近一次错误代码
09 public native int lastErr();
10}
package util;
public class InvokeDll
{
//测试函数
public native int testFunc(int a, int b);
//获取最近一次错误代码
public native int lastErr();
}
注意:函数要用native修饰符
2、用javah命令生成C++可调用的h文件
a、上述java类编译生成类文件InvokeDllclass
b、set classpath="D:\teatInvokeDll\bin",类文件生成在D:\testInvokeDll\bin\util\InvokeDllclass
c、命令行进入D:\teatInvokeDll\bin
d、javah utilInvokeDll
你会发现当前目录下多了一个util_InvokeDllh文件,文件内容如下:
[cpp] view plaincopyprint
01/ DO NOT EDIT THIS FILE - it is machine generated /
02#include
03/ Header for class util_InvokeDll /
04
05#ifndef _Included_util_InvokeDll
06#define _Included_util_InvokeDll
07#ifdef __cplusplus
08extern "C" {
09#endif
10/
11 Class: util_InvokeDll
12 Method: testFunc
13 Signature: (II)I
14 /
15JNIEXPORT jint JNICALL Java_util_InvokeDll_testFunc
16 (JNIEnv , jobject, jint, jint);
17
18/
19 Class: util_InvokeDll
20 Method: lastErr
21 Signature: ()I
22 /
23JNIEXPORT jint JNICALL Java_util_InvokeDll_lastErr
24 (JNIEnv , jobject);
25
26#ifdef __cplusplus
27}
28#endif
29#endif
/ DO NOT EDIT THIS FILE - it is machine generated /
#include
/ Header for class util_InvokeDll /
#ifndef _Included_util_InvokeDll
#define _Included_util_InvokeDll
#ifdef __cplusplus
extern "C" {
#endif
/
Class: util_InvokeDll
Method: testFunc
Signature: (II)I
/
JNIEXPORT jint JNICALL Java_util_InvokeDll_testFunc
(JNIEnv , jobject, jint, jint);
/
Class: util_InvokeDll
Method: lastErr
Signature: ()I
/
JNIEXPORT jint JNICALL Java_util_InvokeDll_lastErr
(JNIEnv , jobject);
#ifdef __cplusplus
}
#endif
#endif
3、在VC中生成dll如java2dlldll
a、新建一个空的dll工程,名叫java2dll。
b、把util_InvokeDllh头文件复制到工程目录下,并添加到header files文件夹里。
c、在source files文件目录中添加一个java2dllcpp,文件内容是头文件里的方法的实现:
[cpp] view plaincopyprint
01#include
02#include "util_InvokeDllh"
03
04JNIEXPORT jint JNICALL Java_util_InvokeDll_testFunc(JNIEnv , jobject, jint a, jint b)
05{
06 int i=0;
07 /HMODULE hModule = ::LoadLibrary("testdll");
08
09 PFUN newfun = (PFUN)::GetProcAddress(hModule,"testFn");
10 i = newfun(a,b);
11 ::FreeLibrary(hModule);
12 /
13 i=a+b;
14 return i;
15}
16
17JNIEXPORT jint JNICALL Java_util_InvokeDll_lastErr(JNIEnv , jobject)
18{
19 int i=0;
20 return i;
21}
#include
#include "util_InvokeDllh"
JNIEXPORT jint JNICALL Java_util_InvokeDll_testFunc(JNIEnv , jobject, jint a, jint b)
{
int i=0;
/HMODULE hModule = ::LoadLibrary("testdll");
PFUN newfun = (PFUN)::GetProcAddress(hModule,"testFn");
i = newfun(a,b);
::FreeLibrary(hModule);
/
i=a+b;
return i;
}
JNIEXPORT jint JNICALL Java_util_InvokeDll_lastErr(JNIEnv , jobject)
{
int i=0;
return i;
}d、点击“运行”->“编译”,这时你会看到很多数据类型没被声明的错误,那是因为util_InvokeDllh使用了很多JAVA_HOME/include/jnih文件自定义的数据类型,而jnih又引
用了JAVA_HOME/include/win32/jni_mdh,这时你需要把jnih和jni_mdh引入到工程里面来,左击工程名“工程属性”->“文件/目录”->“包含文件目录”把JAVA_HOME/include和JAVA_HOME/include/win32文件夹添加,确定后,再次编译一切正常,这时在工程的目录下就找到java2dlldll文件,这样dll文件就生成了
4、调用其他标准的C++动态库,如testdll
例如testdll,是其他人编写的标准的C++动态库,可以在上述注释掉的部分调用,testFn是它里面的函数。
5、在webservice端的java调用
加载库Java2dll,并调用接口函数:
[cpp] view plaincopyprint
01static
02{
03 //动态库dll文件要放到系统目录或用绝对路径凋用Systemload("绝对路径")
04 SystemloadLibrary("Java2dll");
05}
06
07/实例化调用类
08InvokeDll dll = new InvokeDll();
09
10//调用函数
11String strRet = dlltestFunc(a, b)
static
{
//动态库dll文件要放到系统目录或用绝对路径凋用Systemload("绝对路径")
SystemloadLibrary("Java2dll");
}
//实例化调用类
InvokeDll dll = new InvokeDll();
//调用函数
String strRet = dlltestFunc(a, b)
总结:调用过程:java普通类-->java native类-->jni C++ 动态库-->标准C++动态库,最终实现java普通类对标准C++动态库的调用。
0条评论