搭建 Restful Web 服务

古之立大事者,不惟有超世之才,亦必有坚忍不拔之志。这篇文章主要讲述搭建 Restful Web 服务相关的知识,希望能为你提供帮助。
1. 理解 REST    REST 全称是 Representational State Transfer,中文意思是表征性状态转移。它首次出现在2000年Roy Fielding的博士论文中,Roy Fielding是HTTP规范的主要编写者之一。值得注意的是REST并没有一个明确的标准,而更像是一种设计的风格。如果一个架构符合REST的约束条件和原则,我们就称它为RESTful架构。

搭建 Restful Web 服务

文章图片

    理论上REST架构风格并不是绑定在HTTP上,只不过目前HTTP是唯一与REST相关的实例。
1.1. REST 原则
  • 资源 可通过目录结构样式的 URIs 暴露
  • 表述 可以通过 JSON 或 XML 表达的数据对象或属性来传递
  • 消息 使用统一的 HTTP 方法(例如:GET、POST、PUT 和 DELETE)
  • 无状态 客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息 1.2. HTTP 方法【搭建 Restful Web 服务】    使用 HTTP 将 CRUD(create, retrieve, update, delete < 创建、获取、更新、删除—增删改查> )操作映射为 HTTP 请求。如果按照HTTP方法的语义来暴露资源,那么接口将会拥有安全性和幂等性的特性,例如GET和HEAD请求都是安全的, 无论请求多少次,都不会改变服务器状态。而GET、HEAD、PUT和DELETE请求都是幂等的,无论对资源操作多少次, 结果总是一样的,后面的请求并不会产生比第一次更多的影响。
    1.2.1. GET
  • 安全且幂等
  • 获取信息 1.2.2. POST
  • 不安全且不幂等
  • 使用请求中提供的实体执行操作,可用于创建资源或更新资源 1.2.3. PUT
  • 不安全但幂等
  • 使用请求中提供的实体执行操作,可用于创建资源或更新资源 1.2.4. DELETE
  • 不安全但幂等
  • 删除资源
        POST和PUT在创建资源的区别在于,所创建的资源的名称(URI)是否由客户端决定。 例如为我的博文增加一个java的分类,生成的路径就是分类名/categories/java,那么就可以采用PUT方法。不过很多人直接把POST、GET、PUT、DELETE直接对应上CRUD,例如在一个典型的rails实现的RESTful应用中就是这么做的。 1.3. HTTP status codes    状态码指示 HTTP 请求的结果:
  • 1XX:信息
  • 2XX:成功
  • 3XX:转发
  • 4XX:客户端错误
  • 5XX:服务端错误 1.4. 媒体类型    HTTP头中的 Accept 和 Content-Type 可用于描述HTTP请求中发送或请求的内容。如果客户端请求JSON响应,那么可以将 Accept 设为 application/json。相应地,如果发送的内容是XML,那么可以设置 Content-Type 为 application/xml 。
    2. REST API 设计最佳实践    这里介绍一些设计 REST API 的最佳实践,大家先记住下面这句话:
  • GET - /users:返回用户列表
  • GET - /users/100:返回一个特定用户
  • POST - /users:创建一个新用户
  • PUT - /users/200:更新一个特定用户
  • DELETE - /users/711:删除一个特定用户
        不要使用动词:
  • /getAllsers
  • /getUserById
  • /createNewUser
  • /updateUser
  • /deleteUser 2.2 在 HTTP 头中使用适当的序列化格式    客户端和服务端都需要知道通信所用的格式,这个格式要在 HTTP 头中指定:
  • Content-Type 定义请求格式
  • Accept 定义一个可接受的响应格式列表 2.3 Get 方法和查询参数不应当改变状态    使用 PUT, POST 和 DELETE 方法来改变状态,不要使用 GET 方法来改变状态:
  • GET /users/711?activate
  • GET /users/711/activate 2.4. 使用子资源表示关联    如果一个资源与另一个资源关联,使用子资源:
  • GET /cars/711/drivers/ 返回711号汽车的驾驶员列表
  • GET /cars/711/drivers/4 返回711号汽车的第4号驾驶员 2.5. 使用适当的 HTTP 方法 (动词)    再回顾一下这句话:
  • GET:获取在URI资源中指定的表述,响应消息体包含所请求资源的细节。
  • POST:创建一个URI指定的新资源,请求消息体提供新资源的细节。注意,POST也可以触发一些操作,而不一定是要创建新资源。
  • PUT:创建或替代指定URI的资源。请求消息体指定要创建或更新的资源。
  • DELETE:移除指定URI的资源。
    2.6. HTTP 响应状态码    当客户端通过API向服务端发起一个请求时,客户端应当知道反馈:是否失败、通过或者请求错误。HTTP 状态码是一批标准化代码,在不同的场景下有不同的解释。服务器应当总是返回正确的状态码。
        下面是重要的HTTP代码分类:
  • 2xx (成功分类):这些状态码代码请求动作被接收且被服务器成功处理。
    • 200:Ok 表示 GET、PUT 或 POST 请求的标准状态码。
    • 201:Created(已创建)表示实例已被创建,多用于 POST 方法。
    • 204:No Content(无内容)表示请求已被成功处理但没有返回任何内容。常用于 DELETE 方法返回。
  • 3xx (转发分类)
    • 304:Not Modified(无修改)表示客户端已经缓存此响应,无须再次传输相同内容。
  • 4xx (客户端错误分类):这些状态码代表客户端提交了一个错误请求。
    • 400:Bad Request(错误请求)表示客户端请求没被处理,因为服务端无法理解客户端请求。
    • 401:Unauthorized(无授权)表示客户端无权访问资源,应当加上所需的认证信息后再次请求。
    • 403:Forbidden(禁止访问)表示请求有效且客户端已获授权,但客户端无权访问该资源。
    • 404:Not Found(没发现)表示所请求的资源现在不可用。
    • 410:Gone(移除)表示所请求的资源已被移除。
  • 5xx (服务端错误分类)
    • 500:Internal Server Error(内部服务器错误)表示请求有效,但是服务端发生了异常。
    • 503:Service Unavailable(服务不可用)表示服务器关闭或不可用,通常是指服务器处于维护状态。 2.7. 名称规约    你可以遵循任何名称规约,只要保持跨应用一致性即可。如果请求体和响应体是 JSON 类型,那么请遵循驼峰名称规约。
      2.8. 搜索、排序、过滤与分页    上面一些示例都是在一个数据集上的简单查询,对于复杂的数据,我们需要在 GET 方法 API 上加一些参数来处理。下面是一些示例:
  • 排序:这个例子中,客户想获取排序的公司列表,GET /companies 应当在查询时接受多种排序参数。譬如 GET /companies?sort=rank_asc 将以等级升序的方式对公司进行排序。
  • 过滤:要过滤数据集,我们可以通过查询参数传递不同的选项。比如 GET /companies?category=banking& location=india 将过滤分类为银行且位于印度的公司。
  • 搜索:在公司列表中搜索公司名的 API 端点应当是 GET /companies?search=Digital。
  • 分页:当数据集太大时,我们应当将数据集分割成小的数据块,这样有利于提升服务端性能,也方便客户端处理响应。如 GET /companies?page=23 意味着获取公司列表的第 23 页数据。 2.9. Restful API 版本    一般使用不带点的简单数字表示版本,数字前加字母v代表版本号,如下所示:
  • /blog/api/v1
  • http://api.yourservice.com/v1/companies/34/employees 2.10. 处理 JSON 错误体    API 错误处理机制是很重要的,而且要好好规划。极力推荐总是在返回字段中包含错误消息。一个 JSON 错误体应当为开发者提供一些有用的信息:错误消息、错误代码以及详细描述。下面是一个较好的示例:
    "code": 1234, "message": "Something bad happened :(", "description": "More details about the error here"

    2.11. 如何创建 Rest API URL    推荐使用下面格式的 URL:
  • http(s)://域名(:端口号)/表示REST API的值/API版本/识别资源的路径
  • http(s)://表示REST API的域名(:端口号)/API 版本/识别资源的路径
        如下所示:
  • http://example.com/api/v1/members/M000000001
  • http://api.example.com/v1/members/M000000001 3. 开发基于 Spring Boot 的 Restful Web 服务    Spring Boot 提供了构建企业应用中 RESTful Web 服务的极佳支持。
    3.1. 引入依赖    要构建 RESTful Web 服务,我们需要在构建配置文件中加上 Spring Boot Starter Web 依赖。
        对于 Maven 用户,使用以下的代码在 pom.xml 文件中加入依赖:
    < dependency> < groupId> org.springframework.boot< /groupId> < artifactId> spring-boot-starter-web< /artifactId> < /dependency>

        对于 Gradle 用户,使用以下的代码在 build.gradle 文件中加入依赖:
    compile(org.springframework.boot:spring-boot-starter-web)

    3.2. Rest 相关注解    在继续构建 RESTful web 服务前,建议你先要熟悉下面的注解:
    Rest Controller
        @RestController 注解用于定义 RESTful web 服务。它提供 JSON、XML 和自定义响应。语法如下所示:
    @RestController public class ProductServiceController

    Request Mapping
        @RequestMapping 注解用于定义请求 URI 以访问 REST 端点。我们可以定义 Request 方法来消费 produce 对象。默认的请求方法是 GET:
    @RequestMapping(value = "https://www.songbingjia.com/products") public ResponseEntity< Object> getProducts() Request Body @RequestBody 注解用于定义请求体内容类型。 public ResponseEntity< Object> createProduct(@RequestBody Product product)

    Path Variable
        @PathVariable 注解被用于定义自定义或动态的请求 URI,Path variable 被放在请求 URI 中的大括号内,如下所示:
    public ResponseEntity< Object> updateProduct(@PathVariable("id") String id)

    Request Parameter
        @RequestParam 注解被用于从请求 URL 中读取请求参数。缺省情况下是必须的,也可以为请求参数设置默认值。如下所示:
    public ResponseEntity< Object> getProduct(
    @RequestParam(value = https://www.songbingjia.com/android/" name" , required = false, defaultValue = " honey" ) String name)

    3.3. 编写 REST API GET API
        下面的示例代码定义了 HTTP GET 请求方法。在这个例子里,我们使用 HashMap 来在存储 Product。注意我们使用了 POJO 类来存储产品。
        在这里,请求 URI 是 /products,它会从 HashMap 仓储中返回产品列表。下面的控制器类文件包含了 GET 方法的 REST 端点:
    package com.tutorialspoint.demo.controller;

import java.util.HashMap;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.tutorialspoint.demo.model.Product;
@RestController
public class ProductServiceController
private static Map< String, Product> productRepo = new HashMap< > ();
static
Product honey = new Product();
honey.setId(" 1" );
honey.setName(" Honey" );
productRepo.put(honey.getId(), honey);
Product almond = new Product(); almond.setId("2"); almond.setName("Almond"); productRepo.put(almond.getId(), almond);


@RequestMapping(value = https://www.songbingjia.com/android/" /products" )
public ResponseEntity< Object> getProduct()
return new ResponseEntity< > (productRepo.values(), HttpStatus.OK);


### POST API & emsp; & emsp; HTTP POST 请求用于创建资源。这个方法包含请求体。我们可以通过发送请求参数和路径变量来定义自定义或动态 URL。 & emsp; & emsp; 下面的示例代码定义了 HTTP POST 请求方法。在这个例子中,我们使用 HashMap 来存储 Product,这里产品是一个 POJO 类。 & emsp; & emsp; 这里,请求 URI 是 /products,在产品被存入 HashMap 仓储后,它会返回字符串。 ```java package com.tutorialspoint.demo.controller; import java.util.HashMap; import java.util.Map; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.tutorialspoint.demo.model.Product; @RestController public class ProductServiceController private static Map< String, Product> productRepo = new HashMap< > (); @RequestMapping(value = "https://www.songbingjia.com/products", method = RequestMethod.POST) public ResponseEntity< Object> createProduct(@RequestBody Product product) productRepo.put(product.getId(), product); return new ResponseEntity< > ("Product is created successfully", HttpStatus.CREATED);

PUT API
    HTTP PUT 请求用于更新已有的资源。这个方法包含请求体。我们可以通过发送请求参数和路径变量来定义自定义或动态 URL。
    下面的例子展示了如何定义 HTTP PUT 请求方法。在这个例子中,我们使用 HashMap 更新现存的产品。此处,产品是一个 POJO 类。
    这里,请求 URI 是 /products/id,在产品被存入 HashMap 仓储后,它会返回字符串。注意我们使用路径变量 id 定义需要更新的产品 ID:
package com.tutorialspoint.demo.controller; import java.util.HashMap; import java.util.Map; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.tutorialspoint.demo.model.Product; @RestController public class ProductServiceController private static Map< String, Product> productRepo = new HashMap< > (); @RequestMapping(value = "https://www.songbingjia.com/products/id", method = RequestMethod.PUT) public ResponseEntity< Object> updateProduct(@PathVariable("id") String id, @RequestBody Product product) productRepo.remove(id); product.setId(id); productRepo.put(id, product); return new ResponseEntity< > ("Product is updated successsfully", HttpStatus.OK);

DELETE API
    HTTP Delete 请求用于删除存在的资源。这个方法不包含任何请求体。我们可以通过发送请求参数和路径变量来定义自定义或动态 URL。
    下面的例子展示如何定义 HTTP DELETE 请求方法。这个例子中,我们使用 HashMap 来移除现存的产品,用 POJO 来表示。
    请求 URI 是 /products/id 在产品被从 HashMap 仓储中删除后,它会返回字符串。 我们使用路径变量 id 来定义要被删除的产品 ID。
package com.tutorialspoint.demo.controller; import java.util.HashMap; import java.util.Map; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.tutorialspoint.demo.model.Product; @RestController public class ProductServiceController private static Map< String, Product> productRepo = new HashMap< > (); @RequestMapping(value = "https://www.songbingjia.com/products/id", method = RequestMethod.DELETE) public ResponseEntity< Object> delete(@PathVariable("id") String id) productRepo.remove(id); return new ResponseEntity< > ("Product is deleted successsfully", HttpStatus.OK);


    推荐阅读