3.2 consumer proxy invocation handler (#11108)

* add dubbo mvc feign client to support mvc remote call

* add dubbo mvc dispatcher servlet

* add dubbo mvc dispatcher servlet

* add   MvcDispatcherServlet  unit test

* add dubbo mvc feign client to support mvc remote call

* add dubbo mvc dispatcher servlet

* add Dubbo mvc Provider RPCInvocation create

* mvc remove unused code

* remove mvc unused test code

* remove unused code

* BodyProviderParamParser param type modify

* modify pom dependency version

* add  dubbo-metadata-api  dependency

* 新增依赖Jakarta支持 tomcat10

* ApplicationModel load  ParamParser

* ParamParser header & param map convert

* ParamParser header & param map convert

* 1 增加mvc协议
2 删除多余的resource包

* 1 增加mvc协议
2 增加netter server for http

* 重构 NettyHttpClient NettyHttpServer

* add path parse util & requestTemplate

* add mvc consumer http-connection

* mvc change  HttpConnectionPreBuildIntercept name and add SerializeBodyIntercept extend

* 1. 迁移mvc代码到rest下,合并http统一解析逻辑
2. 删除多余新增的remoting 调用模块

* 解决报错

* 1 抽象http 调用
2 改造rest调用风格

* 修改方法命名

* 修改方法名

* 删除多余的clients

* 还原restprotocol,逻辑迁移至新分支  3.2_refactor_rest_protocol

* add provider  PathMatcher

* add   ServiceRestMetadata  PathMatcher

* add   ServiceRestMetadata  PathMatcher

* add jax_rs and mvc annotation class constants

* refactor PathMatcher name

* add annotation class

* maintain rest metadata

* maintain  jax_rs and mvc annotation

* maintain  jax_rs and mvc annotation

* remove mvc module

* remove rest mvc code to code review

* add springmvc & rest annotation api module

* add springmvc & rest annotation api module

* add pathMatcher equal  test

* remove mvc module

* rest provider RPCInvocation build

* RPCInvocation build add dubbo-metadata-api dependency

* RestMethodMetadata add method to consumer service map

* RestMethodMetadata add method to consumer service map

* rest consumer HttpInvokeInvocationHandler

* rest consumer HttpInvokeInvocationHandler

* add dubbo-metadata-api dependency

* dubbo-rpc pom fix

* 移动模块

* 删除模块引用

* consumer metadata  resolve

* RestProtocol consumer

* RestProtocol consumer

* requestConvert adaptive

* JSON serializeObject to outputStream

* JsonUtilsTest  serialize & unSerialize

* ok http client

* http client reconstruct

* rest protocol BaseConvert

* rest protocol  ParamParser reconstruct

* rest protocol  addKeepAliveHeader

* requestTemplate isBodyEmpty()

* remove unused code

* create ConsumerParseContext

* body default byte[0]

* rest http message decode

* writeTextContent &  writeJsonContent & writeFormContent

* remove mvc & rest annotation module

* remove provider code

* fix some pr advice

* Add ASF license header for  newly created files

* move RestClientFactory  code  to dubbo-remoting-http

* exact  request convert code to a factory class

* modify  request convert code

* modify dubbo-dependencies-bom

* modify dubbo-remoting-http pom

* refactor spi  resource file  name

* dubbo-metadata-api remove
dubbo-configcenter-zookeeper dependency for
 cyclic reference

* newly create file add ASF

* fix compile error

* Enhance Code

* add rest protocol ConsumerParamParser

* some fix

* rest json content-type test

* RestProtocol merge doRefer code to protocolBindingRefer

* remove restInvoker

* rest protocol add JsonFactory to support custom

* rest protocol add JsonFactory to support custom

* jaxrs rest ParamNoAnnotatedProcessor  support fix

* add rest http message encode

* add rest http message encode

* rest okHttpClient requestBody fix

* rest jaxrs form content-type support

* RestProtocol protocolBindingRefer reconstruct

* RestProtocol protocolBindingRefer reconstruct  object to invoker & Invocation

* move RequestTemplate to remoting-http

* rest unit test

* add httpclient  & url connection rest client

* Enhance invocation

* enhance

* add ParameterTypesComparator  for metadataMap

* add ASF for new file

* Fix conflict

* Fix header

* fix import

* fix import

* fix import

* fix import

* fix import

* Fix json

* revert some change

* Remove jsonfactory

* Remove jsonfactory

* Simplify rest client

* Fix protocol

* fix codec

* tmp disable test

* rest metadata resolver add  interface judge

* add rest metadata  resolve unit test & fix  AbstractServiceRestMetadataResolver

* org.apache.dubbo.metadata.rest.ParamType null exclude & add default accept header

* RestProtocolTest

refer add context path
unit test

* some fix

* ADD TODO

* RESOLVE HTTP client java.net.SocketException: socket closed

* RESOLVE HTTP client java.net.SocketException: socket closed

* add spring mvc rest protocol unit test

* rest protocol http response code deal

* rest protocol http response message

* fix some review advice

* fix some review advice

* add rest metadata  resolve unit test & fix  AbstractServiceRestMetadataResolver

* org.apache.dubbo.metadata.rest.ParamType null exclude & add default accept header

* RestProtocolTest

refer add context path
unit test

* some fix

* fix some review advice

* add spring mvc rest protocol unit test

* rest protocol http response code deal

* rest protocol http response message

* URLConnectionRestClient getMessage

* remove unused import

* import fix

* code merge

* code merge

* code merge

* code merge

* code merge

* Fix conflicts

* change  restResult InputStream to bytes protect from  fd leak

* merge code

* Fix import

* Fix import

* Fix uts

* Remove unused code

* Fix logger

* Update okhttp version

* Update okhttp version

* rest protocol  add AnotherUserRestService service ut

* rest protocol  add String & byteArray codec

* Fix version

* Fix uts

* rest protocol  add XMLCodec

* stream release

* ServiceRestMetadata port change int to Integer

* service RestMetadata service map init

* xml codec change  for xxe

* Fix import

* code style

* code style

* Fix shade

---------

Co-authored-by: suncr <suncairong@moresec.cn>
Co-authored-by: Liujunjie <42363259+LiujunjieALiling@users.noreply.github.com>
Co-authored-by: junjie3.liu <junjie3.liu@ximalaya.com>
Co-authored-by: Albumen Kevin <jhq0812@gmail.com>
This commit is contained in:
suncairong163 2023-02-22 14:02:16 +08:00 committed by GitHub
parent 10e7162268
commit 02a59e2f40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
151 changed files with 6947 additions and 376 deletions

View File

@ -48,5 +48,4 @@ public interface JSON {
List<Map<String, ?>> checkObjectList(List<?> rawList);
List<String> checkStringList(List<?> rawList);
}

View File

@ -22,7 +22,6 @@ import java.lang.reflect.Type;
import java.util.List;
public class FastJson2Impl extends AbstractJSONImpl {
@Override
public <T> T toJavaObject(String json, Type type) {
return com.alibaba.fastjson2.JSON.parseObject(json, type);

View File

@ -37,4 +37,5 @@ public class FastJsonImpl extends AbstractJSONImpl {
public String toJson(Object obj) {
return com.alibaba.fastjson.JSON.toJSONString(obj, SerializerFeature.DisableCircularReferenceDetect);
}
}

View File

@ -16,8 +16,10 @@
*/
package org.apache.dubbo.common.json.impl;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
@ -25,6 +27,7 @@ import java.lang.reflect.Type;
import java.util.List;
public class JacksonImpl extends AbstractJSONImpl {
private ObjectMapper objectMapper = new ObjectMapper();
private volatile Object jacksonCache = null;

View File

@ -21,8 +21,6 @@ import org.apache.dubbo.metadata.definition.model.MethodDefinition;
import org.apache.dubbo.metadata.definition.model.TypeDefinition;
import org.apache.dubbo.metadata.definition.service.ComplexObject;
import org.apache.dubbo.metadata.definition.service.DemoService;
import org.apache.dubbo.rpc.model.FrameworkModel;
import org.apache.dubbo.rpc.model.FrameworkModel;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
@ -134,4 +132,4 @@ class ServiceDefinitionBuilderTest {
Assertions.assertEquals(Integer.class.getCanonicalName(), listTypeDefinition.getItems().get(0));
}
}
}

View File

@ -35,5 +35,19 @@
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-rest</artifactId>
</dependency>
<!-- Spring Web MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Web MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -16,18 +16,26 @@
*/
package org.apache.dubbo.demo;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import po.TestPO;
import javax.ws.rs.Path;
import javax.ws.rs.GET;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.POST;
import javax.ws.rs.FormParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
@Path("/demoService")
public interface RestDemoService {
@GET
@Path("/hello")
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.TEXT_PLAIN)
Integer hello(@QueryParam("a") Integer a, @QueryParam("b") Integer b);
@GET
@ -42,4 +50,44 @@ public interface RestDemoService {
@GET
@Path("/getRemoteApplicationName")
String getRemoteApplicationName();
@POST
@Path("/testBody1")
@Consumes({MediaType.TEXT_PLAIN})
Integer testBody(Integer b);
@POST
@Path("/testBody2")
@Consumes({MediaType.TEXT_PLAIN})
String testBody2(String b);
@POST
@Path("/testBody3")
@Consumes({MediaType.TEXT_PLAIN})
Boolean testBody2(Boolean b);
@POST
@Path("/testBody3")
@Consumes({MediaType.TEXT_PLAIN})
TestPO testBody2(TestPO b);
@POST
@Path("/testBody5")
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
TestPO testBody5(TestPO testPO);
@POST
@Path("/testForm1")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.TEXT_PLAIN)
String testForm1(@FormParam("name") String test);
@POST
@Path("/testForm2")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_FORM_URLENCODED)
MultivaluedMap testForm2(MultivaluedMap map);
}

View File

@ -0,0 +1,69 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package po;
public class TestPO {
private String name;
private String address;
private int age;
public TestPO(String name, String address, int age) {
this.name = name;
this.address = address;
this.age = age;
}
public TestPO() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public static TestPO getInstance() {
return new TestPO("dubbo", "hangzhou", 10);
}
@Override
public String toString() {
return "TestPO{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
", age=" + age +
'}';
}
}

View File

@ -22,7 +22,11 @@ import org.apache.dubbo.demo.RestDemoService;
import org.apache.dubbo.demo.TripleService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import po.TestPO;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
public class Application {
@ -56,23 +60,22 @@ public class Application {
new Thread(() -> {
while (true) {
try {
String restResult = restDemoService.sayHello("rest");
Object restResult = restDemoService.sayHello("rest");
System.out.println(restResult + " from separated thread.");
restResult = restDemoService.testBody5(TestPO.getInstance());
System.out.println(restResult + " from separated thread.");
} catch (Exception e) {
e.printStackTrace();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}).start();
new Thread(() -> {
while (true) {
try {
String restResult = tripleService.hello();
restResult = restDemoService.hello(1, 2);
System.out.println(restResult + " from separated thread.");
String form1 = restDemoService.testForm1("form1");
System.out.println(form1);
MultivaluedHashMap multivaluedHashMap = new MultivaluedHashMap();
multivaluedHashMap.put("1", Arrays.asList("1"));
multivaluedHashMap.put("2", Arrays.asList("2"));
MultivaluedMap form2 = restDemoService.testForm2(multivaluedHashMap);
System.out.println(form2);
} catch (Exception e) {
e.printStackTrace();
}

View File

@ -19,7 +19,9 @@ package org.apache.dubbo.demo.provider;
import org.apache.dubbo.demo.RestDemoService;
import org.apache.dubbo.rpc.RpcContext;
import po.TestPO;
import javax.ws.rs.core.MultivaluedMap;
import java.util.Map;
public class RestDemoServiceImpl implements RestDemoService {
@ -55,4 +57,39 @@ public class RestDemoServiceImpl implements RestDemoService {
public String getRemoteApplicationName() {
return RpcContext.getServiceContext().getRemoteApplicationName();
}
@Override
public Integer testBody(Integer b) {
return b;
}
@Override
public String testBody2(String b) {
return b;
}
@Override
public Boolean testBody2(Boolean b) {
return b;
}
@Override
public TestPO testBody2(TestPO b) {
return b;
}
@Override
public TestPO testBody5(TestPO testPO) {
return testPO;
}
public String testForm1(String test) {
return test;
}
public MultivaluedMap testForm2(MultivaluedMap map) {
return map;
}
}

View File

@ -117,6 +117,7 @@
<protobuf-java_version>3.19.6</protobuf-java_version>
<javax_annotation-api_version>1.3.2</javax_annotation-api_version>
<servlet_version>3.1.0</servlet_version>
<jakarta.servlet_version>5.0.0</jakarta.servlet_version>
<jetty_version>9.4.50.v20221201</jetty_version>
<validation_new_version>3.0.1</validation_new_version>
<validation_version>1.1.0.Final</validation_version>
@ -139,6 +140,7 @@
<prometheus_client.version>0.16.0</prometheus_client.version>
<reactive.version>1.0.4</reactive.version>
<reactor.version>3.5.2</reactor.version>
<okhttp_version>4.10.0</okhttp_version>
<rs_api_version>2.1.1</rs_api_version>
<resteasy_version>3.15.3.Final</resteasy_version>
@ -419,6 +421,25 @@
<artifactId>javax.servlet-api</artifactId>
<version>${servlet_version}</version>
</dependency>
<!-- support higher tomcat -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>${jakarta.servlet_version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp_version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<version>${okhttp_version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>

View File

@ -906,6 +906,36 @@
META-INF/dubbo/internal/org.apache.dubbo.registry.xds.XdsCertificateSigner
</resource>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>
META-INF/dubbo/internal/org.apache.dubbo.metadata.rest.NoAnnotatedParameterRequestTagProcessor
</resource>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>
META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.message.HttpMessageCodec
</resource>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>
META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.annotation.consumer.HttpConnectionPreBuildIntercept
</resource>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>
META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.consumer.BaseConsumerParamParser
</resource>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>
META-INF/dubbo/internal/org.apache.dubbo.remoting.http.factory.RestClientFactory
</resource>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>

View File

@ -45,14 +45,6 @@
<version>${project.parent.version}</version>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-configcenter-zookeeper</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
<!-- JAX-RS API -->
<dependency>
<groupId>javax.ws.rs</groupId>

View File

@ -41,7 +41,6 @@ import static java.util.Collections.emptySet;
import static java.util.Collections.unmodifiableSet;
import static java.util.stream.Collectors.toSet;
import static java.util.stream.Stream.of;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.COMMON_FAILED_LOAD_MAPPING_CACHE;
import static org.apache.dubbo.common.constants.RegistryConstants.SUBSCRIBED_SERVICE_NAMES_KEY;
import static org.apache.dubbo.common.utils.CollectionUtils.toTreeSet;

View File

@ -16,16 +16,30 @@
*/
package org.apache.dubbo.metadata;
public class MetadataConstants {
public static final String KEY_SEPARATOR = ":";
public static final String DEFAULT_PATH_TAG = "metadata";
public static final String KEY_REVISON_PREFIX = "revision";
public static final String META_DATA_STORE_TAG = ".metaData";
public static final String SERVICE_META_DATA_STORE_TAG = ".smd";
public static final String CONSUMER_META_DATA_STORE_TAG = ".cmd";
public static final String METADATA_PUBLISH_DELAY_KEY = "dubbo.application.metadata.publish.delay";
public static final int DEFAULT_METADATA_PUBLISH_DELAY = 1000;
public static final String METADATA_PROXY_TIMEOUT_KEY = "dubbo.application.metadata.proxy.delay";
public static final int DEFAULT_METADATA_TIMEOUT_VALUE = 5000;
public static String REPORT_CONSUMER_URL_KEY = "report-consumer-definition";
import static org.apache.dubbo.common.utils.ClassUtils.getClassLoader;
import static org.apache.dubbo.common.utils.ClassUtils.resolveClass;
public interface MetadataConstants {
String KEY_SEPARATOR = ":";
String DEFAULT_PATH_TAG = "metadata";
String KEY_REVISON_PREFIX = "revision";
String META_DATA_STORE_TAG = ".metaData";
String SERVICE_META_DATA_STORE_TAG = ".smd";
String CONSUMER_META_DATA_STORE_TAG = ".cmd";
String METADATA_PUBLISH_DELAY_KEY = "dubbo.application.metadata.publish.delay";
int DEFAULT_METADATA_PUBLISH_DELAY = 1000;
String METADATA_PROXY_TIMEOUT_KEY = "dubbo.application.metadata.proxy.delay";
int DEFAULT_METADATA_TIMEOUT_VALUE = 5000;
String REPORT_CONSUMER_URL_KEY = "report-consumer-definition";
String JAVAX_SERVLET_REQ_CLASS_NAME = "javax.servlet.ServletRequest";
Class JAVAX_SERVLET_REQ_CLASS = resolveClass(JAVAX_SERVLET_REQ_CLASS_NAME, getClassLoader());
String JAVAX_SERVLET_RES_CLASS_NAME = "javax.servlet.ServletResponse";
Class JAVAX_SERVLET_RES_CLASS = resolveClass(JAVAX_SERVLET_RES_CLASS_NAME, getClassLoader());
String JAKARTA_SERVLET_REQ_CLASS_NAME = "jakarta.servlet.ServletRequest";
Class JAKARTA_SERVLET_REQ_CLASS = resolveClass(JAKARTA_SERVLET_REQ_CLASS_NAME, getClassLoader());
String JAKARTA_SERVLET_RES_CLASS_NAME = "jakarta.servlet.ServletResponse";
Class JAKARTA_SERVLET_RES_CLASS = resolveClass(JAKARTA_SERVLET_RES_CLASS_NAME, getClassLoader());
String PATH_SEPARATOR = "/";
String APPLICATION_FORM_URLENCODED_VALUE = "application/x-www-form-urlencoded";
String APPLICATION_JSON_VALUE = "application/json";
}

View File

@ -0,0 +1,49 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata;
import java.util.Arrays;
public class ParameterTypesComparator {
private Class[] parameterTypes;
public ParameterTypesComparator(Class[] parameterTypes) {
this.parameterTypes = parameterTypes;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ParameterTypesComparator that = (ParameterTypesComparator) o;
return Arrays.equals(parameterTypes, that.parameterTypes);
}
@Override
public int hashCode() {
return Arrays.hashCode(parameterTypes);
}
public static ParameterTypesComparator getInstance(Class[] parameterTypes) {
return new ParameterTypesComparator(parameterTypes);
}
}

View File

@ -16,12 +16,14 @@
*/
package org.apache.dubbo.metadata.rest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import static org.apache.dubbo.common.utils.AnnotationUtils.getValue;
import static org.apache.dubbo.metadata.rest.AnnotatedMethodParameterProcessor.buildDefaultValue;
import static org.apache.dubbo.common.utils.ClassUtils.getClassLoader;
import static org.apache.dubbo.common.utils.ClassUtils.resolveClass;
/**
* The abstract {@link AnnotatedMethodParameterProcessor} implementation
@ -33,19 +35,39 @@ public abstract class AbstractAnnotatedMethodParameterProcessor implements Annot
@Override
public void process(Annotation annotation, Parameter parameter, int parameterIndex, Method method,
Class<?> serviceType, Class<?> serviceInterfaceClass, RestMethodMetadata restMethodMetadata) {
String annotationValue = getAnnotationValue(annotation, parameter, parameterIndex);
String defaultValue = getDefaultValue(annotation, parameter, parameterIndex);
addArgInfo(parameter, parameterIndex, restMethodMetadata, annotationValue, defaultValue);
process(annotationValue, defaultValue, annotation, parameter, parameterIndex, method, restMethodMetadata);
}
protected void process(String annotationValue, String defaultValue, Annotation annotation, Parameter parameter,
int parameterIndex, Method method, RestMethodMetadata restMethodMetadata) {
}
@Override
public Class getAnnotationClass() {
return resolveClass(getAnnotationName(), getClassLoader());
}
protected void addArgInfo(Parameter parameter, int parameterIndex,
RestMethodMetadata restMethodMetadata, String annotationValue, Object defaultValue) {
ArgInfo argInfo = ArgInfo.build(parameterIndex, parameter)
.setParamAnnotationType(getAnnotationClass())
.setAnnotationNameAttribute(annotationValue).setDefaultValue(defaultValue);
restMethodMetadata.addArgInfo(argInfo);
}
protected String getAnnotationValue(Annotation annotation, Parameter parameter, int parameterIndex) {
return getValue(annotation);
}
protected String getDefaultValue(Annotation annotation, Parameter parameter, int parameterIndex) {
return buildDefaultValue(parameterIndex);
return AnnotatedMethodParameterProcessor.buildDefaultValue(parameterIndex);
}
protected abstract void process(String annotationValue, String defaultValue, Annotation annotation, Object parameter,
int parameterIndex, Method method, RestMethodMetadata restMethodMetadata);
}

View File

@ -0,0 +1,68 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.rest;
import org.apache.dubbo.metadata.rest.media.MediaType;
import java.lang.reflect.Parameter;
import java.util.Set;
import static org.apache.dubbo.common.utils.ClassUtils.getClassLoader;
import static org.apache.dubbo.common.utils.ClassUtils.resolveClass;
public abstract class AbstractNoAnnotatedParameterProcessor implements NoAnnotatedParameterRequestTagProcessor {
public void process(Parameter parameter, int parameterIndex, RestMethodMetadata restMethodMetadata) {
MediaType mediaType = consumerContentType();
if (!contentTypeSupport(restMethodMetadata, mediaType, parameter.getType())) {
return;
}
boolean isFormBody = isFormContentType(restMethodMetadata);
addArgInfo(parameter, parameterIndex, restMethodMetadata, isFormBody);
}
private boolean contentTypeSupport(RestMethodMetadata restMethodMetadata, MediaType mediaType, Class paramType) {
// @RequestParam String,number param
if (mediaType.equals(MediaType.ALL_VALUE) && (String.class == paramType || Number.class.isAssignableFrom(paramType))) {
return true;
}
Set<String> consumes = restMethodMetadata.getRequest().getConsumes();
for (String consume : consumes) {
if (consume.contains(mediaType.value)) {
return true;
}
}
return false;
}
protected boolean isFormContentType(RestMethodMetadata restMethodMetadata) {
return false;
}
protected void addArgInfo(Parameter parameter, int parameterIndex,
RestMethodMetadata restMethodMetadata, boolean isFormBody) {
ArgInfo argInfo = ArgInfo.build(parameterIndex, parameter)
.setParamAnnotationType(resolveClass(defaultAnnotationClassName(restMethodMetadata), getClassLoader()))
.setAnnotationNameAttribute(parameter.getName()).setFormContentType(isFormBody);
restMethodMetadata.addArgInfo(argInfo);
}
}

View File

@ -43,6 +43,8 @@ import static org.apache.dubbo.common.function.ThrowableFunction.execute;
import static org.apache.dubbo.common.utils.AnnotationUtils.isAnyAnnotationPresent;
import static org.apache.dubbo.common.utils.ClassUtils.forName;
import static org.apache.dubbo.common.utils.ClassUtils.getAllInterfaces;
import static org.apache.dubbo.common.utils.MemberUtils.isPrivate;
import static org.apache.dubbo.common.utils.MemberUtils.isStatic;
import static org.apache.dubbo.common.utils.MethodUtils.excludedDeclaredClass;
import static org.apache.dubbo.common.utils.MethodUtils.getAllMethods;
import static org.apache.dubbo.common.utils.MethodUtils.overrides;
@ -56,13 +58,27 @@ import static org.apache.dubbo.common.utils.MethodUtils.overrides;
public abstract class AbstractServiceRestMetadataResolver implements ServiceRestMetadataResolver {
private final Map<String, List<AnnotatedMethodParameterProcessor>> parameterProcessorsMap;
private final Set<NoAnnotatedParameterRequestTagProcessor> noAnnotatedParameterRequestTagProcessors;
public AbstractServiceRestMetadataResolver(ApplicationModel applicationModel) {
this.parameterProcessorsMap = loadAnnotatedMethodParameterProcessors(applicationModel);
this.noAnnotatedParameterRequestTagProcessors = loadNoAnnotatedMethodParameterProcessors(applicationModel);
}
@Override
public final boolean supports(Class<?> serviceType) {
return supports(serviceType, false);
}
@Override
public final boolean supports(Class<?> serviceType, boolean consumer) {
// for consumer
if (consumer) {
// it is possible serviceType is impl
return supports0(serviceType);
}
// for provider
return isImplementedInterface(serviceType) && isServiceAnnotationPresent(serviceType) && supports0(serviceType);
}
@ -72,7 +88,7 @@ public abstract class AbstractServiceRestMetadataResolver implements ServiceRest
protected final boolean isServiceAnnotationPresent(Class<?> serviceType) {
return isAnyAnnotationPresent(serviceType, DubboService.class, Service.class,
com.alibaba.dubbo.config.annotation.Service.class);
com.alibaba.dubbo.config.annotation.Service.class);
}
/**
@ -85,12 +101,18 @@ public abstract class AbstractServiceRestMetadataResolver implements ServiceRest
@Override
public final ServiceRestMetadata resolve(Class<?> serviceType) {
ServiceRestMetadata serviceRestMetadata = new ServiceRestMetadata();
// Process ServiceRestMetadata
processServiceRestMetadata(serviceRestMetadata, serviceType);
return resolve(serviceType, serviceRestMetadata);
}
@Override
public final ServiceRestMetadata resolve(Class<?> serviceType, ServiceRestMetadata serviceRestMetadata) {
serviceRestMetadata.setCodeStyle(this.getClass());
// Process RestMethodMetadata
processAllRestMethodMetadata(serviceRestMetadata, serviceType);
@ -128,10 +150,10 @@ public abstract class AbstractServiceRestMetadataResolver implements ServiceRest
// try the overrider method first
Method serviceMethod = entry.getKey();
// If failed, it indicates the overrider method does not contain metadata , then try the declared method
if (!processRestMethodMetadata(serviceMethod, serviceType, serviceInterfaceClass, serviceRestMetadata.getMeta()::add)) {
if (!processRestMethodMetadata(serviceMethod, serviceType, serviceInterfaceClass, serviceRestMetadata::addRestMethodMetadata)) {
Method declaredServiceMethod = entry.getValue();
processRestMethodMetadata(declaredServiceMethod, serviceType, serviceInterfaceClass,
serviceRestMetadata.getMeta()::add);
serviceRestMetadata::addRestMethodMetadata);
}
}
}
@ -148,8 +170,16 @@ public abstract class AbstractServiceRestMetadataResolver implements ServiceRest
Map<Method, Method> serviceMethodsMap = new LinkedHashMap<>();
// exclude the public methods declared in java.lang.Object.class
List<Method> declaredServiceMethods = new ArrayList<>(getAllMethods(serviceInterfaceClass, excludedDeclaredClass(Object.class)));
// for interface , such as consumer interface
if (serviceType.isInterface()) {
putInterfaceMethodToMap(serviceMethodsMap, declaredServiceMethods);
return unmodifiableMap(serviceMethodsMap);
}
List<Method> serviceMethods = new ArrayList<>(getAllMethods(serviceType, excludedDeclaredClass(Object.class)));
// sort methods
sort(declaredServiceMethods, MethodComparator.INSTANCE);
sort(serviceMethods, MethodComparator.INSTANCE);
@ -158,7 +188,8 @@ public abstract class AbstractServiceRestMetadataResolver implements ServiceRest
for (Method serviceMethod : serviceMethods) {
if (overrides(serviceMethod, declaredServiceMethod)) {
serviceMethodsMap.put(serviceMethod, declaredServiceMethod);
continue;
// once method match ,break for decrease loop times
break;
}
}
}
@ -166,6 +197,17 @@ public abstract class AbstractServiceRestMetadataResolver implements ServiceRest
return unmodifiableMap(serviceMethodsMap);
}
private void putInterfaceMethodToMap(Map<Method, Method> serviceMethodsMap, List<Method> declaredServiceMethods) {
declaredServiceMethods.stream().forEach(method -> {
// filter static private default
if (isStatic(method) || isPrivate(method) || method.isDefault()) {
return;
}
serviceMethodsMap.put(method, method);
});
}
/**
* Resolve the class of Dubbo Service interface
*
@ -212,13 +254,15 @@ public abstract class AbstractServiceRestMetadataResolver implements ServiceRest
RestMethodMetadata metadata = new RestMethodMetadata();
metadata.setCodeStyle(this.getClass());
// to consumer service map
metadata.setReflectMethod(serviceMethod);
MethodDefinition methodDefinition = resolveMethodDefinition(serviceMethod, serviceType, serviceInterfaceClass);
// Set MethodDefinition
metadata.setMethod(methodDefinition);
// process the annotated method parameters
processAnnotatedMethodParameters(serviceMethod, serviceType, serviceInterfaceClass, metadata);
// process produces
Set<String> produces = new LinkedHashSet<>();
processProduces(serviceMethod, serviceType, serviceInterfaceClass, produces);
@ -234,6 +278,10 @@ public abstract class AbstractServiceRestMetadataResolver implements ServiceRest
request.setProduces(produces);
request.setConsumes(consumes);
// process the annotated method parameters
processAnnotatedMethodParameters(serviceMethod, serviceType, serviceInterfaceClass, metadata);
// Post-Process
postResolveRestMethodMetadata(serviceMethod, serviceType, serviceInterfaceClass, metadata);
@ -252,7 +300,7 @@ public abstract class AbstractServiceRestMetadataResolver implements ServiceRest
* @return If capable, return <code>true</code>
*/
protected abstract boolean isRestCapableMethod(Method serviceMethod, Class<?> serviceType, Class<?>
serviceInterfaceClass);
serviceInterfaceClass);
/**
* Resolve the request method
@ -263,7 +311,7 @@ public abstract class AbstractServiceRestMetadataResolver implements ServiceRest
* @return if can't be resolve, return <code>null</code>
*/
protected abstract String resolveRequestMethod(Method serviceMethod, Class<?> serviceType, Class<?>
serviceInterfaceClass);
serviceInterfaceClass);
/**
* Resolve the request path
@ -274,7 +322,7 @@ public abstract class AbstractServiceRestMetadataResolver implements ServiceRest
* @return if can't be resolve, return <code>null</code>
*/
protected abstract String resolveRequestPath(Method serviceMethod, Class<?> serviceType, Class<?>
serviceInterfaceClass);
serviceInterfaceClass);
/**
* Resolve the {@link MethodDefinition}
@ -307,37 +355,56 @@ public abstract class AbstractServiceRestMetadataResolver implements ServiceRest
Class<?> serviceType, Class<?> serviceInterfaceClass,
RestMethodMetadata metadata) {
Annotation[] annotations = parameter.getAnnotations();
if (annotations == null || annotations.length == 0) {
for (NoAnnotatedParameterRequestTagProcessor processor : noAnnotatedParameterRequestTagProcessors) {
processor.process(parameter, parameterIndex, metadata);
}
return;
}
for (Annotation annotation : annotations) {
String annotationType = annotation.annotationType().getName();
parameterProcessorsMap.getOrDefault(annotationType, emptyList())
.forEach(processor -> {
processor.process(annotation, parameter, parameterIndex, serviceMethod, serviceType,
serviceInterfaceClass, metadata);
});
.forEach(processor -> {
processor.process(annotation, parameter, parameterIndex, serviceMethod, serviceType,
serviceInterfaceClass, metadata);
});
}
}
protected abstract void processProduces(Method serviceMethod, Class<?> serviceType, Class<?>
serviceInterfaceClass,
serviceInterfaceClass,
Set<String> produces);
protected abstract void processConsumes(Method serviceMethod, Class<?> serviceType, Class<?>
serviceInterfaceClass,
serviceInterfaceClass,
Set<String> consumes);
protected void postResolveRestMethodMetadata(Method serviceMethod, Class<?> serviceType,
Class<?> serviceInterfaceClass, RestMethodMetadata metadata) {
// parse pathVariable index from url by annotation info
PathUtil.setArgInfoSplitIndex(metadata.getRequest().getPath(), metadata.getArgInfos());
}
private static Map<String, List<AnnotatedMethodParameterProcessor>> loadAnnotatedMethodParameterProcessors(ApplicationModel applicationModel) {
Map<String, List<AnnotatedMethodParameterProcessor>> parameterProcessorsMap = new LinkedHashMap<>();
applicationModel.getExtensionLoader(AnnotatedMethodParameterProcessor.class)
.getSupportedExtensionInstances()
.forEach(processor -> {
List<AnnotatedMethodParameterProcessor> processors =
parameterProcessorsMap.computeIfAbsent(processor.getAnnotationType(), k -> new LinkedList<>());
processors.add(processor);
});
.getSupportedExtensionInstances()
.forEach(processor -> {
List<AnnotatedMethodParameterProcessor> processors =
parameterProcessorsMap.computeIfAbsent(processor.getAnnotationName(), k -> new LinkedList<>());
processors.add(processor);
});
return parameterProcessorsMap;
}
private static Set<NoAnnotatedParameterRequestTagProcessor> loadNoAnnotatedMethodParameterProcessors(ApplicationModel applicationModel) {
Set<NoAnnotatedParameterRequestTagProcessor> supportedExtensionInstances = applicationModel.getExtensionLoader(NoAnnotatedParameterRequestTagProcessor.class)
.getSupportedExtensionInstances();
return supportedExtensionInstances;
}
}

View File

@ -16,6 +16,7 @@
*/
package org.apache.dubbo.metadata.rest;
import org.apache.dubbo.common.extension.ExtensionScope;
import org.apache.dubbo.common.extension.SPI;
import org.apache.dubbo.common.lang.Prioritized;
@ -29,20 +30,28 @@ import java.lang.reflect.Parameter;
*
* @since 2.7.6
*/
@SPI
@SPI(scope = ExtensionScope.FRAMEWORK)
public interface AnnotatedMethodParameterProcessor extends Prioritized {
/**
* The string presenting the annotation name
*
* @return non-null
*/
String getAnnotationName();
/**
* The string presenting the annotation type
*
* @return non-null
*/
String getAnnotationType();
Class getAnnotationClass();
/**
* Process the specified method {@link VariableElement parameter}
*
* @param annotation {@link Annotation the target annotation} whose type is {@link #getAnnotationType()}
* @param annotation {@link Annotation the target annotation} whose type is {@link #getAnnotationName()}
* @param parameter the method parameter
* @param parameterIndex the index of method parameter
* @param method {@link Method method that parameter belongs to}

View File

@ -0,0 +1,151 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.rest;
import java.lang.reflect.Parameter;
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;
public ArgInfo(int index, String name, Class paramType) {
this.index = index;
this.paramName = name;
this.paramType = paramType;
}
public ArgInfo(int index, Parameter parameter) {
this(index, parameter.getName(), parameter.getType());
}
public ArgInfo() {
}
public int getIndex() {
return index;
}
public ArgInfo setIndex(int index) {
this.index = index;
return this;
}
public String getAnnotationNameAttribute() {
if (annotationNameAttribute == null) {
// such as String param no annotation
return paramName;
}
return annotationNameAttribute;
}
public ArgInfo setAnnotationNameAttribute(String annotationNameAttribute) {
this.annotationNameAttribute = annotationNameAttribute;
return this;
}
public Class getParamAnnotationType() {
return paramAnnotationType;
}
public ArgInfo setParamAnnotationType(Class paramAnnotationType) {
this.paramAnnotationType = paramAnnotationType;
return this;
}
public Class getParamType() {
return paramType;
}
public void setParamType(Class paramType) {
this.paramType = paramType;
}
public int getUrlSplitIndex() {
return urlSplitIndex;
}
public void setUrlSplitIndex(int urlSplitIndex) {
this.urlSplitIndex = urlSplitIndex;
}
public static ArgInfo build() {
return new ArgInfo();
}
public static ArgInfo build(int index, Parameter parameter) {
return new ArgInfo(index, parameter);
}
public String getParamName() {
return paramName;
}
public ArgInfo setParamName(String paramName) {
this.paramName = paramName;
return this;
}
public Object getDefaultValue() {
return defaultValue;
}
public ArgInfo setDefaultValue(Object defaultValue) {
this.defaultValue = defaultValue;
return this;
}
public boolean isFormContentType() {
return formContentType;
}
public ArgInfo setFormContentType(boolean isFormContentType) {
this.formContentType = isFormContentType;
return this;
}
}

View File

@ -0,0 +1,62 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.rest;
import static org.apache.dubbo.common.utils.ClassUtils.getClassLoader;
import static org.apache.dubbo.common.utils.ClassUtils.resolveClass;
public interface JAXRSClassConstants extends RestMetadataConstants.JAX_RS {
/**
* The annotation class of @Path
*/
Class PATH_ANNOTATION_CLASS = resolveClass(PATH_ANNOTATION_CLASS_NAME, getClassLoader());
/**
* The annotation class of @FormParam
*/
Class FORM_PARAM_ANNOTATION_CLASS = resolveClass(FORM_PARAM_ANNOTATION_CLASS_NAME, getClassLoader());
/**
* The annotation class of @HeaderParam
*/
Class HEADER_PARAM_ANNOTATION_CLASS = resolveClass(HEADER_PARAM_ANNOTATION_CLASS_NAME, getClassLoader());
/**
* The annotation class of @MatrixParam
*/
Class MATRIX_PARAM_ANNOTATION_CLASS = resolveClass(MATRIX_PARAM_ANNOTATION_CLASS_NAME, getClassLoader());
/**
* The annotation class of @QueryParam
*/
Class QUERY_PARAM_ANNOTATION_CLASS = resolveClass(QUERY_PARAM_ANNOTATION_CLASS_NAME, getClassLoader());
/**
* The annotation class of @Body
*/
Class REST_EASY_BODY_ANNOTATION_CLASS = resolveClass(REST_EASY_BODY_ANNOTATION_CLASS_NAME, getClassLoader());
/**
* The annotation class of @PathParam
*/
Class PATH_PARAM_ANNOTATION_CLASS = resolveClass(PATH_PARAM_ANNOTATION_CLASS_NAME, getClassLoader());
}

View File

@ -0,0 +1,32 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.rest;
import org.apache.dubbo.common.extension.ExtensionScope;
import org.apache.dubbo.common.extension.SPI;
import org.apache.dubbo.metadata.rest.media.MediaType;
import java.lang.reflect.Parameter;
@SPI(scope = ExtensionScope.FRAMEWORK)
public interface NoAnnotatedParameterRequestTagProcessor {
MediaType consumerContentType();
String defaultAnnotationClassName(RestMethodMetadata restMethodMetadata);
void process(Parameter parameter, int parameterIndex, RestMethodMetadata restMethodMetadata);
}

View File

@ -0,0 +1,97 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.rest;
import org.apache.dubbo.metadata.MetadataConstants;
import org.apache.dubbo.metadata.rest.tag.BodyTag;
import java.util.ArrayList;
import java.util.List;
public enum ParamType {
HEADER(addSupportTypes(JAXRSClassConstants.HEADER_PARAM_ANNOTATION_CLASS,
SpringMvcClassConstants.REQUEST_HEADER_ANNOTATION_CLASS)),
PARAM(addSupportTypes(JAXRSClassConstants.QUERY_PARAM_ANNOTATION_CLASS,
SpringMvcClassConstants.REQUEST_PARAM_ANNOTATION_CLASS)),
BODY(addSupportTypes(
JAXRSClassConstants.REST_EASY_BODY_ANNOTATION_CLASS,
SpringMvcClassConstants.REQUEST_BODY_ANNOTATION_CLASS, BodyTag.class)),
// TODO how to match arg type ?
REQ_OR_RES(addSupportTypes(MetadataConstants.JAKARTA_SERVLET_REQ_CLASS,
MetadataConstants.JAKARTA_SERVLET_RES_CLASS,
MetadataConstants.JAVAX_SERVLET_REQ_CLASS,
MetadataConstants.JAKARTA_SERVLET_RES_CLASS)),
PATH(addSupportTypes(JAXRSClassConstants.PATH_PARAM_ANNOTATION_CLASS,
SpringMvcClassConstants.PATH_VARIABLE_ANNOTATION_CLASS)),
FORM(addSupportTypes(JAXRSClassConstants.FORM_PARAM_ANNOTATION_CLASS)),
EMPTY(addSupportTypes());
private List<Class> annotationClasses;
ParamType(List<Class> annotationClasses) {
this.annotationClasses = annotationClasses;
}
public boolean supportAnno(Class anno) {
if (anno == null) {
return false;
}
return this.annotationClasses.contains(anno);
}
public boolean isReqOrRes(Class clazz) {
for (Class annotationClass : annotationClasses) {
if (annotationClass.isAssignableFrom(clazz)) {
return true;
}
}
return false;
}
/**
* exclude null types
*
* @param classes
* @return
*/
private static List<Class> addSupportTypes(Class... classes) {
ArrayList<Class> types = new ArrayList<>();
for (Class clazz : classes) {
if (clazz == null) {
continue;
}
types.add(clazz);
}
return types;
}
}

View File

@ -0,0 +1,146 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.rest;
import java.util.Objects;
public class PathMatcher {
private static final String SEPARATOR = "/";
private String path;
private String version;
private String group;
private Integer port;
private String[] pathSplits;
private boolean hasPathVariable;
public PathMatcher(String path) {
this(path, null, null, 0);
}
public PathMatcher(String path, String version, String group, Integer port) {
this.path = path;
this.pathSplits = path.split(SEPARATOR);
for (String pathSplit : pathSplits) {
if (isPlaceHold(pathSplit)) {
hasPathVariable = true;
break;
}
}
this.version = version;
this.group = group;
this.port = port;
}
public void setPath(String path) {
this.path = path;
}
public void setVersion(String version) {
this.version = version;
}
public void setGroup(String group) {
this.group = group;
}
public void setPort(Integer port) {
this.port = port;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PathMatcher that = (PathMatcher) o;
return pathEqual(that.path) && Objects.equals(version, that.version)
&& Objects.equals(group, that.group) && Objects.equals(port, that.port);
}
@Override
public int hashCode() {
return Objects.hash(version, group, port);
}
private boolean pathEqual(String path) {
// no place hold
if (!hasPathVariable) {
return this.path.equals(path);
}
String[] split = path.split(SEPARATOR);
if (split.length != pathSplits.length) {
return false;
}
for (int i = 0; i < pathSplits.length; i++) {
boolean equals = split[i].equals(pathSplits[i]);
if (equals) {
continue;
} else {
if (placeHoldCompare(pathSplits[i])) {
continue;
} else {
return false;
}
}
}
return true;
}
private boolean placeHoldCompare(String pathSplit) {
boolean startAndEndEqual = isPlaceHold(pathSplit);
// start { end }
if (!startAndEndEqual) {
return false;
}
// exclude {}
boolean lengthCondition = pathSplit.length() >= 3;
if (!lengthCondition) {
return false;
}
return true;
}
private boolean isPlaceHold(String pathSplit) {
return pathSplit.startsWith("{") && pathSplit.endsWith("}");
}
@Override
public String toString() {
return "PathMather{" +
"path='" + path + '\'' +
", version='" + version + '\'' +
", group='" + group + '\'' +
", port='" + port + '\'' +
'}';
}
}

View File

@ -0,0 +1,157 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.rest;
import org.apache.dubbo.metadata.MetadataConstants;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* is used to parse url pathVariable
* <p>
* String[] splits= url.split("/")
* List<String> strings = Arrays.asList(split);
* strings.set(UrlSplitIndex, (String) args.get(argIndex));
*/
public class PathUtil {
private static final String SEPARATOR = MetadataConstants.PATH_SEPARATOR;
/**
* generate real path from rawPath according to argInfo and method args
*
* @param rawPath
* @param argInfos
* @param args
* @return
*/
public static String resolvePathVariable(String rawPath, List<ArgInfo> argInfos, List<Object> args) {
String[] split = rawPath.split(SEPARATOR);
List<String> strings = Arrays.asList(split);
List<ArgInfo> pathArgInfos = new ArrayList<>();
for (ArgInfo argInfo : argInfos) {
if (ParamType.PATH.supportAnno(argInfo.getParamAnnotationType())) {
pathArgInfos.add(argInfo);
}
}
for (ArgInfo pathArgInfo : pathArgInfos) {
strings.set(pathArgInfo.getUrlSplitIndex(), String.valueOf(args.get(pathArgInfo.getIndex())));
}
String pat = SEPARATOR;
for (String string : strings) {
if (string.length() == 0) {
continue;
}
pat = pat + string + SEPARATOR;
}
if (pat.endsWith(SEPARATOR)) {
pat = pat.substring(0, pat.lastIndexOf(SEPARATOR));
}
return pat;
}
/**
* parse pathVariable index from url by annotation info
*
* @param rawPath
* @param argInfos
*/
public static void setArgInfoSplitIndex(String rawPath, List<ArgInfo> argInfos) {
String[] split = rawPath.split(SEPARATOR);
List<PathPair> pathPairs = new ArrayList<>();
for (ArgInfo argInfo : argInfos) {
if (ParamType.PATH.supportAnno(argInfo.getParamAnnotationType())) {
pathPairs.add(new PathPair(argInfo));
}
}
for (int i = 0; i < split.length; i++) {
String s = split[i];
for (PathPair pathPair : pathPairs) {
boolean match = pathPair.match(s);
if (match) {
pathPair.setArgInfoSplitIndex(i);
}
}
}
}
public static class PathPair {
String value;
ArgInfo argInfo;
public PathPair(ArgInfo argInfo) {
this.argInfo = argInfo;
this.value = argInfo.getAnnotationNameAttribute();
}
public String getPatten() {
return "{" + value + "}";
}
public String getLeftPatten() {
return "{" + value;
}
public String getRightPatten() {
return "}";
}
public boolean match(String value) {
return getPatten().equals(value)// for : {id}
|| (value.startsWith(getLeftPatten()) && value.endsWith(getRightPatten()));// for : {id: \d+}
}
public String getValue() {
return value;
}
public void setArgInfo(ArgInfo argInfo) {
this.argInfo = argInfo;
}
public void setArgInfoSplitIndex(int urlSplitIndex) {
this.argInfo.setUrlSplitIndex(urlSplitIndex);
}
}
}

View File

@ -55,6 +55,7 @@ public class RequestMetadata implements Serializable {
private Set<String> produces = new LinkedHashSet<>();
/**
* Default Constructor
*/
@ -202,12 +203,12 @@ public class RequestMetadata implements Serializable {
}
RequestMetadata that = (RequestMetadata) o;
return Objects.equals(method, that.method)
&& Objects.equals(path, that.path)
&& Objects.equals(consumes, that.consumes)
&& Objects.equals(produces, that.produces) &&
// Metadata should not compare the values
Objects.equals(getParamNames(), that.getParamNames())
&& Objects.equals(getHeaderNames(), that.getHeaderNames());
&& Objects.equals(path, that.path)
&& Objects.equals(consumes, that.consumes)
&& Objects.equals(produces, that.produces) &&
// Metadata should not compare the values
Objects.equals(getParamNames(), that.getParamNames())
&& Objects.equals(getHeaderNames(), that.getHeaderNames());
}
@ -215,13 +216,13 @@ public class RequestMetadata implements Serializable {
public int hashCode() {
// The values of metadata should not use for the hashCode() method
return Objects.hash(method, path, consumes, produces, getParamNames(),
getHeaderNames());
getHeaderNames());
}
@Override
public String toString() {
return "RequestMetadata{" + "method='" + method + '\'' + ", path='" + path + '\''
+ ", params=" + params + ", headers=" + headers + ", consumes=" + consumes
+ ", produces=" + produces + '}';
+ ", params=" + params + ", headers=" + headers + ", consumes=" + consumes
+ ", produces=" + produces + '}';
}
}

View File

@ -87,6 +87,16 @@ public interface RestMetadataConstants {
* The annotation class name of @QueryParam
*/
String QUERY_PARAM_ANNOTATION_CLASS_NAME = "javax.ws.rs.QueryParam";
/**
* The annotation class name of @Body
*/
String REST_EASY_BODY_ANNOTATION_CLASS_NAME = "org.jboss.resteasy.annotations.Body";
/**
* The annotation class name of @PathParam
*/
String PATH_PARAM_ANNOTATION_CLASS_NAME = "javax.ws.rs.PathParam";
}
/**
@ -114,6 +124,16 @@ public interface RestMetadataConstants {
*/
String REQUEST_PARAM_ANNOTATION_CLASS_NAME = "org.springframework.web.bind.annotation.RequestParam";
/**
* The annotation class name of @RequestBody
*/
String REQUEST_BODY_ANNOTATION_CLASS_NAME = "org.springframework.web.bind.annotation.RequestBody";
/**
* The annotation class name of @PathVariable
*/
String PATH_VARIABLE_ANNOTATION_CLASS_NAME = "org.springframework.web.bind.annotation.PathVariable";
/**
* The class of @Controller
*

View File

@ -19,9 +19,10 @@ package org.apache.dubbo.metadata.rest;
import org.apache.dubbo.metadata.definition.model.MethodDefinition;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -56,6 +57,17 @@ public class RestMethodMetadata implements Serializable {
private Map<Integer, Boolean> indexToEncoded;
private ServiceRestMetadata serviceRestMetadata;
private List<ArgInfo> argInfos;
private Method reflectMethod;
/**
* make a distinction between mvc & resteasy
*/
private Class codeStyle;
public MethodDefinition getMethod() {
if (method == null) {
method = new MethodDefinition();
@ -112,7 +124,7 @@ public class RestMethodMetadata implements Serializable {
public Map<Integer, Collection<String>> getIndexToName() {
if (indexToName == null) {
indexToName = new HashMap<>();
indexToName = new LinkedHashMap<>();
}
return indexToName;
}
@ -157,6 +169,43 @@ public class RestMethodMetadata implements Serializable {
this.indexToEncoded = indexToEncoded;
}
public ServiceRestMetadata getServiceRestMetadata() {
return serviceRestMetadata;
}
public void setServiceRestMetadata(ServiceRestMetadata serviceRestMetadata) {
this.serviceRestMetadata = serviceRestMetadata;
}
public List<ArgInfo> getArgInfos() {
if (argInfos == null) {
argInfos = new ArrayList<>();
}
return argInfos;
}
public void addArgInfo(ArgInfo argInfo) {
getArgInfos().add(argInfo);
}
public Method getReflectMethod() {
return reflectMethod;
}
public void setReflectMethod(Method reflectMethod) {
this.reflectMethod = reflectMethod;
}
public Class getCodeStyle() {
return codeStyle;
}
public void setCodeStyle(Class codeStyle) {
this.codeStyle = codeStyle;
}
@Override
public boolean equals(Object o) {
if (this == o) {
@ -167,13 +216,13 @@ public class RestMethodMetadata implements Serializable {
}
RestMethodMetadata that = (RestMethodMetadata) o;
return Objects.equals(getMethod(), that.getMethod()) &&
Objects.equals(getRequest(), that.getRequest()) &&
Objects.equals(getUrlIndex(), that.getUrlIndex()) &&
Objects.equals(getBodyIndex(), that.getBodyIndex()) &&
Objects.equals(getHeaderMapIndex(), that.getHeaderMapIndex()) &&
Objects.equals(getBodyType(), that.getBodyType()) &&
Objects.equals(getFormParams(), that.getFormParams()) &&
Objects.equals(getIndexToEncoded(), that.getIndexToEncoded());
Objects.equals(getRequest(), that.getRequest()) &&
Objects.equals(getUrlIndex(), that.getUrlIndex()) &&
Objects.equals(getBodyIndex(), that.getBodyIndex()) &&
Objects.equals(getHeaderMapIndex(), that.getHeaderMapIndex()) &&
Objects.equals(getBodyType(), that.getBodyType()) &&
Objects.equals(getFormParams(), that.getFormParams()) &&
Objects.equals(getIndexToEncoded(), that.getIndexToEncoded());
}
@Override

View File

@ -16,8 +16,12 @@
*/
package org.apache.dubbo.metadata.rest;
import org.apache.dubbo.metadata.ParameterTypesComparator;
import java.io.Serializable;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@ -39,6 +43,32 @@ public class ServiceRestMetadata implements Serializable {
private Set<RestMethodMetadata> meta;
private Integer port;
private boolean consumer;
/**
* make a distinction between mvc & resteasy
*/
private Class codeStyle;
private Map<PathMatcher, RestMethodMetadata> pathToServiceMap = new HashMap<>();
private Map<String, Map<ParameterTypesComparator, RestMethodMetadata>> methodToServiceMap = new HashMap<>();
public ServiceRestMetadata(String serviceInterface, String version, String group, boolean consumer) {
this.serviceInterface = serviceInterface;
this.version = version;
this.group = group;
this.consumer = consumer;
}
public ServiceRestMetadata() {
}
public ServiceRestMetadata(String serviceInterface, String version, String group) {
this(serviceInterface, version, group, false);
}
public String getServiceInterface() {
return serviceInterface;
}
@ -74,6 +104,70 @@ public class ServiceRestMetadata implements Serializable {
this.meta = meta;
}
public void addRestMethodMetadata(RestMethodMetadata restMethodMetadata) {
restMethodMetadata.setServiceRestMetadata(this);
PathMatcher pathMather = new PathMatcher(restMethodMetadata.getRequest().getPath(),
this.getVersion(), this.getGroup(), this.getPort());
addPathToServiceMap(pathMather, restMethodMetadata);
addMethodToServiceMap(restMethodMetadata);
getMeta().add(restMethodMetadata);
}
public Map<PathMatcher, RestMethodMetadata> getPathToServiceMap() {
return pathToServiceMap;
}
public void addPathToServiceMap(PathMatcher pathMather, RestMethodMetadata restMethodMetadata) {
if (this.pathToServiceMap == null) {
this.pathToServiceMap = new HashMap<>();
}
this.pathToServiceMap.put(pathMather, restMethodMetadata);
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
Map<PathMatcher, RestMethodMetadata> pathToServiceMap = getPathToServiceMap();
for (PathMatcher pathMather : pathToServiceMap.keySet()) {
pathMather.setPort(port);
}
}
public boolean isConsumer() {
return consumer;
}
public void setConsumer(boolean consumer) {
this.consumer = consumer;
}
public Map<String, Map<ParameterTypesComparator, RestMethodMetadata>> getMethodToServiceMap() {
return methodToServiceMap;
}
public void addMethodToServiceMap(RestMethodMetadata restMethodMetadata) {
if (this.methodToServiceMap == null) {
this.methodToServiceMap = new HashMap<>();
}
this.methodToServiceMap.computeIfAbsent(restMethodMetadata.getReflectMethod().getName(), k -> new HashMap<>())
.put(ParameterTypesComparator.getInstance(restMethodMetadata.getReflectMethod().getParameterTypes()), restMethodMetadata);
}
public Class getCodeStyle() {
return codeStyle;
}
public void setCodeStyle(Class codeStyle) {
this.codeStyle = codeStyle;
}
@Override
public boolean equals(Object o) {
if (this == o) {
@ -84,14 +178,15 @@ public class ServiceRestMetadata implements Serializable {
}
ServiceRestMetadata that = (ServiceRestMetadata) o;
return Objects.equals(getServiceInterface(), that.getServiceInterface()) &&
Objects.equals(getVersion(), that.getVersion()) &&
Objects.equals(getGroup(), that.getGroup()) &&
Objects.equals(getMeta(), that.getMeta());
Objects.equals(getVersion(), that.getVersion()) &&
Objects.equals(getGroup(), that.getGroup()) &&
Objects.equals(getMeta(), that.getMeta()) &&
Objects.equals(getPort(), that.getPort());
}
@Override
public int hashCode() {
return Objects.hash(getServiceInterface(), getVersion(), getGroup(), getMeta());
return Objects.hash(getServiceInterface(), getVersion(), getGroup(), getMeta(), getPort());
}
@Override
@ -101,6 +196,7 @@ public class ServiceRestMetadata implements Serializable {
sb.append(", version='").append(version).append('\'');
sb.append(", group='").append(group).append('\'');
sb.append(", meta=").append(meta);
sb.append(", port=").append(port);
sb.append('}');
return sb.toString();
}

View File

@ -16,6 +16,7 @@
*/
package org.apache.dubbo.metadata.rest;
import org.apache.dubbo.common.extension.ExtensionScope;
import org.apache.dubbo.common.extension.SPI;
/**
@ -24,7 +25,7 @@ import org.apache.dubbo.common.extension.SPI;
*
* @since 2.7.6
*/
@SPI
@SPI(scope = ExtensionScope.APPLICATION)
public interface ServiceRestMetadataResolver {
/**
@ -35,6 +36,8 @@ public interface ServiceRestMetadataResolver {
*/
boolean supports(Class<?> serviceType);
boolean supports(Class<?> serviceType,boolean consumer);
/**
* Resolve the {@link ServiceRestMetadata REST metadata} from the specified
* Dubbo Service interface or type
@ -43,4 +46,6 @@ public interface ServiceRestMetadataResolver {
* @return
*/
ServiceRestMetadata resolve(Class<?> serviceType);
ServiceRestMetadata resolve(Class<?> serviceType, ServiceRestMetadata serviceRestMetadata);
}

View File

@ -0,0 +1,51 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.rest;
import static org.apache.dubbo.common.utils.ClassUtils.getClassLoader;
import static org.apache.dubbo.common.utils.ClassUtils.resolveClass;
public interface SpringMvcClassConstants extends RestMetadataConstants.SPRING_MVC {
/**
* The annotation class of @RequestMapping
*/
Class REQUEST_MAPPING_ANNOTATION_CLASS = resolveClass(REQUEST_MAPPING_ANNOTATION_CLASS_NAME, getClassLoader());
/**
* The annotation class of @RequestHeader
*/
Class REQUEST_HEADER_ANNOTATION_CLASS = resolveClass(REQUEST_HEADER_ANNOTATION_CLASS_NAME, getClassLoader());
/**
* The annotation class of @RequestParam
*/
Class REQUEST_PARAM_ANNOTATION_CLASS = resolveClass(REQUEST_PARAM_ANNOTATION_CLASS_NAME, getClassLoader());
/**
* The annotation class of @RequestBody
*/
Class REQUEST_BODY_ANNOTATION_CLASS = resolveClass(REQUEST_BODY_ANNOTATION_CLASS_NAME, getClassLoader());
/**
* The annotation class of @RequestBody
*/
Class PATH_VARIABLE_ANNOTATION_CLASS= resolveClass(PATH_VARIABLE_ANNOTATION_CLASS_NAME, getClassLoader());
}

View File

@ -0,0 +1,52 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.rest.jaxrs;
import org.apache.dubbo.metadata.rest.AbstractAnnotatedMethodParameterProcessor;
import org.apache.dubbo.metadata.rest.AnnotatedMethodParameterProcessor;
import org.apache.dubbo.metadata.rest.ArgInfo;
import org.apache.dubbo.metadata.rest.RestMethodMetadata;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import static org.apache.dubbo.metadata.rest.RestMetadataConstants.JAX_RS.REST_EASY_BODY_ANNOTATION_CLASS_NAME;
/**
* The {@link AnnotatedMethodParameterProcessor} implementation for JAX-RS's @FormParam
*
* @since 2.7.6
*/
public class BodyParameterProcessor extends AbstractAnnotatedMethodParameterProcessor {
@Override
public String getAnnotationName() {
return REST_EASY_BODY_ANNOTATION_CLASS_NAME;
}
@Override
public void process(Annotation annotation, Parameter parameter, int parameterIndex, Method method, Class<?> serviceType, Class<?> serviceInterfaceClass, RestMethodMetadata restMethodMetadata) {
ArgInfo argInfo = ArgInfo.
build(parameterIndex, parameter).
setParamAnnotationType(getAnnotationClass());
restMethodMetadata.addArgInfo(argInfo);
}
}

View File

@ -23,6 +23,7 @@ import org.apache.dubbo.metadata.rest.RestMethodMetadata;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.List;
import java.util.Map;
@ -38,12 +39,12 @@ import static org.apache.dubbo.metadata.rest.RestMetadataConstants.JAX_RS.DEFAUL
public class DefaultValueParameterProcessor extends AbstractAnnotatedMethodParameterProcessor {
@Override
public String getAnnotationType() {
public String getAnnotationName() {
return DEFAULT_VALUE_ANNOTATION_CLASS_NAME;
}
@Override
protected void process(String annotationValue, String defaultValue, Annotation annotation, Object parameter,
protected void process(String annotationValue, String defaultValue, Annotation annotation, Parameter parameter,
int parameterIndex, Method method, RestMethodMetadata restMethodMetadata) {
RequestMetadata requestMetadata = restMethodMetadata.getRequest();

View File

@ -28,7 +28,7 @@ import static org.apache.dubbo.metadata.rest.RestMetadataConstants.JAX_RS.FORM_P
public class FormParamParameterProcessor extends ParamAnnotationParameterProcessor {
@Override
public String getAnnotationType() {
public String getAnnotationName() {
return FORM_PARAM_ANNOTATION_CLASS_NAME;
}
}

View File

@ -23,6 +23,7 @@ import org.apache.dubbo.metadata.rest.RestMethodMetadata;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import static org.apache.dubbo.metadata.rest.AnnotatedMethodParameterProcessor.buildDefaultValue;
import static org.apache.dubbo.metadata.rest.RestMetadataConstants.JAX_RS.HEADER_PARAM_ANNOTATION_CLASS_NAME;
@ -35,12 +36,12 @@ import static org.apache.dubbo.metadata.rest.RestMetadataConstants.JAX_RS.HEADER
public class HeaderParamParameterProcessor extends AbstractAnnotatedMethodParameterProcessor {
@Override
public String getAnnotationType() {
public String getAnnotationName() {
return HEADER_PARAM_ANNOTATION_CLASS_NAME;
}
@Override
protected void process(String headerName, String defaultValue, Annotation annotation, Object parameter,
protected void process(String headerName, String defaultValue, Annotation annotation, Parameter parameter,
int parameterIndex, Method method, RestMethodMetadata restMethodMetadata) {
RequestMetadata requestMetadata = restMethodMetadata.getRequest();
// Add the placeholder as header value

View File

@ -85,12 +85,16 @@ public class JAXRSServiceRestMetadataResolver extends AbstractServiceRestMetadat
protected void processProduces(Method serviceMethod, Class<?> serviceType, Class<?> serviceInterfaceClass,
Set<String> produces) {
addAnnotationValues(serviceMethod, PRODUCES_ANNOTATION_CLASS_NAME, produces);
addAnnotationValues(serviceType, PRODUCES_ANNOTATION_CLASS_NAME, produces);
addAnnotationValues(serviceInterfaceClass, PRODUCES_ANNOTATION_CLASS_NAME, produces);
}
@Override
protected void processConsumes(Method serviceMethod, Class<?> serviceType, Class<?> serviceInterfaceClass,
Set<String> consumes) {
addAnnotationValues(serviceMethod, CONSUMES_ANNOTATION_CLASS_NAME, consumes);
addAnnotationValues(serviceType, CONSUMES_ANNOTATION_CLASS_NAME, consumes);
addAnnotationValues(serviceInterfaceClass, CONSUMES_ANNOTATION_CLASS_NAME, consumes);
}
private void addAnnotationValues(Method serviceMethod, String annotationAttributeName, Set<String> result) {
@ -100,4 +104,12 @@ public class JAXRSServiceRestMetadataResolver extends AbstractServiceRestMetadat
Stream.of(value).forEach(result::add);
}
}
private void addAnnotationValues(Class serviceType, String annotationAttributeName, Set<String> result) {
Annotation annotation = findAnnotation(serviceType, annotationAttributeName);
String[] value = getValue(annotation);
if (value != null) {
Stream.of(value).forEach(result::add);
}
}
}

View File

@ -28,7 +28,7 @@ import static org.apache.dubbo.metadata.rest.RestMetadataConstants.JAX_RS.MATRIX
public class MatrixParamParameterProcessor extends ParamAnnotationParameterProcessor {
@Override
public String getAnnotationType() {
public String getAnnotationName() {
return MATRIX_PARAM_ANNOTATION_CLASS_NAME;
}
}

View File

@ -23,6 +23,7 @@ import org.apache.dubbo.metadata.rest.RestMethodMetadata;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
/**
* The abstract {@link AnnotatedMethodParameterProcessor} implementation for JAX-RS's @*Param
@ -30,7 +31,7 @@ import java.lang.reflect.Method;
public abstract class ParamAnnotationParameterProcessor extends AbstractAnnotatedMethodParameterProcessor {
@Override
protected void process(String name, String defaultValue, Annotation annotation, Object parameter,
protected void process(String name, String defaultValue, Annotation annotation, Parameter parameter,
int parameterIndex, Method method, RestMethodMetadata restMethodMetadata) {
RequestMetadata requestMetadata = restMethodMetadata.getRequest();
requestMetadata.addParam(name, defaultValue);

View File

@ -0,0 +1,37 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.rest.jaxrs;
import org.apache.dubbo.metadata.rest.AbstractAnnotatedMethodParameterProcessor;
import org.apache.dubbo.metadata.rest.AnnotatedMethodParameterProcessor;
import static org.apache.dubbo.metadata.rest.RestMetadataConstants.JAX_RS.PATH_PARAM_ANNOTATION_CLASS_NAME;
/**
* The {@link AnnotatedMethodParameterProcessor} implementation for JAX-RS's @PathParam
*
* @since 2.7.6
*/
public class PathParamParameterProcessor extends AbstractAnnotatedMethodParameterProcessor {
@Override
public String getAnnotationName() {
return PATH_PARAM_ANNOTATION_CLASS_NAME;
}
}

View File

@ -28,7 +28,7 @@ import static org.apache.dubbo.metadata.rest.RestMetadataConstants.JAX_RS.QUERY_
public class QueryParamParameterProcessor extends ParamAnnotationParameterProcessor {
@Override
public String getAnnotationType() {
public String getAnnotationName() {
return QUERY_PARAM_ANNOTATION_CLASS_NAME;
}
}

View File

@ -0,0 +1,46 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.rest.media;
public enum MediaType {
ALL_VALUE("*/*"),
APPLICATION_JSON_VALUE("application/json"),
APPLICATION_FORM_URLENCODED_VALUE("application/x-www-form-urlencoded"),
TEXT_PLAIN("text/plain"),
TEXT_XML("text/xml"),
OCTET_STREAM("application/octet-stream"),
;
MediaType(String value) {
this.value = value;
}
public String value;
public static String getAllContentType() {
MediaType[] values = MediaType.values();
StringBuilder stringBuilder = new StringBuilder();
for (MediaType mediaType : values) {
stringBuilder.append(mediaType.value + " ");
}
return stringBuilder.toString();
}
}

View File

@ -0,0 +1,41 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.rest.springmvc;
import org.apache.dubbo.metadata.rest.AbstractNoAnnotatedParameterProcessor;
import org.apache.dubbo.metadata.rest.RestMethodMetadata;
import org.apache.dubbo.metadata.rest.media.MediaType;
import static org.apache.dubbo.metadata.rest.RestMetadataConstants.SPRING_MVC.REQUEST_BODY_ANNOTATION_CLASS_NAME;
import static org.apache.dubbo.metadata.rest.media.MediaType.APPLICATION_FORM_URLENCODED_VALUE;
public class FormBodyNoAnnotatedProcessor extends AbstractNoAnnotatedParameterProcessor {
@Override
public MediaType consumerContentType() {
return APPLICATION_FORM_URLENCODED_VALUE;
}
@Override
public String defaultAnnotationClassName(RestMethodMetadata restMethodMetadata) {
return REQUEST_BODY_ANNOTATION_CLASS_NAME;
}
@Override
protected boolean isFormContentType(RestMethodMetadata restMethodMetadata) {
return true;
}
}

View File

@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.rest.springmvc;
import org.apache.dubbo.metadata.rest.AbstractNoAnnotatedParameterProcessor;
import org.apache.dubbo.metadata.rest.RestMethodMetadata;
import org.apache.dubbo.metadata.rest.media.MediaType;
import static org.apache.dubbo.metadata.rest.RestMetadataConstants.SPRING_MVC.REQUEST_BODY_ANNOTATION_CLASS_NAME;
import static org.apache.dubbo.metadata.rest.media.MediaType.APPLICATION_JSON_VALUE;
public class JsonBodyNoAnnotatedProcessor extends AbstractNoAnnotatedParameterProcessor {
@Override
public MediaType consumerContentType() {
return APPLICATION_JSON_VALUE;
}
@Override
public String defaultAnnotationClassName(RestMethodMetadata restMethodMetadata) {
return REQUEST_BODY_ANNOTATION_CLASS_NAME;
}
}

View File

@ -0,0 +1,43 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.rest.springmvc;
import org.apache.dubbo.metadata.rest.AbstractNoAnnotatedParameterProcessor;
import org.apache.dubbo.metadata.rest.RestMethodMetadata;
import org.apache.dubbo.metadata.rest.jaxrs.JAXRSServiceRestMetadataResolver;
import org.apache.dubbo.metadata.rest.media.MediaType;
import org.apache.dubbo.metadata.rest.tag.BodyTag;
import static org.apache.dubbo.metadata.rest.RestMetadataConstants.SPRING_MVC.REQUEST_PARAM_ANNOTATION_CLASS_NAME;
import static org.apache.dubbo.metadata.rest.media.MediaType.ALL_VALUE;
public class ParamNoAnnotatedProcessor extends AbstractNoAnnotatedParameterProcessor {
@Override
public MediaType consumerContentType() {
return ALL_VALUE;
}
@Override
public String defaultAnnotationClassName(RestMethodMetadata restMethodMetadata) {
if (JAXRSServiceRestMetadataResolver.class.equals(restMethodMetadata.getCodeStyle())) {
return BodyTag.class.getName();
}
return REQUEST_PARAM_ANNOTATION_CLASS_NAME;
}
}

View File

@ -0,0 +1,43 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.rest.springmvc;
import org.apache.dubbo.metadata.rest.AbstractAnnotatedMethodParameterProcessor;
import org.apache.dubbo.metadata.rest.AnnotatedMethodParameterProcessor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Parameter;
import static org.apache.dubbo.metadata.rest.RestMetadataConstants.SPRING_MVC.PATH_VARIABLE_ANNOTATION_CLASS_NAME;
/**
* The {@link AnnotatedMethodParameterProcessor} implementation for Spring Web MVC's @PathVariable
*/
public class PathVariableParameterProcessor extends AbstractAnnotatedMethodParameterProcessor {
@Override
public String getAnnotationName() {
return PATH_VARIABLE_ANNOTATION_CLASS_NAME;
}
@Override
protected String getDefaultValue(Annotation annotation, Parameter parameter, int parameterIndex) {
return null;
}
}

View File

@ -0,0 +1,46 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.rest.springmvc;
import org.apache.dubbo.metadata.rest.AbstractAnnotatedMethodParameterProcessor;
import org.apache.dubbo.metadata.rest.AnnotatedMethodParameterProcessor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Parameter;
import static org.apache.dubbo.metadata.rest.RestMetadataConstants.SPRING_MVC.REQUEST_BODY_ANNOTATION_CLASS_NAME;
/**
* The {@link AnnotatedMethodParameterProcessor} implementation for Spring Web MVC's @RequestBody
*/
public class RequestBodyParameterProcessor extends AbstractAnnotatedMethodParameterProcessor {
@Override
public String getAnnotationName() {
return REQUEST_BODY_ANNOTATION_CLASS_NAME;
}
@Override
protected String getAnnotationValue(Annotation annotation, Parameter parameter, int parameterIndex) {
return null;
}
@Override
protected String getDefaultValue(Annotation annotation, Parameter parameter, int parameterIndex) {
return null;
}
}

View File

@ -21,6 +21,7 @@ import org.apache.dubbo.metadata.rest.RestMethodMetadata;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import static org.apache.dubbo.metadata.rest.RestMetadataConstants.SPRING_MVC.REQUEST_HEADER_ANNOTATION_CLASS_NAME;
@ -30,12 +31,12 @@ import static org.apache.dubbo.metadata.rest.RestMetadataConstants.SPRING_MVC.RE
public class RequestHeaderParameterProcessor extends AbstractRequestAnnotationParameterProcessor {
@Override
public String getAnnotationType() {
public String getAnnotationName() {
return REQUEST_HEADER_ANNOTATION_CLASS_NAME;
}
@Override
protected void process(String name, String defaultValue, Annotation annotation, Object parameter,
protected void process(String name, String defaultValue, Annotation annotation, Parameter parameter,
int parameterIndex, Method method, RestMethodMetadata restMethodMetadata) {
restMethodMetadata.getRequest().addHeader(name, defaultValue);
}

View File

@ -21,6 +21,7 @@ import org.apache.dubbo.metadata.rest.RestMethodMetadata;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import static org.apache.dubbo.metadata.rest.RestMetadataConstants.SPRING_MVC.REQUEST_PARAM_ANNOTATION_CLASS_NAME;
@ -30,14 +31,13 @@ import static org.apache.dubbo.metadata.rest.RestMetadataConstants.SPRING_MVC.RE
public class RequestParamParameterProcessor extends AbstractRequestAnnotationParameterProcessor {
@Override
public String getAnnotationType() {
public String getAnnotationName() {
return REQUEST_PARAM_ANNOTATION_CLASS_NAME;
}
@Override
protected void process(String name, String defaultValue, Annotation annotation, Object parameter, int parameterIndex,
protected void process(String name, String defaultValue, Annotation annotation, Parameter parameter, int parameterIndex,
Method method, RestMethodMetadata restMethodMetadata) {
restMethodMetadata.getRequest().addParam(name, defaultValue);
}
}

View File

@ -89,11 +89,15 @@ public class SpringMvcServiceRestMetadataResolver extends AbstractServiceRestMet
@Override
protected void processProduces(Method serviceMethod, Class<?> serviceType, Class<?> serviceInterfaceClass, Set<String> produces) {
addMediaTypes(serviceMethod, "produces", produces);
addMediaTypes(serviceType, "produces", produces);
addMediaTypes(serviceInterfaceClass, "produces", produces);
}
@Override
protected void processConsumes(Method serviceMethod, Class<?> serviceType, Class<?> serviceInterfaceClass, Set<String> consumes) {
addMediaTypes(serviceMethod, "consumes", consumes);
addMediaTypes(serviceType, "consumes", consumes);
addMediaTypes(serviceInterfaceClass, "consumes", consumes);
}
private String resolveRequestPath(AnnotatedElement annotatedElement) {
@ -124,6 +128,17 @@ public class SpringMvcServiceRestMetadataResolver extends AbstractServiceRestMet
}
}
private void addMediaTypes(Class serviceType, String annotationAttributeName, Set<String> mediaTypesSet) {
Annotation mappingAnnotation = getRequestMapping(serviceType);
String[] mediaTypes = getAttribute(mappingAnnotation, annotationAttributeName);
if (isNotEmpty(mediaTypes)) {
of(mediaTypes).forEach(mediaTypesSet::add);
}
}
private Annotation getRequestMapping(AnnotatedElement annotatedElement) {
// try "@RequestMapping" first
Annotation requestMapping = findAnnotation(annotatedElement, REQUEST_MAPPING_ANNOTATION_CLASS);

View File

@ -0,0 +1,20 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.rest.tag;
public class BodyTag {
}

View File

@ -4,7 +4,11 @@ jax-rs.form-param = org.apache.dubbo.metadata.rest.jaxrs.FormParamParameterProce
jax-rs.matrix-param = org.apache.dubbo.metadata.rest.jaxrs.MatrixParamParameterProcessor
jax-rs.header-param = org.apache.dubbo.metadata.rest.jaxrs.HeaderParamParameterProcessor
jax-rs.default-value-param = org.apache.dubbo.metadata.rest.jaxrs.DefaultValueParameterProcessor
jax-rs.body = org.apache.dubbo.metadata.rest.jaxrs.BodyParameterProcessor
jax-rs.path-param = org.apache.dubbo.metadata.rest.jaxrs.PathParamParameterProcessor
# Spring Web MVC's implementations
spring-webmvc.request-param = org.apache.dubbo.metadata.rest.springmvc.RequestParamParameterProcessor
spring-webmvc.request-header = org.apache.dubbo.metadata.rest.springmvc.RequestHeaderParameterProcessor
spring-webmvc.request-header = org.apache.dubbo.metadata.rest.springmvc.RequestHeaderParameterProcessor
spring-webmvc.request-body = org.apache.dubbo.metadata.rest.springmvc.RequestBodyParameterProcessor
spring-webmvc.request-path = org.apache.dubbo.metadata.rest.springmvc.PathVariableParameterProcessor

View File

@ -0,0 +1,3 @@
body-form=org.apache.dubbo.metadata.rest.springmvc.FormBodyNoAnnotatedProcessor
body-json=org.apache.dubbo.metadata.rest.springmvc.JsonBodyNoAnnotatedProcessor
param=org.apache.dubbo.metadata.rest.springmvc.ParamNoAnnotatedProcessor

View File

@ -0,0 +1,32 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata;
import org.apache.dubbo.metadata.rest.PathMatcher;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class PathMatcherTest {
@Test
public void testPathMatcher() {
PathMatcher pathMather = new PathMatcher("/a/b/c/{path1}/d/{path2}/e");
PathMatcher pathMather1 = new PathMatcher("/a/b/c/1/d/2/e");
Assertions.assertEquals(true, pathMather.equals(pathMather1));
}
}

View File

@ -63,6 +63,21 @@ public class DefaultRestService implements RestService {
return null;
}
@Override
public void noAnnotationJsonBody(User user) {
}
@Override
public void noAnnotationFormBody(User user) {
}
@Override
public void noAnnotationParam(String text) {
}
public User user(User user) {
return user;
}

View File

@ -39,4 +39,8 @@ public interface RestService {
User requestBodyMap(Map<String, Object> data, String param);
Map<String, Object> requestBodyUser(User user);
void noAnnotationJsonBody(User user);
void noAnnotationFormBody(User user);
void noAnnotationParam(String text);
}

View File

@ -19,13 +19,13 @@ package org.apache.dubbo.metadata.rest;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.HashMap;
import java.util.Map;
@ -92,4 +92,22 @@ public class SpringRestService implements RestService {
map.put("age", user.getAge());
return map;
}
@PostMapping(value = "/request/body/user/json", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
@Override
public void noAnnotationJsonBody(User user) {
}
@PostMapping(value = "/request/body/user/form", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
@Override
public void noAnnotationFormBody(User user) {
}
@PostMapping(value = "/request/body/user/param")
@Override
public void noAnnotationParam(String text) {
}
}

View File

@ -27,6 +27,7 @@ import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import java.util.HashMap;
import java.util.Map;
@ -104,4 +105,27 @@ public class StandardRestService implements RestService {
map.put("age", user.getAge());
return map;
}
@Path("noAnnotationJsonBody/json")
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Override
public void noAnnotationJsonBody(User user) {
}
@Path("noAnnotationFormBody/form")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@POST
@Override
public void noAnnotationFormBody(User user) {
}
@Path("noAnnotationParam/text")
@POST
@Override
public void noAnnotationParam(String text) {
}
}

View File

@ -0,0 +1,45 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.rest.api;
import org.apache.dubbo.metadata.rest.User;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("u")
@Consumes({MediaType.APPLICATION_JSON, MediaType.TEXT_XML})
@Produces({MediaType.APPLICATION_JSON})
public interface AnotherUserRestService {
@GET
@Path("{id : \\d+}")
User getUser(@PathParam("id") Long id);
@POST
@Path("register")
String registerUser(User user);
@GET
@Path("context")
String getContext();
}

View File

@ -0,0 +1,65 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.rest.api;
import org.apache.dubbo.metadata.rest.User;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
public interface JaxrsRestService {
@Path("/param")
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.TEXT_PLAIN)
@GET
String param(@QueryParam("param") String param);
@Path("/header")
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.TEXT_PLAIN)
@GET
String header(@HeaderParam("header") String header);
@Path("/body")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@POST
User body(User user);
@Path("/multiValue")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_FORM_URLENCODED)
@POST
MultivaluedMap multiValue(MultivaluedMap map);
@Path("/pathVariable/{a}")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_FORM_URLENCODED)
@POST
String pathVariable(@PathParam("a") String a);
}

View File

@ -0,0 +1,50 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.rest.api;
import org.apache.dubbo.metadata.rest.User;
import javax.ws.rs.core.MultivaluedMap;
public class JaxrsRestServiceImpl implements JaxrsRestService {
@Override
public String param(String param) {
return param;
}
@Override
public String header(String header) {
return header;
}
@Override
public User body(User user) {
return user;
}
@Override
public MultivaluedMap multiValue(MultivaluedMap map) {
return map;
}
@Override
public String pathVariable(String a) {
return a;
}
}

View File

@ -0,0 +1,52 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.rest.api;
import org.apache.dubbo.metadata.rest.User;
import org.springframework.http.MediaType;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.PathVariable;
@RestController
public interface SpringRestService {
@RequestMapping(value = "/param", method = RequestMethod.GET, consumes = MediaType.TEXT_PLAIN_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
String param(@RequestParam("param") String param);
@RequestMapping(value = "/header", method = RequestMethod.GET, consumes = MediaType.TEXT_PLAIN_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
String header(@RequestHeader("header") String header);
@RequestMapping(value = "/header", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
User body(@RequestBody User user);
@RequestMapping(value = "/multiValue", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
MultiValueMap multiValue(@RequestBody MultiValueMap map);
@RequestMapping(value = "/pathVariable/{a}", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
String pathVariable(@PathVariable String a);
}

View File

@ -0,0 +1,53 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.rest.api;
import org.apache.dubbo.metadata.rest.User;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SpringRestServiceImpl implements SpringRestService {
@Override
public String param(String param) {
return param;
}
@Override
public String header(String header) {
return header;
}
@Override
public User body(User user) {
return user;
}
@Override
public MultiValueMap multiValue(MultiValueMap map) {
return map;
}
@Override
public String pathVariable(String a) {
return a;
}
}

View File

@ -17,16 +17,27 @@
package org.apache.dubbo.metadata.rest.jaxrs;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.common.utils.JsonUtils;
import org.apache.dubbo.metadata.rest.ClassPathServiceRestMetadataReader;
import org.apache.dubbo.metadata.rest.DefaultRestService;
import org.apache.dubbo.metadata.rest.PathUtil;
import org.apache.dubbo.metadata.rest.RestMethodMetadata;
import org.apache.dubbo.metadata.rest.RestService;
import org.apache.dubbo.metadata.rest.ServiceRestMetadata;
import org.apache.dubbo.metadata.rest.SpringRestService;
import org.apache.dubbo.metadata.rest.StandardRestService;
import org.apache.dubbo.metadata.rest.api.AnotherUserRestService;
import org.apache.dubbo.metadata.rest.api.JaxrsRestService;
import org.apache.dubbo.metadata.rest.api.JaxrsRestServiceImpl;
import org.apache.dubbo.metadata.rest.api.SpringRestService;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -42,6 +53,7 @@ class JAXRSServiceRestMetadataResolverTest {
private JAXRSServiceRestMetadataResolver instance = new JAXRSServiceRestMetadataResolver(ApplicationModel.defaultModel());
@Test
void testSupports() {
// JAX-RS RestService class
@ -57,6 +69,7 @@ class JAXRSServiceRestMetadataResolverTest {
}
@Test
@Disabled
void testResolve() {
// Generated by "dubbo-metadata-processor"
ClassPathServiceRestMetadataReader reader = new ClassPathServiceRestMetadataReader("META-INF/dubbo/jax-rs-service-rest-metadata.json");
@ -70,4 +83,75 @@ class JAXRSServiceRestMetadataResolverTest {
assertEquals(expectedServiceRestMetadata, serviceRestMetadata);
}
@Test
void testResolves() {
testResolve(JaxrsRestService.class);
testResolve(JaxrsRestServiceImpl.class);
}
void testResolve(Class service) {
// Generated by "dubbo-metadata-processor"
List<String> jsons = Arrays.asList(
"{\"argInfos\":[{\"annotationNameAttribute\":\"a\",\"defaultValue\":\"{0}\",\"formContentType\":false,\"index\":0,\"paramAnnotationType\":\"javax.ws.rs.PathParam\",\"paramName\":\"a\",\"paramType\":\"java.lang.String\",\"urlSplitIndex\":2}],\"codeStyle\":\"org.apache.dubbo.metadata.rest.jaxrs.JAXRSServiceRestMetadataResolver\",\"indexToName\":{0:[\"a\"]},\"method\":{\"annotations\":[],\"parameters\":[]},\"request\":{\"consumes\":[\"application/x-www-form-urlencoded\"],\"headerNames\":[],\"headers\":{},\"method\":\"POST\",\"paramNames\":[],\"params\":{},\"path\":\"/pathVariable/{a}\",\"produces\":[\"application/x-www-form-urlencoded\"]}}",
"{\"argInfos\":[{\"annotationNameAttribute\":\"map\",\"formContentType\":true,\"index\":0,\"paramAnnotationType\":\"org.springframework.web.bind.annotation.RequestBody\",\"paramName\":\"map\",\"paramType\":\"javax.ws.rs.core.MultivaluedMap\",\"urlSplitIndex\":0}],\"codeStyle\":\"org.apache.dubbo.metadata.rest.jaxrs.JAXRSServiceRestMetadataResolver\",\"indexToName\":{0:[\"map\"]},\"method\":{\"annotations\":[],\"parameters\":[]},\"request\":{\"consumes\":[\"application/x-www-form-urlencoded\"],\"headerNames\":[],\"headers\":{},\"method\":\"POST\",\"paramNames\":[],\"params\":{},\"path\":\"/multiValue\",\"produces\":[\"application/x-www-form-urlencoded\"]}}",
"{\"argInfos\":[{\"annotationNameAttribute\":\"user\",\"formContentType\":false,\"index\":0,\"paramAnnotationType\":\"org.springframework.web.bind.annotation.RequestBody\",\"paramName\":\"user\",\"paramType\":\"org.apache.dubbo.metadata.rest.User\",\"urlSplitIndex\":0}],\"codeStyle\":\"org.apache.dubbo.metadata.rest.jaxrs.JAXRSServiceRestMetadataResolver\",\"indexToName\":{0:[\"user\"]},\"method\":{\"annotations\":[],\"parameters\":[]},\"request\":{\"consumes\":[\"application/json\"],\"headerNames\":[],\"headers\":{},\"method\":\"POST\",\"paramNames\":[],\"params\":{},\"path\":\"/body\",\"produces\":[\"application/json\"]}}",
"{\"argInfos\":[{\"annotationNameAttribute\":\"param\",\"defaultValue\":\"{0}\",\"formContentType\":false,\"index\":0,\"paramAnnotationType\":\"javax.ws.rs.QueryParam\",\"paramName\":\"param\",\"paramType\":\"java.lang.String\",\"urlSplitIndex\":0}],\"codeStyle\":\"org.apache.dubbo.metadata.rest.jaxrs.JAXRSServiceRestMetadataResolver\",\"indexToName\":{0:[\"param\"]},\"method\":{\"annotations\":[],\"parameters\":[]},\"request\":{\"consumes\":[\"text/plain\"],\"headerNames\":[],\"headers\":{},\"method\":\"GET\",\"paramNames\":[\"param\"],\"params\":{\"param\":[\"{0}\"]},\"path\":\"/param\",\"produces\":[\"text/plain\"]}}",
"{\"argInfos\":[{\"annotationNameAttribute\":\"header\",\"defaultValue\":\"{0}\",\"formContentType\":false,\"index\":0,\"paramAnnotationType\":\"javax.ws.rs.HeaderParam\",\"paramName\":\"header\",\"paramType\":\"java.lang.String\",\"urlSplitIndex\":0}],\"codeStyle\":\"org.apache.dubbo.metadata.rest.jaxrs.JAXRSServiceRestMetadataResolver\",\"indexToName\":{0:[\"header\"]},\"method\":{\"annotations\":[],\"parameters\":[]},\"request\":{\"consumes\":[\"text/plain\"],\"headerNames\":[\"header\"],\"headers\":{\"header\":[\"{0}\"]},\"method\":\"GET\",\"paramNames\":[],\"params\":{},\"path\":\"/header\",\"produces\":[\"text/plain\"]}}"
);
ServiceRestMetadata jaxrsRestMetadata = new ServiceRestMetadata();
jaxrsRestMetadata.setServiceInterface(service.getName());
ServiceRestMetadata jaxrsMetadata = instance.resolve(service, jaxrsRestMetadata);
List<String> jsonsTmp = new ArrayList<>();
for (RestMethodMetadata restMethodMetadata : jaxrsMetadata.getMeta()) {
restMethodMetadata.setServiceRestMetadata(null);
restMethodMetadata.setReflectMethod(null);
restMethodMetadata.setMethod(null);
jsonsTmp.add(JsonUtils.getJson().toJson(restMethodMetadata));
}
Comparator<String> comparator = new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
};
jsons.sort(comparator);
jsonsTmp.sort(comparator);
for (int i = 0; i < jsons.size(); i++) {
assertEquals(jsons.get(i), jsonsTmp.get(i));
}
}
@Test
public void testJaxrsPathPattern() {
Class service = AnotherUserRestService.class;
ServiceRestMetadata jaxrsRestMetadata = new ServiceRestMetadata();
jaxrsRestMetadata.setServiceInterface(service.getName());
ServiceRestMetadata jaxrsMetadata = instance.resolve(service, jaxrsRestMetadata);
RestMethodMetadata[] objects = jaxrsMetadata.getMeta().toArray(new RestMethodMetadata[0]);
RestMethodMetadata object = null;
for (RestMethodMetadata obj : objects) {
if ("getUser".equals(obj.getReflectMethod().getName())) {
object = obj;
}
}
Assertions.assertEquals("/u/1", PathUtil.resolvePathVariable("u/{id : \\d+}", object.getArgInfos(), Arrays.asList(1)));
}
}

View File

@ -17,16 +17,23 @@
package org.apache.dubbo.metadata.rest.springmvc;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.common.utils.JsonUtils;
import org.apache.dubbo.metadata.rest.ClassPathServiceRestMetadataReader;
import org.apache.dubbo.metadata.rest.DefaultRestService;
import org.apache.dubbo.metadata.rest.RestMethodMetadata;
import org.apache.dubbo.metadata.rest.RestService;
import org.apache.dubbo.metadata.rest.ServiceRestMetadata;
import org.apache.dubbo.metadata.rest.SpringRestService;
import org.apache.dubbo.metadata.rest.StandardRestService;
import org.apache.dubbo.metadata.rest.api.SpringRestService;
import org.apache.dubbo.metadata.rest.api.SpringRestServiceImpl;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -45,18 +52,19 @@ class SpringMvcServiceRestMetadataResolverTest {
@Test
void testSupports() {
// Spring MVC RestService class
assertTrue(instance.supports(SpringRestService.class));
assertTrue(instance.supports(SpringRestService.class, true));
// JAX-RS RestService class
assertFalse(instance.supports(StandardRestService.class));
assertFalse(instance.supports(StandardRestService.class, true));
// Default RestService class
assertFalse(instance.supports(DefaultRestService.class));
assertFalse(instance.supports(DefaultRestService.class, true));
// No annotated RestService class
assertFalse(instance.supports(RestService.class));
assertFalse(instance.supports(RestService.class, true));
// null
assertFalse(instance.supports(null));
assertFalse(instance.supports(null, true));
}
@Test
@Disabled
void testResolve() {
// Generated by "dubbo-metadata-processor"
ClassPathServiceRestMetadataReader reader = new ClassPathServiceRestMetadataReader("META-INF/dubbo/spring-mvc-service-rest-metadata.json");
@ -70,4 +78,51 @@ class SpringMvcServiceRestMetadataResolverTest {
assertEquals(expectedServiceRestMetadata, serviceRestMetadata);
}
@Test
void testResolves() {
testResolve(SpringRestService.class);
testResolve(SpringRestServiceImpl.class);
}
void testResolve(Class service) {
List<String> jsons = Arrays.asList(
"{\"argInfos\":[{\"annotationNameAttribute\":\"\",\"formContentType\":false,\"index\":0,\"paramAnnotationType\":\"org.springframework.web.bind.annotation.PathVariable\",\"paramName\":\"a\",\"paramType\":\"java.lang.String\",\"urlSplitIndex\":2}],\"codeStyle\":\"org.apache.dubbo.metadata.rest.springmvc.SpringMvcServiceRestMetadataResolver\",\"indexToName\":{0:[\"a\"]},\"method\":{\"annotations\":[],\"parameters\":[]},\"request\":{\"consumes\":[\"application/x-www-form-urlencoded\"],\"headerNames\":[],\"headers\":{},\"method\":\"POST\",\"paramNames\":[],\"params\":{},\"path\":\"/pathVariable/{a}\",\"produces\":[\"application/x-www-form-urlencoded\"]}}",
"{\"argInfos\":[{\"annotationNameAttribute\":\"user\",\"formContentType\":false,\"index\":0,\"paramAnnotationType\":\"org.springframework.web.bind.annotation.RequestBody\",\"paramName\":\"user\",\"paramType\":\"org.apache.dubbo.metadata.rest.User\",\"urlSplitIndex\":0}],\"codeStyle\":\"org.apache.dubbo.metadata.rest.springmvc.SpringMvcServiceRestMetadataResolver\",\"indexToName\":{0:[\"user\"]},\"method\":{\"annotations\":[],\"parameters\":[]},\"request\":{\"consumes\":[\"application/json\"],\"headerNames\":[],\"headers\":{},\"method\":\"POST\",\"paramNames\":[],\"params\":{},\"path\":\"/header\",\"produces\":[\"application/json\"]}}",
"{\"argInfos\":[{\"annotationNameAttribute\":\"param\",\"defaultValue\":\"{0}\",\"formContentType\":false,\"index\":0,\"paramAnnotationType\":\"org.springframework.web.bind.annotation.RequestParam\",\"paramName\":\"param\",\"paramType\":\"java.lang.String\",\"urlSplitIndex\":0}],\"codeStyle\":\"org.apache.dubbo.metadata.rest.springmvc.SpringMvcServiceRestMetadataResolver\",\"indexToName\":{0:[\"param\"]},\"method\":{\"annotations\":[],\"parameters\":[]},\"request\":{\"consumes\":[\"text/plain\"],\"headerNames\":[],\"headers\":{},\"method\":\"GET\",\"paramNames\":[\"param\"],\"params\":{\"param\":[\"{0}\"]},\"path\":\"/param\",\"produces\":[\"text/plain\"]}}",
"{\"argInfos\":[{\"annotationNameAttribute\":\"map\",\"formContentType\":false,\"index\":0,\"paramAnnotationType\":\"org.springframework.web.bind.annotation.RequestBody\",\"paramName\":\"map\",\"paramType\":\"org.springframework.util.MultiValueMap\",\"urlSplitIndex\":0}],\"codeStyle\":\"org.apache.dubbo.metadata.rest.springmvc.SpringMvcServiceRestMetadataResolver\",\"indexToName\":{0:[\"map\"]},\"method\":{\"annotations\":[],\"parameters\":[]},\"request\":{\"consumes\":[\"application/x-www-form-urlencoded\"],\"headerNames\":[],\"headers\":{},\"method\":\"POST\",\"paramNames\":[],\"params\":{},\"path\":\"/multiValue\",\"produces\":[\"application/x-www-form-urlencoded\"]}}",
"{\"argInfos\":[{\"annotationNameAttribute\":\"header\",\"defaultValue\":\"{0}\",\"formContentType\":false,\"index\":0,\"paramAnnotationType\":\"org.springframework.web.bind.annotation.RequestHeader\",\"paramName\":\"header\",\"paramType\":\"java.lang.String\",\"urlSplitIndex\":0}],\"codeStyle\":\"org.apache.dubbo.metadata.rest.springmvc.SpringMvcServiceRestMetadataResolver\",\"indexToName\":{0:[\"header\"]},\"method\":{\"annotations\":[],\"parameters\":[]},\"request\":{\"consumes\":[\"text/plain\"],\"headerNames\":[\"header\"],\"headers\":{\"header\":[\"{0}\"]},\"method\":\"GET\",\"paramNames\":[],\"params\":{},\"path\":\"/header\",\"produces\":[\"text/plain\"]}}"
);
ServiceRestMetadata springRestMetadata = new ServiceRestMetadata();
springRestMetadata.setServiceInterface(service.getName());
ServiceRestMetadata springMetadata = instance.resolve(service, springRestMetadata);
List<String> jsonsTmp = new ArrayList<>();
for (RestMethodMetadata restMethodMetadata : springMetadata.getMeta()) {
restMethodMetadata.setServiceRestMetadata(null);
restMethodMetadata.setReflectMethod(null);
restMethodMetadata.setMethod(null);
jsonsTmp.add(JsonUtils.getJson().toJson(restMethodMetadata));
}
Comparator<String> comparator = new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
};
jsons.sort(comparator);
jsonsTmp.sort(comparator);
for (int i = 0; i < jsons.size(); i++) {
assertEquals(jsons.get(i), jsonsTmp.get(i));
}
}
}

View File

@ -17,12 +17,6 @@
package org.apache.dubbo.metrics.filter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.config.ApplicationConfig;
@ -31,13 +25,25 @@ import org.apache.dubbo.metrics.collector.DefaultMetricsCollector;
import org.apache.dubbo.metrics.model.MetricsKey;
import org.apache.dubbo.metrics.model.sample.GaugeMetricSample;
import org.apache.dubbo.metrics.model.sample.MetricSample;
import org.apache.dubbo.rpc.*;
import org.apache.dubbo.rpc.AppResponse;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.RpcInvocation;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import static org.apache.dubbo.common.constants.CommonConstants.$INVOKE;
import static org.apache.dubbo.common.constants.CommonConstants.GENERIC_PARAMETER_DESC;
import static org.apache.dubbo.common.constants.MetricsConstants.TAG_GROUP_KEY;

View File

@ -17,7 +17,6 @@
package org.apache.dubbo.metrics.prometheus;
import org.apache.dubbo.common.utils.NetUtils;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.MetricsConfig;
import org.apache.dubbo.config.nested.PrometheusConfig;

View File

@ -18,11 +18,9 @@
package org.apache.dubbo.qos.command;
import org.apache.dubbo.qos.command.exception.NoSuchCommandException;
import org.apache.dubbo.qos.command.exception.PermissionDenyException;
import org.apache.dubbo.qos.common.QosConfiguration;
import org.apache.dubbo.qos.permission.PermissionLevel;
import org.apache.dubbo.rpc.model.FrameworkModel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

View File

@ -31,9 +31,7 @@ import org.apache.dubbo.config.context.ConfigManager;
import org.apache.dubbo.metadata.report.MetadataReport;
import org.apache.dubbo.metadata.report.MetadataReportInstance;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.apache.dubbo.rpc.model.FrameworkModel;
import org.checkerframework.checker.units.qual.A;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

View File

@ -110,9 +110,9 @@ public interface Constants {
int DEFAULT_IO_THREADS = Math.min(Runtime.getRuntime().availableProcessors() + 1, 32);
String EVENT_LOOP_BOSS_POOL_NAME = "NettyServerBoss";
String EVENT_LOOP_BOSS_POOL_NAME = "NettyServerBoss";
String EVENT_LOOP_WORKER_POOL_NAME = "NettyServerWorker";
String EVENT_LOOP_WORKER_POOL_NAME = "NettyServerWorker";
String NETTY_EPOLL_ENABLE_KEY = "netty.epoll.enable";
@ -148,4 +148,14 @@ public interface Constants {
String CONNECTIONS_KEY = "connections";
int DEFAULT_BACKLOG = 1024;
String CONNECTION = "Connection";
String KEEP_ALIVE = "keep-alive";
String KEEP_ALIVE_HEADER = "Keep-Alive";
String OK_HTTP = "ok-http";
String URL_CONNECTION = "url-connection";
String APACHE_HTTP_CLIENT = "apache-http-client";
}

View File

@ -59,5 +59,23 @@
<version>4.5.14</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-api</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
</dependencies>
</project>
</project>

View File

@ -0,0 +1,43 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.remoting.http;
import org.apache.dubbo.remoting.http.config.HttpClientConfig;
public abstract class BaseRestClient<CLIENT> implements RestClient {
protected CLIENT client;
protected HttpClientConfig clientConfig;
public BaseRestClient(HttpClientConfig clientConfig) {
this.clientConfig = clientConfig;
client = createHttpClient(clientConfig);
}
protected abstract CLIENT createHttpClient(HttpClientConfig clientConfig);
public HttpClientConfig getClientConfig() {
return clientConfig;
}
public CLIENT getClient() {
return client;
}
}

View File

@ -0,0 +1,303 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.remoting.http;
import org.apache.dubbo.remoting.Constants;
import org.apache.dubbo.rpc.Invocation;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class RequestTemplate implements Serializable {
private static final long serialVersionUID = 1L;
public static final String CONTENT_ENCODING = "Content-Encoding";
private static final String CONTENT_LENGTH = "Content-Length";
public static final String ENCODING_GZIP = "gzip";
public static final String ENCODING_DEFLATE = "deflate";
private static final List<String> EMPTY_ARRAYLIST = new ArrayList<>();
private final Map<String, Collection<String>> queries = new LinkedHashMap<String, Collection<String>>();
private final Map<String, Collection<String>> headers = new LinkedHashMap<String, Collection<String>>();
private String httpMethod;
private String path;
private String address;
private Object body;
private byte[] byteBody = new byte[0];
private String protocol = "http://";
private final Invocation invocation;
private String contextPath = "";
public RequestTemplate(Invocation invocation, String httpMethod, String address) {
this(invocation, httpMethod, address, "");
}
public RequestTemplate(Invocation invocation, String httpMethod, String address, String contextPath) {
this.httpMethod = httpMethod;
this.address = address;
this.invocation = invocation;
this.contextPath = contextPath;
}
public String getURL() {
StringBuilder stringBuilder = new StringBuilder(getProtocol() + address);
stringBuilder.append(getUri());
return stringBuilder.toString();
}
public String getUri() {
StringBuilder stringBuilder = new StringBuilder(getContextPath() + path);
return stringBuilder.append(getQueryString()).toString();
}
public String getQueryString() {
if (queries.isEmpty()) {
return "";
}
StringBuilder queryBuilder = new StringBuilder("?");
for (String field : queries.keySet()) {
Collection<String> queryValues = queries.get(field);
if (queryValues == null || queryValues.isEmpty()) {
continue;
}
for (String value : queryValues) {
queryBuilder.append('&');
queryBuilder.append(field);
if (value == null) {
continue;
}
queryBuilder.append('=');
queryBuilder.append(value);
}
}
return queryBuilder.toString();
}
public RequestTemplate path(String path) {
this.path = path;
return this;
}
public String getHttpMethod() {
return httpMethod;
}
public RequestTemplate httpMethod(String httpMethod) {
this.httpMethod = httpMethod;
return this;
}
public byte[] getSerializedBody() {
return byteBody;
}
public void serializeBody(byte[] body) {
addHeader(CONTENT_LENGTH, body.length); // must header
this.byteBody = body;
}
public boolean isBodyEmpty() {
return getUnSerializedBody() == null;
}
public RequestTemplate body(Object body) {
this.body = body;
return this;
}
public Object getUnSerializedBody() {
return body;
}
public Map<String, Collection<String>> getAllHeaders() {
return headers;
}
public Collection<String> getHeaders(String name) {
return headers.get(name);
}
public String getHeader(String name) {
if (headers.containsKey(name)) {
Collection<String> headers = getHeaders(name);
if (headers.isEmpty()) {
return null;
}
String[] strings = headers.toArray(new String[0]);
return strings[0];
} else {
return null;
}
}
public Collection<String> getEncodingValues() {
if (headers.containsKey(CONTENT_ENCODING)) {
return headers.get(CONTENT_ENCODING);
}
return EMPTY_ARRAYLIST;
}
public boolean isGzipEncodedRequest() {
return getEncodingValues().contains(ENCODING_GZIP);
}
public boolean isDeflateEncodedRequest() {
return getEncodingValues().contains(ENCODING_DEFLATE);
}
public void addHeader(String key, String value) {
addValueByKey(key, value, this.headers);
}
public void addHeader(String key, Object value) {
addValueByKey(key, String.valueOf(value), this.headers);
}
public void addKeepAliveHeader(int time) {
addHeader(Constants.KEEP_ALIVE_HEADER, time);
addHeader(Constants.CONNECTION, Constants.KEEP_ALIVE);
}
public void addHeaders(String key, Collection<String> values) {
Collection<String> header = getHeaders(key);
if (header == null) {
header = new HashSet<>();
this.headers.put(key, header);
}
header.addAll(values);
}
public void addParam(String key, String value) {
addValueByKey(key, value, this.queries);
}
public void addParam(String key, Object value) {
addParam(key, String.valueOf(value));
}
public Map<String, Collection<String>> getQueries() {
return queries;
}
public Collection<String> getParam(String key) {
return getQueries().get(key);
}
public void addParams(String key, Collection<String> values) {
Collection<String> params = getParam(key);
if (params == null) {
params = new HashSet<>();
this.headers.put(key, params);
}
params.addAll(values);
}
public void addValueByKey(String key, String value, Map<String, Collection<String>> maps) {
if (value == null) {
return;
}
Collection<String> values = null;
if (!maps.containsKey(key)) {
values = new HashSet<>();
maps.put(key, values);
}
values = maps.get(key);
values.add(value);
}
public Integer getContentLength() {
if (!getAllHeaders().containsKey(CONTENT_LENGTH)) {
return null;
}
HashSet<String> strings = (HashSet<String>) getAllHeaders().get(CONTENT_LENGTH);
return Integer.parseInt(new ArrayList<>(strings).get(0));
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
addHeader("Host", address);// must header
this.address = address;
}
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public Invocation getInvocation() {
return invocation;
}
public String getContextPath() {
if (contextPath == null || contextPath.length() == 0) {
return "";
}
if (contextPath.startsWith("/")) {
return contextPath;
} else {
return "/" + contextPath;
}
}
public void setContextPath(String contextPath) {
this.contextPath = contextPath;
}
}

View File

@ -0,0 +1,50 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.remoting.http;
import org.apache.dubbo.remoting.RemotingException;
import java.util.concurrent.CompletableFuture;
public interface RestClient {
/**
* send message.
*
* @param message
* @throws RemotingException
*/
CompletableFuture<RestResult> send(RequestTemplate message);
/**
* close the channel.
*/
void close();
/**
* Graceful close the channel.
*/
void close(int timeout);
/**
* is closed.
*
* @return closed
*/
boolean isClosed();
}

View File

@ -0,0 +1,35 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.remoting.http;
import java.io.IOException;
import java.util.List;
import java.util.Map;
public interface RestResult {
String getContentType();
byte[] getBody() throws IOException;
Map<String, List<String>> headers();
byte[] getErrorResponse() throws IOException;
int getResponseCode() throws IOException;
String getMessage() throws IOException;
}

View File

@ -0,0 +1,62 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.remoting.http.config;
public class HttpClientConfig {
private int readTimeout = 6 * 1000;
private int writeTimeout = 6 * 1000;
private int connectTimeout = 6 * 1000;
private int chunkLength = 8196;
private int HTTP_CLIENT_CONNECTION_MANAGER_MAX_PER_ROUTE = 20;
private int HTTP_CLIENT_CONNECTION_MANAGER_MAX_TOTAL = 20;
private int HTTPCLIENT_KEEP_ALIVE_DURATION = 30 * 1000;
private int HTTP_CLIENT_CONNECTION_MANAGER_CLOSE_WAIT_TIME_MS = 1000;
private int HTTP_CLIENT_CONNECTION_MANAGER_CLOSE_IDLE_TIME_S = 30;
public HttpClientConfig() {
}
public int getReadTimeout() {
return readTimeout;
}
public void setReadTimeout(int readTimeout) {
this.readTimeout = readTimeout;
}
public int getWriteTimeout() {
return writeTimeout;
}
public void setWriteTimeout(int writeTimeout) {
this.writeTimeout = writeTimeout;
}
public int getConnectTimeout() {
return connectTimeout;
}
public void setConnectTimeout(int connectTimeout) {
this.connectTimeout = connectTimeout;
}
public int getChunkLength() {
return chunkLength;
}
}

View File

@ -0,0 +1,87 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.remoting.http.factory;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.remoting.http.RestClient;
import org.apache.dubbo.remoting.http.config.HttpClientConfig;
import org.apache.dubbo.rpc.RpcException;
/**
* AbstractHttpClientFactory
*/
public abstract class AbstractHttpClientFactory implements RestClientFactory {
protected final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass());
// TODO load config
protected HttpClientConfig httpClientConfig = new HttpClientConfig();
//////////////////////////////////////// implements start ///////////////////////////////////////////////
@Override
public RestClient createRestClient(URL url) throws RpcException {
beforeCreated(url);
// create a raw client
RestClient restClient = doCreateRestClient(url);
// postprocessor
afterCreated(restClient);
return restClient;
}
//////////////////////////////////////// implements end ///////////////////////////////////////////////
//////////////////////////////////////// inner methods ///////////////////////////////////////////////
protected void beforeCreated(URL url) {
}
protected abstract RestClient doCreateRestClient(URL url) throws RpcException;
protected void afterCreated(RestClient client) {
}
//////////////////////////////////////// inner methods ///////////////////////////////////////////////
}

View File

@ -0,0 +1,38 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.remoting.http.factory;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.ExtensionScope;
import org.apache.dubbo.common.extension.SPI;
import org.apache.dubbo.remoting.Constants;
import org.apache.dubbo.remoting.http.RestClient;
import org.apache.dubbo.rpc.RpcException;
/**
* RestClientFactory. (API/SPI, Singleton, ThreadSafe)
*/
@SPI(value = Constants.OK_HTTP, scope = ExtensionScope.FRAMEWORK)
public interface RestClientFactory {
@Adaptive({Constants.CLIENT_KEY})
RestClient createRestClient(URL url) throws RpcException;
}

View File

@ -0,0 +1,37 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.remoting.http.factory.impl;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.remoting.Constants;
import org.apache.dubbo.remoting.http.RestClient;
import org.apache.dubbo.remoting.http.factory.AbstractHttpClientFactory;
import org.apache.dubbo.remoting.http.restclient.HttpClientRestClient;
import org.apache.dubbo.rpc.RpcException;
@Activate(Constants.APACHE_HTTP_CLIENT)
public class ApacheHttpClientFactory extends AbstractHttpClientFactory {
@Override
protected RestClient doCreateRestClient(URL url) throws RpcException {
return new HttpClientRestClient(httpClientConfig);
}
}

View File

@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.remoting.http.factory.impl;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.remoting.Constants;
import org.apache.dubbo.remoting.http.RestClient;
import org.apache.dubbo.remoting.http.factory.AbstractHttpClientFactory;
import org.apache.dubbo.remoting.http.restclient.OKHttpRestClient;
import org.apache.dubbo.rpc.RpcException;
@Activate(Constants.OK_HTTP)
public class OkHttpClientFactory extends AbstractHttpClientFactory {
@Override
protected RestClient doCreateRestClient(URL url) throws RpcException {
return new OKHttpRestClient(httpClientConfig);
}
}

View File

@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.remoting.http.factory.impl;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.remoting.Constants;
import org.apache.dubbo.remoting.http.RestClient;
import org.apache.dubbo.remoting.http.factory.AbstractHttpClientFactory;
import org.apache.dubbo.remoting.http.restclient.URLConnectionRestClient;
import org.apache.dubbo.rpc.RpcException;
@Activate(Constants.URL_CONNECTION)
public class URLConnectionClientFactory extends AbstractHttpClientFactory {
@Override
protected RestClient doCreateRestClient(URL url) throws RpcException {
return new URLConnectionRestClient(httpClientConfig);
}
}

View File

@ -0,0 +1,153 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.remoting.http.restclient;
import org.apache.dubbo.remoting.http.RequestTemplate;
import org.apache.dubbo.remoting.http.RestClient;
import org.apache.dubbo.remoting.http.RestResult;
import org.apache.dubbo.remoting.http.config.HttpClientConfig;
import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
public class HttpClientRestClient implements RestClient {
private final CloseableHttpClient closeableHttpClient;
private final HttpClientConfig httpClientConfig;
public HttpClientRestClient(HttpClientConfig clientConfig) {
closeableHttpClient = createHttpClient();
httpClientConfig = clientConfig;
}
@Override
public CompletableFuture<RestResult> send(RequestTemplate requestTemplate) {
HttpRequestBase httpRequest = null;
String httpMethod = requestTemplate.getHttpMethod();
if ("GET".equals(httpMethod)) {
httpRequest = new HttpGet(requestTemplate.getURL());
} else if ("POST".equals(httpMethod)) {
HttpPost httpPost = new HttpPost(requestTemplate.getURL());
httpPost.setEntity(new ByteArrayEntity(requestTemplate.getSerializedBody()));
httpRequest = httpPost;
}
Map<String, Collection<String>> allHeaders = requestTemplate.getAllHeaders();
allHeaders.remove("Content-Length");
// header
for (String headerName : allHeaders.keySet()) {
Collection<String> headerValues = allHeaders.get(headerName);
for (String headerValue : headerValues) {
httpRequest.addHeader(headerName, headerValue);
}
}
httpRequest.setConfig(getRequestConfig(httpClientConfig));
CompletableFuture<RestResult> future = new CompletableFuture<>();
try {
CloseableHttpResponse response = closeableHttpClient.execute(httpRequest);
future.complete(new RestResult() {
@Override
public String getContentType() {
return response.getFirstHeader("Content-Type").getValue();
}
@Override
public byte[] getBody() throws IOException {
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 response.getStatusLine().getReasonPhrase();
}
});
} catch (IOException e) {
future.completeExceptionally(e);
}
return future;
}
private RequestConfig getRequestConfig(HttpClientConfig clientConfig) {
// TODO config
return RequestConfig.custom().build();
}
@Override
public void close() {
try {
closeableHttpClient.close();
} catch (IOException e) {
}
}
@Override
public void close(int timeout) {
}
@Override
public boolean isClosed() {
// TODO close judge
return true;
}
public CloseableHttpClient createHttpClient() {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
return HttpClients.custom().setConnectionManager(connectionManager).build();
}
}

View File

@ -0,0 +1,144 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.remoting.http.restclient;
import org.apache.dubbo.remoting.http.RequestTemplate;
import org.apache.dubbo.remoting.http.RestClient;
import org.apache.dubbo.remoting.http.RestResult;
import org.apache.dubbo.remoting.http.config.HttpClientConfig;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.internal.http.HttpMethod;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class OKHttpRestClient implements RestClient {
private final OkHttpClient okHttpClient;
private final HttpClientConfig httpClientConfig;
public OKHttpRestClient(HttpClientConfig clientConfig) {
this.okHttpClient = createHttpClient(clientConfig);
this.httpClientConfig = clientConfig;
}
@Override
public CompletableFuture<RestResult> send(RequestTemplate requestTemplate) {
Request.Builder builder = new Request.Builder();
// url
builder.url(requestTemplate.getURL());
Map<String, Collection<String>> allHeaders = requestTemplate.getAllHeaders();
// header
for (String headerName : allHeaders.keySet()) {
Collection<String> headerValues = allHeaders.get(headerName);
for (String headerValue : headerValues) {
builder.addHeader(headerName, headerValue);
}
}
RequestBody requestBody = null;
if (HttpMethod.permitsRequestBody(requestTemplate.getHttpMethod())) {
requestBody = RequestBody.create(null, requestTemplate.getSerializedBody());
}
builder.method(requestTemplate.getHttpMethod(), requestBody);
CompletableFuture<RestResult> future = new CompletableFuture<>();
okHttpClient.newCall(builder.build()).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
future.completeExceptionally(e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
future.complete(new RestResult() {
@Override
public String getContentType() {
return response.header("Content-Type");
}
@Override
public byte[] getBody() throws IOException {
ResponseBody body = response.body();
return body == null ? null : body.bytes();
}
@Override
public Map<String, List<String>> headers() {
return response.headers().toMultimap();
}
@Override
public byte[] getErrorResponse() throws IOException {
return getBody();
}
@Override
public int getResponseCode() throws IOException {
return response.code();
}
@Override
public String getMessage() throws IOException {
return response.message();
}
});
}
});
return future;
}
@Override
public void close() {
okHttpClient.connectionPool().evictAll();
}
@Override
public void close(int timeout) {
}
@Override
public boolean isClosed() {
return okHttpClient.retryOnConnectionFailure();
}
public OkHttpClient createHttpClient(HttpClientConfig httpClientConfig) {
OkHttpClient client = new OkHttpClient.Builder().
readTimeout(httpClientConfig.getReadTimeout(), TimeUnit.SECONDS).
writeTimeout(httpClientConfig.getWriteTimeout(), TimeUnit.SECONDS).
connectTimeout(httpClientConfig.getConnectTimeout(), TimeUnit.SECONDS).
build();
return client;
}
}

View File

@ -0,0 +1,181 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.remoting.http.restclient;
import org.apache.dubbo.remoting.http.RequestTemplate;
import org.apache.dubbo.remoting.http.RestClient;
import org.apache.dubbo.remoting.http.RestResult;
import org.apache.dubbo.remoting.http.config.HttpClientConfig;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.GZIPOutputStream;
public class URLConnectionRestClient implements RestClient {
private final HttpClientConfig clientConfig;
public URLConnectionRestClient(HttpClientConfig clientConfig) {
this.clientConfig = clientConfig;
}
@Override
public CompletableFuture<RestResult> send(RequestTemplate requestTemplate) {
CompletableFuture<RestResult> future = new CompletableFuture<>();
try {
HttpURLConnection connection = (HttpURLConnection) new URL(requestTemplate.getURL()).openConnection();
connection.setConnectTimeout(clientConfig.getConnectTimeout());
connection.setReadTimeout(clientConfig.getReadTimeout());
connection.setAllowUserInteraction(false);
connection.setInstanceFollowRedirects(true);
connection.setRequestMethod(requestTemplate.getHttpMethod());
// writeHeaders
for (String field : requestTemplate.getAllHeaders().keySet()) {
for (String value : requestTemplate.getHeaders(field)) {
connection.addRequestProperty(field, value);
}
}
// writeBody
boolean gzipEncodedRequest = requestTemplate.isGzipEncodedRequest();
boolean deflateEncodedRequest = requestTemplate.isDeflateEncodedRequest();
if (requestTemplate.isBodyEmpty()) {
future.complete(new RestResult() {
@Override
public String getContentType() {
return connection.getContentType();
}
@Override
public byte[] getBody() throws IOException {
return IOUtils.toByteArray(connection.getInputStream());
}
@Override
public Map<String, List<String>> headers() {
return connection.getHeaderFields();
}
@Override
public byte[] getErrorResponse() throws IOException {
return IOUtils.toByteArray(connection.getErrorStream());
}
@Override
public int getResponseCode() throws IOException {
return connection.getResponseCode();
}
@Override
public String getMessage() throws IOException {
return connection.getResponseMessage();
}
});
return future;
}
Integer contentLength = requestTemplate.getContentLength();
if (contentLength != null) {
connection.setFixedLengthStreamingMode(contentLength);
} else {
connection.setChunkedStreamingMode(clientConfig.getChunkLength());
}
connection.setDoOutput(true);
OutputStream out = connection.getOutputStream();
if (gzipEncodedRequest) {
out = new GZIPOutputStream(out);
} else if (deflateEncodedRequest) {
out = new DeflaterOutputStream(out);
}
try {
out.write(requestTemplate.getSerializedBody());
} finally {
try {
out.close();
} catch (IOException suppressed) {
}
}
future.complete(new RestResult() {
@Override
public String getContentType() {
return connection.getContentType();
}
@Override
public byte[] getBody() throws IOException {
return IOUtils.toByteArray(connection.getInputStream());
}
@Override
public Map<String, List<String>> headers() {
return connection.getHeaderFields();
}
@Override
public byte[] getErrorResponse() throws IOException {
return IOUtils.toByteArray(connection.getErrorStream());
}
@Override
public int getResponseCode() throws IOException {
return connection.getResponseCode();
}
@Override
public String getMessage() throws IOException {
return connection.getResponseMessage();
}
});
} catch (Exception e) {
future.completeExceptionally(e);
}
return future;
}
@Override
public void close() {
}
@Override
public void close(int timeout) {
}
@Override
public boolean isClosed() {
return true;
}
}

View File

@ -0,0 +1,4 @@
ok-http=org.apache.dubbo.remoting.http.factory.impl.OkHttpClientFactory
url-connection=org.apache.dubbo.remoting.http.factory.impl.URLConnectionClientFactory
apache-http-client=org.apache.dubbo.remoting.http.factory.impl.ApacheHttpClientFactory

View File

@ -29,7 +29,6 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.opentest4j.AssertionFailedError;
import java.lang.reflect.Field;
import java.util.Map;

View File

@ -45,19 +45,14 @@ import java.util.concurrent.CopyOnWriteArrayList;
import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.ANYHOST_VALUE;
import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.PATH_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_UNSUPPORTED;
import static org.apache.dubbo.rpc.Constants.TOKEN_KEY;
/**
* AbstractProxyProtocol
*/
public abstract class AbstractProxyProtocol extends AbstractProtocol {
private final List<Class<?>> rpcExceptions = new CopyOnWriteArrayList<Class<?>>();
protected final List<Class<?>> rpcExceptions = new CopyOnWriteArrayList<Class<?>>();
protected ProxyFactory proxyFactory;
@ -139,48 +134,6 @@ public abstract class AbstractProxyProtocol extends AbstractProtocol {
return exporter;
}
@Override
protected <T> Invoker<T> protocolBindingRefer(final Class<T> type, final URL url) throws RpcException {
final Invoker<T> target = proxyFactory.getInvoker(doRefer(type, url), type, url);
Invoker<T> invoker = new AbstractInvoker<T>(type, url, new String[]{INTERFACE_KEY, GROUP_KEY, TOKEN_KEY}) {
@Override
protected Result doInvoke(Invocation invocation) {
try {
invocation.setAttachment(PATH_KEY, getUrl().getPath());
invocation.setAttachment(VERSION_KEY, version);
Result result = target.invoke(invocation);
// FIXME result is an AsyncRpcResult instance.
Throwable e = result.getException();
if (e != null) {
for (Class<?> rpcException : rpcExceptions) {
if (rpcException.isAssignableFrom(e.getClass())) {
throw getRpcException(type, url, invocation, e);
}
}
}
return result;
} catch (RpcException e) {
if (e.getCode() == RpcException.UNKNOWN_EXCEPTION) {
e.setCode(getErrorCode(e.getCause()));
}
throw e;
} catch (Throwable e) {
throw getRpcException(type, url, invocation, e);
}
}
@Override
public void destroy() {
super.destroy();
target.destroy();
invokers.remove(this);
AbstractProxyProtocol.this.destroyInternal(url);
}
};
invokers.add(invoker);
return invoker;
}
// used to destroy unused clients and other resource
protected void destroyInternal(URL url) {
// subclass override
@ -207,8 +160,6 @@ public abstract class AbstractProxyProtocol extends AbstractProtocol {
protected abstract <T> Runnable doExport(T impl, Class<T> type, URL url) throws RpcException;
protected abstract <T> T doRefer(Class<T> type, URL url) throws RpcException;
protected class ProxyProtocolServer implements ProtocolServer {
private RemotingServer server;

View File

@ -18,11 +18,9 @@ package org.apache.dubbo.rpc;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.url.component.URLParam;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

View File

@ -16,7 +16,7 @@
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<parent>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc</artifactId>
<version>${revision}</version>
@ -112,10 +112,48 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-metadata-api</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
<scope>compile</scope>
</dependency>
<!-- Spring Web MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -16,16 +16,16 @@
*/
package org.apache.dubbo.rpc.protocol.rest;
import io.netty.channel.ChannelOption;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.utils.NetUtils;
import io.netty.channel.ChannelOption;
import org.jboss.resteasy.plugins.server.netty.NettyJaxrsServer;
import org.jboss.resteasy.spi.ResteasyDeployment;
import java.util.HashMap;
import java.util.Map;
import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_THREADS;
import static org.apache.dubbo.common.constants.CommonConstants.IO_THREADS_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.THREADS_KEY;

View File

@ -19,32 +19,31 @@ package org.apache.dubbo.rpc.protocol.rest;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.reference.ReferenceCountedResource;
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.apache.dubbo.remoting.http.RestClient;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_ERROR_CLOSE_CLIENT;
public class ReferenceCountedClient extends ReferenceCountedResource {
public class ReferenceCountedClient<T extends RestClient> extends ReferenceCountedResource {
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(ReferenceCountedClient.class);
private final ResteasyClient resteasyClient;
private T client;
public ReferenceCountedClient(ResteasyClient resteasyClient) {
this.resteasyClient = resteasyClient;
public ReferenceCountedClient(T client) {
this.client = client;
}
public ResteasyClient getClient() {
return resteasyClient;
public T getClient() {
return client;
}
public boolean isDestroyed() {
return resteasyClient.isClosed();
return client.isClosed();
}
@Override
protected void destroy() {
try {
resteasyClient.close();
client.close();
} catch (Exception e) {
logger.error(PROTOCOL_ERROR_CLOSE_CLIENT, "", "", "Close resteasy client error", e);
}

View File

@ -19,72 +19,73 @@ package org.apache.dubbo.rpc.protocol.rest;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.utils.ConcurrentHashMapUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.remoting.http.HttpBinder;
import org.apache.dubbo.metadata.ParameterTypesComparator;
import org.apache.dubbo.metadata.rest.RestMethodMetadata;
import org.apache.dubbo.metadata.rest.media.MediaType;
import org.apache.dubbo.remoting.http.RequestTemplate;
import org.apache.dubbo.remoting.http.RestClient;
import org.apache.dubbo.remoting.http.RestResult;
import org.apache.dubbo.remoting.http.factory.RestClientFactory;
import org.apache.dubbo.remoting.http.servlet.BootstrapListener;
import org.apache.dubbo.remoting.http.servlet.ServletManager;
import org.apache.dubbo.rpc.AppResponse;
import org.apache.dubbo.rpc.AsyncRpcResult;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.ProtocolServer;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.model.FrameworkModel;
import org.apache.dubbo.rpc.protocol.AbstractInvoker;
import org.apache.dubbo.rpc.protocol.AbstractProxyProtocol;
import org.apache.dubbo.rpc.protocol.rest.annotation.consumer.HttpConnectionConfig;
import org.apache.dubbo.rpc.protocol.rest.annotation.consumer.HttpConnectionCreateContext;
import org.apache.dubbo.rpc.protocol.rest.annotation.consumer.HttpConnectionPreBuildIntercept;
import org.apache.dubbo.rpc.protocol.rest.annotation.metadata.MetadataResolver;
import org.apache.dubbo.rpc.protocol.rest.exception.HttpClientException;
import org.apache.dubbo.rpc.protocol.rest.exception.RemoteServerInternalException;
import org.apache.dubbo.rpc.protocol.rest.message.HttpMessageCodecManager;
import org.apache.dubbo.rpc.protocol.rest.util.MediaTypeUtil;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.SocketConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
import org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine;
import org.jboss.resteasy.util.GetRestful;
import javax.servlet.ServletContext;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.WebApplicationException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN;
import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_TIMEOUT;
import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_ERROR_CLOSE_CLIENT;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_ERROR_CLOSE_SERVER;
import static org.apache.dubbo.remoting.Constants.CONNECTIONS_KEY;
import static org.apache.dubbo.remoting.Constants.CONNECT_TIMEOUT_KEY;
import static org.apache.dubbo.remoting.Constants.DEFAULT_CONNECT_TIMEOUT;
import static org.apache.dubbo.remoting.Constants.SERVER_KEY;
import static org.apache.dubbo.rpc.protocol.rest.Constants.EXTENSION_KEY;
import static org.apache.dubbo.rpc.Constants.TOKEN_KEY;
public class RestProtocol extends AbstractProxyProtocol {
private static final int DEFAULT_PORT = 80;
private static final String DEFAULT_SERVER = "jetty";
private static final int HTTPCLIENTCONNECTIONMANAGER_MAXPERROUTE = 20;
private static final int HTTPCLIENTCONNECTIONMANAGER_MAXTOTAL = 20;
private static final int HTTPCLIENT_KEEPALIVEDURATION = 30 * 1000;
private static final int HTTPCLIENTCONNECTIONMANAGER_CLOSEWAITTIME_MS = 1000;
private static final int HTTPCLIENTCONNECTIONMANAGER_CLOSEIDLETIME_S = 30;
private static final String DEFAULT_CLIENT = org.apache.dubbo.remoting.Constants.OK_HTTP;
private final RestServerFactory serverFactory = new RestServerFactory();
private final ConcurrentMap<String, ReferenceCountedClient> clients = new ConcurrentHashMap<>();
private final ConcurrentMap<String, ReferenceCountedClient<? extends RestClient>> clients = new ConcurrentHashMap<>();
private volatile ConnectionMonitor connectionMonitor;
private final RestClientFactory clientFactory;
public RestProtocol() {
private final Set<HttpConnectionPreBuildIntercept> httpConnectionPreBuildIntercepts;
public RestProtocol(FrameworkModel frameworkModel) {
super(WebApplicationException.class, ProcessingException.class);
this.clientFactory = frameworkModel.getExtensionLoader(RestClientFactory.class).getAdaptiveExtension();
this.httpConnectionPreBuildIntercepts = frameworkModel.getExtensionLoader(HttpConnectionPreBuildIntercept.class).getSupportedExtensionInstances();
}
public void setHttpBinder(HttpBinder httpBinder) {
serverFactory.setHttpBinder(httpBinder);
}
@Override
public int getDefaultPort() {
@ -135,79 +136,100 @@ public class RestProtocol extends AbstractProxyProtocol {
};
}
@Override
protected <T> T doRefer(Class<T> serviceType, URL url) throws RpcException {
ReferenceCountedClient referenceCountedClient = ConcurrentHashMapUtils.computeIfAbsent(clients, url.getAddress(), _key -> {
// TODO more configs to add
return createReferenceCountedClient(url);
});
protected <T> Invoker<T> protocolBindingRefer(final Class<T> type, final URL url) throws RpcException {
if (referenceCountedClient.isDestroyed()) {
referenceCountedClient = createReferenceCountedClient(url);
clients.put(url.getAddress(), referenceCountedClient);
}
referenceCountedClient.retain();
ReferenceCountedClient<? extends RestClient> refClient =
clients.computeIfAbsent(url.getAddress(), key -> createReferenceCountedClient(url));
ResteasyClient resteasyClient = referenceCountedClient.getClient();
for (String clazz : COMMA_SPLIT_PATTERN.split(url.getParameter(EXTENSION_KEY, ""))) {
if (!StringUtils.isEmpty(clazz)) {
refClient.retain();
// resolve metadata
Map<String, Map<ParameterTypesComparator, RestMethodMetadata>> metadataMap = MetadataResolver.resolveConsumerServiceMetadata(type, url);
Invoker<T> invoker = new AbstractInvoker<T>(type, url, new String[]{INTERFACE_KEY, GROUP_KEY, TOKEN_KEY}) {
@Override
protected Result doInvoke(Invocation invocation) {
try {
resteasyClient.register(Thread.currentThread().getContextClassLoader().loadClass(clazz.trim()));
} catch (ClassNotFoundException e) {
throw new RpcException("Error loading JAX-RS extension class: " + clazz.trim(), e);
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);
for (HttpConnectionPreBuildIntercept intercept : httpConnectionPreBuildIntercepts) {
intercept.intercept(httpConnectionCreateContext);
}
CompletableFuture<RestResult> future = refClient.getClient().send(requestTemplate);
CompletableFuture<AppResponse> responseFuture = new CompletableFuture<>();
AsyncRpcResult asyncRpcResult = new AsyncRpcResult(responseFuture, invocation);
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;
}
}
}
// TODO protocol
ResteasyWebTarget target = resteasyClient.target("http://" + url.getAddress() + "/" + getContextPath(url));
return target.proxy(serviceType);
@Override
public void destroy() {
super.destroy();
invokers.remove(this);
destroyInternal(url);
}
};
invokers.add(invoker);
return invoker;
}
private ReferenceCountedClient createReferenceCountedClient(URL url) {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
// 20 is the default maxTotal of current PoolingClientConnectionManager
connectionManager.setMaxTotal(url.getParameter(CONNECTIONS_KEY, HTTPCLIENTCONNECTIONMANAGER_MAXTOTAL));
connectionManager.setDefaultMaxPerRoute(url.getParameter(CONNECTIONS_KEY, HTTPCLIENTCONNECTIONMANAGER_MAXPERROUTE));
if (connectionMonitor == null) {
connectionMonitor = new ConnectionMonitor();
connectionMonitor.start();
}
connectionMonitor.addConnectionManager(url.getAddress(), connectionManager);
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(url.getParameter(CONNECT_TIMEOUT_KEY, DEFAULT_CONNECT_TIMEOUT))
.setSocketTimeout(url.getParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT))
.build();
private ReferenceCountedClient<? extends RestClient> createReferenceCountedClient(URL url) throws RpcException {
SocketConfig socketConfig = SocketConfig.custom()
.setSoKeepAlive(true)
.setTcpNoDelay(true)
.build();
// url -> RestClient
RestClient restClient = clientFactory.createRestClient(url);
CloseableHttpClient httpClient = HttpClientBuilder.create()
.setConnectionManager(connectionManager)
.setKeepAliveStrategy((response, context) -> {
HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
while (it.hasNext()) {
HeaderElement he = it.nextElement();
String param = he.getName();
String value = he.getValue();
if (value != null && param.equalsIgnoreCase(TIMEOUT_KEY)) {
return Long.parseLong(value) * 1000;
}
}
return HTTPCLIENT_KEEPALIVEDURATION;
})
.setDefaultRequestConfig(requestConfig)
.setDefaultSocketConfig(socketConfig)
.build();
ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(httpClient/*, localContext*/);
ResteasyClient resteasyClient = new ResteasyClientBuilder().httpEngine(engine).build();
resteasyClient.register(RpcContextFilter.class);
return new ReferenceCountedClient(resteasyClient);
return new ReferenceCountedClient<>(restClient);
}
@Override
@ -223,10 +245,6 @@ public class RestProtocol extends AbstractProxyProtocol {
}
super.destroy();
if (connectionMonitor != null) {
connectionMonitor.shutdown();
}
for (Map.Entry<String, ProtocolServer> entry : serverMap.entrySet()) {
try {
if (logger.isInfoEnabled()) {
@ -281,55 +299,9 @@ public class RestProtocol extends AbstractProxyProtocol {
ReferenceCountedClient referenceCountedClient = clients.get(url.getAddress());
if (referenceCountedClient != null && referenceCountedClient.release()) {
clients.remove(url.getAddress());
connectionMonitor.destroyManager(url);
}
} catch (Exception e) {
logger.warn(PROTOCOL_ERROR_CLOSE_CLIENT, "", "", "Failed to close unused resources in rest protocol. interfaceName [" + url.getServiceInterface() + "]", e);
}
}
protected class ConnectionMonitor extends Thread {
private volatile boolean shutdown;
/**
* The lifecycle of {@code PoolingHttpClientConnectionManager} instance is bond with ReferenceCountedClient
*/
private final Map<String, PoolingHttpClientConnectionManager> connectionManagers = new ConcurrentHashMap<>();
public void addConnectionManager(String address, PoolingHttpClientConnectionManager connectionManager) {
connectionManagers.putIfAbsent(address, connectionManager);
}
@Override
public void run() {
try {
while (!shutdown) {
synchronized (this) {
wait(HTTPCLIENTCONNECTIONMANAGER_CLOSEWAITTIME_MS);
for (PoolingHttpClientConnectionManager connectionManager : connectionManagers.values()) {
connectionManager.closeExpiredConnections();
connectionManager.closeIdleConnections(HTTPCLIENTCONNECTIONMANAGER_CLOSEIDLETIME_S, TimeUnit.SECONDS);
}
}
}
} catch (InterruptedException ex) {
shutdown();
}
}
public void shutdown() {
shutdown = true;
connectionManagers.clear();
synchronized (this) {
notifyAll();
}
}
// destroy the connection manager of a specific address when ReferenceCountedClient is destroyed.
private void destroyManager(URL url) {
PoolingHttpClientConnectionManager connectionManager = connectionManagers.remove(url.getAddress());
if (connectionManager != null) {
connectionManager.close();
}
}
}
}

View File

@ -17,19 +17,15 @@
package org.apache.dubbo.rpc.protocol.rest;
import org.apache.dubbo.remoting.http.HttpBinder;
import org.apache.dubbo.rpc.model.FrameworkModel;
/**
* Only the server that implements servlet container
* could support something like @Context injection of servlet objects.
*
*/
public class RestServerFactory {
private HttpBinder httpBinder;
public void setHttpBinder(HttpBinder httpBinder) {
this.httpBinder = httpBinder;
}
private static final HttpBinder httpBinder = FrameworkModel.defaultModel().getAdaptiveExtension(HttpBinder.class);
public RestProtocolServer createServer(String name) {
// TODO move names to Constants

View File

@ -0,0 +1,51 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.protocol.rest.annotation;
import org.apache.dubbo.metadata.rest.ArgInfo;
import java.util.List;
public class BaseParseContext {
protected List<Object> args;
protected List<ArgInfo> argInfos;
public List<Object> getArgs() {
return args;
}
public void setArgs(List<Object> args) {
this.args = args;
}
public List<ArgInfo> getArgInfos() {
return argInfos;
}
public void setArgInfos(List<ArgInfo> argInfos) {
this.argInfos = argInfos;
}
public ArgInfo getArgInfoByIndex(int index) {
return getArgInfos().get(index);
}
}

View File

@ -0,0 +1,24 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.protocol.rest.annotation;
import org.apache.dubbo.metadata.rest.ArgInfo;
public interface ParamParser<T> {
void parse(T parseContext, ArgInfo argInfo);
}

View File

@ -0,0 +1,61 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.protocol.rest.annotation;
import org.apache.dubbo.metadata.rest.ArgInfo;
import org.apache.dubbo.rpc.model.FrameworkModel;
import org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.consumer.BaseConsumerParamParser;
import org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.consumer.ConsumerParseContext;
import java.util.List;
import java.util.Set;
public class ParamParserManager {
private static final Set<BaseConsumerParamParser> consumerParamParsers =
FrameworkModel.defaultModel().getExtensionLoader(BaseConsumerParamParser.class).getSupportedExtensionInstances();
/**
* provider Design Description:
* <p>
* Object[] args=new Object[0];
* List<Object> argsList=new ArrayList<>;</>
* <p>
* setValueByIndex(int index,Object value);
* <p>
* args=toArray(new Object[0]);
*/
public void consumerParamParse(ConsumerParseContext parseContext) {
List<Object> args = parseContext.getArgs();
for (int i = 0; i < args.size(); i++) {
for (BaseConsumerParamParser paramParser : consumerParamParsers) {
ArgInfo argInfoByIndex = parseContext.getArgInfoByIndex(i);
if (!paramParser.paramTypeMatch(argInfoByIndex)) {
continue;
}
paramParser.parse(parseContext, argInfoByIndex);
}
}
}
}

View File

@ -0,0 +1,51 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.protocol.rest.annotation.consumer;
import org.apache.dubbo.common.serialize.Constants;
public class HttpConnectionConfig {
private int connectTimeout;
private int readTimeout;
private int chunkLength = 8196;
private byte serialization = Constants.FASTJSON2_SERIALIZATION_ID;
private int keepAlive = 60;
public int getConnectTimeout() {
return connectTimeout;
}
public int getReadTimeout() {
return readTimeout;
}
public int getChunkLength() {
return chunkLength;
}
public byte getSerialization() {
return serialization;
}
public int getKeepAlive() {
return keepAlive;
}
}

View File

@ -0,0 +1,81 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.protocol.rest.annotation.consumer;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.metadata.rest.RestMethodMetadata;
import org.apache.dubbo.metadata.rest.ServiceRestMetadata;
import org.apache.dubbo.remoting.http.RequestTemplate;
import org.apache.dubbo.rpc.Invocation;
public class HttpConnectionCreateContext {
private RequestTemplate requestTemplate;
private HttpConnectionConfig connectionConfig;
private RestMethodMetadata restMethodMetadata;
private Invocation invocation;
private URL url;
public HttpConnectionCreateContext() {
}
public void setRequestTemplate(RequestTemplate requestTemplate) {
this.requestTemplate = requestTemplate;
}
public void setConnectionConfig(HttpConnectionConfig connectionConfig) {
this.connectionConfig = connectionConfig;
}
public RequestTemplate getRequestTemplate() {
return requestTemplate;
}
public HttpConnectionConfig getConnectionConfig() {
return connectionConfig;
}
public ServiceRestMetadata getServiceRestMetadata() {
return restMethodMetadata.getServiceRestMetadata();
}
public RestMethodMetadata getRestMethodMetadata() {
return restMethodMetadata;
}
public void setRestMethodMetadata(RestMethodMetadata restMethodMetadata) {
this.restMethodMetadata = restMethodMetadata;
}
public Invocation getInvocation() {
return invocation;
}
public void setInvocation(Invocation invocation) {
this.invocation = invocation;
}
public URL getUrl() {
return url;
}
public void setUrl(URL url) {
this.url = url;
}
}

View File

@ -0,0 +1,25 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.protocol.rest.annotation.consumer;
import org.apache.dubbo.common.extension.ExtensionScope;
import org.apache.dubbo.common.extension.SPI;
@SPI(scope = ExtensionScope.FRAMEWORK)
public interface HttpConnectionPreBuildIntercept {
void intercept(HttpConnectionCreateContext connectionCreateContext);
}

View File

@ -0,0 +1,48 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.protocol.rest.annotation.consumer.inercept;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.metadata.rest.ServiceRestMetadata;
import org.apache.dubbo.remoting.http.RequestTemplate;
import org.apache.dubbo.rpc.protocol.rest.annotation.consumer.HttpConnectionConfig;
import org.apache.dubbo.rpc.protocol.rest.annotation.consumer.HttpConnectionCreateContext;
import org.apache.dubbo.rpc.protocol.rest.annotation.consumer.HttpConnectionPreBuildIntercept;
import org.apache.dubbo.rpc.protocol.rest.constans.RestConstant;
@Activate(value = RestConstant.ADD_MUST_ATTTACHMENT,order = 1)
public class AddMustAttachmentIntercept implements HttpConnectionPreBuildIntercept {
@Override
public void intercept(HttpConnectionCreateContext connectionCreateContext) {
RequestTemplate requestTemplate = connectionCreateContext.getRequestTemplate();
ServiceRestMetadata serviceRestMetadata = connectionCreateContext.getServiceRestMetadata();
HttpConnectionConfig connectionConfig = connectionCreateContext.getConnectionConfig();
requestTemplate.addHeader(RestConstant.GROUP, serviceRestMetadata.getGroup());
requestTemplate.addHeader(RestConstant.VERSION, serviceRestMetadata.getVersion());
requestTemplate.addHeader(RestConstant.PATH, serviceRestMetadata.getServiceInterface());
requestTemplate.addKeepAliveHeader(connectionConfig.getKeepAlive());
}
}

Some files were not shown because too many files have changed in this diff Show More