- Rest Protocol
- Http Protocol Messages
- Current Support Granularity:
- Rest Annotation Parsing (ServiceRestMetadataResolver)
- Encoding Examples
- Flowchart
- Scenarios:
- Consumer TODO List (functionality has been initially implemented and can parse response)
- Provider TODO List (to be implemented)
- Rest User Manual and Demo:
- Feedback
Rest Protocol
This article will introduce Dubbo’s Rest protocol.
Note
Starting from Dubbo version 3.3, the Rest protocol has been moved to the Extensions library, and the Triple protocol provides more comprehensive support for Rest. For more details, see the Triple Rest User Manual. If you wish to continue using the original Rest protocol, you can introduce the corresponding dubbo-spi-extensions library dependency.
A lighter, Dubbo-style Rest protocol for interoperation within microservices (Spring Cloud Alibaba).
Annotation Parsing
Message Encoding and Decoding
RestClient
RestServer (Netty)
Supported content-types: text, json, xml, form (to be extended later).
Annotations:
param, header, body, pathvariable (spring mvc & resteasy)
Http Protocol Messages
POST /test/path? HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-type: application/json
{"name":"dubbo","age":10,"address":"hangzhou"}
Dubbo HTTP (header)
// service key header
path: com.demo.TestInterface
group: demo
port: 80
version: 1.0.0
// Ensure long connection
Keep-Alive, Connection: keep-alive
Keep-alive: 60
// RPCContext Attachment
userId: 123456
Current Support Granularity:
Data Location | content-type | spring Annotation | resteasy Annotation |
---|---|---|---|
body | No requirements | RequestBody | No annotation means body |
querystring(?test=demo) | No requirements | RequestParam | QueryParam |
header | No requirements | RequestHeader | PathParam |
form | application/x-www-form-urlencoded | RequestParam RequestBody | FormParam |
path | No requirements | PathVariable | PathParam |
method | No requirements | PostMapping GetMapping | GET POST |
url | PostMapping GetMapping path attribute | Path | |
content-type | PostMapping GetMapping consumers attribute | Consumers | |
Accept | PostMapping GetMapping produces attribute | Produces |
Rest Annotation Parsing (ServiceRestMetadataResolver)
JAXRSServiceRestMetadataResolver
SpringMvcServiceRestMetadataResolver
ServiceRestMetadata
public class ServiceRestMetadata implements Serializable {
private String serviceInterface; // com.demo.TestInterface
private String version; // 1.0.0
private String group; // demo
private Set<RestMethodMetadata> meta; // method metadata
private int port; // port for provider service key
private boolean consumer; // consumer flag
/**
* makes a distinction between mvc & resteasy
*/
private Class codeStyle; //
/**
* for provider
*/
private Map<PathMatcher, RestMethodMetadata> pathToServiceMap;
/**
* for consumer
*/
private Map<String, Map<ParameterTypesComparator, RestMethodMetadata>> methodToServiceMa
RestMethodMetadata
public class RestMethodMetadata implements Serializable {
private MethodDefinition method; // method definition info (name ,paramType, returnType)
private RequestMetadata request; // request metadata
private Integer urlIndex;
private Integer bodyIndex;
private Integer headerMapIndex;
private String bodyType;
private Map<Integer, Collection<String>> indexToName;
private List<String> formParams;
private Map<Integer, Boolean> indexToEncoded;
private ServiceRestMetadata serviceRestMetadata;
private List<ArgInfo> argInfos;
private Method reflectMethod;
/**
* makes a distinction between mvc & resteasy
*/
private Class codeStyle;
ArgInfo
public class ArgInfo {
/**
* method arg index 0,1,2,3
*/
private int index;
/**
* method annotation name or name
*/
private String annotationNameAttribute;
/**
* param annotation type
*/
private Class paramAnnotationType;
/**
* param Type
*/
private Class paramType;
/**
* param name
*/
private String paramName;
/**
* url split("/") String[n] index
*/
private int urlSplitIndex;
private Object defaultValue;
private boolean formContentType;
RequestMetadata
public class RequestMetadata implements Serializable {
private static final long serialVersionUID = -240099840085329958L;
private String method; // request method
private String path; // request url
private Map<String, List<String>> params; // param parameters? splicing
private Map<String, List<String>> headers; // header;
private Set<String> consumes; // content-type;
private Set<String> produces; // Accept;
Consumer Code:
refer:
@Override
protected <T> Invoker<T> protocolBindingRefer(final Class<T> type, final URL url) throws RpcException {
// restClient spi creation
ReferenceCountedClient<? extends RestClient> refClient =
clients.computeIfAbsent(url.getAddress(), key -> createReferenceCountedClient(url, clients));
refClient.retain();
// resolve metadata
Map<String, Map<ParameterTypesComparator, RestMethodMetadata>> metadataMap = MetadataResolver.resolveConsumerServiceMetadata(type, url);
ReferenceCountedClient<? extends RestClient> finalRefClient = refClient;
Invoker<T> invoker = new AbstractInvoker<T>(type, url, new String[]{INTERFACE_KEY, GROUP_KEY, TOKEN_KEY}) {
@Override
protected Result doInvoke(Invocation invocation) {
try {
// Obtain method metadata
RestMethodMetadata restMethodMetadata = metadataMap.get(invocation.getMethodName()).get(ParameterTypesComparator.getInstance(invocation.getParameterTypes()));
RequestTemplate requestTemplate = new RequestTemplate(invocation, restMethodMetadata.getRequest().getMethod(), url.getAddress(), getContextPath(url));
HttpConnectionCreateContext httpConnectionCreateContext = new HttpConnectionCreateContext();
// TODO dynamic load config
httpConnectionCreateContext.setConnectionConfig(new HttpConnectionConfig());
httpConnectionCreateContext.setRequestTemplate(requestTemplate);
httpConnectionCreateContext.setRestMethodMetadata(restMethodMetadata);
httpConnectionCreateContext.setInvocation(invocation);
httpConnectionCreateContext.setUrl(url);
// http information building interceptor
for (HttpConnectionPreBuildIntercept intercept : httpConnectionPreBuildIntercepts) {
intercept.intercept(httpConnectionCreateContext);
}
CompletableFuture<RestResult> future = finalRefClient.getClient().send(requestTemplate);
CompletableFuture<AppResponse> responseFuture = new CompletableFuture<>();
AsyncRpcResult asyncRpcResult = new AsyncRpcResult(responseFuture, invocation);
// response handling
future.whenComplete((r, t) -> {
if (t != null) {
responseFuture.completeExceptionally(t);
} else {
AppResponse appResponse = new AppResponse();
try {
int responseCode = r.getResponseCode();
MediaType mediaType = MediaType.TEXT_PLAIN;
if (400 < responseCode && responseCode < 500) {
throw new HttpClientException(r.getMessage());
} else if (responseCode >= 500) {
throw new RemoteServerInternalException(r.getMessage());
} else if (responseCode < 400) {
mediaType = MediaTypeUtil.convertMediaType(r.getContentType());
}
Object value = HttpMessageCodecManager.httpMessageDecode(r.getBody(),
restMethodMetadata.getReflectMethod().getReturnType(), mediaType);
appResponse.setValue(value);
Map<String, String> headers = r.headers()
.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
appResponse.setAttachments(headers);
responseFuture.complete(appResponse);
} catch (Exception e) {
responseFuture.completeExceptionally(e);
}
}
});
return asyncRpcResult;
} catch (RpcException e) {
if (e.getCode() == RpcException.UNKNOWN_EXCEPTION) {
e.setCode(getErrorCode(e.getCause()));
}
throw e;
}
}
@Override
public void destroy() {
super.destroy();
invokers.remove(this);
destroyInternal(url);
}
};
invokers.add(invoker);
return invoker;
Provider Code:
export:
public <T> Exporter<T> export(final Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
final String uri = serviceKey(url);
Exporter<T> exporter = (Exporter<T>) exporterMap.get(uri);
if (exporter != null) {
// When modifying the configuration through override, you need to re-expose the newly modified service.
if (Objects.equals(exporter.getInvoker().getUrl(), invoker.getUrl())) {
return exporter;
}
}
// TODO addAll metadataMap to RPCInvocationBuilder metadataMap
Map<PathMatcher, RestMethodMetadata> metadataMap = MetadataResolver.resolveProviderServiceMetadata(url.getServiceModel().getProxyObject().getClass(),url);
PathAndInvokerMapper.addPathAndInvoker(metadataMap, invoker);
final Runnable runnable = doExport(proxyFactory.getProxy(invoker, true), invoker.getInterface(), invoker.getUrl());
exporter = new AbstractExporter<T>(invoker) {
@Override
public void afterUnExport() {
exporterMap.remove(uri);
if (runnable != null) {
try {
runnable.run();
} catch (Throwable t) {
logger.warn(PROTOCOL_UNSUPPORTED, "", "", t.getMessage(), t);
}
}
}
};
exporterMap.put(uri, exporter);
return exporter;
}
RestHandler
private class RestHandler implements HttpHandler<HttpServletRequest, HttpServletResponse> {
@Override
public void handle(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException {
// There are servlet request and nettyRequest
RequestFacade request = RequestFacadeFactory.createRequestFacade(servletRequest);
RpcContext.getServiceContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort());
// dispatcher.service(request, servletResponse);
Pair<RpcInvocation, Invoker> build = null;
try {
// Create RPCInvocation based on request information
build = RPCInvocationBuilder.build(request, servletRequest, servletResponse);
} catch (PathNoFoundException e) {
servletResponse.setStatus(404);
}
Invoker invoker = build.getSecond();
Result invoke = invoker.invoke(build.getFirst());
// TODO handling exceptions
if (invoke.hasException()) {
servletResponse.setStatus(500);
} else {
try {
Object value = invoke.getValue();
String accept = request.getHeader(RestConstant.ACCEPT);
MediaType mediaType = MediaTypeUtil.convertMediaType(accept);
// TODO write response
HttpMessageCodecManager.httpMessageEncode(servletResponse.getOutputStream(), value, invoker.getUrl(), mediaType);
servletResponse.setStatus(200);
} catch (Exception e) {
servletResponse.setStatus(500);
}
}
// TODO add Attachment header
}
}
RPCInvocationBuilder
{
private static final ParamParserManager paramParser = new ParamParserManager();
public static Pair<RpcInvocation, Invoker> build(RequestFacade request, Object servletRequest, Object servletResponse) {
// Obtain invoker
Pair<Invoker, RestMethodMetadata> invokerRestMethodMetadataPair = getRestMethodMetadata(request);
RpcInvocation rpcInvocation = createBaseRpcInvocation(request, invokerRestMethodMetadataPair.getSecond());
ProviderParseContext parseContext = createParseContext(request, servletRequest, servletResponse, invokerRestMethodMetadataPair.getSecond());
// Parameter building
Object[] args = paramParser.providerParamParse(parseContext);
rpcInvocation.setArguments(args);
return Pair.make(rpcInvocation, invokerRestMethodMetadataPair.getFirst());
}
private static ProviderParseContext createParseContext(RequestFacade request, Object servletRequest, Object servletResponse, RestMethodMetadata restMethodMetadata) {
ProviderParseContext parseContext = new ProviderParseContext(request);
parseContext.setResponse(servletResponse);
parseContext.setRequest(servletRequest);
Object[] objects = new Object[restMethodMetadata.getArgInfos().size()];
parseContext.setArgs(Arrays.asList(objects));
parseContext.setArgInfos(restMethodMetadata.getArgInfos());
return parseContext;
}
private static RpcInvocation createBaseRpcInvocation(RequestFacade request, RestMethodMetadata restMethodMetadata) {
RpcInvocation rpcInvocation = new RpcInvocation();
int localPort = request.getLocalPort();
String localAddr = request.getLocalAddr();
int remotePort = request.getRemotePort();
String remoteAddr = request.getRemoteAddr();
String HOST = request.getHeader(RestConstant.HOST);
String GROUP = request.getHeader(RestConstant.GROUP);
String PATH = request.getHeader(RestConstant.PATH);
String VERSION = request.getHeader(RestConstant.VERSION);
String METHOD = restMethodMetadata.getMethod().getName();
String[] PARAMETER_TYPES_DESC = restMethodMetadata.getMethod().getParameterTypes();
rpcInvocation.setParameterTypes(restMethodMetadata.getReflectMethod().getParameterTypes());
rpcInvocation.setMethodName(METHOD);
rpcInvocation.setAttachment(RestConstant.GROUP, GROUP);
rpcInvocation.setAttachment(RestConstant.METHOD, METHOD);
rpcInvocation.setAttachment(RestConstant.PARAMETER_TYPES_DESC, PARAMETER_TYPES_DESC);
rpcInvocation.setAttachment(RestConstant.PATH, PATH);
rpcInvocation.setAttachment(RestConstant.VERSION, VERSION);
rpcInvocation.setAttachment(RestConstant.HOST, HOST);
rpcInvocation.setAttachment(RestConstant.REMOTE_ADDR, remoteAddr);
rpcInvocation.setAttachment(RestConstant.LOCAL_ADDR, localAddr);
rpcInvocation.setAttachment(RestConstant.REMOTE_PORT, remotePort);
rpcInvocation.setAttachment(RestConstant.LOCAL_PORT, localPort);
Enumeration<String> attachments = request.getHeaders(RestConstant.DUBBO_ATTACHMENT_HEADER);
while (attachments != null && attachments.hasMoreElements()) {
String s = attachments.nextElement();
String[] split = s.split("=");
rpcInvocation.setAttachment(split[0], split[1]);
}
// TODO set path, version, group and so on
return rpcInvocation;
}
private static Pair<Invoker, RestMethodMetadata> getRestMethodMetadata(RequestFacade request) {
String path = request.getRequestURI();
String version = request.getHeader(RestConstant.VERSION);
String group = request.getHeader(RestConstant.GROUP);
int port = request.getIntHeader(RestConstant.REST_PORT);
return PathAndInvokerMapper.getRestMethodMetadata(path, version, group, port);
}
}
Encoding Examples
API
mvc:
@RestController()
@RequestMapping("/demoService")
public interface DemoService {
@RequestMapping(value = "/hello", method = RequestMethod.GET)
Integer hello(@RequestParam Integer a, @RequestParam Integer b);
@RequestMapping(value = "/error", method = RequestMethod.GET)
String error();
@RequestMapping(value = "/say", method = RequestMethod.POST, consumes = MediaType.TEXT_PLAIN_VALUE)
String sayHello(@RequestBody String name);
}
resteasy:
@Path("/demoService")
public interface RestDemoService {
@GET
@Path("/hello")
Integer hello(@QueryParam("a")Integer a, @QueryParam("b") Integer b);
@GET
@Path("/error")
String error();
@POST
@Path("/say")
@Consumes({MediaType.TEXT_PLAIN})
String sayHello(String name);
boolean isCalled();
}
impl(service)
@DubboService()
public class RestDemoServiceImpl implements RestDemoService {
private static Map<String, Object> context;
private boolean called;
@Override
public String sayHello(String name) {
called = true;
return "Hello, " + name;
}
public boolean isCalled() {
return called;
}
@Override
public Integer hello(Integer a, Integer b) {
context = RpcContext.getServerAttachment().getObjectAttachments();
return a + b;
}
@Override
public String error() {
throw new RuntimeException();
}
public static Map<String, Object> getAttachments() {
return context;
}
}
Flowchart
Consumer
Provider (RestServer)
Scenarios:
Non-Dubbo intercommunication (Spring Cloud Alibaba intercommunication)
Intercommunication conditions:
Protocol | Dubbo | Spring Cloud Alibaba | Intercommunication | |
---|---|---|---|---|
Communication Protocol | rest | spring web/resteasy encoding style | integrates feignclient, ribbon (spring web encoding style) | Yes |
triple | ||||
dubbo | ||||
grpc | ||||
hessian | ||||
Registration Center | zookeeper | |||
nacos | Supports | Supports | Application-level registration |
2. Dubbo Double Registration
Complete application-level registration, (dubo2-dubbo3 migration), dubbo version upgrade
3. Multi-Protocol Publishing
Configuration:
<dubbo:service interface="org.apache.dubbo.samples.DemoService" protocol="dubbo, grpc, rest"/>
4. Cross-Language
5. Multi-Protocol Interaction
6. Protocol Migration
Rest encoding style
HTTP protocol is more universally applicable for cross-language calls
Dubbo rest calls other HTTP services
Other HTTP clients call dubbo rest
dubbo restServer can directly interact with other web services, browsers, etc.
Consumer TODO List (functionality has been initially implemented and can parse response)
org/apache/dubbo/rpc/protocol/rest/RestProtocol.java:157 dynamic load config
org/apache/dubbo/remoting/http/factory/AbstractHttpClientFactory.java:50 load config HttpClientConfig
org/apache/dubbo/rpc/protocol/rest/annotation/metadata/MetadataResolver.java:52 support Dubbo style service
org/apache/dubbo/remoting/http/restclient/HttpClientRestClient.java:120 TODO config
org/apache/dubbo/remoting/http/restclient/HttpClientRestClient.java:140 TODO close judge
org/apache/dubbo/rpc/protocol/rest/message/decode/MultiValueCodec.java:35 TODO java bean get set convert
Provider TODO List (to be implemented)
Implement HTTP protocol-supporting NettyServer based on Netty
No annotation protocol definition
Supplement scenarios on the official website
Rest User Manual and Demo:
Feedback
Was this page helpful?
Yes No
Last modified September 30, 2024: Update & Translate Overview Docs (#3040) (d37ebceaea7)