Triple Rest 用户手册

本文是Triple Rest的用户使用手册

前言

从 Dubbo 3.3 版本开始,Triple 协议重用已有的 HTTP 协议栈,实现了全面的 REST 风格服务导出能力。无需使用泛化或网关层协议转换,无需配置,用户即可通过 HTTP 协议去中心化直接访问后端的 Triple 协议服务。同时,针对高级 REST 用法,如路径定制、输出格式定制和异常处理,提供了丰富的注解和 SPI 扩展支持。其主要特性包括:

  • Triple协议融合
    重用Triple原有HTTP协议栈, 无需额外配置或新增端口,即可同时支持 HTTP/1、HTTP/2 和 HTTP/3 协议的访问。
  • 去中心化
    可直接对外暴露 Rest API,不再依赖网关应用进行流量转发,从而提升性能,并降低因网关引发的稳定性风险。安全问题可通过应用内部扩展解决,这一实践已在淘宝内部的 MTOP 中得到验证。
  • 支持已有servlet设施
    支持 Servlet API 和 Filter,用户可以重用现有基于 Servlet API 的安全组件。通过实现一个 Servlet Filter,即可集成 OAuth 和 Spring Security 等安全框架。
  • 多种方言
    考虑到大部分用户习惯使用 SpringMVC 或 JAX-RS 进行 REST API 开发,Triple Rest 允许继续沿用这些方式定义服务,并支持大部分扩展和异常处理机制(具备原框架 80% 以上的功能)。对于追求轻量级的用户,可使用 Basic 方言,Triple 的开箱即用 REST 访问能力即基于此方言导出服务。
  • 扩展能力强
    提供超过 20 个 扩展点,用户不仅可以轻松实现自定义方言,还能灵活定制参数获取、类型转换、错误处理等逻辑。
  • 开箱即用
    REST 能力开箱即用,只需启用 Triple 协议,即具备 REST 直接访问服务能力。
  • 高性能路由
    路由部分采用优化的 Radix Tree 和 Zero Copy 技术,提升路由性能。
  • OpenAPI无缝集成(TBD)
    即将完成 OpenAPI 集成,开箱即用支持导出 OpenAPI Schema, 引入 Swagger 依赖可直接使用 Web UI 来进行服务测试。有了 OpenAPI Schema 可使用 PostmanApifox 等API工具来管理和测试 API,利用 OpenAPI 生态可轻松实现跨语言调用。未来会进一步支持 Schema First 的方式,先和前端团队一起定义 OpenAPI, 前端基于 OpenAPI 来生成调用代码和 Mock,后端基于 OpenAPI 来生成 Stub 来开发服务,极大提升协同效率。

快速开始

让我们从一个简单的例子开始了解 Triple Rest。您可以直接下载已有的示例项目以快速上手,假设您已经安装好 Java、Maven 和 Git

下载并运行示例

# 获取示例代码
git clone --depth=1 https://github.com/apache/dubbo-samples.git
cd dubbo-samples/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-basic
# 直接运行
mvn spring-boot:run
# 或打包后运行
mvn clean package -DskipTests
java -jar target/dubbo-samples-triple-rest-basic-1.0.0-SNAPSHOT.jar

当然,也可以直接用IDE导入工程后直接执行 org.apache.dubbo.rest.demo.BasicRestApplication#main 来运行,并通过下断点 debug 的方式来深入理解原理。

示例代码

// 服务接口
package org.apache.dubbo.rest.demo;

import org.apache.dubbo.remoting.http12.rest.Mapping;
import org.apache.dubbo.remoting.http12.rest.Param;

public interface DemoService {
    String hello(String name);

    @Mapping(path = "/hi", method = HttpMethods.POST)
    String hello(User user, @Param(value = "c", type = ParamType.Header) int count);
}

// 服务实现
@DubboService
public class DemoServiceImpl implements DemoService {
    @Override
    public String hello(String name) {
        return "Hello " + name;
    }

    @Override
    public String hello(User user, int count) {
        return "Hello " + user.getTitle() + ". " + user.getName() + ", " + count;
    }
}

// 模型
@Data
public class User {
    private String title;
    private String name;
}

测试基本服务

curl -v "http://127.0.0.1:8081/org.apache.dubbo.rest.demo.DemoService/hello?name=world"
# 输出如下
#> GET /org.apache.dubbo.rest.demo.DemoService/hello?name=world HTTP/1.1
#> Host: 127.0.0.1:8081
#> User-Agent: curl/8.7.1
#> Accept: */*
#>
#* Request completely sent off
#< HTTP/1.1 200 OK
#< content-type: application/json
#< alt-svc: h2=":8081"
#< content-length: 13
#<
#"Hello world"

代码讲解:
可以看到输出了 “Hello world” ,有双引号是因为默认输出 content-type 为 application/json
通过这个例子可以了解 Triple 默认将服务导出到 /{serviceInterface}/{methodName}路径,并支持通过url方式传递参数

测试高级服务

curl -v -H "c: 3" -d 'name=Yang' "http://127.0.0.1:8081/org.apache.dubbo.rest.demo.DemoService/hi.txt?title=Mr"
# 输出如下
#> POST /org.apache.dubbo.rest.demo.DemoService/hi.txt?title=Mr HTTP/1.1
#> Host: 127.0.0.1:8081
#> User-Agent: curl/8.7.1
#> Accept: */*
#> c: 3
#> Content-Length: 9
#> Content-Type: application/x-www-form-urlencoded
#>
#* upload completely sent off: 9 bytes
#< HTTP/1.1 200 OK
#< content-type: text/plain
#< alt-svc: h2=":8081"
#< content-length: 17
#<
#Hello Mr. Yang, 3

代码讲解:
可以看到输出 Hello Mr. Yang, 3 ,没有双引号是因为通过指定后缀 txt 的方式要求用 text/plain 输出
通过这个例子可以了解如何通过 Mapping 注解来定制路径,通过 Param 注解来定制参数来源,并支持通过 post body 或 url方式传递参数,详细说明参见: Basic使用指南

观察日志

可以通过打开 debug 日志的方式来了解rest的启动和响应请求过程

logging:
  level:
    "org.apache.dubbo.rpc.protocol.tri": debug
    "org.apache.dubbo.remoting": debug

打开后可以观察到 Rest 映射注册和请求响应过程

# 注册mapping
DEBUG o.a.d.r.p.t.TripleProtocol               :  [DUBBO] Register triple grpc mapping: 'org.apache.dubbo.rest.demo.DemoService' -> invoker[tri://192.168.2.216:8081/org.apache.dubbo.rest.demo.DemoService]
 INFO .r.p.t.r.m.DefaultRequestMappingRegistry :  [DUBBO] BasicRequestMappingResolver resolving rest mappings for ServiceMeta{interface=org.apache.dubbo.rest.demo.DemoService, service=DemoServiceImpl@2a8f6e6} at url [tri://192.168.2.216:8081/org.apache.dubbo.rest.demo.DemoService]
DEBUG .r.p.t.r.m.DefaultRequestMappingRegistry :  [DUBBO] Register rest mapping: '/org.apache.dubbo.rest.demo.DemoService/hi' -> mapping=RequestMapping{name='DemoServiceImpl#hello', path=PathCondition{paths=[org.apache.dubbo.rest.demo.DemoService/hi]}, methods=MethodsCondition{methods=[POST]}}, method=MethodMeta{method=org.apache.dubbo.rest.demo.DemoService.hello(User, int), service=DemoServiceImpl@2a8f6e6}
DEBUG .r.p.t.r.m.DefaultRequestMappingRegistry :  [DUBBO] Register rest mapping: '/org.apache.dubbo.rest.demo.DemoService/hello' -> mapping=RequestMapping{name='DemoServiceImpl#hello~S', path=PathCondition{paths=[org.apache.dubbo.rest.demo.DemoService/hello]}}, method=MethodMeta{method=org.apache.dubbo.rest.demo.DemoService.hello(String), service=DemoServiceImpl@2a8f6e6}
 INFO .r.p.t.r.m.DefaultRequestMappingRegistry :  [DUBBO] Registered 2 REST mappings for service [DemoServiceImpl@44627686] at url [tri://192.168.2.216:8081/org.apache.dubbo.rest.demo.DemoService] in 11ms

# 请求响应
DEBUG .a.d.r.p.t.r.m.RestRequestHandlerMapping :  [DUBBO] Received http request: DefaultHttpRequest{method='POST', uri='/org.apache.dubbo.rest.demo.DemoService/hi.txt?title=Mr', contentType='application/x-www-form-urlencoded'}
DEBUG .r.p.t.r.m.DefaultRequestMappingRegistry :  [DUBBO] Matched rest mapping=RequestMapping{name='DemoServiceImpl#hello', path=PathCondition{paths=[/org.apache.dubbo.rest.demo.DemoService/hi]}, methods=MethodsCondition{methods=[POST]}}, method=MethodMeta{method=org.apache.dubbo.rest.demo.DemoService.hello(User, int), service=DemoServiceImpl@2a8f6e6}
DEBUG .a.d.r.p.t.r.m.RestRequestHandlerMapping :  [DUBBO] Content-type negotiate result: request='application/x-www-form-urlencoded', response='text/plain'
DEBUG .d.r.h.AbstractServerHttpChannelObserver :  [DUBBO] Http response body is: '"Hello Mr. Yang, 3"'
DEBUG .d.r.h.AbstractServerHttpChannelObserver :  [DUBBO] Http response headers sent: {:status=[200], content-type=[text/plain], alt-svc=[h2=":8081"], content-length=[17]}

通用功能

路径映射

兼容 SpringMVC 和 JAX-RS 的映射方式,相关文档:

还支持通过实现 SPI org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver 来自定义路径映射

支持的模式

  1. books 字符串常量,最基本的类型,匹配一个固定的段
  2. ? 匹配一个字符
  3. * 匹配路径段中的零个或多个字符
  4. ** 匹配直到路径末尾的零个或多个路径段
  5. {spring} 匹配一个路径段并将其捕获为名为 “spring” 的变量
  6. {spring:[a-z]+} 使用正则表达式 [a-z]+ 匹配路径段,并将其捕获为名为 “spring” 的路径变量
  7. {*spring} 匹配直到路径末尾的零个或多个路径段,并将其捕获为名为 “spring” 的变量,如果写 {*} 表示不捕获

示例(来自Spring文档)

  • /pages/t?st.html — 匹配 /pages/test.html 以及 /pages/tXst.html,但不匹配 /pages/toast.html
  • /resources/*.png — 匹配 resources 目录中的所有 .png 文件
  • com/**/test.jsp — 匹配 com 路径下的所有 test.jsp 文件
  • org/springframework/**/*.jsp — 匹配 org/springframework 路径下的所有 .jsp 文件
  • /resources/** — 匹配 /resources/ 路径下的所有文件,包括 /resources/image.png/resources/css/spring.css
  • /resources/{*path} — 匹配 /resources/ 下的所有文件,以及 /resources,并将其相对路径捕获在名为 “path” 的变量中;例如, /resources/image.png 会匹配到 “path” → “/image.png”,/resources/css/spring.css 会匹配到 “path” → “/css/spring.css”
  • /resources/{filename:\\w+}.dat — 匹配 /resources/spring.dat 并将值 “spring” 分配给 filename 变量
  • /{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+} — 匹配 /example-2.1.5.htmlnameexampleversion2.1.5ext.html

小技巧如果使用正则不希望跨段可以使用 {name:[^/]+} 来匹配

映射匹配完整流程

具体的匹配处理代码:DefaultRequestMappingRegistry.java RequestMapping.java

  1. 使用 PathUtils.normalize 对路径进行清洗,去掉诸如 /one/../ /one/./ 之类间接路径,保证一定已 / 开头
  2. 检查 http method 是否匹配
  3. 检查 path 是否匹配
  4. 检查 paramter 是否匹配(JAX-RS不支持)
  5. 检查 header 是否匹配
  6. 检查 content-type 是否匹配(Consumes)
  7. 检查 accept 是否匹配 (Produces)
  8. 检查 serviceGroupserviceVersion 是否匹配
  9. 检查 method 首字母签名是否匹配
  10. 未找到任何匹配项,如果尾 / 匹配开启并且路径 / 结尾则去掉尾 / 尝试从第2步开始匹配
  11. 未找到任何匹配项,如果扩展名匹配开启并且扩展名被支持,则去掉扩展名尝试从第2步开始匹配
  12. 如果最后一段路径包含 ~ 表示开启 method 首字母签名匹配,尝试从第2步开始匹配
  13. 如果候选项为0,匹配结束,返回null
  14. 如果候选项为0,匹配结束,返回命中项
  15. 如果不止一个候选项,对候选项进行排序
  16. 对第一项和第二项进行顺序比较
  17. 结果为0表示无法确认最终匹配项,抛异常失败
  18. 第一项胜出,匹配结束,返回命中项

路径重复问题

与 Spring 不同,Spring 在路径完全相同时会直接报错并阻止启动,而 Triple Rest 具备开箱即用的特性,为了避免影响现有服务,默认只会打印 WARN 日志。在运行时,如果最终无法确定最高优先级的映射,才会抛出错误。

入参类型

不同方言支持的入参类型不同,详情请参见各方言使用指南。
还支持通过实现 SPI org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver 来自定义入参解析

通用类型参数

名称说明Basic 注解SpringMVC注解JAX-RS注解数组或集合处理方式Map处理方式
ParamQuery或Form的参数@Param@RequestParam-多值所有参数的Map
Queryurl上带的参数--@QueryParam多值所有Query参数的Map
Formform表单带的参数--@FormParam多值所有Form参数的Map
HeaderHTTP头@Param(type=Header)@RequestHeader@HeaderParam多值所有Header参数的Map
CookieCookie值@Param(type=Cookie)@CookieValue@CookieParam多值所有Cookie参数的Map
AttributeRequest属性@Param(type=Attribute)@RequestAttribute-多值所有Attribute的Map
PartMultipart文件@Param(type=Part)@RequestHeader@HeaderParam多值所有Part的Map
Body请求body@Param(type=Body)@RequestBody@Body尝试解析为数组或集合尝试解析为目标类型
PathVariablepath变量@Param(type=PathVariable)@PathVariable@PathParam单值数组或集合单值Map
MatrixVariablematrix变量@Param(type=MatrixVariable)@MatrixVariable@MatrixParam多值单值Map
Beanjava bean无需注解@ModelAttribute@BeanParam尝试解析为Bean数组或集合-

特殊类型参数

类型说明激活条件
org.apache.dubbo.remoting.http12.HttpRequestHttpRequest对象默认激活
org.apache.dubbo.remoting.http12.HttpResponseHttpResponse对象默认激活
org.apache.dubbo.remoting.http12.HttpMethods请求Http方法默认激活
java.util.Locale请求Locale默认激活
java.io.InputStream请求输入流默认激活
java.io.OutputStream响应输出流默认激活
javax.servlet.http.HttpServletRequestServlet HttpRequest对象引入Servlet API jar
javax.servlet.http.HttpServletResponseServlet HttpResponse对象同上
javax.servlet.http.HttpSessionServlet HttpSession对象同上
javax.servlet.http.CookieServlet Cookie对象同上
java.io.ReaderServlet Request Reader对象同上
java.io.WriterServlet Response Writer对象同上

无注解参数

不同方言处理方式不同,请参见各方言使用说明

无入参方式获取 HTTP 输入输出参数

可通过 RpcContext 来获取

// Dubbo http req/resp
HttpRequest request = RpcContext.getServiceContext().getRequest(HttpRequest.class);
HttpResponse response = RpcContext.getServiceContext().getRequest(HttpResponse.class);
// Servlet http req/resp
HttpServletRequest request = RpcContext.getServiceContext().getRequest(HttpServletRequest.class);
HttpServletResponse response = RpcContext.getServiceContext().getRequest(HttpServletResponse.class);

拿到request之后,通过 attribute 可以访问一些内置属性,参见:RestConstants.java

参数类型转换

默认支持大部分从 String 到目标类型的参数类型转换,主要包括以下几大类:

  • Jdk内置类型,包括基础类型和日期、Optional等
  • 数组类型
  • 集合类型
  • Map类型

同时也完整支持泛型类型,包括复杂嵌套,具体地实现代码参见: GeneralTypeConverter.java
还支持通过实现SPI org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentConverter 来自定义参数类型转换

Source TypeTarget TypeDescriptionDefault Value
StringdoubleConverts to a double0.0d
StringfloatConverts to a float0.0f
StringlongConverts to a long0L
StringintConverts to an integer0
StringshortConverts to a short0
StringcharConverts to a character0
StringbyteConverts to a byte0
StringbooleanConverts to a booleanfalse
StringBigIntegerConverts to a BigIntegernull
StringBigDecimalConverts to a BigDecimalnull
StringDateConverts to a Datenull
StringCalendarConverts to a Calendarnull
StringTimestampConverts to a Timestampnull
StringInstantConverts to an Instantnull
StringZonedDateTimeConverts to a ZonedDateTimenull
StringLocalDateConverts to a LocalDatenull
StringLocalTimeConverts to a LocalTimenull
StringLocalDateTimeConverts to a LocalDateTimenull
StringZoneIdConverts to a ZoneIdnull
StringTimeZoneConverts to a TimeZonenull
StringFileConverts to a Filenull
StringPathConverts to a Pathnull
StringCharsetConverts to a Charsetnull
StringInetAddressConverts to an InetAddressnull
StringURIConverts to a URInull
StringURLConverts to a URLnull
StringUUIDConverts to a UUIDnull
StringLocaleConverts to a Localenull
StringCurrencyConverts to a Currencynull
StringPatternConverts to a Patternnull
StringClassConverts to a Classnull
Stringbyte[]Converts to a byte arraynull
Stringchar[]Converts to a char arraynull
StringOptionalIntConverts to an OptionalIntnull
StringOptionalLongConverts to an OptionalLongnull
StringOptionalDoubleConverts to an OptionalDoublenull
StringEnum classEnum.valueOfnull
StringArray or CollectionSplit by commanull
StringSpecified classTry JSON String to Objectnull
StringSpecified classTry construct with single Stringnull
StringSpecified classTry call static method valueOfnull

支持的Content-Type

默认支持以下 Content-Type,提供相应的编码和解码功能。
还支持通过实现SPI org.apache.dubbo.remoting.http12.message.(HttpMessageDecoderFactory|HttpMessageEncoderFactory)来扩展

Media TypeDescription
application/jsonJSON format
application/xmlXML format
application/yamlYAML format
application/octet-streamBinary data
application/grpcgRPC format
application/grpc+protogRPC with Protocol Buffers
application/x-www-form-urlencodedURL-encoded form data
multipart/form-dataForm data with file upload
text/jsonJSON format as text
text/xmlXML format as text
text/yamlYAML format as text
text/cssCSS format
text/javascriptJavaScript format as text
text/htmlHTML format
text/plainPlain text

内容协商

支持完善的内容协商机制,可根据映射或输入来协商输出的 Content-Type,具体流程如下:

  1. 尝试读取 Mapping 指定的 mediaType,获取 Produces指定的 mediaType 列表,并将通配符匹配到合适的 Media Type。例如Spring的: @RequestMapping(produces = "application/json")
  2. 尝试通过 Accept 头查找 mediaType,解析请求的 Accept 头,并将通配符匹配到合适的 Media Type。例如: Accept: application/json
  3. 尝试通过 format 参数查找 mediaType,读取 format 参数值,做为文件后缀匹配到合适的 Media Type。例如 /hello?format=yml
  4. 尝试通过请求路径的扩展名查找 mediaType,扩展名做为文件后缀匹配到合适的 Media Type。例如 /hello.txt
  5. 尝试读取请求的 Content-Type 头做为 Media Type(两种form类型除外)。例如 Content-Type: application/json
  6. 使用 application/json 兜底

CORS支持

提供完整的CORS支持,通过配置全局参数即可启用,默认行为和SpringMVC一致,同时在SpringMVC方言中,也支持通过 @CrossOrigin 来做精细化配置。
支持的CORS 配置项参见:8.4CORS配置

自定义HTTP输出

很多场景需要对HTTP输出进行定制,比如做302跳转,写Http头,为此 Triple Rest提供以下通用方案,同时也支持各方言的特定写法,详情参见各方言使用指南

  • 返回值设置为: org.apache.dubbo.remoting.http12.HttpResult 可通过 HttpResult#builder 来构建
  • 抛出Payload异常: throws new org.apache.dubbo.remoting.http12.exception.HttpResultPayloadException(HttpResult) 示例代码:
throw new HttpResult.found("https://a.com").

toPayload();

此异常已避免填充错误栈,对性能无太大影响,并且不用考虑返回值逻辑,推荐用这个方式来定制输出

  • 获取 HttpResponse 后自定义,实例代码:
HttpResponse response = RpcContext.getServiceContext().getRequest(HttpResponse.class);

response.

sendRedirect("https://a.com");
response.

setStatus(404);
response.

outputStream().

write(data);
// 写完输出建议 commit 来避免被其他扩展改写
response.

commit();

如果只是增加 http header 推荐用这个方式

自定义JSON解析和输出

支持Jackson、fastjson2、fastjson和gson等多种JSON框架,使用前请确保对应jar依赖已被引入

指定使用的JSON框架

dubbo.protocol.triple.rest.json-framework=jackson

通过 JsonUtil SPI 定制

可通过实现 SPI org.apache.dubbo.common.json.JsonUtil 方式来自定义JSON处理,具体可以参考 org/apache/dubbo/common/json/impl 已有实现,建议继承现有实现并重写

异常处理

未被处理的异常最终被转换成 ErrorResponse 类编码后输出:


@Data
public class ErrorResponse {
    /**
     * http status code
     */
    private String status;

    /**
     * exception message
     */
    private String message;
}

注意对于500及以上错误,为避免泄露服务端内部信息,默认只会输出 message “Internal Server Error”,如果需要自定义 message 可创建继承自 org.apache.dubbo.remoting.http12.exception.HttpStatusException 异常并重写 getDisplayMessage 方法。
提供了以下通用方法来定制异常处理:

  • 参考 9.2自定义异常返回结果 使用SPI 自定义全局异常处理
  • 使用 Dubbo的 Filter SPI 来加工转换异常,如果需要访问 Http 上下文,可继承 org.apache.dubbo.rpc.protocol.tri.rest.filter.RestFilterAdapter
  • 使用 SPI org.apache.dubbo.rpc.protocol.tri.rest.filter.RestFilter 来转换异常,使用上更轻量并提供路径匹配配置能力

注意后2项只能拦截 invoke 链中出现的异常,如果在路径匹配阶段出现异常,只有有方法1能处理

Basic使用指南

示例参见:dubbo-samples-triple-rest/dubbo-samples-triple-rest-basic

路径映射

Basic做为开箱即用 Rest 映射,默认会将方法映射到: /{contextPath}/{serviceInterface}/{methodName} ,其中 /{contextPath} 如果协议没有配置会忽略,即为:/{serviceInterface}/{methodName}
映射的自定义通过注解 org.apache.dubbo.remoting.http12.rest.Mapping 来支持,属性说明如下:

配置名说明默认行为
value映射的 URL 路径,可以是一个或多个路径。空数组
path映射的 URL 路径,与 value 相同,可以是一个或多个路径。空数组
method支持的 HTTP 方法列表,例如 GETPOST 等。空数组(支持所有方法)
params请求必须包含的参数列表。空数组
headers请求必须包含的头部列表。空数组
consumes处理请求的内容类型(Content-Type),可以是一个或多个类型。空数组
produces生成响应的内容类型(Content-Type),可以是一个或多个类型。空数组
enabled是否启用该映射。true(启用)
  • 属性支持用占位符方式配置:@Mapping("${prefix}/hi")
  • 如果不希望特定服务或方法被 rest 导出,可以通过设置 @Mapping(enabled = false) 解决

入参类型

通用入参见:3.2入参类型

无注解参数

Basic 的无注解参数由类:FallbackArgumentResolver.java 支持,具体处理流程如下:
rest-arg.jpg

SpringMVC使用指南

示例参见:dubbo-samples-triple-rest/dubbo-samples-triple-rest-springmvc

路径映射

直接参考SpringMVC文档即可,支持绝大多数特性,Mapping Requests :: Spring Framework
注意无需 @Controller@RestController 注解,除了 @RequestMapping 还支持新的 @HttpExchange

入参类型

通用入参

参见:3.2入参类型

注解类型参数

参见 3.2.1通用类型参数

特殊类型参数

类型说明激活条件
org.springframework.web.context.request.WebRequestWebRequest对象引入SpringWeb依赖
org.springframework.web.context.request.NativeWebRequestNativeWebRequest对象同上
org.springframework.http.HttpEntityHttp实体同上
org.springframework.http.HttpHeadersHttp头同上
org.springframework.util.MultiValueMap多值Map同上

无注解参数

参数类型转换

优先使用 Spring 的 org.springframework.core.convert.ConversionService 来转换参数,如果应用为spring boot应用则默认使用 mvcConversionService 否则使用 org.springframework.core.convert.support.DefaultConversionService#getSharedInstance 获取共享 ConversionService
如果 ConversionService 不支持则会回退到通用类型转换:3.3参数类型转换

异常处理

除了支持 3.8异常处理 中提到的方式,还支持 Spring @ExceptionHandler 注解方式,Exceptions :: Spring Framework ,注意通过这种方式仅能处理方法调用时抛出的异常,其他异常无法捕获

CORS配置

除了支持 8.4CORS配置 全局配置,还支持 Spring @CrossOrigin 来精细化配置,CORS :: Spring Framework

自定义HTTP输出

支持以下 Spring 自定义方式:

  1. 使用 @ResponseStatus 注解
  2. 返回 org.springframework.http.ResponseEntity 对象

支持的扩展

JAX-RS使用指南

示例参见:dubbo-samples-triple-rest/dubbo-samples-triple-rest-jaxrs

路径映射

Service需要显式添加注解@Path,方法需要添加@GET、@POST、@HEAD等请求方法注解
直接参考Resteasy文档即可,支持绝大多数特性,Chapter 4. Using @Path and @GET, @POST, etc

入参类型

通用入参

参见:3.2入参类型

注解类型参数

注解参数位置说明
@QueryParamquerystring?a=a&b=b对应的参数
@HeaderParamheader
@PathParampath
@FormParamformbody为key1=value2&key2=value2格式
无注解body不显式使用注解

特殊类型参数

类型说明激活条件
javax.ws.rs.core.CookieCookie对象引入Jax-rs依赖
javax.ws.rs.core.Form表单对象同上
javax.ws.rs.core.HttpHeadersHttp头同上
javax.ws.rs.core.MediaType媒体类型同上
javax.ws.rs.core.MultivaluedMap多值Map同上
javax.ws.rs.core.UriInfoUri信息同上

无注解参数

  • 如果是基本类型 ( 根据 TypeUtils#isSimpleProperty 判断),直接从Parameter中获取
  • 如果非基本类型, 将其视为请求体 (body)来解码对象

参数类型转换

可通过扩展自定义参数转换,扩展接口:

org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver
javax.ws.rs.ext.ParamConverterProvider

异常处理

可通过扩展自定义异常处理,扩展接口:

javax.ws.rs.ext.ExceptionMapper
org.apache.dubbo.remoting.http12.ExceptionHandler

CORS配置

支持 8.4CORS配置 全局配置

自定义HTTP输出

支持以下 Jaxrs 自定义方式:

  • 返回 javax.ws.rs.core.Response 对象

支持的扩展

  1. javax.ws.rs.container.ContainerRequestFilter
    请求过滤器,允许在请求到达资源方法之前对请求进行预处理。
  2. javax.ws.rs.container.ContainerResponseFilter
    响应过滤器,允许在响应离开资源方法之后对响应进行后处理。
  3. javax.ws.rs.ext.ExceptionMapper
    异常映射器,将抛出的异常映射为HTTP响应。
  4. javax.ws.rs.ext.ParamConverterProvider
    参数转换器,允许将请求参数转换为资源方法的参数类型。
  5. javax.ws.rs.ext.ReaderInterceptor
    读取拦截器,允许在读取请求实体时进行拦截和处理。
  6. javax.ws.rs.ext.WriterInterceptor
    写入拦截器,允许在写入响应实体时进行拦截和处理。

Servlet使用指南

同时低版本javax和高版本jakarta servlet API,jakarta API 优先级更高,只需要引入jar即可使用HttpServletRequest和HttpServletResponse作为参数

使用 Filter 扩展

方法1,实现 Filter 接口和 org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension 接口,然后注册SPI

import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension;

import javax.servlet.Filter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class DemoFilter implements Filter, RestExtension {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        chain.doFilter(request, response);
    }

    @Override
    public String[] getPatterns() {
        return new String[]{"/demo/**", "!/demo/one"};
    }

    @Override
    public int getPriority() {
        return -200;
    }
}

方法2,实现 Supplier<Filter> 接口和 org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension 接口,然后注册SPI

public class DemoFilter implements Supplier<Filter>, RestExtension {

    private final Filter filter = new SsoFilter();

    @Override
    public Filter get() {
        return filter;
    }
}

这种方式对于重用已有 Filter 非常方便,甚至可以从 Spring Context 中获取 Filter 实例并注册

public class DemoFilter implements Supplier<Filter>, RestExtension {

    private final Filter filter = new SsoFilter();

    public DemoFilter(FrameworkModel frameworkModel) {
        SpringExtensionInjector injector = SpringExtensionInjector.get(frameworkModel.defaultApplication());
        filter = injector.getInstance(SsoFilter.class, null);
    }

    @Override
    public Filter get() {
        return filter;
    }
}

HttpSession支持

实现 SPI org.apache.dubbo.rpc.protocol.tri.rest.support.servlet.HttpSessionFactory

尚不支持的特性

  • Filter 中 wrap request 和 response对象不会生效,原因是 Rest支持的 过滤器种类很多,使用wrapper会导致反复嵌套,处理过于复杂
  • 不支持 request.getRequestDispatcher

安全配置

当 REST 服务直接暴露在公网时,存在被攻击的安全风险。因此在暴露服务之前,需要充分评估风险并选择合适的认证方式来保证安全性。Triple 提供了多种安全认证机制,同时用户也可以自行实现相应的扩展来对访问进行安全校验。

Basic 认证

要启用 Basic 认证,请修改以下配置:

dubbo:
  provider:
    auth: true
    authenticator: basic
    username: admin  
    password: admin

启用后,所有 HTTP 请求都需要通过 Basic 认证才能访问。

如果是 RPC 调用,还需要在消费者端配置相应的用户名和密码:

dubbo:
  consumer:
    auth: true
    authenticator: basic  
    username: admin
    password: admin

这样配置后,provider 和 consumer 之间的通信将使用 Basic 认证来保证安全性。请确保在生产环境中使用强密码,并考虑采用 HTTPS 来加密传输。

认证扩展

实现自定义 Authenticator

可通过 SPI org.apache.dubbo.auth.spi.Authenticator 来自定义认证,并通过配置 dubbo.provider.authenticator 来选择启用的 Authenticator

实现 HTTP 请求过滤

可通过 SPI org.apache.dubbo.rpc.HeaderFilterorg.apache.dubbo.rpc.protocol.tri.rest.filter.RestFilter 来自定义 HTTP 过滤器逻辑

全局参数配置

大小写敏感

配置名:dubbo.protocol.triple.rest.case-sensitive-match
是否路径匹配应区分大小写。如果启用,映射到 /users 的方法不会匹配到 /Users
默认为 true

尾匹配

配置名:dubbo.protocol.triple.rest.trailing-slash-match
是否路径匹配应匹配带有尾部斜杠的路径。如果启用,映射到 /users 的方法也会匹配到 /users/
默认为 true

扩展名匹配

配置名:dubbo.protocol.triple.rest.suffix-pattern-match
是否路径匹配使用后缀模式匹配(.*) ,如果启用,映射到 /users 的方法也会匹配到 /users.* ,后缀内容协商会被同时启用,媒体类型从URL后缀推断,例如 .json 对应 application/json
默认为 true

CORS配置

配置名说明默认值
dubbo.protocol.triple.rest.cors.allowed-origins允许跨域请求的来源列表,可以是具体域名或特殊值 * 代表所有来源。未设置(不允许任何来源)
dubbo.protocol.triple.rest.cors.allowed-methods允许的 HTTP 方法列表,例如 GETPOSTPUT 等,特殊值 * 代表所有方法。未设置(仅允许 GETHEAD
dubbo.protocol.triple.rest.cors.allowed-headers预检请求中允许的请求头列表,特殊值 * 代表所有请求头。未设置
dubbo.protocol.triple.rest.cors.exposed-headers实际响应中可以暴露给客户端的响应头列表,特殊值 * 代表所有响应头。未设置
dubbo.protocol.triple.rest.cors.allow-credentials是否支持用户凭证。未设置(不支持用户凭证)
dubbo.protocol.triple.rest.cors.max-age预检请求的响应可以被客户端缓存的时间(以秒为单位)。未设置

高级使用指南

支持的扩展点汇总

  1. javax.servlet.Filter
    Servlet API过滤器。
  2. org.apache.dubbo.rpc.protocol.tri.rest.support.servlet.HttpSessionFactory
    用于在Servlet API中支持 HttpSession。
  3. javax.ws.rs.container.ContainerRequestFilter
    用于在JAX-RS中实现请求过滤器,允许在请求到达资源方法之前对请求进行预处理。
  4. javax.ws.rs.container.ContainerResponseFilter
    用于在JAX-RS中实现响应过滤器,允许在响应离开资源方法之后对响应进行后处理。
  5. javax.ws.rs.ext.ExceptionMapper
    用于在JAX-RS中实现异常映射器,将抛出的异常映射为HTTP响应。
  6. javax.ws.rs.ext.ParamConverterProvider
    用于在JAX-RS中提供参数转换器,允许将请求参数转换为资源方法的参数类型。
  7. javax.ws.rs.ext.ReaderInterceptor
    用于在JAX-RS中实现读取拦截器,允许在读取请求实体时进行拦截和处理。
  8. javax.ws.rs.ext.WriterInterceptor
    用于在JAX-RS中实现写入拦截器,允许在写入响应实体时进行拦截和处理。
  9. org.springframework.web.servlet.HandlerInterceptor
    用于在Spring MVC中实现处理器拦截器。
  10. org.apache.dubbo.remoting.http12.ExceptionHandler
    提供异常自定义处理机制。
  11. org.apache.dubbo.remoting.http12.message.HttpMessageAdapterFactory
    提供HTTP消息的适配和转换功能。
  12. org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory
    提供HTTP消息的解码功能。
  13. org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory
    提供HTTP消息的编码功能。
  14. org.apache.dubbo.rpc.HeaderFilter
    用于在Dubbo RPC中实现头部过滤器,允许对请求和响应的头部进行过滤和处理。
  15. org.apache.dubbo.rpc.protocol.tri.rest.filter.RestHeaderFilterAdapter
    头部过滤器适配器,提供访问http输入输出能力。
  16. org.apache.dubbo.rpc.protocol.tri.rest.filter.RestFilterAdapter
    Dubbo Filter Rest适配器,提供访问http输入输出能力。
  17. org.apache.dubbo.rpc.protocol.tri.route.RequestHandlerMapping
    用于在Dubbo Triple中实现请求映射能力。
  18. org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver
    用于解析REST请求映射。
  19. org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit
    提供REST相关的工具和实用程序。
  20. org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentConverter
    提供参数的类型转换功能。
  21. org.apache.dubbo.rpc.protocol.tri.rest.argument.ArgumentResolver
    提供参数的解析功能。
  22. org.apache.dubbo.rpc.protocol.tri.rest.filter.RestFilter
    提供REST请求和响应的过滤功能。
  23. org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtensionAdapter
    提供RestExtension的adapt能力,将已有filter接口映射到RestFilter接口。

自定义异常返回结果

通过 SPI org.apache.dubbo.remoting.http12.ExceptionHandler 来自定义异常处理逻辑

public interface ExceptionHandler<E extends Throwable, T> {
    /**
     * Resolves the log level for a given throwable.
     */
    default Level resolveLogLevel(E throwable) {
        return null;
    }

    /**
     * Handle the exception and return a result.
     */
    default T handle(E throwable, RequestMetadata metadata, MethodDescriptor descriptor) {
        return null;
    }
}

实现 SPI 并将泛型 E 指定为需要处理的异常类型

  • resolveLogLevel
    Dubbo框架内部会打印Rest处理异常日志,可以通过实现这个方法来自定义需要打印的日志级别或忽略日志。
  • handle
    如果返回结果不是 null ,则将直接输出返回结果,可以通过返回 org.apache.dubbo.remoting.http12.HttpResult 来定制输出的 headers 和 status code。

打开debug日志

logging:
  level:
    "org.apache.dubbo.rpc.protocol.tri": debug
    "org.apache.dubbo.remoting": debug

开启 debug 日志会输出详细的启动日志和请求响应日志,便于排查问题。

打开verbose输出

dubbo:
  protocol:
    triple:
      verbose: true

开启 verbose 输出会将内部错误堆栈返回给调用方,并输出更多错误日志,便于排查问题。

最后修改 September 10, 2024: Fix links (f6d9e2e3f52)