美文网首页
【Spring Boot】构建RESTful API——(二)

【Spring Boot】构建RESTful API——(二)

作者: Jotyy | 来源:发表于2018-05-21 14:59 被阅读0次

    一、RESTful API设计规范

    参考知乎上的《RESTful API最佳实践》一文,总结的RESTful API设计规范如下:

    1.URI
    • 应该将API部署在专用域名之下:https://api.example.com

    • 不用大写

    • 用中杠-不用下杠_;

    • 参数列表要encode;

    • URI中不应该出现动词,动词应该使用HTTP方法表示,但是如果无法表示,也可使用动词,例如:search没有对应的HTTP方法,可以在路径中使用search,更加直观;

    • URI中的名词表示资源集合,使用复数形式;

    • 虽然/在URI中表达层级,但是避免为了追求REST导致层级过深,适当使用参数表示。

      GET /comments/tid?tid=1&page=1
      
    2.Request:通过标准http方法对资源CRUD
    • GET:查询资源

      GET /comments //获取所有评论
      GET /comments/tid/1 //获取文章tid为1的所有评论
      
    • POST:创建资源

      POST /comments/tid/1 //为tid为1的文章创建评论
      
    • PUT:更新资源

      PUT /comments/cid/like/1 //为cid为1的评论点赞
      
    • DELETE:删除资源

      DELETE /comments/cid/1 //删除cid为1的评论
      
    3.Response
    • 采用JSON,不要使用XML

    • 默认情况下JSON外层不需要嵌套大括号,API需要支持JSONP跨域访问或者客户端无法访问HTTP header才需要加上嵌套大括号

    • 默认情况下不要过滤API输出中的空格,并且要支持gzip

    4.API版本控制
    • 在URI中存放:GET /v1/comments;
    • 客户端在Accept Header中存放:Accept: application/vnd.github.v3+json,服务器自定义Header返回当前版本信息:X-GitHub-Media-Type: github.v3; format=json(GitHub在用);
    • 以上两种方法根据情况选择,Github用的方式是REST中所要求的方式;
    • 测试API和正式API要进行区分,方式通过如上两种方式实现。
    5.速度限制

    为了避免请求泛滥,给API设置速度限制很重要。为此 RFC 6585 引入了HTTP状态码429(too many requests)。加入速度设置之后,应该提示用户,至于如何提示标准上没有说明,不过流行的方法是使用HTTP的返回头。
    下面是几个必须的返回头(依照twitter的命名规则):

    • X-Rate-Limit-Limit :当前时间段允许的并发请求数
    • X-Rate-Limit-Remaining:当前时间段保留的请求数。
    • X-Rate-Limit-Reset:当前时间段剩余秒数

    为什么使用当前时间段剩余秒数而不是时间戳?

    时间戳保存的信息很多,但是也包含了很多不必要的信息,用户只需要知道还剩几秒就可以再发请求了这样也避免了clock skew问题。

    6.缓存

    HTTP提供了自带的缓存框架。你需要做的是在返回的时候加入一些返回头信息,在接受输入的时候加入输入验证。基本两种方法:

    • ETag:当生成请求的时候,在HTTP头里面加入ETag,其中包含请求的校验和和哈希值,这个值和在输入变化的时候也应该变化。如果输入的HTTP请求包含IF-NONE-MATCH头以及一个ETag值,那么API应该返回304 not modified状态码,而不是常规的输出结果。
    • Last-Modified:和etag一样,只是多了一个时间戳。返回头里的Last-Modified:包含了 RFC 1123 时间戳,它和IF-MODIFIED-SINCE一致。HTTP规范里面有三种date格式,服务器应该都能处理。
    7.覆盖HTTP方法

    一些HTTP客户端只支持GET和POST请求。为了能够加强这些客户端的访问能力,API需要能够覆盖HTTP方法。尽管这里没有任何强制的标准,但流行的做法是API会接收一个请求头X-HTTP-Method-Override,它的值可以是PUT、PATCH或者DELETE三者之一。

    注意,用来覆盖HTTP方法的header只能在POST请求中被接受。GET请求永远不能修改服务器上的数据

    8.过滤信息

    如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。
    下面是一些常见的参数:

    ?limit=10:指定返回记录的数量
    ?offset=10:指定返回记录的开始位置。
    ?page=2&per_page=100:指定第几页,以及每页的记录数。
    ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
    ?animal_type_id=1:指定筛选条件
    
    9.错误处理

    就像HTML的出错页面向访问者展示了有用的错误消息一样,API也应该用之前熟悉易读的格式来提供有用的错误消息。错误的表现形式应该跟其他资源保持一致,只是用一些自己的字段。

    API应该一直返回合理的HTTP状态码。API错误一般情况下分成两类:代表客户端错误的400系列状态码和代表服务端错误的500系列状态码。API至少把所有400系列错误统一用易读的JSON格式来展示。如果可能(比如,如果负载均衡和反向代理能够创建自定义错误内容的话),500系列的状态码也这么弄。

    JSON错误内容应该为开发者提供一些东西 - 有用的错误消息,唯一的错误码(通过它可以在文档中找到更多错误细节),可能的话提供错误细节描述。用JSON格式来输出错误看起来这样:

    {
      "code" : 1234,
      "message" : "Something bad happened :(",
      "description" : "More details about the error here"
    }
    

    对于PUT、PATCH和POST的请求进行的校验错误需要嵌套多个字段。最佳做法是用固定的错误码来表示校验失败,然后在额外的errors字段中提供错误的细节,像这样:

    {
      "code" : 1024,
      "message" : "Validation Failed",
      "errors" : [
        {
          "code" : 5432,
          "field" : "first_name",
          "message" : "First name cannot have fancy characters"
        },
        {
           "code" : 5622,
           "field" : "password",
           "message" : "Password cannot be blank"
        }
      ]
    }
    
    10.HTTP状态码

    HTTP定义了很多有意义的状态码,你可以在你的API中使用。这些状态码可以帮助API消费者用来路由它们获取到的响应内容。整理了一个你肯定会用到的状态码列表:

    • 200 OK - 对成功的GET、PUT、PATCH或DELETE操作进行响应。也可以被用在不创建新资源的POST操作上
    • 201 Created - 对创建新资源的POST操作进行响应。应该带着指向新资源地址的Location header)
    • 204 No Content - 对不会返回响应体的成功请求进行响应(比如DELETE请求)
    • 304 Not Modified - HTTP缓存header生效的时候用
    • 400 Bad Request - 请求异常,比如请求中的body无法解析
    • 401 Unauthorized - 没有进行认证或者认证非法。当API通过浏览器访问的时候,可以用来弹出一个认证对话框
    • 403 Forbidden - 当认证成功,但是认证过的用户没有访问资源的权限
    • 404 Not Found - 当一个不存在的资源被请求
    • 405 Method Not Allowed - 所请求的HTTP方法不允许当前认证用户访问
    • 410 Gone - 表示当前请求的资源不再可用。当调用老版本API的时候很有用
    • 415 Unsupported Media Type - 如果请求中的内容类型是错误的
    • 422 Unprocessable Entity - 用来表示校验错误
    • 429 Too Many Requests - 由于请求频次达到上限而被拒绝访问
    11.认证

    RESTful API应该是无状态。这意味着对请求的认证不应该基于cookie或者session。相反,每个请求应该带有一些认证凭证。

    如果一直使用SSL,认证凭证可以简单的使用随机生成的access token,把其做为HTTP Basic Auth中user name字段的值传给API。这么做的好处是可以通过浏览器访问 - 如果浏览器从服务器收到401 Unauthorized状态码,它将会弹出一个对话框让人输出认证凭证。

    当然,这种基于token来进行基本认证的方法只能当用户从API管理后台拷贝了一个token到自己的代码中才行。如果搞不到token,只能使用OAuth 2来把安全token传递给第三方。OAuth 2使用Bearer token,并且也是基于SSL来保证传输安全。

    支持JSONP的API可能需要第三种方法来实现认证,因为JSONP的请求没法发送HTTP Basic Auth凭证或者Bearer token。这种情况下,可以使用一个额外的查询参数access_token。注意:使用查询参数来传递token存在一个固有的安全隐患,因为大多数web服务器会在服务器日志中保存查询参数。

    不管怎么样,以上三种方法是用来在API之间传输token的方法。实际传输的token可以是一样的。

    12.使用SSL

    一定要使用SSL。没有例外。如今,你的web API可以从任何有互联网的地方(像图书馆,咖啡馆,机场等等)被访问到。这些地方并不都是安全的。很多地方根本没有对网络连接进行加密,如果认证凭证被劫持的话,这样访问者很容易被窃听或者被冒充。

    一直使用SSL的另一个优势是,加密的连接简化了用户认证的工作 - 你可以使用简单的access token,而不需要对每个API请求进行签名。

    需要注意的一件事是以非SSL的形式访问API的URL。不要把请求跳转到它们的SSL版本上。直接抛出一个严重错误!

    13.Hypermedia API

    RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
    比如,当用户向http://api.example.com的根目录发出请求,会得到这样一个文档。

    {"link": {
      "rel":   "collection https://www.example.com/comments",
      "href":  "https://api.example.com/comments",
      "title": "List of comments",
      "type":  "application/vnd.yourformat+json"
    }}
    

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

    在进行分页查询时可以返回下一页的URI,如果没有说明服务器已经取到最后一条数据了,客户端可以减少不必要的请求以及URI的构造,建议在分页的情况下使用。

    二、构建一个RESTful API

    首先,在Spring Boot中我们会使用到这些注解。

    • @Controller: 修饰class,用来创建处理http请求的对象
    • @RestController:Spring4之后加入的注解,原本在@Controller中返回json需要@ResponseBody来配合,如果直接用@RestController就不需要再配置,默认返回json格式,所以一般直接使用这个
    • @RequestMapping:配置url映射

    接下来,我们设计RESTful API接口如下:

    请求类型 URL 功能说明
    GET /girls 查询所有女生
    POST /girls 创建一个女生
    GET /gilrs/id 根据id查询一个女生信息
    PUT /gilrs/id 根据id更新一个女生信息
    DELETE /gilrs/id 根据id删除一个女生信息

    新建Girl实体类:

    public class Girl {
        
        private Integer id;
        
        private String name;
        
        private String cupSize;
        
        //省略setter和getter..
    }
    

    创建GirlController类

    @RestController
    public class GirlController {
    
        //创建线程安全的Map
        static Map<Integer,Girl> girls = Collections.synchronizedMap(
                new HashMap<Integer, Girl>()
        );
    
        @GetMapping(value = "/girls")
        public List<Girl> getGirls(){
            List<Girl> res = new ArrayList<Girl>(girls.values());
            return res;
        }
    
        @PostMapping(value="/girls")
        public String postUser(@ModelAttribute Girl girl) {
            girls.put(girl.getId(), girl);
            return "success";
        }
    
        @GetMapping(value="/girls/{id}")
        public Girl getUser(@PathVariable Integer id) {
            return girls.get(id);
        }
    
        @PutMapping(value="/girls/{id}")
        public String putUser(@PathVariable Integer id, @ModelAttribute Girl girl) {
            Girl u = girls.get(id);
            u.setName(girl.getName());
            u.setCupSize(girl.getCupSize());
            girls.put(id, u);
            return "success";
        }
    
        @DeleteMapping(value="/girls/{id}")
        public String deleteUser(@PathVariable Long id) {
            girls.remove(id);
            return "success";
        }
    }
    
    

    运行程序,就能够简单的实现了这个RESTful API的功能。可以在POSTMAN程序中进行简单的测试,但是我们会发现一个严重的问题,我们写的这个程序没有数据,所以接下来我们要来了解如何在Spring Boot中使用数据库。

    相关文章

      网友评论

          本文标题:【Spring Boot】构建RESTful API——(二)

          本文链接:https://www.haomeiwen.com/subject/zcnfjftx.html