1.3.四叶葎:Http方法

    东风袅袅泛崇光,香雾空蒙月转廊。只恐夜深花睡去,故烧高烛照红妆。——苏轼《海棠》

「壹」浅谈RESTful

    首先,读者需要知道:RESTful是一种架构风格,又称为架构指导规范,不是标准——注意最后一句话,它和我们熟知的XML、SOAP 标准不可同日而语。简单说,RESTful不是标准,它只是一种架构风格,也是随着最近几年的SOA架构流行而风靡起来的。

1.1. SOAP vs RESTful

    RESTful作为一种新的架构风格,它和传统意义上常用的SOAP有什么区别呢?首先读者需要排除一些误区:

  1. 使用了HTTP方法中的PUT/DELETE就叫做RESTful(错的)。

  2. 使用了Json数据格式就是RESTful,而Xml数据格式就是SOAP(错的)。

    SOAP全称为Simple Object Access Protocol——简单对象访问协议,它是用来描述Web服务消息格式的一种规范,基于XML标准定制,在这种Web服务中,所有的通信格式都是XML的。

    RESTful又称为REST、全称为Representational State Transfer——表述性状态传递,它是Roy Fielding博士在2000年中的博士论文中提出来的一种软件架构风格,相比于SOAP,它可以降低开发复杂度,提高系统的可伸缩性。

    简单说,二者真正的区别在——是否对HTTP协议进行了语义上的表述。HTTP协议是应用层协议,设计它的最初目的就是面向应用的标准网络协议。SOAP在表述语义上,并没有使用HTTP协议的语义,而是定义了自己的SOAPHeader、SOAPBody、Envelop 等节点,而这种情况下,SOAP把HTTP协议当做了:“传输层”协议在使用,“应用语义”却隐藏于SOAP规范之中。相反REST回归到HTTP协议的本源,它完全围绕着HTTP协议本身来设计,而不是重新封装,包括设计时去考虑客户端偏好、服务端偏好、条件查询、查询缓存,HTTP幂等性等——这都是REST应用设计对开发人员的挑战。

1.2. 幂等性

    HTTP协议的幂等性(Idempotence)在原生定义中如下:

Methods can also have the property of "idempotence" in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request.

    读者需要理解两点:

  • HTTP协议的幂等性原本不是为了REST的Web服务设计的,而是为了分布式计算设计的。

  • 它定义了这样一种特性:针对某一个API,一次请求和多次(n)请求应该具有同样的副作用和结果。

    HTTP协议的原生方法对幂等性的描述如下:

方法名幂等性

GET

HEAD

OPTIONS

DELETE

PUT

POST

    简单说在HTTP常用的方法中,只有POST方法天生就不具有幂等性,而我们可以在实现上采取另外的技巧去解决它的这个缺陷。

    关于幂等性的场景,举个例子——当你发送请求:GET http://www.bank.com/account/123456,该请求不会改变资源状态,调用一次和调用N次的结果相同,所以是没有副作用的。——这里Todd Wei也强调了,调用N次不影响不是说每次GET的数据结果相同(123456这条记录的数据有可能更新过),而是资源的语义, 账号=133456 的账号信息,不论一次还是多次,它的这种对状态的表述语义不发生变化。


「贰」Zero中的Http方法

2.1. 初识

    Zero是如何实现RESTful中不同的HTTP方法的呢?先看下边代码:

package cn.vertxup.micro.method;

import io.vertx.core.json.JsonObject;
import io.vertx.up.annotations.EndPoint;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;

@EndPoint
public class GetAgent {
    /*
     * 返回值:
     * {
     *     "data": {
     *         "name": ???
     *     }
     * }
     */
    @GET
    @Path("/hi/method/get-first")
    public JsonObject sayGet(@QueryParam("name") String name){
        final JsonObject response = new JsonObject();
        response.put("name", name);
        return response;
    }
}

    上边代码实现了Http方法中的GET方法,当您使用工具postman测试的时候,就可以得到对应的响应结果,并且在启动zero容器时可以看到如下输出:

[ μηδέν ] ( Uri Register ) "/hi/method/get" has been deployed by ZeroHttpAgent, Options = ...

    注意上边的方法定义中是可以传入name参数的,如果发送请求地址改成:/hi/method/get-first?name=Lang,那么将会得到如下输出:

{
    "data": {
        "name": "Lang"
    }
}

    @QueryParamJSR311中的标准注解,zero对标准注解的使用方法是完全支持的,不仅如此,还扩展了原生注解用于更多业务类场景的数据规范,这些内容将在参数章节单独介绍。

2.2. 完整例子

再看一个比较完整的例子:

package cn.vertxup.micro.method;

import io.vertx.core.json.JsonObject;
import io.vertx.up.annotations.EndPoint;

import jakarta.ws.rs.*;

@EndPoint
public class FourAgent {
    /*
     * 返回值:
     * {
     *     "data": {
     *         "name": ???
     *     }
     * }
     */
    @GET
    @Path("/hi/method/get")
    public JsonObject sayGet(@QueryParam("name") final String name) {
        final JsonObject response = new JsonObject();
        response.put("name", name);
        return response;
    }

    /*
     * 返回值:
     * {
     *     "data": {
     *         ???
     *     }
     * }
     */
    @POST
    @Path("/hi/method/post")
    public JsonObject sayPost(@BodyParam final JsonObject body) {
        final JsonObject response = new JsonObject();
        response.mergeIn(body, true);
        return response;
    }

    /*
     * 返回值:
     * {
     *     "data": {
     *         "key": "???",
     *         ???
     *     }
     * }
     */
    @PUT
    @Path("/hi/method/put/:id")
    public JsonObject sayPut(@BodyParam final JsonObject body,
                             @PathParam("id") final String id) {
        final JsonObject response = new JsonObject();
        response.mergeIn(body, true);
        response.put("key", id);
        return response;
    }

    /*
     * 返回值:
     * {
     *     "data": {
     *         "deleted": ???
     *     }
     * }
     */
    @DELETE
    @Path("/hi/method/delete/:id")
    public JsonObject sayDelete(@PathParam("id") final String id) {
        final JsonObject response = new JsonObject();
        response.put("deleted", id);
        return response;
    }
}

    开发了上述接口过后,可以使用Postman对每个接口进行单独测试,分别得到对应注释中的响应结果,注意此处对于PUT/DELETE使用了@PathParam ,该注解也属于JSR311,唯一不同的是:@BodyParam并不是JSR311中的注解,它是zero框架中的扩展注解,除开这些注释外zero还扩展了更多JSR311中的注解,形成统一规范的“框架协议” ——通常Body参数只在POSTPUT方法中使用,而不在GET/DELETE中使用。

2.3. 关于GET/DELETE的Body问题

    通常来讲,HTTP协议中的GET/DELETE是不推荐带Body的,但是为了满足业务场景的需求,zero定义了两种特殊的注解扩展JSR311 ,在zero中,不论是GET/DELETE还是其他方法,只要使用了下边介绍的注解,都可以直接读取到Http中的Body数据(再强调一遍,下边的方法在JSR311规范中是不存在的)。

  • jakarta.ws.rs.BodyParam:处理字符流请求体数据。

  • jakarta.ws.rs.StreamParam:处理字节流请求体数据(一般用于上传下载)。

zero中对于第三方开源项目的扩展,全部写在第三方的原始包中,这样可能会对读者产生误解,但这种方式更容易说明框架内部某些第三方插件的源头,如扩展的JSR311在javax.ws.rs包中,扩展的Jackson在com.fasterxml.jackson.databind包中。

参考下边代码:

package cn.vertxup.micro.method;

import io.vertx.core.json.JsonObject;
import io.vertx.up.annotations.EndPoint;

import jakarta.ws.rs.BodyParam;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@EndPoint
public class BodyAgent {
    /*
     * 返回值:
     * {
     *     "data": {
     *         ???
     *     }
     * }
     */
    @GET
    @Path("/hi/body/get")
    public JsonObject sayPost(@BodyParam final JsonObject body) {
        final JsonObject response = new JsonObject();
        response.mergeIn(body, true);
        return response;
    }

    /*
     * 返回值:
     * {
     *     "data": {
     *         ???
     *     }
     * }
     */
    @DELETE
    @Path("/hi/body/delete")
    public JsonObject sayDelete(@BodyParam final JsonObject body) {
        final JsonObject response = new JsonObject();
        response.mergeIn(body, true);
        return response;
    }
}

    通过Postman发送请求则可以得到注解中的响应数据。

「叁」常用错误响应表

    zero 框架在处理一些常见的通用请求时,对容错系统进行了重新设计,如何处理容错信息我会在后边的教程中逐步说明,但是 zero 针对通常会出现的业务场景错误信息会有统一的数据规范。

404

    zero 没有对默认的 404 定制过,如果发送请求,该资源不存在,那么会直接使用 vert.x 中的默认定义,返回下边的HTML片段:

<html>

<body>
	<h1>Resource not found</h1>
</body>

</html>

405

    如果请求资源有数据,但使用的方法不当,那么会直接返回下边信息:

  1. 状态代码 405

  2. 状态信息 Method Not Allowed

  3. 返回的 Http 响应体为空

400

    如果请求格式和定义格式不相同,那么会直接返回下边内容:

  1. 状态代码 400

  2. 状态信息 Bad Request

    返回的Http响应体为:

{
    "code": -60004,
    "message": "[ERR-60004] (JsonObjectSaber) Web Exception occus: (400) - Zero system detect conversation from \"\" to type \"class io.vertx.core.json.JsonObject\", but its conflict."
}

500

    如果出现了常用的 500 异常,则直接返回下边内容:

  1. 状态代码 500

  2. 状态信息 Internal Server Error

    返回的Http响应体为:

{
    "code": -60007,
    "message": "[ERR-60007] (Envelop) Web Exception occus: (500) - The system detected internal error, contact administrator and check details = For input string: \"HX\"."
}

注,message中包含了异常的详细信息,而且在后端日志中可以看到对应的异常堆栈日志。

「肆」总结

    本章我们主要学习 zero 中如何处理RESTful常用的四种基本请求:@GET, @POST, @DELETE, @PUT,这是很多开发人员的常用需求,并且简单谈到了 RESTful 的 Web服务的基本概念。

四叶葎别称四角金,叶四片轮生,影射到四种 Http 方法中,这四种方法也是 RESTful 开发中常用的方法。

Last updated