RESTful发展背景及简介
网络应用程序,分为前端和后端两个部分。当前的发展趋势,就是前端设备层出不穷(手机、平板、桌面电脑、其他专用设备…)。因此,必须有一种统一的机制,方便不同的前端设备与后端进行通信。这导致API构架的流行,甚至出现"APIFirst"的设计思想。RESTful API是目前比较成熟的一套互联网应用程序的API设计理论。
REST(Representational State Transfer)表述性状态转换,REST指的是一组架构约束条件和原则。 如果一个架构符合REST的约束条件和原则,我们就称它为RESTful架构。REST本身并没有创造新的技术、组件或服务,而隐藏在RESTful背后的理念就是使用Web的现有特征和能力, 更好地使用现有Web标准中的一些准则和约束。虽然REST本身受Web技术的影响很深, 但是理论上REST架构风格并不是绑定在HTTP上,只不过目前HTTP是唯一与REST相关的实例。
RESTful架构
(1)每一个URI代表一种资源;
(2)客户端和服务器之间,传递这种资源的某种表现层;
(3)客户端通过HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。
符合上述REST原则的架构方式称为RESTful
URI命名规则
1. 全部小写,用中划线连接
命名规则永远是一个具有争议的话题,在RESTful接口中,对于URI一个普遍接受的规则是:全部小写,用中划线连接。
之所以不混用大小写字母,是因为早期的URI一般都是表示服务器上的文件路径,而不同服务器对大小写的敏感性是不同的,为了兼容不同服务器所以才规定不能混用大小写字母,然后用中划线或下划线连接多个单词。
我们如今开发的api,URI一般不再表示服务器上的文件了,而是一种程序使用的路由,而各类开发程序对字符串的判断也都是区分大小写的,所以就算URI混用了大小写一般也不会有问题。但是为了与那些仍旧表示服务器文件路径的URI保持一致,更多的人仍旧愿意沿用全部使用小写字母的规则。至于连接多个单词使用中划线还是下划线,我只能说,中划线使用得更加广泛。
2.单数复数
英文中,名词是有单复数之分的,所以在对资源命名时究竟是用单数还是复数,这也成为了一个有争议的问题。
我的使用习惯是,用单数来表示单个资源,用复数来表示集合资源。比如分页查询时用的是复数,单条数据查询时用的时单数
参考:https://blog.csdn.net/haochen_net/article/details/78159949
URL和URI的区别
URI包括URL和URN两个类别,URL是URI的子集,所以URL一定是URI,而URI不一定是URL
URI = Universal Resource Identifier 统一资源标志符,用来标识抽象或物理资源的一个紧凑字符串。
URL = Universal Resource Locator 统一资源定位符,一种定位资源的主要访问机制的字符串,一个标准的URL必须包括:protocol、host、port、path、parameter、anchor。
URN = Universal Resource Name 统一资源名称,通过特定命名空间中的唯一名称或ID来标识资源。
参考:https://blog.csdn.net/koflance/article/details/79635240
接口示例
传统URL请求格式:
http://127.0.0.1/user/query/1 GET 根据用户id查询用户数据
http://127.0.0.1/user/save POST 新增用户
http://127.0.0.1/user/update POST 修改用户信息
http://127.0.0.1/user/delete GET/POST 删除用户信息
RESTful请求格式:
http://127.0.0.1/user/1 GET 根据用户id查询用户数据
http://127.0.0.1/user POST 新增用户
http://127.0.0.1/user PUT 修改用户信息
http://127.0.0.1/user DELETE 删除用户信息
原文链接:https://blog.csdn.net/x541211190/article/details/81141459
HTTP动词
常用的方法有GET, POST, PUT, DELETE,此外有时还会用到HEAD、PATCH和OPTION。前面说我们系统的设计是以资源为核心的,URI的命名要使用名词,那我们要如何来表示一个操作动作呢?就是依靠HTTP方法,也叫HTTP动词。也就是通过这种动名词的组合来表示一次操作,在REST中会被叫做状态转移。
简单的讲GET是查询操作,不会产生状态转移, PUT和PATCH是更新操作,会将资源状态转移为客户端期望的新的状态,也有可能是一个从无到有的转移, DELETE表示删除,将资源状态转移为删除,POST表示添加一个资源,即资源状态从无转移到有,另外POST也被认为是一个万能的方法,可以用于所有的操作,包括写操作和复杂的查询操作。
GET(SELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。 DELETE(DELETE):从服务器删除资源。
还有两个不常用的HTTP动词。
HEAD:获取资源的元数据。
OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
HTTP动词 | 安全性 | 幂等性 | 功能性 |
---|---|---|---|
GET | 是 | 是 | 查询 |
POST | 否 | 否 | 创建 |
PUT | 否 | 是 | 更新 |
DELETE | 否 | 是 | 删除 |
HEAD | 是 | 是 | 查询 |
PATCH | 否 | 否 | 更新 |
幂等性:对同一REST接口的多次访问,得到的资源状态是相同的。
安全性:对该REST接口访问,不会使服务器端资源的状态发生改变。
PATCH方法是新引入的,是对PUT方法的补充,用来对已知资源进行局部更新
patch只传部分字段到指定资源去,表示该请求是一个局部更新,后端仅更新接收到的字段。
put虽然也是更新资源,但要求前端提供的一定是一个完整的资源对象,理论上说,如果你用了put,但却没有提供完整的对象,那么缺了的那些字段应该被清空
http响应状态码
200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - [* ]:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
401 Unauthorized - [* ]:表示用户没有权限(令牌、用户名、密码错误)。
403 Forbidden - [* ] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 NOT FOUND -[* ]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
406 Not Acceptable -[GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone-[GET]:用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
500 INTERNAL SERVER ERROR - [* ]:服务器发生错误,用户将无法判断发出的请求是否成功。
状态码的完全列表参见这里。
RESTful的使用
UserEntity
package com.honger1234.restfulapi.entity;
import lombok.Data;
/**
* @Description: 用户实体类
* @author: zt
* @date: 2020年3月26日
*/
@Data
public class UserEntity {
private Long id;
private String name;
private Integer age;
}
UserController
package com.honger1234.restfulapi.controller;
import com.honger1234.restfulapi.entity.UserEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
/**
* @Description: RESTful API
* @author: zt
* @date: 2020年3月26日
*/
@RestController
@RequestMapping(value="/users")
public class UserController {
static Map<Long, UserEntity> users = Collections.synchronizedMap(new HashMap<Long, UserEntity>());
static {
//初始化map模拟数据库存储数据
UserEntity user = new UserEntity();
user.setId(1L);
user.setName("RESTful API测试");
user.setAge(20);
users.put(user.getId(),user);
}
@RequestMapping(value="/", method= RequestMethod.GET)
public List<UserEntity> getUserList() {
// 处理"/users/"的GET请求,用来获取用户列表
// 还可以通过@RequestParam从页面中传递参数来进行查询条件或者翻页信息的传递
List<UserEntity> r = new ArrayList<UserEntity>(users.values());
return r;
}
@RequestMapping(value="/", method=RequestMethod.POST)
public UserEntity postUser(@ModelAttribute UserEntity user) {
// 处理"/users/"的POST请求,用来创建User
// 除了@ModelAttribute绑定参数之外,还可以通过@RequestParam从页面中传递参数
users.put(user.getId(), user);
return user;
}
@RequestMapping(value="/{id}", method=RequestMethod.GET)
public UserEntity getUser(@PathVariable Long id) {
// 处理"/users/{id}"的GET请求,用来获取url中id值的User信息
// url中的id可通过@PathVariable绑定到函数的参数中
return users.get(id);
}
@RequestMapping(value="/{id}", method=RequestMethod.PUT)
public UserEntity putUser(@PathVariable Long id, @ModelAttribute UserEntity user) {
// 处理"/users/{id}"的PUT请求,用来更新User信息
UserEntity u = users.get(id);
u.setName(user.getName());
u.setAge(user.getAge());
users.put(id, u);
return u;
}
@RequestMapping(value="/{id}", method=RequestMethod.DELETE)
public String deleteUser(@PathVariable Long id) {
// 处理"/users/{id}"的DELETE请求,用来删除User
users.remove(id);
return "success";
}
}
REST接口测试类 UserControllerTest
package com.honger1234.restfulapi.controller;
import com.honger1234.restfulapi.entity.UserEntity;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
class UserControllerTest {
private MockMvc mvc;
@BeforeEach
void setUp() {
mvc = MockMvcBuilders.standaloneSetup(
new UserController()).build();
}
@Test
void getUserList() throws Exception {
// 1、get查一下user列表,应该有一条数据
RequestBuilder request = get("/users/");
ResultActions resultActions = mvc.perform(request)
.andExpect(status().isOk());
// .andExpect(content().string(equalTo("[]")));
//将返回的信息做utf-8处理防止中文乱码
resultActions.andReturn().getResponse().setCharacterEncoding("UTF-8");
//添加输出
resultActions.andDo(print()).andExpect(status().isOk());
}
@Test
void postUser() throws Exception {
// 2、post提交一个user
RequestBuilder request = post("/users")
.param("id", "2")
.param("name", "测试大师")
.param("age", "20");
ResultActions resultActions = mvc.perform(request)
//.andDo(print()) 没做utf-8处理在这里打印可能出现中文乱码
// .andDo(print())
// .andExpect(content().string(equalTo("success")))
;
//将返回的信息做utf-8处理防止中文乱码
resultActions.andReturn().getResponse().setCharacterEncoding("UTF-8");
//添加输出
resultActions.andDo(print()).andExpect(status().isOk());
}
@Test
void getUser() throws Exception {
// 3、获取一个id为1的user
RequestBuilder request = get("/users/1");
ResultActions resultActions = mvc.perform(request)
.andExpect(status().isOk());
// .andExpect(content().string(equalTo("[{\"id\":1,\"name\":\"测试大师\",\"age\":20}]")));
//将返回的信息做utf-8处理防止中文乱码
resultActions.andReturn().getResponse().setCharacterEncoding("UTF-8");
//添加输出
resultActions.andDo(print()).andExpect(status().isOk());
}
@Test
void putUser() throws Exception {
// 4、put修改id为1的user
UserEntity user = new UserEntity();
user.setId(3L);
user.setName("更新API");
user.setAge(30);
RequestBuilder request = put("/users/1")
.param("user", user.toString());
ResultActions resultActions = mvc.perform(request)
.andExpect(status().isOk());
//将返回的信息做utf-8处理防止中文乱码
resultActions.andReturn().getResponse().setCharacterEncoding("UTF-8");
//添加输出
resultActions.andDo(print()).andExpect(status().isOk());
}
@Test
void deleteUser() throws Exception {
// 5、del删除id为1的user
RequestBuilder request = delete("/users/1");
ResultActions resultActions = mvc.perform(request)
.andExpect(content().string(equalTo("success")));
//将返回的信息做utf-8处理防止中文乱码
resultActions.andReturn().getResponse().setCharacterEncoding("UTF-8");
//添加输出
resultActions.andDo(print()).andExpect(status().isOk());
}
}
结语
至此,RESTful的介绍和使用已经完成,RESTful已在各互联网公司接口定义中,成为主流的范式,RESTful减少了传统请求的拆装箱操作,结构清晰,在接口定义中受到前后端开发者的青睐。