HTTP 协议规范

HTTP Json 协议规范

注意

从 Dubbo 3.3 版本开始,Rest 协议已移至 Extensions 库,由 Triple 协议来对 Rest 提供更全面的支持,具体参见 Triple Rest用户手册, 如需继续使用原 Rest 协议,可引入对应 dubbo-spi-extensions 库依赖

什么是 Dubbo Http

基于 spring web 和 resteasy 注解编码风格,通过http协议进行服务间调用互通,dubbo protocol扩展实现的协议

为什么选择Dubbo Http

  • dubbo http 可以实现微服务与dubbo之间的互通
  • 多协议发布服务,可以实现服务协议的平滑迁移
  • http的通用性,解决跨语言互通
  • 最新版本的http 无需添加其他组件,更轻量
  • resteasy以及spring web的编码风格,上手更快

协议规范

  • Request

相对于原生的http协议dubbo http 请求增加version和group两个header用于确定服务的唯一, 如果provider一端没有声明group和version,http请求时就不需要传递这连个header,反之必须要传递目标 服务的group和version, 如果使用dubbo http的RestClient这两个header将会默认通过attachment传递 为区别于其他的header,attachment将会增加rest-service-前缀,因此通过其他形式的http client调用 dubbo http服务需要传递 rest-service-version 和 rest-service-group 两个header

  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"
  • content-type支持
    • application/json
    • application/x-www-form-urlencoded
    • text/plain
    • text/xml

目前支持以上media,后面还会对type进行扩展

快速入门

详细的依赖以及spring配置,可以参见dubbo 项目的dubbo-demo-xml模块 https://github.com/apache/dubbo/tree/3.2/dubbo-demo/dubbo-demo-xml

  • spring web 编码 在使用dubbo http的spring web编码时,类注解我们要求必须出现@RequestMapping或者@Controller,以此来判断用户使用的编码风格,决定使用对应的SpringMvcServiceRestMetadataResolver 注解解析器进行元注解解析,Provider一侧我们允许用户使用实现类作为Dubbo Service(相比之前dubbo service export时service必须是接口的要求)

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 编码 JaxRs注解使用的时候我们要求service 类上必须使用@Path注解,来确定使用JAXRSServiceRestMetadataResolver 注解解析器来解析注解元信息

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. }

使用场景

因为dubbo http consumer一端实现http 调用的RestClient 实现有三种形式:httpclient,okhttp,URLConnection(jdk内置) 默认请情况下采用okhttp,因此在使用dubbo http 去调用其他http服务时,需要添加引入的依赖有

  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. <dependency>
  12. <groupId>org.apache.httpcomponents</groupId>
  13. <artifactId>httpclient</artifactId>
  14. <version>${httpclient_version}</version>
  15. </dependency>
  • 微服务服务调用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. }
  • 跨语言调用

    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()
  • 多协议发布

    • dubbo 协议的代码使用http 进行数据请求测试
    • 服务协议迁移
  1. @DubboService(interfaceClass = HttpService.class, protocol = "rest,dubbo", version = "1.0.0", group = "test")
  2. public class HttpServiceImpl implements HttpService {
  3. @Override
  4. public String http(String invokeType) {
  5. return "Rest http request test success! by invokeType: " + invokeType;
  6. }
  7. }
  • http client组件调用dubbo http(可以不引入 service api)
  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. }

3.2 与 3.0 HTTP 实现对比

因为 Dubbo Java 3.2 内部移除了原本 Resteasy 的实现,因此在对 Resteasy 内置的 Response,extend,ExceptionMapper 支持上将会有所变化 ExceptionMapper 转换成了org.apache.dubbo.rpc.protocol.rest.exception.mapper.ExceptionHandler,Response后面也会做适配处理

版本JaxRsj2eeweb容器(tomcat/jetty)spring webhttp client(okhttp/httpclient/jdk URLConnnection )
3.0依赖与resteasy的client和server遵循j2ee规范依赖常见web容器不依赖不依赖
3.2不依赖(仅需要JaxRs注解包)不遵循netty实现的http服务器仅依赖spring web注解内部实现RestClient依赖http client(默认为okhttp)

最后修改 September 13, 2024: Refactor website structure (#2860) (1a4b998f54b)