文章目录

  • RestAPI
  • Content-Type
  • Servlet
  • HttpServletRequest
  • 请求行
  • 请求头
  • 请求参数
  • HttpServletResponse
  • 状态码
  • SpringBoot中支持
  • Controller与Method
  • RequestMapping
  • ExceptionHandler
  • 统一异常捕捉
  • 参数自动验证
  • API接口示例
  • Form方式请求
  • 文件上传


RestAPI

RESTful是目前非常流行的一种互联网软件架构,其核心概念是资源(网络中任何东西都看作资源)。通过URL定位资源,用HTTP请求方式表示操作:

  • URL:要什么(资源)?
  • HTTP Method:干什么(动作)?
  • Status Code:结果怎样?

Content-Type

MediaType,即是Internet Media Type,互联网媒体类型;也叫做MIME类型,在Http协议消息头中,使用Content-Type来表示具体请求中的媒体类型信息。常见的媒体格式类型如下:

  • text/html : HTML格式
  • text/plain :纯文本格式
  • text/xml : XML格式
  • image/gif :gif图片格式
  • image/jpeg :jpg图片格式
  • image/png:png图片格式
  • application/xhtml+xml :XHTML格式
  • application/xml : XML数据格式
  • application/atom+xml :Atom XML聚合格式
  • application/json : JSON数据格式
  • application/pdf :pdf格式
  • application/msword : Word文档格式
  • application/octet-stream : 二进制流数据(如常见的文件下载)
  • application/x-www-form-urlencoded : form表单数据(会被编码为key/value格式发送到服务器)
  • multipart/form-data : 需要在表单中进行文件上传时,就需要使用该格式

Servlet

Servlet 是运行在 Web 服务器或应用服务器上的程序,它是’Web 浏览器或其他 HTTP 客户端的请求‘和‘ HTTP 服务器上’的数据库或应用程序之间的中间层。

api java开发rest java rest api_RestAPI

在接收到客户端请求时,Servlet容器会为请求创建代表HTTP请求的ServletRequest对象,和对此请求进行响应的ServletResponse对象,并将这两个对象传给service方法,由service方法来处理请求。service方法会根据请求类型来做转发,调度不同的方法来处理请求。

HttpServletRequest

HttpServletRequest中包含了客户端HTTP请求的所有信息,主要为三部分信息:请求行、请求头、请求正文。

请求行

请求行中包含:请求方法、资源名、路径、Http版本等信息。HttpServletRequest中实现了一揽子的方法,以便简单快捷的操作获取:

方法声明

功能说明

String getMethod()

获取请求方式:如GET、POST等

String getRequestURI()

获取资源名称部分:即位于URL的主机与端口之后,参数部分之前部分

String getQueryString()

获取参数部分:即URL中问号(?)以后的内容

String getProtocol()

获取协议名和版本:如HTTP/1.0或HTTP/1.1等

String getContextPath()

获取URL中属于WEB应用程序的路径(以/开始,不含末尾/),表示相对于WEB站点根目录的部分,若请求属于根目录,则返回空字符串

String getServletPath()

获取Servlet的名称或所映射的目录

String getRemoteAddr()

获取远端(客户端)的IP地址

String getRemoteHost()

获取客户端的完整主机名;若无法解析出,则返回其IP地址

int getRemotePort()

客户端连接的端口号

String getLocalAddr()

获取WEB服务器上接收当前请求的IP地址

String getLocalName()

获取WEB服务器上接收当前请求的主机名

int getLocalPort()

获取WEB服务器上接收当前请求的端口号

String getServerName()

获取当前请求所指向的主机名:即HTTP请求消息中Host头字段所对应的主机名部分

int getServerPort()

获取当前请求所所连接的服务器端口号:即HTTP请求消息中Host头字段所对应的端口号部分

String getScheme()

获取请求协议名:如http、https、ftp等

StringBuffer getRequestURL()

获取客户端发起请求时的完整URL,包括协议、服务器名、端口号、资源路径等,但不包括后面查询参数部分

请求头

通过请求头可向服务器传递附加信息,如:可接受的数据类型、是否保持TCP连接等。Servlet中也提供了一系列操作方法:

方法声明

功能说明

String getHeader(String name)

获取指定头的值:若头中未包含name,则返回null;若包含多个name字段,则返回第一个对应的值

Enumeration getHeaders(String name)

返回指定头字段的可枚举集合对象

Enumeration getHeaderNames()

返回所有请求头字段名的可枚举集合对象

int getIntHeader(String name)

返回指定头字段对应int值:若字段不存在,返回-1;若字段不是int类型的,抛出NumberFormatExcption

long getDateHeader(String name)

返回指定头字段对应GMT时间戳(毫秒级)

String getContentType()

获取Content-Type字段值

int getContentLength()

获取Content-Length头字段的值

String getCharacterEncoding()

获取消息实体部分的字符集编码

Cookie[] getCookies()

获取所有客户端传递过来的Cookie对象,若没有返回null

请求参数

请求参数中包含用户提交的数据,是请求中最重要的部分。Servlet中也提供了一系列操作方法:

方法声明

功能说明

String getParameter(String name)

获取指定名称的参数值:若没有,返回null;若有参数名,但没有参数值,返回空字符串;若有多个,返回第一个

String[] getParameterValues(String name)

返回指定参数对应的所有值

Enumeration getParameterNames()

返回请求消息中所有参数名的可枚举对象

Map getParameterMap()

返回参数的Map对象(键值对)

HttpServletResponse

HttpServletResponse用于完成Http的响应。

状态码

默认情况下会返回如下状态码:

  • 200:程序正常执行(完整处理了Http请求)时返回的错误码;
  • 404:url路径错误时;
  • 500:程序执行过程中直接抛出异常时,返回此错误码;

也可通过函数设定:

方法声明

功能说明

void setStatus(int sc)

设定响应状态码

void sendError(int sc)

使用指定的状态码向客户端返回一个错误响应,并清空缓冲区

void sendError(int sc, Sting msg)

设定状态码与错误信息,并清空缓冲区

SpringBoot中支持

Spring Boot全面支持开发RESTful程序,通过不同的注解来支持前端的请求。

Controller与Method

Spring中通过注解来定义控制器与API接口:

  • @Controller:响应页面,表示当前的类为控制器;
  • @RestController:是@ResponseBody和@Controller的结合,表明当前类是控制器且返回的是一组数据(ResponseBody)而非页面;
  • @RequestMapping:告诉控制器URL映射的映射方式,以下是@RequestMapping(method = RequestMethod.XXX)的简写
  • @GetMapping:对应查询,表明是一个查询URL映射;
  • @PostMapping:对应增加,表明是一个增加URL映射;
  • @PutMapping:对应更新,表明是一个更新URL映射;
  • @DeleteMapping:对应删除,表明是一个删除URL映射;
@RestController
@RequestMapping("test") // 路径
public class TestController {
    // ...
}

RequestMapping

RequestMapping 用于映射请求。用于类级别时,将特定请求(路径)映射此类控制器上;用于方法时,会进一步控制映射关系:

  • value: 指定请求的实际地址, 比如 /action/info;
  • method: 指定请求的method类型, GET、POST、PUT、DELETE等;
  • consumes: 指定处理请求的提交内容类型(Content-Type),例如application/json, text/html;
  • produces: 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回;
  • params: 指定request中必须包含某些参数值是,才让该方法处理;
  • headers: 指定request中必须包含某些指定的header值,才能让该方法处理请求;

其中,consumes, produces使用content-typ信息进行过滤信息;headers中可以使用content-type进行过滤和判断。

可通过value映射到多个路径(可通过../mult, ../one, ../two来访问):

@GetMapping(value = {"multi", "one", "two"})
public String multiMap(HttpServletRequest request) {
    return request.getRequestURI();
}

ExceptionHandler

通过ExceptionHandler可以统一处理异常,先定义统一的异常类:

public class WebTestRuntimeException extends RuntimeException {
    private static final long serialVersionUID = 1L;
    
    private int code;
    private Exception inner;

    public WebTestRuntimeException(int errorCode, String errorMsg)     {
        super(errorMsg);
        this.code = errorCode;
    }
	// ...
}

以及统一的应答类:

public class BasicResponse implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private int               error;
    private String            errorMsg;
    private Object            result           = null;
	// ...
}

统一异常捕捉

定义如下类统一处理异常:

@Slf4j
@ControllerAdvice
public class ControllerExceptionHandler {
    @ResponseBody
    @ExceptionHandler
    public BasicResponse WebExceptionHandler(Exception ex) throws NoSuchFieldException {
        BindingResult bindingResult = null;
        if (ex instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException exArg = (MethodArgumentNotValidException) ex;
            bindingResult = exArg.getBindingResult();
        } else if (ex instanceof BindException) {
            bindingResult = ((BindException) ex).getBindingResult();
        }

        try {
            if (bindingResult != null && bindingResult.hasErrors()) {
                log.error("WebTest全局异常处理信息:{}", ex.getMessage());
                String fieldError = bindingResult.getFieldError().getDefaultMessage();
                return buildInvalidResult(fieldError);
            }

            if (ex instanceof ConstraintViolationException) {
                log.error("WebTest全局异常处理信息:{}", ex.getMessage());
                ConstraintViolationException e = (ConstraintViolationException) ex;
                String fieldError = e.getConstraintViolations().stream().findFirst().get().getMessage();
                return buildInvalidResult(fieldError);
            }
        } catch (Exception e) {
            log.error("process argumentsError throws error", e);
            return new BasicResponse(ErrorCodeEnum.ERROR_Invalid_Argument.getCode(), ex.getMessage());
        }

        if (ex instanceof WebTestRuntimeException) {
            log.error("WebTest全局异常处理信息:{}", ex.getMessage());
            WebTestRuntimeException hnException = (WebTestRuntimeException) ex;
            return new BasicResponse(hnException.getCode(), hnException.getMessage());
        }
        log.error("WebTest全局异常处理信息", ex);
        return new BasicResponse(ErrorCodeEnum.ERROR_FAIL.getCode(), ex.getMessage());
    }

    private BasicResponse buildInvalidResult(String field) {
        String[] fieldError = field.split("=",2);

        Integer strCode;
        String strMsg;
        if(fieldError.length>1){
            strCode =Integer.valueOf(fieldError[0].trim());
            strMsg = fieldError[1];
        }
        else{
            strCode = ErrorCodeEnum.ERROR_Invalid_Argument.getCode();
            strMsg = fieldError[0];
        }
        return new BasicResponse(strCode, strMsg.trim());
    }
}

buildInvalidResult是根据参数上@Validated验证失败(抛出ConstraintViolationException等异常)构造错误异常返回。

参数自动验证

Spring验证框架提供了入参检验注解:

api java开发rest java rest api_RestAPI_02

为了使用Validated自动验证,定义时需要增加注解(@Pattern注解允许对应变量为空,只有非空时才做验证):

@Data
@ApiModel
public class AddUserRequest implements Serializable {
    private static final long serialVersionUID = 1L;

	@NotBlank(message = "100001= idName不能为空")
    private String idName;

    @Pattern(regexp = "[0-3]", message = "100001= type错误,只能(0-3)的数字")
    private String type; // 可以为空;非空只能是0~3的数字

	@Pattern(regexp = "|\\d{7,11}", message = "100001= phone格式不正确(11位数字)")
    private String phone; // 可以为空;非空只只能是7~11位数字

	@NotNull(message = "100001= isSelf不能为空")
	@Range(min = 0, max = 10, message = "100001= count必须是0~10")
    private Integer count; // Range注解不能处理null,需要增加NotNull限制
}

为了使前面的注解生效,需要在参数上添加@Validated注解:

@PostMapping(value = "addUser")
public BasicResponse addUser(@Validated @RequestBody AddUserRequest reqBody) {
    // ...
}

当传入的参数验证失败时,会抛出ConstraintViolationException异常,然后就会由前面的ControllerExceptionHandler捕捉。

API接口示例

RestAPI接口统一放在Controller类中:

@RestController
@RequestMapping("test")
public class TestController {
    @GetMapping("paramGet")  // 请求 ../test/paramGet?msg=12
    public String paramGet(HttpServletRequest request, HttpServletResponse resp, String msg) throws IOException {
        resp.setStatus(2XX); // 设定返回状态码,默认200
        return msg;
    }

    @PutMapping(value = "paramPut") // 请求 ../test/paramPut?msg=12&others=34
    public String paramPut(@RequestParam String msg, @RequestParam String others) {
		// ...
    }

    @PutMapping("testPut") // data以body方式传递
    public void testPut(@RequestBody TestData data) {
		// ...
    }

    @DeleteMapping(value = "testDelete") // 请求 ../test/testDelete?id=12
    public String testDelete(@RequestParam String id) {
		// ...
    }

    @PostMapping("testPost")
    public void testPost(@RequestBody TestData data) {
		// 自动解析为类(需要Json格式匹配)
    }

    @PostMapping(value = "postMsg", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public String postMsg(@RequestBody String msg) {
		// 所有body以字符串方式获取,然后自己解析 
    }
        
    @GetMapping("/msg/{key}") // 请求 ../test/12
    public String pathMsg(@PathVariable("key") String key) {
        // 从路径上获取值,key为12
    }
}

Form方式请求

form是一种常用的请求方式,其参数是作为body方式传递(POST中body默认是Json字符串;GET中参数是放在URL上):

//  curl -X POST ".../test/formMsg" -H "accept: */*" -H "Content-Type: application/x-www-form-urlencoded" -d "msg=123&others=234"
@PostMapping(value = "formMsg", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String formMsg(@RequestParam String msg, @RequestParam String others) {
	// ...
}

文件上传

通过MultipartFile可以方便地上传文件:

@PostMapping(value = "/uploadMulti", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String uploadMulti(@RequestParam("file") MultipartFile[] files) {
    for (MultipartFile partFile : files) {
        _logger.info("File: {}, Ori: {}, Type: {}, Size: {}",
                partFile.getName(), partFile.getOriginalFilename(),
                partFile.getContentType(), partFile.getSize());

        StringBuilder sb = new StringBuilder("\n");
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(partFile.getInputStream()))) {
            String line = reader.readLine();
            while (line != null) {
                sb.append(line);
                sb.append("\n");

                line = reader.readLine();
            }

            _logger.info("Contents: {}", sb.toString());
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    return "Files: " + Arrays.stream(files).map(MultipartFile::getOriginalFilename).collect(Collectors.joining("; "));
}

// curl -X POST ".../test/uploadFile" -H "accept: */*" -H "Content-Type: multipart/form-data" -F "file=@Python-learning.md;type="
@PostMapping(value = "/uploadFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String uploadFile(@RequestParam("file") MultipartFile file) {
    _logger.info("File: {}, Ori: {}, Type: {}, Size: {}",
            file.getName(), file.getOriginalFilename(),
            file.getContentType(), file.getSize());

    StringBuilder sb = new StringBuilder("\n");
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream()))) {
        String line = reader.readLine();
        while (line != null) {
            sb.append(line);
            sb.append("\n");

            line = reader.readLine();
        }

        _logger.info("Contents: {}", sb.toString());
    } catch (Exception ex) {
        ex.printStackTrace();
    }

    return String.format("received: %s, size: %d, type: %s", file.getOriginalFilename(),
            file.getSize(), file.getContentType());
}

swagger中无法处理上传多个文件,通过OkHttp可以方便地实现:

api java开发rest java rest api_RestAPI_03