RestFul服务

发表时间:2017-07-18 18:41:40 浏览量( 57 ) 留言数( 0 )

学习目标:

1、了解上面是RestFul服务

2、了解RestFul的风格

3、了解springMVC对RestFul的支持


学习过程:

一、远程接口

学些rest之前,我们简单说一下下面几个概念   

   1、 RPC接口:Remote Procedure Calls 远程过程调用 (RPC) 是一种协议,程序可使用这种协议向网络中的另一台计算机上的程序请求服务。由于使用 RPC 的程序不必了解支持通信的网络协议的情况,因此 RPC 提高了程序的互操作性。在 RPC 中,发出请求的程序是客户程序,而提供服务的程序是服务器。 RPC(远程过程调用)是一项广泛用于支持分布式应用程序(不同组件分布在不同计算机上的应用程序)的技术。RPC 的主要目的是为组件提供一种相互通信的方式,使这些组件之间能够相互发出请求并传递这些请求的结果。 没有语言限制。

   2、 基于SOAP的Webservice接口:Webservice是系统对外的接口,比如你要从别的网站或服务器上获取资源或信息,别人肯定不会把数据库共享给你,他只能给你提供一个他们写好的方法来获取数据,你引用他提供的接口就能使用他写好的方法,从而达到数据共享的目的。Webservice本来是个通用词汇代表所有基于web的服务(可以参考维基web service词条),后来似乎变成了某种基于XML的RPC协议的专用词汇。至于这个协议具体到底是XML-RPC还是SOAP RPC我已经不记得了,估计也就“企业级”Java应用还在用。以SOAP为例,一个RPC call就是把一个XML文档post到某个URL下,这个xml文档里写明我要调用的函数名和参数。服务端会返回一个xml把结果返回。这样的设计是把HTTP当传输层,可以把传输层替换成其他协议只要能在客户端服务端之间传输xml就可以。

  3、 REST风格的WebService : 简称 REST,是描述了一个架构样式的网络系统,其核心是面向资源,REST专门针对网络应用设计和开发方式,以降低开发的复杂性,提高系统的可伸缩性。REST提出设计概念和准则为。REST是完全不同的思路,它充分利用了HTTP协议的4个主要verb把RPC操作分成4类:- GET:进行幂等的资源获取操作- POST:创建资源- PATCH:修改资源- DELETE:删除资源仔细想一下这其实就是数据库的CRUD操作POST=create,GET=read,PATCH=update,DELETE=delete。RESTful是个形容词,形容根据这个思路设计出来的REST风格的API真正的REST API很少见,绝大多数只是像REST而已,只用GET/POST两个verb。

  4、  OAuth(开放授权)是一个开放的授权标准,允许用户让第三方应用访问该用户在某一web服务上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。

    OAuth允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的第三方系统(例如,视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容。

    正是由于OAUTH的严谨性和安全性,现在OAUTH已成为RESTful架构风格中最常用的认证机制,和RESTful架构风格一起,成为企业级服务的标配。

    目前OAuth已经从OAuth1.0发展到OAuth2.0,但这二者并非平滑过渡升级,OAuth2.0在保证安全性的前提下大大减少了客户端开发的复杂性,因此,Gevin建议在实战应用中采用OAuth2.0认证机制。


二、RestFul风格的规范

   RestFul的风格规范并不具有强制性。完全按照RestFul风格规范的接口定义也比较少。我们很多时候并没有使用DELETE和PATCH等操作。更多的时候只是使用GET和POST。但是也有一些规范是非常推荐使用的,比如使用JSON作为标准的格式等。

1、协议

API与用户的通信协议,总是使用HTTPs协议。


2、域名

应该尽量将API部署在专用域名之下。

https://api.example.com

如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。

https://example.org/api/


3、版本(Versioning)

应该将API的版本号放入URL。

https://api.example.com/v1/

另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。


4、路径(Endpoint)

路径又称"终点"(endpoint),表示API的具体网址。

如果集合永远是单元素的,那就应该用单数

在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数。


举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。

https://api.example.com/v1/zoos

https://api.example.com/v1/animals

https://api.example.com/v1/employees


5、HTTP动词

对于资源的具体操作类型,由HTTP动词表示。

常用的HTTP动词有下面五个(括号里是对应的SQL命令)。

GET(SELECT):从服务器取出资源(一项或多项)。

POST(CREATE):在服务器新建一个资源。

PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。

PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。

DELETE(DELETE):从服务器删除资源。

还有两个不常用的HTTP动词。


HEAD:获取资源的元数据。

OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。

下面是一些例子。

GET /zoos:列出所有动物园

POST /zoos:新建一个动物园

GET /zoos/ID:获取某个指定动物园的信息

PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)

PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)

DELETE /zoos/ID:删除某个动物园

GET /zoos/ID/animals:列出某个指定动物园的所有动物

DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物


6、过滤信息(Filtering)

如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。


下面是一些常见的参数。

?limit=10:指定返回记录的数量

?offset=10:指定返回记录的开始位置。

?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。

?animal_type_id=1:指定筛选条件

参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。



7、状态码(Status Codes)

服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。

200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。

201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。

204 NO CONTENT - [DELETE]:用户删除数据成功。

400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。。

404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。

500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

状态码的完全列表参见这里。


8、错误处理(Error handling)

如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。

{

    error: "Invalid API key"

}


9、返回结果

针对不同操作,服务器向用户返回的结果应该符合以下规范。


GET /collection:返回资源对象的列表(数组)

GET /collection/resource:返回单个资源对象

POST /collection:返回新生成的资源对象

PUT /collection/resource:返回完整的资源对象

PATCH /collection/resource:返回完整的资源对象

DELETE /collection/resource:返回一个空文档


10、Hypermedia API

RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。

比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。


{"link": {

  "rel":   "collection https://www.example.com/zoos",

  "href":  "https://api.example.com/zoos",

  "title": "List of zoos",

  "type":  "application/vnd.yourformat+json"

}}

上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系 (collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。


Hypermedia API的设计被称为HATEOAS。Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。

{

  "current_user_url": "https://api.github.com/user",

  "authorizations_url": "https://api.github.com/authorizations",

  // ...

}

从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。

{

  "message": "Requires authentication",

  "documentation_url": "https://developer.github.com/v3"

}

上面代码表示,服务器给出了提示信息,以及文档的网址。


11、其他

(1)API的身份认证应该使用OAuth 2.0框架。

(2)服务器返回的数据格式,应该尽量使用JSON,避免使用XML。



三、Spring MVC对restful

   Spring MVC对restful的支持非常好,JSON都是可以默认自动转换的。我们可以定义当前整个类是RestController。下面只是简单的给一个示例,大家可以自己多上机测试的。

@RestController  
public class HelloWorldRestController {  

       
    @RequestMapping(value = "/user/", method = RequestMethod.GET)  
    public List<User> listAllUsers() {  
        List<User> users = new ArrayList<>();
        
        return users;
    }  
   
  
       
    @RequestMapping(value = "/user/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)  
    public User getUser(@PathVariable("id") long id) {  
        System.out.println("Fetching User with id " + id);  
        User user = new User();
        return user;  
    }  
   
       
     
  
       
    @RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)  
    public User updateUser(@PathVariable("id") long id, @RequestBody User user) {  
        System.out.println("Updating User " + id);  
           
       
        return user;  
    }  
    
       
    @RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)  
    public ResponseEntity<User> deleteUser(@PathVariable("id") long id) {  
        System.out.println("Fetching & Deleting User with id " + id);  
   
    
        return new ResponseEntity<User>(HttpStatus.NO_CONTENT);  
    }  
   
    @RequestMapping(value = "/user/", method = RequestMethod.DELETE)  
    public ResponseEntity<User> deleteAllUsers() {  
        System.out.println("Deleting All Users");  
   
 
        return new ResponseEntity<User>(HttpStatus.NO_CONTENT);  
    }  
   
}