HTTP Protocol Specification

HTTP Json Protocol Specification

Note

Starting from Dubbo version 3.3, the Rest protocol has been moved to the Extensions library, with the Triple protocol providing more comprehensive support for Rest. For details, refer to the Triple Rest User Manual. If you wish to continue using the original Rest protocol, you can import the corresponding dubbo-spi-extensions library dependency.

What is Dubbo Http

A protocol implemented through the Dubbo protocol extension, based on the coding style of Spring Web and Resteasy, enabling inter-service calls via the HTTP protocol.

Why Choose Dubbo Http

  • Enables interoperability between microservices and Dubbo.
  • Multi-protocol service publishing allows for smooth migration of service protocols.
  • The universality of HTTP addresses cross-language interoperability.
  • The latest version of HTTP does not require additional components, making it lighter.
  • Resteasy and Spring Web coding styles allow for quicker onboarding.

Protocol Specification

  • Request

Compared to the native HTTP protocol, the Dubbo Http request adds two headers, version and group, to determine the service’s uniqueness. If the provider does not declare the group and version, these headers do not need to be passed during the HTTP request; otherwise, they must be passed. When using the Dubbo Http’s RestClient, these headers will be passed by default via attachment with the prefix rest-service-. Therefore, other HTTP clients need to pass rest-service-version and rest-service-group headers.

  1. POST /test/path HTTP/1.1
  2. Host: localhost:8080
  3. Content-type: application/json
  4. Accept: text/html
  5. rest-service-version: 1.0.0
  6. rest-service-group: dubbo
  7. {"name":"dubbo","age":10,"address":"hangzhou"}
  • Response
  1. HTTP/1.1 200
  2. Content-Type: text/html
  3. Content-Length: 4
  4. Date: Fri, 28 Apr 2023 14:16:42 GMT
  5. "success"
  • Supported content-types
    • application/json
    • application/x-www-form-urlencoded
    • text/plain
    • text/xml

Currently, the above media types are supported, and further extensions will be made.

Quick Start

For detailed dependencies and Spring configurations, refer to the Dubbo project’s dubbo-demo-xml module: https://github.com/apache/dubbo/tree/3.2/dubbo-demo/dubbo-demo-xml

  • Spring Web Coding When using Dubbo Http with Spring Web coding, class annotations must include @RequestMapping or @Controller to determine the user’s coding style, which decides which SpringMvcServiceRestMetadataResolver annotation parser should be used for meta-annotation parsing. On the provider side, users are allowed to use implementation classes as Dubbo Services, unlike the previous requirement that the service must be an interface.

API

  1. @RequestMapping("/spring/demo/service")
  2. public interface SpringRestDemoService {
  3. @RequestMapping(method = RequestMethod.GET, value = "/hello")
  4. Integer hello(@RequestParam("a") Integer a, @RequestParam("b") Integer b);
  5. @RequestMapping(method = RequestMethod.GET, value = "/error")
  6. String error();
  7. @RequestMapping(method = RequestMethod.POST, value = "/say")
  8. String sayHello(@RequestBody String name);
  9. @RequestMapping(method = RequestMethod.POST, value = "/testFormBody", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
  10. Long testFormBody(@RequestBody Long number);
  11. @RequestMapping(method = RequestMethod.POST, value = "/testJavaBeanBody", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
  12. User testJavaBeanBody(@RequestBody User user);
  13. @RequestMapping(method = RequestMethod.GET, value = "/primitive")
  14. int primitiveInt(@RequestParam("a") int a, @RequestParam("b") int b);
  15. @RequestMapping(method = RequestMethod.GET, value = "/primitiveLong")
  16. long primitiveLong(@RequestParam("a") long a, @RequestParam("b") Long b);
  17. @RequestMapping(method = RequestMethod.GET, value = "/primitiveByte")
  18. long primitiveByte(@RequestParam("a") byte a, @RequestParam("b") Long b);
  19. @RequestMapping(method = RequestMethod.POST, value = "/primitiveShort")
  20. long primitiveShort(@RequestParam("a") short a, @RequestParam("b") Long b, @RequestBody int c);
  21. @RequestMapping(method = RequestMethod.GET, value = "/testMapParam")
  22. String testMapParam(@RequestParam Map<String, String> params);
  23. @RequestMapping(method = RequestMethod.GET, value = "/testMapHeader")
  24. String testMapHeader(@RequestHeader Map<String, String> headers);
  25. @RequestMapping(method = RequestMethod.POST, value = "/testMapForm", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
  26. List<String> testMapForm(MultiValueMap<String, String> params);
  27. @RequestMapping(method = RequestMethod.GET, value = "/headerInt")
  28. int headerInt(@RequestHeader("header") int header);
  29. }

Provider

  1. @DubboService(interfaceClass = SpringRestDemoService.class ,protocol = "rest")
  2. public class SpringRestDemoServiceImpl implements SpringRestDemoService {
  3. @Override
  4. public String sayHello(String name) {
  5. return "Hello, " + name;
  6. }
  7. @Override
  8. public Long testFormBody(Long number) {
  9. return number;
  10. }
  11. @Override
  12. public User testJavaBeanBody(User user) {
  13. return user;
  14. }
  15. @Override
  16. public int primitiveInt(int a, int b) {
  17. return a + b;
  18. }
  19. @Override
  20. public long primitiveLong(long a, Long b) {
  21. return a + b;
  22. }
  23. @Override
  24. public long primitiveByte(byte a, Long b) {
  25. return a + b;
  26. }
  27. @Override
  28. public long primitiveShort(short a, Long b, int c) {
  29. return a + b;
  30. }
  31. @Override
  32. public String testMapParam(Map<String, String> params) {
  33. return params.get("param");
  34. }
  35. @Override
  36. public String testMapHeader(Map<String, String> headers) {
  37. return headers.get("header");
  38. }
  39. @Override
  40. public List<String> testMapForm(MultiValueMap<String, String> params) {
  41. return params.get("form");
  42. }
  43. @Override
  44. public int headerInt(int header) {
  45. return header;
  46. }
  47. @Override
  48. public Integer hello(Integer a, Integer b) {
  49. return a + b;
  50. }
  51. @Override
  52. public String error() {
  53. throw new RuntimeException("test error");
  54. }
  55. }

Consumer

  1. @Component
  2. public class SpringRestDemoServiceConsumer {
  3. @DubboReference(interfaceClass = SpringRestDemoService.class )
  4. SpringRestDemoService springRestDemoService;
  5. public void invoke(){
  6. String hello = springRestDemoService.sayHello("hello");
  7. assertEquals("Hello, hello", hello);
  8. Integer result = springRestDemoService.primitiveInt(1, 2);
  9. Long resultLong = springRestDemoService.primitiveLong(1, 2l);
  10. long resultByte = springRestDemoService.primitiveByte((byte) 1, 2l);
  11. long resultShort = springRestDemoService.primitiveShort((short) 1, 2l, 1);
  12. assertEquals(result, 3);
  13. assertEquals(resultShort, 3l);
  14. assertEquals(resultLong, 3l);
  15. assertEquals(resultByte, 3l);
  16. assertEquals(Long.valueOf(1l), springRestDemoService.testFormBody(1l));
  17. MultiValueMap<String, String> forms = new LinkedMultiValueMap<>();
  18. forms.put("form", Arrays.asList("F1"));
  19. assertEquals(Arrays.asList("F1"), springRestDemoService.testMapForm(forms));
  20. assertEquals(User.getInstance(), springRestDemoService.testJavaBeanBody(User.getInstance()));
  21. }
  22. private void assertEquals(Object returnStr, Object exception) {
  23. boolean equal = returnStr != null && returnStr.equals(exception);
  24. if (equal) {
  25. return;
  26. } else {
  27. throw new RuntimeException();
  28. }
  29. }
  30. }
  • JaxRs Coding When using JaxRs annotations, it’s required that the service class must have the @Path annotation to determine the use of JAXRSServiceRestMetadataResolver for annotation parsing.

API

  1. @Path("/jaxrs/demo/service")
  2. public interface JaxRsRestDemoService {
  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. String sayHello(String name);
  12. @POST
  13. @Path("/testFormBody")
  14. Long testFormBody(@FormParam("number") Long number);
  15. @POST
  16. @Path("/testJavaBeanBody")
  17. @Consumes({MediaType.APPLICATION_JSON})
  18. User testJavaBeanBody(User user);
  19. @GET
  20. @Path("/primitive")
  21. int primitiveInt(@QueryParam("a") int a, @QueryParam("b") int b);
  22. @GET
  23. @Path("/primitiveLong")
  24. long primitiveLong(@QueryParam("a") long a, @QueryParam("b") Long b);
  25. @GET
  26. @Path("/primitiveByte")
  27. long primitiveByte(@QueryParam("a") byte a, @QueryParam("b") Long b);
  28. @POST
  29. @Path("/primitiveShort")
  30. long primitiveShort(@QueryParam("a") short a, @QueryParam("b") Long b, int c);
  31. @GET
  32. @Path("testMapParam")
  33. @Produces({MediaType.TEXT_PLAIN})
  34. @Consumes({MediaType.TEXT_PLAIN})
  35. String testMapParam(@QueryParam("test") Map<String, String> params);
  36. @GET
  37. @Path("testMapHeader")
  38. @Produces({MediaType.TEXT_PLAIN})
  39. @Consumes({MediaType.TEXT_PLAIN})
  40. String testMapHeader(@HeaderParam("test") Map<String, String> headers);
  41. @POST
  42. @Path("testMapForm")
  43. @Produces({MediaType.APPLICATION_JSON})
  44. @Consumes({MediaType.APPLICATION_FORM_URLENCODED})
  45. List<String> testMapForm(MultivaluedMap<String, String> params);
  46. @POST
  47. @Path("/header")
  48. @Consumes({MediaType.TEXT_PLAIN})
  49. String header(@HeaderParam("header") String header);
  50. @POST
  51. @Path("/headerInt")
  52. @Consumes({MediaType.TEXT_PLAIN})
  53. int headerInt(@HeaderParam("header") int header);
  54. }

Provider

  1. @DubboService(interfaceClass =JaxRsRestDemoService.class ,protocol = "rest",version = "1.0.0",group = "test")
  2. public class JaxRsRestDemoServiceImpl implements JaxRsRestDemoService {
  3. @Override
  4. public String sayHello(String name) {
  5. return "Hello, " + name;
  6. }
  7. @Override
  8. public Long testFormBody(Long number) {
  9. return number;
  10. }
  11. @Override
  12. public User testJavaBeanBody(User user) {
  13. return user;
  14. }
  15. @Override
  16. public int primitiveInt(int a, int b) {
  17. return a + b;
  18. }
  19. @Override
  20. public long primitiveLong(long a, Long b) {
  21. return a + b;
  22. }
  23. @Override
  24. public long primitiveByte(byte a, Long b) {
  25. return a + b;
  26. }
  27. @Override
  28. public long primitiveShort(short a, Long b, int c) {
  29. return a + b;
  30. }
  31. @Override
  32. public String testMapParam(Map<String, String> params) {
  33. return params.get("param");
  34. }
  35. @Override
  36. public String testMapHeader(Map<String, String> headers) {
  37. return headers.get("header");
  38. }
  39. @Override
  40. public List<String> testMapForm(MultivaluedMap<String, String> params) {
  41. return params.get("form");
  42. }
  43. @Override
  44. public String header(String header) {
  45. return header;
  46. }
  47. @Override
  48. public int headerInt(int header) {
  49. return header;
  50. }
  51. @Override
  52. public Integer hello(Integer a, Integer b) {
  53. return a + b;
  54. }
  55. @Override
  56. public String error() {
  57. throw new RuntimeException("test error");
  58. }
  59. }

Consumer

  1. @Component
  2. public class JaxRsRestDemoService {
  3. @DubboReference(interfaceClass = JaxRsRestDemoService.class)
  4. JaxRsRestDemoService jaxRsRestDemoService;
  5. public void jaxRsRestDemoServiceTest(ClassPathXmlApplicationContext context) {
  6. JaxRsRestDemoService jaxRsRestDemoService = context.getBean("jaxRsRestDemoService", JaxRsRestDemoService.class);
  7. String hello = jaxRsRestDemoService.sayHello("hello");
  8. assertEquals("Hello, hello", hello);
  9. Integer result = jaxRsRestDemoService.primitiveInt(1, 2);
  10. Long resultLong = jaxRsRestDemoService.primitiveLong(1, 2l);
  11. long resultByte = jaxRsRestDemoService.primitiveByte((byte) 1, 2l);
  12. long resultShort = jaxRsRestDemoService.primitiveShort((short) 1, 2l, 1);
  13. assertEquals(result, 3);
  14. assertEquals(resultShort, 3l);
  15. assertEquals(resultLong, 3l);
  16. assertEquals(resultByte, 3l);
  17. assertEquals(Long.valueOf(1l), jaxRsRestDemoService.testFormBody(1l));
  18. MultivaluedMapImpl<String, String> forms = new MultivaluedMapImpl<>();
  19. forms.put("form", Arrays.asList("F1"));
  20. assertEquals(Arrays.asList("F1"), jaxRsRestDemoService.testMapForm(forms));
  21. assertEquals(User.getInstance(), jaxRsRestDemoService.testJavaBeanBody(User.getInstance()));
  22. }
  23. }

Use Cases

As the Dubbo Http consumer implements the RestClient in three forms: httpclient, okhttp, URLConnection (jdk built-in), okhttp is the default option. When using Dubbo Http to call other HTTP services, the required dependencies to include are:

  1. <dependency>
  2. <groupId>org.apache.dubbo</groupId>
  3. <artifactId>dubbo-rpc-rest</artifactId>
  4. <version>${dubbo-rpc-rest_version}</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>com.squareup.okhttp3</groupId>
  8. <artifactId>mockwebserver</artifactId>
  9. <version>${okhttp_version}</version>
  10. </dependency>
  11. or
  12. <dependency>
  13. <groupId>org.apache.httpcomponents</groupId>
  14. <artifactId>httpclient</artifactId>
  15. <version>${httpclient_version}</version>
  16. </dependency>
  • Microservices invoking Dubbo Http
  1. /**
  2. * URL rest://localhost:8888/services
  3. * rest: protocol
  4. * localhost:8888: server address
  5. * services: context path
  6. */
  7. @DubboReference(interfaceClass = HttpService.class ,url = "rest://localhost:8888/services",version = "1.0.0",group = "test")
  8. HttpService httpService;
  9. public void invokeHttpService() {
  10. String http = httpService.http("Others Java Architecture Invoke Dubbo Rest");
  11. System.out.println(http);
  12. }
  • Cross-language invocation

    python

    1. import requests
    2. url = 'http://localhost:8888/services/curl'
    3. headers = {
    4. 'rest-service-group': 'test',
    5. 'rest-service-version': '1.0.0'
    6. }
    7. response = requests.get(url, headers=headers)

    go

    1. import (
    2. "fmt"
    3. "net/http"
    4. )
    5. func main() {
    6. url := "http://localhost:8888/services/curl"
    7. req, err := http.NewRequest("GET", url, nil)
    8. if err != nil {
    9. fmt.Println("Error creating request:", err)
    10. return
    11. }
    12. req.Header.Set("rest-service-group", "test")
    13. req.Header.Set("rest-service-version", "1.0.0")
    14. client := &http.Client{}
    15. resp, err := client.Do(req)
    16. if err != nil {
    17. fmt.Println("Error sending request:", err)
    18. return
    19. }
    20. defer resp.Body.Close()
  1. - Multi-protocol publishing
  2. - Testing code requests using HTTP for the Dubbo protocol
  3. - Service protocol migration
  4. ````java
  5. @DubboService(interfaceClass = HttpService.class, protocol = "rest,dubbo", version = "1.0.0", group = "test")
  6. public class HttpServiceImpl implements HttpService {
  7. @Override
  8. public String http(String invokeType) {
  9. return "Rest http request test success! by invokeType: " + invokeType;
  10. }
  11. }
  • HTTP client component invoking Dubbo Http (service API can be excluded)
  1. public class HttpClientInvoke {
  2. private final String versionHeader = RestHeaderEnum.VERSION.getHeader();
  3. private final String groupHeader = RestHeaderEnum.GROUP.getHeader();
  4. /**
  5. * contextPath services
  6. */
  7. private final String url = "http://localhost:8888/services/http";
  8. public void httpServiceHttpClientInvoke() throws IOException {
  9. CloseableHttpClient httpClient = createHttpClient();
  10. HttpRequestBase httpUriRequest = new HttpGet(url);
  11. httpUriRequest.addHeader(versionHeader, "1.0.0");
  12. httpUriRequest.addHeader(RestConstant.ACCEPT, "text/plain");
  13. httpUriRequest.addHeader(groupHeader, "test");
  14. httpUriRequest.addHeader("type", "Http Client Invoke Dubbo Rest Service");
  15. CloseableHttpResponse response = httpClient.execute(httpUriRequest);
  16. RestResult restResult = parseResponse(response);
  17. System.out.println(new String(restResult.getBody()));
  18. }
  19. private RestResult parseResponse(CloseableHttpResponse response) {
  20. return new RestResult() {
  21. @Override
  22. public String getContentType() {
  23. return response.getFirstHeader("Content-Type").getValue();
  24. }
  25. @Override
  26. public byte[] getBody() throws IOException {
  27. if (response.getEntity() == null) {
  28. return new byte[0];
  29. }
  30. return IOUtils.toByteArray(response.getEntity().getContent());
  31. }
  32. @Override
  33. public Map<String, List<String>> headers() {
  34. return Arrays.stream(response.getAllHeaders()).collect(Collectors.toMap(Header::getName, h -> Collections.singletonList(h.getValue())));
  35. }
  36. @Override
  37. public byte[] getErrorResponse() throws IOException {
  38. return getBody();
  39. }
  40. @Override
  41. public int getResponseCode() {
  42. return response.getStatusLine().getStatusCode();
  43. }
  44. @Override
  45. public String getMessage() throws IOException {
  46. return appendErrorMessage(response.getStatusLine().getReasonPhrase(),
  47. new String(getErrorResponse()));
  48. }
  49. };
  50. }
  51. private CloseableHttpClient createHttpClient() {
  52. PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
  53. return HttpClients.custom().setConnectionManager(connectionManager).build();
  54. }
  55. }

Comparison of HTTP Implementations between 3.2 and 3.0

Due to the removal of the original Resteasy implementation in Dubbo Java 3.2, there will be changes in support for Resteasy’s built-in Response, extend, and ExceptionMapper. ExceptionMapper has been transformed into org.apache.dubbo.rpc.protocol.rest.exception.mapper.ExceptionHandler, and Response will undergo adaptations later.

VersionJaxRsj2eeWeb container (tomcat/jetty)Spring WebHTTP client (okhttp/httpclient/jdk URLConnection)
3.0Depends on Resteasy Client and ServerFollows j2ee specificationsRelies on common web containersNo dependenceNo dependence
3.2No dependence (only requires JaxRs annotation package)Not followedHTTP server implemented with NettyOnly depends on Spring Web annotationsInternal implementation of RestClient relies on HTTP client (default is okhttp)

Feedback

Was this page helpful?

Yes No

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