Attention: The feature described in this doc is still under development or is in a very early stage, please keep updated!
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.
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.
POST /test/path HTTP/1.1
Host: localhost:8080
Content-type: application/json
Accept: text/html
rest-service-version: 1.0.0
rest-service-group: dubbo
{"name":"dubbo","age":10,"address":"hangzhou"}
HTTP/1.1 200
Content-Type: text/html
Content-Length: 4
Date: Fri, 28 Apr 2023 14:16:42 GMT
"success"
Currently, the above media types are supported, and further extensions will be made.
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
API
@RequestMapping("/spring/demo/service")
public interface SpringRestDemoService {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Integer hello(@RequestParam("a") Integer a, @RequestParam("b") Integer b);
@RequestMapping(method = RequestMethod.GET, value = "/error")
String error();
@RequestMapping(method = RequestMethod.POST, value = "/say")
String sayHello(@RequestBody String name);
@RequestMapping(method = RequestMethod.POST, value = "/testFormBody", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
Long testFormBody(@RequestBody Long number);
@RequestMapping(method = RequestMethod.POST, value = "/testJavaBeanBody", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
User testJavaBeanBody(@RequestBody User user);
@RequestMapping(method = RequestMethod.GET, value = "/primitive")
int primitiveInt(@RequestParam("a") int a, @RequestParam("b") int b);
@RequestMapping(method = RequestMethod.GET, value = "/primitiveLong")
long primitiveLong(@RequestParam("a") long a, @RequestParam("b") Long b);
@RequestMapping(method = RequestMethod.GET, value = "/primitiveByte")
long primitiveByte(@RequestParam("a") byte a, @RequestParam("b") Long b);
@RequestMapping(method = RequestMethod.POST, value = "/primitiveShort")
long primitiveShort(@RequestParam("a") short a, @RequestParam("b") Long b, @RequestBody int c);
@RequestMapping(method = RequestMethod.GET, value = "/testMapParam")
String testMapParam(@RequestParam Map<String, String> params);
@RequestMapping(method = RequestMethod.GET, value = "/testMapHeader")
String testMapHeader(@RequestHeader Map<String, String> headers);
@RequestMapping(method = RequestMethod.POST, value = "/testMapForm", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
List<String> testMapForm(MultiValueMap<String, String> params);
@RequestMapping(method = RequestMethod.GET, value = "/headerInt")
int headerInt(@RequestHeader("header") int header);
}
Provider
@DubboService(interfaceClass = SpringRestDemoService.class ,protocol = "rest")
public class SpringRestDemoServiceImpl implements SpringRestDemoService {
@Override
public String sayHello(String name) {
return "Hello, " + name;
}
@Override
public Long testFormBody(Long number) {
return number;
}
@Override
public User testJavaBeanBody(User user) {
return user;
}
@Override
public int primitiveInt(int a, int b) {
return a + b;
}
@Override
public long primitiveLong(long a, Long b) {
return a + b;
}
@Override
public long primitiveByte(byte a, Long b) {
return a + b;
}
@Override
public long primitiveShort(short a, Long b, int c) {
return a + b;
}
@Override
public String testMapParam(Map<String, String> params) {
return params.get("param");
}
@Override
public String testMapHeader(Map<String, String> headers) {
return headers.get("header");
}
@Override
public List<String> testMapForm(MultiValueMap<String, String> params) {
return params.get("form");
}
@Override
public int headerInt(int header) {
return header;
}
@Override
public Integer hello(Integer a, Integer b) {
return a + b;
}
@Override
public String error() {
throw new RuntimeException("test error");
}
}
Consumer
@Component
public class SpringRestDemoServiceConsumer {
@DubboReference(interfaceClass = SpringRestDemoService.class )
SpringRestDemoService springRestDemoService;
public void invoke(){
String hello = springRestDemoService.sayHello("hello");
assertEquals("Hello, hello", hello);
Integer result = springRestDemoService.primitiveInt(1, 2);
Long resultLong = springRestDemoService.primitiveLong(1, 2l);
long resultByte = springRestDemoService.primitiveByte((byte) 1, 2l);
long resultShort = springRestDemoService.primitiveShort((short) 1, 2l, 1);
assertEquals(result, 3);
assertEquals(resultShort, 3l);
assertEquals(resultLong, 3l);
assertEquals(resultByte, 3l);
assertEquals(Long.valueOf(1l), springRestDemoService.testFormBody(1l));
MultiValueMap<String, String> forms = new LinkedMultiValueMap<>();
forms.put("form", Arrays.asList("F1"));
assertEquals(Arrays.asList("F1"), springRestDemoService.testMapForm(forms));
assertEquals(User.getInstance(), springRestDemoService.testJavaBeanBody(User.getInstance()));
}
private void assertEquals(Object returnStr, Object exception) {
boolean equal = returnStr != null && returnStr.equals(exception);
if (equal) {
return;
} else {
throw new RuntimeException();
}
}
}
API
@Path("/jaxrs/demo/service")
public interface JaxRsRestDemoService {
@GET
@Path("/hello")
Integer hello(@QueryParam("a") Integer a, @QueryParam("b") Integer b);
@GET
@Path("/error")
String error();
@POST
@Path("/say")
String sayHello(String name);
@POST
@Path("/testFormBody")
Long testFormBody(@FormParam("number") Long number);
@POST
@Path("/testJavaBeanBody")
@Consumes({MediaType.APPLICATION_JSON})
User testJavaBeanBody(User user);
@GET
@Path("/primitive")
int primitiveInt(@QueryParam("a") int a, @QueryParam("b") int b);
@GET
@Path("/primitiveLong")
long primitiveLong(@QueryParam("a") long a, @QueryParam("b") Long b);
@GET
@Path("/primitiveByte")
long primitiveByte(@QueryParam("a") byte a, @QueryParam("b") Long b);
@POST
@Path("/primitiveShort")
long primitiveShort(@QueryParam("a") short a, @QueryParam("b") Long b, int c);
@GET
@Path("testMapParam")
@Produces({MediaType.TEXT_PLAIN})
@Consumes({MediaType.TEXT_PLAIN})
String testMapParam(@QueryParam("test") Map<String, String> params);
@GET
@Path("testMapHeader")
@Produces({MediaType.TEXT_PLAIN})
@Consumes({MediaType.TEXT_PLAIN})
String testMapHeader(@HeaderParam("test") Map<String, String> headers);
@POST
@Path("testMapForm")
@Produces({MediaType.APPLICATION_JSON})
@Consumes({MediaType.APPLICATION_FORM_URLENCODED})
List<String> testMapForm(MultivaluedMap<String, String> params);
@POST
@Path("/header")
@Consumes({MediaType.TEXT_PLAIN})
String header(@HeaderParam("header") String header);
@POST
@Path("/headerInt")
@Consumes({MediaType.TEXT_PLAIN})
int headerInt(@HeaderParam("header") int header);
}
Provider
@DubboService(interfaceClass =JaxRsRestDemoService.class ,protocol = "rest",version = "1.0.0",group = "test")
public class JaxRsRestDemoServiceImpl implements JaxRsRestDemoService {
@Override
public String sayHello(String name) {
return "Hello, " + name;
}
@Override
public Long testFormBody(Long number) {
return number;
}
@Override
public User testJavaBeanBody(User user) {
return user;
}
@Override
public int primitiveInt(int a, int b) {
return a + b;
}
@Override
public long primitiveLong(long a, Long b) {
return a + b;
}
@Override
public long primitiveByte(byte a, Long b) {
return a + b;
}
@Override
public long primitiveShort(short a, Long b, int c) {
return a + b;
}
@Override
public String testMapParam(Map<String, String> params) {
return params.get("param");
}
@Override
public String testMapHeader(Map<String, String> headers) {
return headers.get("header");
}
@Override
public List<String> testMapForm(MultivaluedMap<String, String> params) {
return params.get("form");
}
@Override
public String header(String header) {
return header;
}
@Override
public int headerInt(int header) {
return header;
}
@Override
public Integer hello(Integer a, Integer b) {
return a + b;
}
@Override
public String error() {
throw new RuntimeException("test error");
}
}
Consumer
@Component
public class JaxRsRestDemoService {
@DubboReference(interfaceClass = JaxRsRestDemoService.class)
JaxRsRestDemoService jaxRsRestDemoService;
public void jaxRsRestDemoServiceTest(ClassPathXmlApplicationContext context) {
JaxRsRestDemoService jaxRsRestDemoService = context.getBean("jaxRsRestDemoService", JaxRsRestDemoService.class);
String hello = jaxRsRestDemoService.sayHello("hello");
assertEquals("Hello, hello", hello);
Integer result = jaxRsRestDemoService.primitiveInt(1, 2);
Long resultLong = jaxRsRestDemoService.primitiveLong(1, 2l);
long resultByte = jaxRsRestDemoService.primitiveByte((byte) 1, 2l);
long resultShort = jaxRsRestDemoService.primitiveShort((short) 1, 2l, 1);
assertEquals(result, 3);
assertEquals(resultShort, 3l);
assertEquals(resultLong, 3l);
assertEquals(resultByte, 3l);
assertEquals(Long.valueOf(1l), jaxRsRestDemoService.testFormBody(1l));
MultivaluedMapImpl<String, String> forms = new MultivaluedMapImpl<>();
forms.put("form", Arrays.asList("F1"));
assertEquals(Arrays.asList("F1"), jaxRsRestDemoService.testMapForm(forms));
assertEquals(User.getInstance(), jaxRsRestDemoService.testJavaBeanBody(User.getInstance()));
}
}
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:
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-rest</artifactId>
<version>${dubbo-rpc-rest_version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<version>${okhttp_version}</version>
</dependency>
or
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient_version}</version>
</dependency>
/**
* URL rest://localhost:8888/services
* rest: protocol
* localhost:8888: server address
* services: context path
*/
@DubboReference(interfaceClass = HttpService.class ,url = "rest://localhost:8888/services",version = "1.0.0",group = "test")
HttpService httpService;
public void invokeHttpService() {
String http = httpService.http("Others Java Architecture Invoke Dubbo Rest");
System.out.println(http);
}
Cross-language invocation
python
import requests
url = 'http://localhost:8888/services/curl'
headers = {
'rest-service-group': 'test',
'rest-service-version': '1.0.0'
}
response = requests.get(url, headers=headers)
go
import (
"fmt"
"net/http"
)
func main() {
url := "http://localhost:8888/services/curl"
req, err := http.NewRequest("GET", url, nil)
if err != nil {
fmt.Println("Error creating request:", err)
return
}
req.Header.Set("rest-service-group", "test")
req.Header.Set("rest-service-version", "1.0.0")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error sending request:", err)
return
}
defer resp.Body.Close()
- Multi-protocol publishing
- Testing code requests using HTTP for the Dubbo protocol
- Service protocol migration
````java
@DubboService(interfaceClass = HttpService.class, protocol = "rest,dubbo", version = "1.0.0", group = "test")
public class HttpServiceImpl implements HttpService {
@Override
public String http(String invokeType) {
return "Rest http request test success! by invokeType: " + invokeType;
}
}
public class HttpClientInvoke {
private final String versionHeader = RestHeaderEnum.VERSION.getHeader();
private final String groupHeader = RestHeaderEnum.GROUP.getHeader();
/**
* contextPath services
*/
private final String url = "http://localhost:8888/services/http";
public void httpServiceHttpClientInvoke() throws IOException {
CloseableHttpClient httpClient = createHttpClient();
HttpRequestBase httpUriRequest = new HttpGet(url);
httpUriRequest.addHeader(versionHeader, "1.0.0");
httpUriRequest.addHeader(RestConstant.ACCEPT, "text/plain");
httpUriRequest.addHeader(groupHeader, "test");
httpUriRequest.addHeader("type", "Http Client Invoke Dubbo Rest Service");
CloseableHttpResponse response = httpClient.execute(httpUriRequest);
RestResult restResult = parseResponse(response);
System.out.println(new String(restResult.getBody()));
}
private RestResult parseResponse(CloseableHttpResponse response) {
return new RestResult() {
@Override
public String getContentType() {
return response.getFirstHeader("Content-Type").getValue();
}
@Override
public byte[] getBody() throws IOException {
if (response.getEntity() == null) {
return new byte[0];
}
return IOUtils.toByteArray(response.getEntity().getContent());
}
@Override
public Map<String, List<String>> headers() {
return Arrays.stream(response.getAllHeaders()).collect(Collectors.toMap(Header::getName, h -> Collections.singletonList(h.getValue())));
}
@Override
public byte[] getErrorResponse() throws IOException {
return getBody();
}
@Override
public int getResponseCode() {
return response.getStatusLine().getStatusCode();
}
@Override
public String getMessage() throws IOException {
return appendErrorMessage(response.getStatusLine().getReasonPhrase(),
new String(getErrorResponse()));
}
};
}
private CloseableHttpClient createHttpClient() {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
return HttpClients.custom().setConnectionManager(connectionManager).build();
}
}
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.
Version | JaxRs | j2ee | Web container (tomcat/jetty) | Spring Web | HTTP client (okhttp/httpclient/jdk URLConnection) |
---|---|---|---|---|---|
3.0 | Depends on Resteasy Client and Server | Follows j2ee specifications | Relies on common web containers | No dependence | No dependence |
3.2 | No dependence (only requires JaxRs annotation package) | Not followed | HTTP server implemented with Netty | Only depends on Spring Web annotations | Internal implementation of RestClient relies on HTTP client (default is okhttp) |