Rest Protocol

This article will introduce the REST/HTTP protocol design of Dubbo.

This article will introduce the REST/HTTP protocol design of Dubbo.

RestProtocol Design

Original Version Dubbo Rest

consumer

restClient supports relying on resteasy and does not support spring mvc.

provider (heavier)

Depends on web container (tomcat, jetty), servlet mode, jaxrs netty server.

New Version Dubbo Rest

Lighter, with a Dubbo-style REST that is interoperable in a microservices system (Springcloud Alibaba).

1. Annotation Parsing

2. Message Codec

3. RestClient

4. RestServer (netty)

Support Levels:

content-type: text, json, xml, form (will be extended later)

Annotations:

param, header, body, pathvariable (spring mvc & resteasy)

Http Protocol Message

  1. POST /test/path? HTTP/1.1
  2. Host: localhost:8080
  3. Connection: keep-alive
  4. Content-type: application/json
  5. {"name":"dubbo","age":10,"address":"hangzhou"}

Dubbo Http (header)

  1. // service key header
  2. path: com.demo.TestInterface
  3. group: demo
  4. port: 80
  5. version: 1.0.0
  6. // ensure keep-alive
  7. Keep-Alive,Connection: keep-alive
  8. Keep-alive: 60
  9. // RPCContext Attachment
  10. userId: 123456

Support Granularity

Data Locationcontent-typespring annotationsresteasy annotations
bodyNo requirementsRequestBodyNo annotations means body
querystring(?test=demo)No requirementsRequestParamQueryParam
headerNo requirementsRequestHeaderPathParam
formapplication/x-www-form-urlencodedRequestParam RequestBodyFormParam
pathNo requirementsPathVariablePathParam
methodNo requirementsPostMapping GetMappingGET POST
urlPostMapping GetMapping path attributePath
content-typePostMapping GetMapping consumers attributeConsumers
AcceptPostMapping GetMapping produces attributeProduces

Rest Annotation Parsing

ServiceRestMetadataResolver

  1. JAXRSServiceRestMetadataResolver
  2. SpringMvcServiceRestMetadataResolver

ServiceRestMetadata

  1. public class ServiceRestMetadata implements Serializable {
  2. private String serviceInterface; // com.demo.TestInterface
  3. private String version;// 1.0.0
  4. private String group;// demo
  5. private Set<RestMethodMetadata> meta;// method meta information
  6. private int port;// port for provider service key
  7. private boolean consumer;// consumer flag
  8. /**
  9. * make a distinction between mvc & resteasy
  10. */
  11. private Class codeStyle;//
  12. /**
  13. * for provider
  14. */
  15. private Map<PathMatcher, RestMethodMetadata> pathToServiceMap;
  16. /**
  17. * for consumer
  18. */
  19. private Map<String, Map<ParameterTypesComparator, RestMethodMetadata>> methodToServiceMap;

RestMethodMetadata

  1. public class RestMethodMetadata implements Serializable {
  2. private MethodDefinition method; // method definition info (name, paramType, returnType)
  3. private RequestMetadata request;// request meta info
  4. private Integer urlIndex;
  5. private Integer bodyIndex;
  6. private Integer headerMapIndex;
  7. private String bodyType;
  8. private Map<Integer, Collection<String>> indexToName;
  9. private List<String> formParams;
  10. private Map<Integer, Boolean> indexToEncoded;
  11. private ServiceRestMetadata serviceRestMetadata;
  12. private List<ArgInfo> argInfos;
  13. private Method reflectMethod;
  14. /**
  15. * make a distinction between mvc & resteasy
  16. */
  17. private Class codeStyle;

ArgInfo

  1. public class ArgInfo {
  2. /**
  3. * method arg index 0,1,2,3
  4. */
  5. private int index;
  6. /**
  7. * method annotation name or name
  8. */
  9. private String annotationNameAttribute;
  10. /**
  11. * param annotation type
  12. */
  13. private Class paramAnnotationType;
  14. /**
  15. * param Type
  16. */
  17. private Class paramType;
  18. /**
  19. * param name
  20. */
  21. private String paramName;
  22. /**
  23. * url split("/") String[n] index
  24. */
  25. private int urlSplitIndex;
  26. private Object defaultValue;
  27. private boolean formContentType;

RequestMetadata

  1. public class RequestMetadata implements Serializable {
  2. private static final long serialVersionUID = -240099840085329958L;
  3. private String method;// request method
  4. private String path;// request url
  5. private Map<String, List<String>> params // param parameters? concatenated
  6. private Map<String, List<String>> headers// header;
  7. private Set<String> consumes // content-type;
  8. private Set<String> produces // Accept;

Consumer Code

refer

  1. @Override
  2. protected <T> Invoker<T> protocolBindingRefer(final Class<T> type, final URL url) throws RpcException {
  3. // restClient spi creation
  4. ReferenceCountedClient<? extends RestClient> refClient =
  5. clients.computeIfAbsent(url.getAddress(), key -> createReferenceCountedClient(url, clients));
  6. refClient.retain();
  7. // resolve metadata
  8. Map<String, Map<ParameterTypesComparator, RestMethodMetadata>> metadataMap = MetadataResolver.resolveConsumerServiceMetadata(type, url);
  9. ReferenceCountedClient<? extends RestClient> finalRefClient = refClient;
  10. Invoker<T> invoker = new AbstractInvoker<T>(type, url, new String[]{INTERFACE_KEY, GROUP_KEY, TOKEN_KEY}) {
  11. @Override
  12. protected Result doInvoke(Invocation invocation) {
  13. try {
  14. // get method meta info
  15. RestMethodMetadata restMethodMetadata = metadataMap.get(invocation.getMethodName()).get(ParameterTypesComparator.getInstance(invocation.getParameterTypes()));
  16. RequestTemplate requestTemplate = new RequestTemplate(invocation, restMethodMetadata.getRequest().getMethod(), url.getAddress(), getContextPath(url));
  17. HttpConnectionCreateContext httpConnectionCreateContext = new HttpConnectionCreateContext();
  18. // TODO dynamic load config
  19. httpConnectionCreateContext.setConnectionConfig(new HttpConnectionConfig());
  20. httpConnectionCreateContext.setRequestTemplate(requestTemplate);
  21. httpConnectionCreateContext.setRestMethodMetadata(restMethodMetadata);
  22. httpConnectionCreateContext.setInvocation(invocation);
  23. httpConnectionCreateContext.setUrl(url);
  24. // http info build interceptor
  25. for (HttpConnectionPreBuildIntercept intercept : httpConnectionPreBuildIntercepts) {
  26. intercept.intercept(httpConnectionCreateContext);
  27. }
  28. CompletableFuture<RestResult> future = finalRefClient.getClient().send(requestTemplate);
  29. CompletableFuture<AppResponse> responseFuture = new CompletableFuture<>();
  30. AsyncRpcResult asyncRpcResult = new AsyncRpcResult(responseFuture, invocation);
  31. // response handling
  32. future.whenComplete((r, t) -> {
  33. if (t != null) {
  34. responseFuture.completeExceptionally(t);
  35. } else {
  36. AppResponse appResponse = new AppResponse();
  37. try {
  38. int responseCode = r.getResponseCode();
  39. MediaType mediaType = MediaType.TEXT_PLAIN;
  40. if (400 < responseCode && responseCode < 500) {
  41. throw new HttpClientException(r.getMessage());
  42. } else if (responseCode >= 500) {
  43. throw new RemoteServerInternalException(r.getMessage());
  44. } else if (responseCode < 400) {
  45. mediaType = MediaTypeUtil.convertMediaType(r.getContentType());
  46. }
  47. Object value = HttpMessageCodecManager.httpMessageDecode(r.getBody(),
  48. restMethodMetadata.getReflectMethod().getReturnType(), mediaType);
  49. appResponse.setValue(value);
  50. Map<String, String> headers = r.headers()
  51. .entrySet()
  52. .stream()
  53. .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
  54. appResponse.setAttachments(headers);
  55. responseFuture.complete(appResponse);
  56. } catch (Exception e) {
  57. responseFuture.completeExceptionally(e);
  58. }
  59. }
  60. });
  61. return asyncRpcResult;
  62. } catch (RpcException e) {
  63. if (e.getCode() == RpcException.UNKNOWN_EXCEPTION) {
  64. e.setCode(getErrorCode(e.getCause()));
  65. }
  66. throw e;
  67. }
  68. }
  69. @Override
  70. public void destroy() {
  71. super.destroy();
  72. invokers.remove(this);
  73. destroyInternal(url);
  74. }
  75. };
  76. invokers.add(invoker);
  77. return invoker;

Provider Code

export

  1. public <T> Exporter<T> export(final Invoker<T> invoker) throws RpcException {
  2. URL url = invoker.getUrl();
  3. final String uri = serviceKey(url);
  4. Exporter<T> exporter = (Exporter<T>) exporterMap.get(uri);
  5. if (exporter != null) {
  6. // When modifying the configuration through override, you need to re-expose the newly modified service.
  7. if (Objects.equals(exporter.getInvoker().getUrl(), invoker.getUrl())) {
  8. return exporter;
  9. }
  10. }
  11. // TODO addAll metadataMap to RPCInvocationBuilder metadataMap
  12. Map<PathMatcher, RestMethodMetadata> metadataMap = MetadataResolver.resolveProviderServiceMetadata(url.getServiceModel().getProxyObject().getClass(),url);
  13. PathAndInvokerMapper.addPathAndInvoker(metadataMap, invoker);
  14. final Runnable runnable = doExport(proxyFactory.getProxy(invoker, true), invoker.getInterface(), invoker.getUrl());
  15. exporter = new AbstractExporter<T>(invoker) {
  16. @Override
  17. public void afterUnExport() {
  18. exporterMap.remove(uri);
  19. if (runnable != null) {
  20. try {
  21. runnable.run();
  22. } catch (Throwable t) {
  23. logger.warn(PROTOCOL_UNSUPPORTED, "", "", t.getMessage(), t);
  24. }
  25. }
  26. }
  27. };
  28. exporterMap.put(uri, exporter);
  29. return exporter;
  30. }

RestHandler

  1. private class RestHandler implements HttpHandler<HttpServletRequest, HttpServletResponse> {
  2. @Override
  3. public void handle(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException {
  4. // has servlet request and nettyRequest
  5. RequestFacade request = RequestFacadeFactory.createRequestFacade(servletRequest);
  6. RpcContext.getServiceContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort());
  7. // dispatcher.service(request, servletResponse);
  8. Pair<RpcInvocation, Invoker> build = null;
  9. try {
  10. // create RPCInvocation based on request info
  11. build = RPCInvocationBuilder.build(request, servletRequest, servletResponse);
  12. } catch (PathNoFoundException e) {
  13. servletResponse.setStatus(404);
  14. }
  15. Invoker invoker = build.getSecond();
  16. Result invoke = invoker.invoke(build.getFirst());
  17. // TODO handling exceptions
  18. if (invoke.hasException()) {
  19. servletResponse.setStatus(500);
  20. } else {
  21. try {
  22. Object value = invoke.getValue();
  23. String accept = request.getHeader(RestConstant.ACCEPT);
  24. MediaType mediaType = MediaTypeUtil.convertMediaType(accept);
  25. // TODO write response
  26. HttpMessageCodecManager.httpMessageEncode(servletResponse.getOutputStream(), value, invoker.getUrl(), mediaType);
  27. servletResponse.setStatus(200);
  28. } catch (Exception e) {
  29. servletResponse.setStatus(500);
  30. }
  31. }
  32. // TODO add Attachment header
  33. }
  34. }

RPCInvocationBuilder

  1. {
  2. private static final ParamParserManager paramParser = new ParamParserManager();
  3. public static Pair<RpcInvocation, Invoker> build(RequestFacade request, Object servletRequest, Object servletResponse) {
  4. // get invoker
  5. Pair<Invoker, RestMethodMetadata> invokerRestMethodMetadataPair = getRestMethodMetadata(request);
  6. RpcInvocation rpcInvocation = createBaseRpcInvocation(request, invokerRestMethodMetadataPair.getSecond());
  7. ProviderParseContext parseContext = createParseContext(request, servletRequest, servletResponse, invokerRestMethodMetadataPair.getSecond());
  8. // parameter construction
  9. Object[] args = paramParser.providerParamParse(parseContext);
  10. rpcInvocation.setArguments(args);
  11. return Pair.make(rpcInvocation, invokerRestMethodMetadataPair.getFirst());
  12. }
  13. private static ProviderParseContext createParseContext(RequestFacade request, Object servletRequest, Object servletResponse, RestMethodMetadata restMethodMetadata) {
  14. ProviderParseContext parseContext = new ProviderParseContext(request);
  15. parseContext.setResponse(servletResponse);
  16. parseContext.setRequest(servletRequest);
  17. Object[] objects = new Object[restMethodMetadata.getArgInfos().size()];
  18. parseContext.setArgs(Arrays.asList(objects));
  19. parseContext.setArgInfos(restMethodMetadata.getArgInfos());
  20. return parseContext;
  21. }
  22. private static RpcInvocation createBaseRpcInvocation(RequestFacade request, RestMethodMetadata restMethodMetadata) {
  23. RpcInvocation rpcInvocation = new RpcInvocation();
  24. int localPort = request.getLocalPort();
  25. String localAddr = request.getLocalAddr();
  26. int remotePort = request.getRemotePort();
  27. String remoteAddr = request.getRemoteAddr();
  28. String HOST = request.getHeader(RestConstant.HOST);
  29. String GROUP = request.getHeader(RestConstant.GROUP);
  30. String PATH = request.getHeader(RestConstant.PATH);
  31. String VERSION = request.getHeader(RestConstant.VERSION);
  32. String METHOD = restMethodMetadata.getMethod().getName();
  33. String[] PARAMETER_TYPES_DESC = restMethodMetadata.getMethod().getParameterTypes();
  34. rpcInvocation.setParameterTypes(restMethodMetadata.getReflectMethod().getParameterTypes());
  35. rpcInvocation.setMethodName(METHOD);
  36. rpcInvocation.setAttachment(RestConstant.GROUP, GROUP);
  37. rpcInvocation.setAttachment(RestConstant.METHOD, METHOD);
  38. rpcInvocation.setAttachment(RestConstant.PARAMETER_TYPES_DESC, PARAMETER_TYPES_DESC);
  39. rpcInvocation.setAttachment(RestConstant.PATH, PATH);
  40. rpcInvocation.setAttachment(RestConstant.VERSION, VERSION);
  41. rpcInvocation.setAttachment(RestConstant.HOST, HOST);
  42. rpcInvocation.setAttachment(RestConstant.REMOTE_ADDR, remoteAddr);
  43. rpcInvocation.setAttachment(RestConstant.LOCAL_ADDR, localAddr);
  44. rpcInvocation.setAttachment(RestConstant.REMOTE_PORT, remotePort);
  45. rpcInvocation.setAttachment(RestConstant.LOCAL_PORT, localPort);
  46. Enumeration<String> attachments = request.getHeaders(RestConstant.DUBBO_ATTACHMENT_HEADER);
  47. while (attachments != null && attachments.hasMoreElements()) {
  48. String s = attachments.nextElement();
  49. String[] split = s.split("=");
  50. rpcInvocation.setAttachment(split[0], split[1]);
  51. }
  52. // TODO set path,version,group and so on
  53. return rpcInvocation;
  54. }
  55. private static Pair<Invoker, RestMethodMetadata> getRestMethodMetadata(RequestFacade request) {
  56. String path = request.getRequestURI();
  57. String version = request.getHeader(RestConstant.VERSION);
  58. String group = request.getHeader(RestConstant.GROUP);
  59. int port = request.getIntHeader(RestConstant.REST_PORT);
  60. return PathAndInvokerMapper.getRestMethodMetadata(path, version, group, port);
  61. }
  62. }

Coding Examples

API

mvc

  1. @RestController()
  2. @RequestMapping("/demoService")
  3. public interface DemoService {
  4. @RequestMapping(value = "/hello", method = RequestMethod.GET)
  5. Integer hello(@RequestParam Integer a, @RequestParam Integer b);
  6. @RequestMapping(value = "/error", method = RequestMethod.GET)
  7. String error();
  8. @RequestMapping(value = "/say", method = RequestMethod.POST, consumes = MediaType.TEXT_PLAIN_VALUE)
  9. String sayHello(@RequestBody String name);
  10. }

resteasy:

  1. @Path("/demoService")
  2. public interface RestDemoService {
  3. @GET
  4. @Path("/hello")
  5. Integer hello(@QueryParam("a")Integer a,@QueryParam("b") Integer b);
  6. @GET
  7. @Path("/error")
  8. String error();
  9. @POST
  10. @Path("/say")
  11. @Consumes({MediaType.TEXT_PLAIN})
  12. String sayHello(String name);
  13. boolean isCalled();
  14. }

impl(service)

  1. @DubboService()
  2. public class RestDemoServiceImpl implements RestDemoService {
  3. private static Map<String, Object> context;
  4. private boolean called;
  5. @Override
  6. public String sayHello(String name) {
  7. called = true;
  8. return "Hello, " + name;
  9. }
  10. public boolean isCalled() {
  11. return called;
  12. }
  13. @Override
  14. public Integer hello(Integer a, Integer b) {
  15. context = RpcContext.getServerAttachment().getObjectAttachments();
  16. return a + b;
  17. }
  18. @Override
  19. public String error() {
  20. throw new RuntimeException();
  21. }
  22. public static Map<String, Object> getAttachments() {
  23. return context;
  24. }
  25. }

Flow Chart

Consumer

image

Provider(RestServer)

image

Scenarios

1. Interoperability of Systems

Non-Dubbo System Interoperability (Springcloud Alibaba Interoperability)

Interoperability Conditions:

ProtocolDubboSpringCloud AlibabaInteroperation
Communication Protocolrestspring web/resteasy encoding styleintegrates feignclient, ribbon (spring web encoding style)Yes
triple
dubbo
grpc
hessian
Registration Centerzookeeper
nacossupportedsupportedapplication-level registration

2. Dubbo Dual Registration

Complete application-level registration (from dubbo2 to dubbo3 migration), dubbo version upgrade

image

image

3. Multi-Protocol Publishing

Configuration:

  1. <dubbo:service interface="org.apache.dubbo.samples.DemoService" protocol="dubbo, grpc, rest"/>

4. Cross-Language

image

5. Multi-Protocol Interaction

image

6. Protocol Migration

image

Rest encoding style

HTTP protocol is more universally applicable for cross-language calls.

Dubbo Rest calls other HTTP services.

Other HttpClient calls Dubbo Rest.

Dubbo RestServer can directly interact with other web services, browsers, and clients via HTTP.

Consumer TODO LIST

Features have been initially implemented and can parse responses.

  1. org/apache/dubbo/rpc/protocol/rest/RestProtocol.java:157 dynamic load config

  2. org/apache/dubbo/remoting/http/factory/AbstractHttpClientFactory.java:50 load config HttpClientConfig

  3. org/apache/dubbo/rpc/protocol/rest/annotation/metadata/MetadataResolver.java:52 support Dubbo style service

  4. org/apache/dubbo/remoting/http/restclient/HttpClientRestClient.java:120 TODO config

  5. org/apache/dubbo/remoting/http/restclient/HttpClientRestClient.java:140 TODO close judgment

  6. org/apache/dubbo/rpc/protocol/rest/message/decode/MultiValueCodec.java:35 TODO java bean get set convert

Provider TODO LIST

To be implemented

Netty implementation supporting HTTP protocol for NettyServer.

No annotation protocol definition.

Website scenario supplements.

Rest User Documentation and Demo

Feedback

Was this page helpful?

Yes No

Last modified September 30, 2024: Update & Translate Overview Docs (#3040) (d37ebceaea7)