RestFul API unified format return + global exception handling

Background

Today, with the prevalence of distributed and microservices, most of the projects adopt the microservice framework, with the front and back end separated. The front-end interacts with the back-end. The front-end requests the URL path according to the agreement, and passes in the relevant parameters. The back-end server receives the request, processes the business, and returns the data to the front-end.

So it is very important to unify the return value of the interface and ensure the idempotence of the return value of the interface.

2, Unified format design

2.1 general form of uniform results

  • Example:
{
	# Whether the response is successful
	success: true,
	# Response status code
	code: 200,		
	# Response data
	data: Object
	# Return error message
	message: "",
}

2.2 result class enumeration

public enum ResultCodeEnum {
    /*** General part 100 - 599***/
    // Successful request
    SUCCESS(200, "successful"),
    // redirect
    REDIRECT(301, "redirect"),
    // Resource not found
    NOT_FOUND(404, "not found"),
    // Server error
    SERVER_ERROR(500,"server error"),

    /*** Here, error codes can be separated with different area levels according to different modules, for example:***/

    // 1000-1999 interval indicates user module error
    // 2000-2999 interval indicates order module error
    // 3000-3999 indicates commodity module error
    // . . . 

    ;
    /**
     * Response status code
     */
    private Integer code;
    /**
     * Response information
     */
    private String message;

    ResultCodeEnum(Integer code, String msg) {
        this.code = code;
        this.message = msg;
    }

    public Integer getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}
  • Code: response status code

In general, you can add whatever you need during development. However, for specification purposes, we should refer to the status code returned by the HTTP request.

code interval type Meaning
1** 100-199 information The server receives the request and needs the requester to continue
2** 200-299 Success Request received and processed successfully
3** 300-399 redirect Further action is required to complete the request
4** 400-499 Client error The request contains a syntax error or could not be completed
5** 500-599 Server error An error occurred while the server was processing

Common HTTP status codes:

  1. 200 - Request succeeded;
  2. 301 - resources (web pages, etc.) are permanently transferred to other URL s;
  3. 404 - the requested resource (web page, etc.) does not exist;
  4. 500 - internal server error.
  • Message: error message

When an error occurs, how to give a friendly prompt?

  1. Give corresponding error code location according to code;
  2. Record the error description in message, so that the interface caller can understand the error in detail.

2.3 unified results

public class HttpResult <T> implements Serializable {

    /**
     * Whether the response is successful
     */
    private Boolean success;
    /**
     * Response status code
     */
    private Integer code;
    /**
     * Response data
     */
    private T data;
    /**
     * error message
     */
    private String message;

    // Constructor start
    /**
     * Parameterless constructor (the constructor is private and cannot be created directly outside)
     */
    private HttpResult() {
        this.code = 200;
        this.success = true;
    }
    /**
     * Parametrical constructor
     * @param obj
     */
    private HttpResult(T obj) {
        this.code = 200;
        this.data = obj;
        this.success = true;
    }

    /**
     * Parametrical constructor
     * @param resultCode
     */
    private HttpResult(ResultCodeEnum resultCode) {
        this.success = false;
        this.code = resultCode.getCode();
        this.message = resultCode.getMessage();
    }
    // Constructor end

    /**
     * General return success (no result returned)
     * @param <T>
     * @return
     */
    public static<T> HttpResult<T> success(){
        return new HttpResult();
    }

    /**
     * Return success (with returned results)
     * @param data
     * @param <T>
     * @return
     */
    public static<T> HttpResult<T> success(T data){
        return new HttpResult<T>(data);
    }

    /**
     * General return failed
     * @param resultCode
     * @param <T>
     * @return
     */
    public static<T> HttpResult<T> failure(ResultCodeEnum resultCode){
        return  new HttpResult<T>(resultCode);
    }

    public Boolean getSuccess() {
        return success;
    }

    public void setSuccess(Boolean success) {
        this.success = success;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return "HttpResult{" +
                "success=" + success +
                ", code=" + code +
                ", data=" + data +
                ", message='" + message + '\'' +
                '}';
    }
}

Explain:

  1. The constructor is private and cannot be created directly outside;
  2. Only static methods of uniform return classes can be called to return objects;
  3. success is a Boolean value, through which you can directly observe whether the request is successful;
  4. Data represents the response data, which is used to return the data required by the client after the request is successful.

3, Test and summary

3.1 simple interface test

@RestController
@RequestMapping("/httpRest")
@Api(tags = "Uniform results test")
public class HttpRestController {

    @ApiOperation(value = "General return success (no result returned)", httpMethod = "GET")
    @GetMapping("/success")
    public HttpResult success(){
        return HttpResult.success();
    }

    @ApiOperation(value = "Return success (with returned results)", httpMethod = "GET")
    @GetMapping("/successWithData")
    public HttpResult successWithData(){
        return HttpResult.success("Eolian blog");
    }

    @ApiOperation(value = "General return failed", httpMethod = "GET")
    @GetMapping("/failure")
    public HttpResult failure(){
        return HttpResult.failure(ResultCodeEnum.NOT_FOUND);
    }

}

Here, the configuration of Swagger and spring MVC has not been pasted out. See Github sample code for details.

3.2 return results

http://localhost:8080/swagger-ui.html#/

{
  "code": 200,
  "success": true
}
{
  "code": 200,
  "data": "Eolian blog",
  "success": true
}
{
  "code": 404,
  "message": "not found",
  "success": false
}

4, Global exception handling

When using the unified return result, there is another situation, that is, the error report of the program is the result of the runtime exception. Some exceptions are thrown in the business and some are unpredictable.

Therefore, we need to define a unified global exception, catch all exceptions in the Controller, handle them properly, and return them as a result.

4.1 design idea:

  1. Define an exception class (such as TokenVerificationException) to catch exceptions for projects or businesses;
  2. Use @ ExceptionHandler annotation to catch custom exception and general exception;
  3. Use @ controlleradvise to integrate the method of @ ExceptionHandler into a class;
  4. The abnormal object information is added to the unified result enumeration;

4.2 user defined exception

public class TokenVerificationException extends RuntimeException {

    /**
     * Error code
     */
    protected Integer code;

    protected String msg;

    public Integer getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    /**
     * There is a parameter constructor. The return code is in the enumeration class, where you can specify the error information
     * @param msg
     */
    public TokenVerificationException(String msg) {
        super(msg);
    }
}

4.3 unified exception processor

@ControllerAdvice annotation is a kind of Advice that acts on the control layer. It can collect the general @ ExceptionHandler, @ InitBinder and @ ModelAttributes methods into one type and apply them to all controllers.

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * Anomaly capture
     * @param e Exception caught
     * @return Encapsulated return object
     **/
    @ExceptionHandler(Exception.class)
    public HttpResult handlerException(Exception e) {
        ResultCodeEnum resultCodeEnum;
        // Custom exception
        if (e instanceof TokenVerificationException) {
            resultCodeEnum = ResultCodeEnum.TOKEN_VERIFICATION_ERROR;
            resultCodeEnum.setMessage(getConstraintViolationErrMsg(e));
            log.error("tokenVerificationException: {}", resultCodeEnum.getMessage());
        }else {
            // Other exceptions. When we define multiple exceptions, we can add judgment and record here
            resultCodeEnum = ResultCodeEnum.SERVER_ERROR;
            resultCodeEnum.setMessage(e.getMessage());
            log.error("common exception:{}", JSON.toJSONString(e));
        }
        return HttpResult.failure(resultCodeEnum);
    }

    /**
     * Get error information
     * @param ex
     * @return
     */
    private String getConstraintViolationErrMsg(Exception ex) {
        // validTest1.id: id must be a positive number
        // validTest1.id: id must be a positive number, validTest1.name: length must be in valid range
        String message = ex.getMessage();
        try {
            int startIdx = message.indexOf(": ");
            if (startIdx < 0) {
                startIdx = 0;
            }
            int endIdx = message.indexOf(", ");
            if (endIdx < 0) {
                endIdx = message.length();
            }
            message = message.substring(startIdx, endIdx);
            return message;
        } catch (Throwable throwable) {
            log.info("ex caught", throwable);
            return message;
        }
    }
}
  • Explain
  1. I use @ restcontrolleradvise, which is equivalent to @ controlleradvise + @ ResponseBody
  2. Error enumeration class is omitted here, see Github code.

5, Test and summary

5.1 test interface

@RestController
@RequestMapping("/exception")
@Api(tags = "Abnormal test interface")
public class ExceptionRestController {

    @ApiOperation(value = "Business exception(token abnormal)", httpMethod = "GET")
    @GetMapping("/token")
    public HttpResult token() {
        // Throw token exception in simulation business layer
        throw new TokenVerificationException("token Expired");
    }


    @ApiOperation(value = "Other anomalies", httpMethod = "GET")
    @GetMapping("/errorException")
    public HttpResult errorException() {
        //Here, a different exception is intentionally caused and is not handled
        Integer.parseInt("abc123");
        return HttpResult.success();
    }
}

5.2 return results

http://localhost:8080/swagger-ui.html#/

{
  "code": 500,
  "message": "For input string: \"abc123\"",
  "success": false
}
{
  "code": 4000,
  "message": "token Expired",
  "success": false
}

5.3 summary

@Rest controlleradvice and @ ExceptionHandler will catch all the exceptions of the result interface and encapsulate them into the result set of HttpResult defined by us. However, they cannot handle the exceptions in the interceptor

Six, summary

No one is suitable for all kinds of situations, such as paging. You can also add a static scheme to return paging results. The specific implementation is not shown here. Therefore, it is very good to have a certain readability, which is suitable for you. You are welcome to give opinions and suggestions from the bigwigs with different opinions.

6.1 example code

Github sample code

6.2 technical exchange

  1. Eolian blog
  2. Dust blog - gold digger
  3. FengChen blog blog Garden
  4. Github

Tags: Java github Spring JSON REST

Posted on Mon, 23 Mar 2020 23:48:31 -0400 by AliasZero