diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PathUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PathUtils.java index c291d792e6..b40e26dceb 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PathUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/PathUtils.java @@ -40,8 +40,8 @@ public interface PathUtils { paths.addAll(asList(subPaths)); return normalize(paths.stream() - .filter(StringUtils::isNotEmpty) - .collect(Collectors.joining(SLASH))); + .filter(StringUtils::isNotEmpty) + .collect(Collectors.joining(SLASH))); } /** @@ -67,7 +67,6 @@ public interface PathUtils { while (normalizedPath.contains("//")) { normalizedPath = replace(normalizedPath, "//", "/"); } - return normalizedPath; } diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/vo/UserVo.java b/dubbo-common/src/test/java/org/apache/dubbo/common/vo/UserVo.java new file mode 100644 index 0000000000..336a0562ad --- /dev/null +++ b/dubbo-common/src/test/java/org/apache/dubbo/common/vo/UserVo.java @@ -0,0 +1,84 @@ +/* + * 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.common.vo; + +import java.util.Objects; + +public class UserVo { + private String name; + private String addr; + private int age; + + public UserVo(String name, String addr, int age) { + this.name = name; + this.addr = addr; + this.age = age; + } + + public UserVo() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddr() { + return addr; + } + + public void setAddr(String addr) { + this.addr = addr; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public static UserVo getInstance() { + return new UserVo("dubbo", "hangzhou", 10); + } + + @Override + public String toString() { + return "UserVo{" + + "name='" + name + '\'' + + ", addr='" + addr + '\'' + + ", age=" + age + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UserVo userVo = (UserVo) o; + return age == userVo.age && Objects.equals(name, userVo.name) && Objects.equals(addr, userVo.addr); + } + + @Override + public int hashCode() { + return Objects.hash(name, addr, age); + } +} diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultApplicationDeployer.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultApplicationDeployer.java index 07f5358d6e..61c2c2f1b8 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultApplicationDeployer.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/deploy/DefaultApplicationDeployer.java @@ -1129,7 +1129,6 @@ public class DefaultApplicationDeployer extends AbstractDeployer3.22.2 1.3.2 3.1.0 - 5.0.0 9.4.51.v20230217 3.0.2 1.1.0.Final @@ -423,13 +422,6 @@ ${servlet_version} - - - jakarta.servlet - jakarta.servlet-api - ${jakarta.servlet_version} - - com.squareup.okhttp3 okhttp diff --git a/dubbo-distribution/dubbo-all/pom.xml b/dubbo-distribution/dubbo-all/pom.xml index 688477c2c2..ade64fe84d 100644 --- a/dubbo-distribution/dubbo-all/pom.xml +++ b/dubbo-distribution/dubbo-all/pom.xml @@ -1308,6 +1308,13 @@ + + + META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.provider.BaseProviderParamParser + + + diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataConstants.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataConstants.java index 3fe976a1de..0ec12681ab 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataConstants.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataConstants.java @@ -16,30 +16,17 @@ */ package org.apache.dubbo.metadata; -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"; } diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/AbstractNoAnnotatedParameterProcessor.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/AbstractNoAnnotatedParameterProcessor.java index 10748375b9..4541e52761 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/AbstractNoAnnotatedParameterProcessor.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/AbstractNoAnnotatedParameterProcessor.java @@ -26,19 +26,20 @@ import static org.apache.dubbo.common.utils.ClassUtils.resolveClass; public abstract class AbstractNoAnnotatedParameterProcessor implements NoAnnotatedParameterRequestTagProcessor { - public void process(Parameter parameter, int parameterIndex, RestMethodMetadata restMethodMetadata) { + public boolean process(Parameter parameter, int parameterIndex, RestMethodMetadata restMethodMetadata) { MediaType mediaType = consumerContentType(); if (!contentTypeSupport(restMethodMetadata, mediaType, parameter.getType())) { - return; + return false; } boolean isFormBody = isFormContentType(restMethodMetadata); addArgInfo(parameter, parameterIndex, restMethodMetadata, isFormBody); + return true; } 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))) { + if (mediaType.equals(MediaType.ALL_VALUE) && (String.class == paramType || paramType.isPrimitive() || Number.class.isAssignableFrom(paramType))) { return true; } diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/AbstractServiceRestMetadataResolver.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/AbstractServiceRestMetadataResolver.java index 574a61e2c3..206fd1fad7 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/AbstractServiceRestMetadataResolver.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/AbstractServiceRestMetadataResolver.java @@ -79,7 +79,8 @@ public abstract class AbstractServiceRestMetadataResolver implements ServiceRest } // for provider - return isImplementedInterface(serviceType) && isServiceAnnotationPresent(serviceType) && supports0(serviceType); + // for xml config bean && isServiceAnnotationPresent(serviceType) + return isImplementedInterface(serviceType) && supports0(serviceType); } protected final boolean isImplementedInterface(Class serviceType) { @@ -150,10 +151,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::addRestMethodMetadata)) { + if (!processRestMethodMetadata(serviceMethod, serviceType, serviceInterfaceClass, serviceRestMetadata::addRestMethodMetadata, serviceRestMetadata)) { Method declaredServiceMethod = entry.getValue(); processRestMethodMetadata(declaredServiceMethod, serviceType, serviceInterfaceClass, - serviceRestMetadata::addRestMethodMetadata); + serviceRestMetadata::addRestMethodMetadata, serviceRestMetadata); } } } @@ -234,7 +235,8 @@ public abstract class AbstractServiceRestMetadataResolver implements ServiceRest */ protected boolean processRestMethodMetadata(Method serviceMethod, Class serviceType, Class serviceInterfaceClass, - Consumer metadataToProcess) { + Consumer metadataToProcess, + ServiceRestMetadata serviceRestMetadata) { if (!isRestCapableMethod(serviceMethod, serviceType, serviceInterfaceClass)) { return false; @@ -274,6 +276,7 @@ public abstract class AbstractServiceRestMetadataResolver implements ServiceRest // Initialize RequestMetadata RequestMetadata request = metadata.getRequest(); request.setPath(requestPath); + request.appendContextPathFromUrl(serviceRestMetadata.getContextPathFromUrl()); request.setMethod(requestMethod); request.setProduces(produces); request.setConsumes(consumes); @@ -359,9 +362,12 @@ public abstract class AbstractServiceRestMetadataResolver implements ServiceRest if (annotations == null || annotations.length == 0) { for (NoAnnotatedParameterRequestTagProcessor processor : noAnnotatedParameterRequestTagProcessors) { - processor.process(parameter, parameterIndex, metadata); + // no annotation only one default annotationType + if (processor.process(parameter, parameterIndex, metadata)) { + return; + } } - return; + } for (Annotation annotation : annotations) { diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/ArgInfo.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/ArgInfo.java index 599726e1e0..e4f9693083 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/ArgInfo.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/ArgInfo.java @@ -19,6 +19,9 @@ package org.apache.dubbo.metadata.rest; import java.lang.reflect.Parameter; +/** + * description of service method args info + */ public class ArgInfo { /** * method arg index 0,1,2,3 @@ -148,4 +151,18 @@ public class ArgInfo { this.formContentType = isFormContentType; return this; } + + @Override + public String toString() { + return "ArgInfo{" + + "index=" + index + + ", annotationNameAttribute='" + annotationNameAttribute + '\'' + + ", paramAnnotationType=" + paramAnnotationType + + ", paramType=" + paramType + + ", paramName='" + paramName + '\'' + + ", urlSplitIndex=" + urlSplitIndex + + ", defaultValue=" + defaultValue + + ", formContentType=" + formContentType + + '}'; + } } diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/NoAnnotatedParameterRequestTagProcessor.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/NoAnnotatedParameterRequestTagProcessor.java index 70f673c7e7..376e224524 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/NoAnnotatedParameterRequestTagProcessor.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/NoAnnotatedParameterRequestTagProcessor.java @@ -28,5 +28,5 @@ public interface NoAnnotatedParameterRequestTagProcessor { String defaultAnnotationClassName(RestMethodMetadata restMethodMetadata); - void process(Parameter parameter, int parameterIndex, RestMethodMetadata restMethodMetadata); + boolean process(Parameter parameter, int parameterIndex, RestMethodMetadata restMethodMetadata); } diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/ParamType.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/ParamType.java index f517794d63..43902cfd6e 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/ParamType.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/ParamType.java @@ -16,8 +16,8 @@ */ package org.apache.dubbo.metadata.rest; -import org.apache.dubbo.metadata.MetadataConstants; import org.apache.dubbo.metadata.rest.tag.BodyTag; +import org.apache.dubbo.metadata.rest.tag.ParamTag; import java.util.ArrayList; import java.util.List; @@ -27,21 +27,17 @@ public enum ParamType { SpringMvcClassConstants.REQUEST_HEADER_ANNOTATION_CLASS)), PARAM(addSupportTypes(JAXRSClassConstants.QUERY_PARAM_ANNOTATION_CLASS, - SpringMvcClassConstants.REQUEST_PARAM_ANNOTATION_CLASS)), + SpringMvcClassConstants.REQUEST_PARAM_ANNOTATION_CLASS, ParamTag.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)), + FORM(addSupportTypes(JAXRSClassConstants.FORM_PARAM_ANNOTATION_CLASS, + SpringMvcClassConstants.REQUEST_BODY_ANNOTATION_CLASS)), EMPTY(addSupportTypes()); private List annotationClasses; @@ -59,16 +55,6 @@ public enum ParamType { 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 * diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/PathMatcher.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/PathMatcher.java index 6e7dcdcbe9..12d0644ac8 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/PathMatcher.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/PathMatcher.java @@ -19,22 +19,34 @@ package org.apache.dubbo.metadata.rest; import java.util.Objects; +/** + * for http request path match + */ public class PathMatcher { private static final String SEPARATOR = "/"; private String path; - private String version; - private String group; - private Integer port; + private String version;// service version + private String group;// service group + private Integer port;// service port private String[] pathSplits; private boolean hasPathVariable; + private String contextPath; public PathMatcher(String path) { - this(path, null, null, 0); + this(path, null, null, null); } public PathMatcher(String path, String version, String group, Integer port) { this.path = path; + dealPathVariable(path); + this.version = version; + this.group = group; + this.port = (port == null || port == -1 || port == 0) ? null : port; + } + + + private void dealPathVariable(String path) { this.pathSplits = path.split(SEPARATOR); for (String pathSplit : pathSplits) { @@ -44,12 +56,9 @@ public class PathMatcher { break; } } - this.version = version; - this.group = group; - this.port = port; } - public void setPath(String path) { + private void setPath(String path) { this.path = path; } @@ -65,13 +74,39 @@ public class PathMatcher { this.port = port; } + public void setContextPath(String contextPath) { + + + contextPath = contextPathFormat(contextPath); + + + this.contextPath = contextPath; + + setPath(contextPath + path); + + dealPathVariable(path); + + } + + public static PathMatcher getInvokeCreatePathMatcher(String path, String version, String group, Integer port) { + return new PathMatcher(path, version, group, port); + } + + public boolean hasPathVariable() { + return hasPathVariable; + } + + public Integer getPort() { + return 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) + return pathEqual(that) + && Objects.equals(version, that.version) && Objects.equals(group, that.group) && Objects.equals(port, that.port); } @@ -80,26 +115,28 @@ public class PathMatcher { return Objects.hash(version, group, port); } - private boolean pathEqual(String path) { + private boolean pathEqual(PathMatcher pathMatcher) { + // no place hold - if (!hasPathVariable) { - return this.path.equals(path); + if (!pathMatcher.hasPathVariable) { + return this.path.equals(pathMatcher.path); } - String[] split = path.split(SEPARATOR); + String[] pathSplits = pathMatcher.pathSplits; + String[] thisPathSplits = this.pathSplits; - if (split.length != pathSplits.length) { + if (thisPathSplits.length != pathSplits.length) { return false; } for (int i = 0; i < pathSplits.length; i++) { - boolean equals = split[i].equals(pathSplits[i]); + boolean equals = thisPathSplits[i].equals(pathSplits[i]); if (equals) { continue; } else { - if (placeHoldCompare(pathSplits[i])) { + if (placeHoldCompare(pathSplits[i], thisPathSplits[i])) { continue; } else { return false; @@ -111,8 +148,8 @@ public class PathMatcher { } - private boolean placeHoldCompare(String pathSplit) { - boolean startAndEndEqual = isPlaceHold(pathSplit); + private boolean placeHoldCompare(String pathSplit, String pathToCompare) { + boolean startAndEndEqual = isPlaceHold(pathSplit) || isPlaceHold(pathToCompare); // start { end } if (!startAndEndEqual) { @@ -120,7 +157,7 @@ public class PathMatcher { } // exclude {} - boolean lengthCondition = pathSplit.length() >= 3; + boolean lengthCondition = pathSplit.length() >= 3 || pathToCompare.length() >= 3; if (!lengthCondition) { return false; @@ -134,13 +171,35 @@ public class PathMatcher { } + private String contextPathFormat(String contextPath) { + + + if (contextPath == null || contextPath.equals(SEPARATOR) || contextPath.length() == 0) { + return ""; + } + + + return pathFormat(contextPath); + } + + private String pathFormat(String path) { + if (path.startsWith(SEPARATOR)) { + return path; + } else { + return SEPARATOR + path; + } + } + + @Override public String toString() { - return "PathMather{" + + return "PathMatcher{" + "path='" + path + '\'' + ", version='" + version + '\'' + ", group='" + group + '\'' + - ", port='" + port + '\'' + + ", port=" + port + + ", hasPathVariable=" + hasPathVariable + + ", contextPath='" + contextPath + '\'' + '}'; } } diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/RequestMetadata.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/RequestMetadata.java index 77d2d4d2bb..66ff17f5ad 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/RequestMetadata.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/RequestMetadata.java @@ -32,6 +32,7 @@ import java.util.Set; import static java.util.Collections.unmodifiableMap; import static org.apache.dubbo.common.utils.PathUtils.normalize; +import static org.apache.dubbo.common.utils.StringUtils.SLASH; import static org.apache.dubbo.common.utils.StringUtils.isBlank; /** @@ -76,6 +77,11 @@ public class RequestMetadata implements Serializable { public void setPath(String path) { this.path = normalize(path); + + if (!path.startsWith(SLASH)) { + this.path = SLASH + path; + } + } public Map> getParams() { @@ -193,6 +199,13 @@ public class RequestMetadata implements Serializable { return this; } + public void appendContextPathFromUrl(String contextPathFromUrl) { + if (contextPathFromUrl == null) { + return; + } + setPath(contextPathFromUrl + path); + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/RestMethodMetadata.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/RestMethodMetadata.java index a2c718c563..0abc5ebd22 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/RestMethodMetadata.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/RestMethodMetadata.java @@ -57,8 +57,6 @@ public class RestMethodMetadata implements Serializable { private Map indexToEncoded; - private ServiceRestMetadata serviceRestMetadata; - private List argInfos; private Method reflectMethod; @@ -169,15 +167,6 @@ public class RestMethodMetadata implements Serializable { this.indexToEncoded = indexToEncoded; } - - public ServiceRestMetadata getServiceRestMetadata() { - return serviceRestMetadata; - } - - public void setServiceRestMetadata(ServiceRestMetadata serviceRestMetadata) { - this.serviceRestMetadata = serviceRestMetadata; - } - public List getArgInfos() { if (argInfos == null) { argInfos = new ArrayList<>(); @@ -232,17 +221,19 @@ public class RestMethodMetadata implements Serializable { @Override public String toString() { - final StringBuilder sb = new StringBuilder("RestMethodMetadata{"); - sb.append("method=").append(method); - sb.append(", request=").append(request); - sb.append(", urlIndex=").append(urlIndex); - sb.append(", bodyIndex=").append(bodyIndex); - sb.append(", headerMapIndex=").append(headerMapIndex); - sb.append(", bodyType='").append(bodyType).append('\''); - sb.append(", indexToName=").append(indexToName); - sb.append(", formParams=").append(formParams); - sb.append(", indexToEncoded=").append(indexToEncoded); - sb.append('}'); - return sb.toString(); + return "RestMethodMetadata{" + + "method=" + method + + ", request=" + request + + ", urlIndex=" + urlIndex + + ", bodyIndex=" + bodyIndex + + ", headerMapIndex=" + headerMapIndex + + ", bodyType='" + bodyType + '\'' + + ", indexToName=" + indexToName + + ", formParams=" + formParams + + ", indexToEncoded=" + indexToEncoded + + ", argInfos=" + argInfos + + ", reflectMethod=" + reflectMethod + + ", codeStyle=" + codeStyle + + '}'; } } diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/ServiceRestMetadata.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/ServiceRestMetadata.java index 19f0aab77b..55970c7fe7 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/ServiceRestMetadata.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/ServiceRestMetadata.java @@ -16,6 +16,7 @@ */ package org.apache.dubbo.metadata.rest; +import org.apache.dubbo.common.utils.PathUtils; import org.apache.dubbo.metadata.ParameterTypesComparator; import java.io.Serializable; @@ -47,12 +48,15 @@ public class ServiceRestMetadata implements Serializable { private boolean consumer; + private String contextPathFromUrl; + /** * make a distinction between mvc & resteasy */ private Class codeStyle; - private Map pathToServiceMap = new HashMap<>(); + private Map pathToServiceMapContainPathVariable = new HashMap<>(); + private Map pathToServiceMapUnContainPathVariable = new HashMap<>(); private Map> methodToServiceMap = new HashMap<>(); public ServiceRestMetadata(String serviceInterface, String version, String group, boolean consumer) { @@ -105,7 +109,6 @@ public class ServiceRestMetadata implements Serializable { } public void addRestMethodMetadata(RestMethodMetadata restMethodMetadata) { - restMethodMetadata.setServiceRestMetadata(this); PathMatcher pathMather = new PathMatcher(restMethodMetadata.getRequest().getPath(), this.getVersion(), this.getGroup(), this.getPort()); addPathToServiceMap(pathMather, restMethodMetadata); @@ -113,28 +116,57 @@ public class ServiceRestMetadata implements Serializable { getMeta().add(restMethodMetadata); } - public Map getPathToServiceMap() { - return pathToServiceMap; + + public Map getPathContainPathVariableToServiceMap() { + return pathToServiceMapContainPathVariable; + } + + public Map getPathUnContainPathVariableToServiceMap() { + return pathToServiceMapUnContainPathVariable; } public void addPathToServiceMap(PathMatcher pathMather, RestMethodMetadata restMethodMetadata) { - if (this.pathToServiceMap == null) { - this.pathToServiceMap = new HashMap<>(); - } - this.pathToServiceMap.put(pathMather, restMethodMetadata); + if (pathMather.hasPathVariable()) { + doublePathCheck(pathToServiceMapContainPathVariable, pathMather, restMethodMetadata, true); + } else { + doublePathCheck(pathToServiceMapUnContainPathVariable, pathMather, restMethodMetadata, false); + } } + private void doublePathCheck(Map pathMatcherRestMethodMetadataMap, + PathMatcher pathMather, + RestMethodMetadata restMethodMetadata, boolean containPathVariable) { + if (pathMatcherRestMethodMetadataMap.containsKey(pathMather)) { + if (containPathVariable) { + throw new IllegalArgumentException("dubbo rest metadata resolve double path error,and contain path variable is: " + + pathMather + ", rest method metadata is: " + restMethodMetadata); + + } else { + throw new IllegalArgumentException("dubbo rest metadata resolve double path error,and do not contain path variable is: " + + pathMather + ", rest method metadata is: " + restMethodMetadata); + } + } + + pathMatcherRestMethodMetadataMap.put(pathMather, restMethodMetadata); + + } + + public Integer getPort() { return port; } public void setPort(Integer port) { this.port = port; - Map pathToServiceMap = getPathToServiceMap(); - for (PathMatcher pathMather : pathToServiceMap.keySet()) { + setPort(port, getPathContainPathVariableToServiceMap()); + setPort(port, getPathUnContainPathVariableToServiceMap()); + } + + private void setPort(Integer port, Map pathToServiceMapContainPathVariable) { + for (PathMatcher pathMather : pathToServiceMapContainPathVariable.keySet()) { pathMather.setPort(port); } } @@ -168,6 +200,14 @@ public class ServiceRestMetadata implements Serializable { this.codeStyle = codeStyle; } + public String getContextPathFromUrl() { + return contextPathFromUrl == null ? "" : contextPathFromUrl; + } + + public void setContextPathFromUrl(String contextPathFromUrl) { + this.contextPathFromUrl = PathUtils.normalize(contextPathFromUrl); + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/media/MediaType.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/media/MediaType.java index 8b5c96e405..2414e4ffae 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/media/MediaType.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/media/MediaType.java @@ -17,6 +17,9 @@ package org.apache.dubbo.metadata.rest.media; +import java.util.Arrays; +import java.util.List; + public enum MediaType { ALL_VALUE("*/*"), APPLICATION_JSON_VALUE("application/json"), @@ -43,4 +46,11 @@ public enum MediaType { } return stringBuilder.toString(); } + + public static List getSupportMediaTypes() { + return Arrays.asList(APPLICATION_JSON_VALUE, + APPLICATION_FORM_URLENCODED_VALUE, + TEXT_PLAIN,TEXT_XML,OCTET_STREAM); + } + } diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/springmvc/FormBodyNoAnnotatedProcessor.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/springmvc/FormBodyNoAnnotatedProcessor.java index 60bcbc9265..38dc88663f 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/springmvc/FormBodyNoAnnotatedProcessor.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/springmvc/FormBodyNoAnnotatedProcessor.java @@ -19,8 +19,8 @@ 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 org.apache.dubbo.metadata.rest.tag.BodyTag; -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 { @@ -31,7 +31,7 @@ public class FormBodyNoAnnotatedProcessor extends AbstractNoAnnotatedParameterPr @Override public String defaultAnnotationClassName(RestMethodMetadata restMethodMetadata) { - return REQUEST_BODY_ANNOTATION_CLASS_NAME; + return BodyTag.class.getName(); } @Override diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/springmvc/JsonBodyNoAnnotatedProcessor.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/springmvc/JsonBodyNoAnnotatedProcessor.java index 7fc55d5538..64df7adf51 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/springmvc/JsonBodyNoAnnotatedProcessor.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/springmvc/JsonBodyNoAnnotatedProcessor.java @@ -19,8 +19,8 @@ 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 org.apache.dubbo.metadata.rest.tag.BodyTag; -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 { @@ -31,6 +31,6 @@ public class JsonBodyNoAnnotatedProcessor extends AbstractNoAnnotatedParameterPr @Override public String defaultAnnotationClassName(RestMethodMetadata restMethodMetadata) { - return REQUEST_BODY_ANNOTATION_CLASS_NAME; + return BodyTag.class.getName(); } } diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/springmvc/ParamNoAnnotatedProcessor.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/springmvc/ParamNoAnnotatedProcessor.java index b0591e4886..a591c28961 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/springmvc/ParamNoAnnotatedProcessor.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/springmvc/ParamNoAnnotatedProcessor.java @@ -21,8 +21,8 @@ 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 org.apache.dubbo.metadata.rest.tag.ParamTag; -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 { @@ -38,6 +38,6 @@ public class ParamNoAnnotatedProcessor extends AbstractNoAnnotatedParameterProce return BodyTag.class.getName(); } - return REQUEST_PARAM_ANNOTATION_CLASS_NAME; + return ParamTag.class.getName(); } } diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/tag/BodyTag.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/tag/BodyTag.java index 67dd291a8e..a1038c8718 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/tag/BodyTag.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/tag/BodyTag.java @@ -16,5 +16,8 @@ */ package org.apache.dubbo.metadata.rest.tag; -public class BodyTag { +/** + * for @RequestBody class no found + */ +public interface BodyTag { } diff --git a/dubbo-test/dubbo-test-common/src/main/java/org/apache/dubbo/test/common/api/SpringmvcDemoService.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/tag/ParamTag.java similarity index 85% rename from dubbo-test/dubbo-test-common/src/main/java/org/apache/dubbo/test/common/api/SpringmvcDemoService.java rename to dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/tag/ParamTag.java index 18726a05d1..66249b43aa 100644 --- a/dubbo-test/dubbo-test-common/src/main/java/org/apache/dubbo/test/common/api/SpringmvcDemoService.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/rest/tag/ParamTag.java @@ -14,8 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.test.common.api; +package org.apache.dubbo.metadata.rest.tag; - -public interface SpringmvcDemoService { +/** + * for @RequestParam or @QueryParam class no found + */ +public interface ParamTag { } diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/PathMatcherTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/PathMatcherTest.java index e2498c2b95..8e56cc202d 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/PathMatcherTest.java +++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/PathMatcherTest.java @@ -23,10 +23,46 @@ import org.junit.jupiter.api.Test; public class PathMatcherTest { @Test - public void testPathMatcher() { - PathMatcher pathMather = new PathMatcher("/a/b/c/{path1}/d/{path2}/e"); + void testPathMatcher() { + PathMatcher pathMatherMeta = 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)); + + PathMatcher requestPathMather = new PathMatcher("/a/b/c/1/d/2/e"); + Assertions.assertEquals(requestPathMather, pathMatherMeta); + + PathMatcher requestPathMather1 = new PathMatcher("/{c}/b/c/1/d/2/e"); + Assertions.assertEquals(requestPathMather, requestPathMather1); + + PathMatcher pathMatcher = new PathMatcher("/{d}/b/c/1/d/2/e"); + + pathMatcher.setGroup(null); + pathMatcher.setPort(null); + pathMatcher.setVersion(null); + pathMatcher.setContextPath(""); + + Assertions.assertEquals(pathMatherMeta, pathMatcher); + } + + @Test + void testEqual() { + PathMatcher pathMatherMeta = new PathMatcher("/a/b/c"); + pathMatherMeta.setContextPath("/context"); + PathMatcher pathMatherMeta1 = new PathMatcher("/a/b/d"); + + pathMatherMeta1.setContextPath("/context"); + Assertions.assertNotEquals(pathMatherMeta, pathMatherMeta1); + + pathMatherMeta1 = new PathMatcher("/a/b/c"); + pathMatherMeta1.setContextPath("/context"); + + Assertions.assertEquals(pathMatherMeta, pathMatherMeta1); + + pathMatherMeta.setContextPath("context"); + + pathMatherMeta1.setContextPath("context"); + + + Assertions.assertEquals(pathMatherMeta, pathMatherMeta1); + Assertions.assertEquals(pathMatherMeta.toString(), pathMatherMeta1.toString()); } } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/TypeUtil.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/TestMediaType.java similarity index 62% rename from dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/TypeUtil.java rename to dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/TestMediaType.java index 9d768affc2..f81509e0ca 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/TypeUtil.java +++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/TestMediaType.java @@ -14,25 +14,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.rpc.protocol.rest.util; +package org.apache.dubbo.metadata; -public class TypeUtil { +import org.apache.dubbo.metadata.rest.media.MediaType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; - public static boolean isNumber(Class clazz) { - return Number.class.isAssignableFrom(clazz); +public class TestMediaType { + + @Test + void testGetAll() { + Assertions.assertDoesNotThrow(() -> { + MediaType.getAllContentType(); + MediaType.getSupportMediaTypes(); + }); } - - public static boolean isPrimitive(Class clazz) { - return clazz.isPrimitive(); - } - - public static boolean isString(Class clazz) { - return clazz == String.class; - } - - public static boolean isNumberType(Class clazz) { - return clazz.isPrimitive() || isNumber(clazz); - } - - } diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/api/JaxrsRestDoubleCheckContainsPathVariableService.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/api/JaxrsRestDoubleCheckContainsPathVariableService.java new file mode 100644 index 0000000000..8f5e7c1e11 --- /dev/null +++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/api/JaxrsRestDoubleCheckContainsPathVariableService.java @@ -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.api; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; + +@Path("/test") +public interface JaxrsRestDoubleCheckContainsPathVariableService { + @Path("/a/{b}") + @Consumes(MediaType.TEXT_PLAIN) + @Produces(MediaType.TEXT_PLAIN) + @GET + String param(@QueryParam("param") String param); + + + @Path("/{b}/b") + @Consumes(MediaType.TEXT_PLAIN) + @Produces(MediaType.TEXT_PLAIN) + @GET + String header(@HeaderParam("header") String header); +} diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/api/JaxrsRestDoubleCheckService.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/api/JaxrsRestDoubleCheckService.java new file mode 100644 index 0000000000..ad0b3658c9 --- /dev/null +++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/api/JaxrsRestDoubleCheckService.java @@ -0,0 +1,39 @@ +/* + * 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 javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +@Path("/test") +public interface JaxrsRestDoubleCheckService { + @Path("/param") + @Consumes(MediaType.TEXT_PLAIN) + @Produces(MediaType.TEXT_PLAIN) + @GET + String param(@QueryParam("param") String param); + + @Path("/param") + @Consumes(MediaType.TEXT_PLAIN) + @Produces(MediaType.TEXT_PLAIN) + @GET + String header(@HeaderParam("header") String header); +} diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/api/JaxrsRestService.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/api/JaxrsRestService.java index ec25773e71..ff9909ddb2 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/api/JaxrsRestService.java +++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/api/JaxrsRestService.java @@ -61,5 +61,11 @@ public interface JaxrsRestService { @POST String pathVariable(@PathParam("a") String a); + @Path("/noAnno") + @Consumes(MediaType.TEXT_PLAIN) + @Produces(MediaType.TEXT_PLAIN) + @POST + String noAnno(String a); + } diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/api/JaxrsRestServiceImpl.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/api/JaxrsRestServiceImpl.java index b38dd83b3b..62cf3578ef 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/api/JaxrsRestServiceImpl.java +++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/api/JaxrsRestServiceImpl.java @@ -47,4 +47,9 @@ public class JaxrsRestServiceImpl implements JaxrsRestService { public String pathVariable(String a) { return a; } + + @Override + public String noAnno(String a) { + return a; + } } diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/api/SpringRestService.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/api/SpringRestService.java index 00639e8c31..3a6e591df1 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/api/SpringRestService.java +++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/api/SpringRestService.java @@ -38,7 +38,7 @@ public interface SpringRestService { @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) + @RequestMapping(value = "/body", 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) @@ -48,5 +48,14 @@ public interface SpringRestService { @RequestMapping(value = "/pathVariable/{a}", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE) String pathVariable(@PathVariable String a); + @RequestMapping(value = "/noAnnoParam", method = RequestMethod.POST, consumes = MediaType.TEXT_PLAIN_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + String noAnnoParam(String a); + + @RequestMapping(value = "/noAnnoNumber", method = RequestMethod.POST, consumes = MediaType.ALL_VALUE, produces = MediaType.ALL_VALUE) + int noAnnoNumber(Integer b); + + + @RequestMapping(value = "/noAnnoPrimitive", method = RequestMethod.POST, consumes = MediaType.ALL_VALUE, produces = MediaType.ALL_VALUE) + int noAnnoPrimitive(int c); } diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/api/SpringRestServiceImpl.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/api/SpringRestServiceImpl.java index 8262d4fe66..3176f158b8 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/api/SpringRestServiceImpl.java +++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/api/SpringRestServiceImpl.java @@ -50,4 +50,19 @@ public class SpringRestServiceImpl implements SpringRestService { return a; } + @Override + public String noAnnoParam(String a) { + return a; + } + + @Override + public int noAnnoNumber(Integer b) { + return b; + } + + @Override + public int noAnnoPrimitive(int c) { + return c; + } + } diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/jaxrs/JAXRSServiceRestMetadataResolverTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/jaxrs/JAXRSServiceRestMetadataResolverTest.java index 98713cfd06..cbf94fa5b2 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/jaxrs/JAXRSServiceRestMetadataResolverTest.java +++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/jaxrs/JAXRSServiceRestMetadataResolverTest.java @@ -95,14 +95,13 @@ class JAXRSServiceRestMetadataResolverTest { // Generated by "dubbo-metadata-processor" List jsons = Arrays.asList( - + "{\"argInfos\":[{\"annotationNameAttribute\":\"a\",\"formContentType\":false,\"index\":0,\"paramAnnotationType\":\"org.apache.dubbo.metadata.rest.tag.BodyTag\",\"paramName\":\"a\",\"paramType\":\"java.lang.String\",\"urlSplitIndex\":0}],\"codeStyle\":\"org.apache.dubbo.metadata.rest.jaxrs.JAXRSServiceRestMetadataResolver\",\"indexToName\":{0:[\"a\"]},\"method\":{\"annotations\":[],\"parameters\":[]},\"request\":{\"consumes\":[\"text/plain\"],\"headerNames\":[],\"headers\":{},\"method\":\"POST\",\"paramNames\":[],\"params\":{},\"path\":\"/noAnno\",\"produces\":[\"text/plain\"]}}", "{\"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\":\"map\",\"formContentType\":true,\"index\":0,\"paramAnnotationType\":\"org.apache.dubbo.metadata.rest.tag.BodyTag\",\"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.apache.dubbo.metadata.rest.tag.BodyTag\",\"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(); @@ -112,7 +111,6 @@ class JAXRSServiceRestMetadataResolverTest { List jsonsTmp = new ArrayList<>(); for (RestMethodMetadata restMethodMetadata : jaxrsMetadata.getMeta()) { - restMethodMetadata.setServiceRestMetadata(null); restMethodMetadata.setReflectMethod(null); restMethodMetadata.setMethod(null); jsonsTmp.add(JsonUtils.getJson().toJson(restMethodMetadata)); @@ -136,7 +134,7 @@ class JAXRSServiceRestMetadataResolverTest { @Test - public void testJaxrsPathPattern() { + void testJaxrsPathPattern() { Class service = AnotherUserRestService.class; ServiceRestMetadata jaxrsRestMetadata = new ServiceRestMetadata(); jaxrsRestMetadata.setServiceInterface(service.getName()); @@ -151,7 +149,7 @@ class JAXRSServiceRestMetadataResolverTest { } - Assertions.assertEquals("/u/1", PathUtil.resolvePathVariable("u/{id : \\d+}", object.getArgInfos(), Arrays.asList(1))); + Assertions.assertEquals("/u/1", PathUtil.resolvePathVariable("/u/{id : \\d+}", object.getArgInfos(), Arrays.asList(1))); } } diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/jaxrs/JaxrsRestDoubleCheckTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/jaxrs/JaxrsRestDoubleCheckTest.java new file mode 100644 index 0000000000..97ce07d5ec --- /dev/null +++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/jaxrs/JaxrsRestDoubleCheckTest.java @@ -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.metadata.rest.jaxrs; + +import org.apache.dubbo.metadata.rest.ServiceRestMetadata; +import org.apache.dubbo.metadata.rest.api.JaxrsRestDoubleCheckContainsPathVariableService; +import org.apache.dubbo.metadata.rest.api.JaxrsRestDoubleCheckService; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class JaxrsRestDoubleCheckTest { + private JAXRSServiceRestMetadataResolver instance = new JAXRSServiceRestMetadataResolver(ApplicationModel.defaultModel()); + + + @Test + void testDoubleCheckException() { + + Assertions.assertThrows(IllegalArgumentException.class, () -> { + ServiceRestMetadata resolve = new ServiceRestMetadata(); + resolve.setServiceInterface(JaxrsRestDoubleCheckService.class.getName()); + instance.resolve(JaxrsRestDoubleCheckService.class, resolve); + }); + + Assertions.assertThrows(IllegalArgumentException.class, () -> { + ServiceRestMetadata resolve = new ServiceRestMetadata(); + resolve.setServiceInterface(JaxrsRestDoubleCheckContainsPathVariableService.class.getName()); + instance.resolve(JaxrsRestDoubleCheckContainsPathVariableService.class, resolve); + }); + + + } + +} diff --git a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/springmvc/SpringMvcServiceRestMetadataResolverTest.java b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/springmvc/SpringMvcServiceRestMetadataResolverTest.java index b48c77ad1a..eb5da11a56 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/springmvc/SpringMvcServiceRestMetadataResolverTest.java +++ b/dubbo-metadata/dubbo-metadata-api/src/test/java/org/apache/dubbo/metadata/rest/springmvc/SpringMvcServiceRestMetadataResolverTest.java @@ -20,6 +20,7 @@ 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.PathMatcher; import org.apache.dubbo.metadata.rest.RestMethodMetadata; import org.apache.dubbo.metadata.rest.RestService; import org.apache.dubbo.metadata.rest.ServiceRestMetadata; @@ -28,6 +29,7 @@ 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.Assertions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -35,6 +37,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; +import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -87,9 +90,11 @@ class SpringMvcServiceRestMetadataResolverTest { void testResolve(Class service) { List jsons = Arrays.asList( - + "{\"argInfos\":[{\"annotationNameAttribute\":\"b\",\"formContentType\":false,\"index\":0,\"paramAnnotationType\":\"org.apache.dubbo.metadata.rest.tag.ParamTag\",\"paramName\":\"b\",\"paramType\":\"java.lang.Integer\",\"urlSplitIndex\":0}],\"codeStyle\":\"org.apache.dubbo.metadata.rest.springmvc.SpringMvcServiceRestMetadataResolver\",\"indexToName\":{0:[\"b\"]},\"method\":{\"annotations\":[],\"parameters\":[]},\"request\":{\"consumes\":[\"*/*\"],\"headerNames\":[],\"headers\":{},\"method\":\"POST\",\"paramNames\":[],\"params\":{},\"path\":\"/noAnnoNumber\",\"produces\":[\"*/*\"]}}", + "{\"argInfos\":[{\"annotationNameAttribute\":\"c\",\"formContentType\":false,\"index\":0,\"paramAnnotationType\":\"org.apache.dubbo.metadata.rest.tag.ParamTag\",\"paramName\":\"c\",\"paramType\":\"int\",\"urlSplitIndex\":0}],\"codeStyle\":\"org.apache.dubbo.metadata.rest.springmvc.SpringMvcServiceRestMetadataResolver\",\"indexToName\":{0:[\"c\"]},\"method\":{\"annotations\":[],\"parameters\":[]},\"request\":{\"consumes\":[\"*/*\"],\"headerNames\":[],\"headers\":{},\"method\":\"POST\",\"paramNames\":[],\"params\":{},\"path\":\"/noAnnoPrimitive\",\"produces\":[\"*/*\"]}}", + "{\"argInfos\":[{\"annotationNameAttribute\":\"a\",\"formContentType\":false,\"index\":0,\"paramAnnotationType\":\"org.apache.dubbo.metadata.rest.tag.ParamTag\",\"paramName\":\"a\",\"paramType\":\"java.lang.String\",\"urlSplitIndex\":0}],\"codeStyle\":\"org.apache.dubbo.metadata.rest.springmvc.SpringMvcServiceRestMetadataResolver\",\"indexToName\":{0:[\"a\"]},\"method\":{\"annotations\":[],\"parameters\":[]},\"request\":{\"consumes\":[\"text/plain\"],\"headerNames\":[],\"headers\":{},\"method\":\"POST\",\"paramNames\":[],\"params\":{},\"path\":\"/noAnnoParam\",\"produces\":[\"text/plain\"]}}", "{\"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\":\"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\":\"/body\",\"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\"]}}" @@ -104,7 +109,6 @@ class SpringMvcServiceRestMetadataResolverTest { List jsonsTmp = new ArrayList<>(); for (RestMethodMetadata restMethodMetadata : springMetadata.getMeta()) { - restMethodMetadata.setServiceRestMetadata(null); restMethodMetadata.setReflectMethod(null); restMethodMetadata.setMethod(null); jsonsTmp.add(JsonUtils.getJson().toJson(restMethodMetadata)); @@ -125,4 +129,34 @@ class SpringMvcServiceRestMetadataResolverTest { } } + + @Test + void testDoubleCheck() { + + ServiceRestMetadata springRestMetadata = new ServiceRestMetadata(); + springRestMetadata.setServiceInterface(SpringRestServiceImpl.class.getName()); + ServiceRestMetadata springMetadata = instance.resolve(SpringRestServiceImpl.class, springRestMetadata); + + springMetadata.setContextPathFromUrl("context"); + + Assertions.assertEquals("context", springMetadata.getContextPathFromUrl()); + + springMetadata.setContextPathFromUrl("//context"); + Assertions.assertEquals("/context", springMetadata.getContextPathFromUrl()); + springMetadata.setPort(404); + Map pathContainPathVariableToServiceMap = springMetadata.getPathContainPathVariableToServiceMap(); + + for (PathMatcher pathMatcher : pathContainPathVariableToServiceMap.keySet()) { + Assertions.assertTrue(pathMatcher.hasPathVariable()); + Assertions.assertEquals(404, pathMatcher.getPort()); + } + + + Map pathUnContainPathVariableToServiceMap = springMetadata.getPathUnContainPathVariableToServiceMap(); + + for (PathMatcher pathMatcher : pathUnContainPathVariableToServiceMap.keySet()) { + Assertions.assertFalse(pathMatcher.hasPathVariable()); + Assertions.assertEquals(404, pathMatcher.getPort()); + } + } } diff --git a/dubbo-metadata/dubbo-metadata-processor/src/main/java/org/apache/dubbo/metadata/annotation/processing/rest/AbstractServiceRestMetadataResolver.java b/dubbo-metadata/dubbo-metadata-processor/src/main/java/org/apache/dubbo/metadata/annotation/processing/rest/AbstractServiceRestMetadataResolver.java index c4d85e98a9..a52bd25710 100644 --- a/dubbo-metadata/dubbo-metadata-processor/src/main/java/org/apache/dubbo/metadata/annotation/processing/rest/AbstractServiceRestMetadataResolver.java +++ b/dubbo-metadata/dubbo-metadata-processor/src/main/java/org/apache/dubbo/metadata/annotation/processing/rest/AbstractServiceRestMetadataResolver.java @@ -94,8 +94,8 @@ public abstract class AbstractServiceRestMetadataResolver implements ServiceRest sort(serviceMethods, ExecutableElementComparator.INSTANCE); serviceMethods.forEach(serviceMethod -> { - resolveRestMethodMetadata(processingEnv, serviceType, serviceInterfaceType, serviceMethod) - .ifPresent(serviceRestMetadata.getMeta()::add); + resolveRestMethodMetadata(processingEnv, serviceType, serviceInterfaceType, serviceMethod, serviceRestMetadata) + .ifPresent(serviceRestMetadata.getMeta()::add); }); } finally { @@ -110,7 +110,8 @@ public abstract class AbstractServiceRestMetadataResolver implements ServiceRest protected Optional resolveRestMethodMetadata(ProcessingEnvironment processingEnv, TypeElement serviceType, TypeElement serviceInterfaceType, - ExecutableElement serviceMethod) { + ExecutableElement serviceMethod, + ServiceRestMetadata serviceRestMetadata) { ExecutableElement restCapableMethod = findRestCapableMethod(processingEnv, serviceType, serviceInterfaceType, serviceMethod); @@ -150,6 +151,8 @@ public abstract class AbstractServiceRestMetadataResolver implements ServiceRest // Initialize RequestMetadata RequestMetadata request = metadata.getRequest(); request.setPath(requestPath); + request.appendContextPathFromUrl(serviceRestMetadata.getContextPathFromUrl()); + request.setMethod(requestMethod); request.setProduces(produces); request.setConsumes(consumes); @@ -237,9 +240,9 @@ public abstract class AbstractServiceRestMetadataResolver implements ServiceRest parameter.getAnnotationMirrors().forEach(annotation -> { String annotationType = annotation.getAnnotationType().toString(); parameterProcessorsMap.getOrDefault(annotationType, emptyList()) - .forEach(parameterProcessor -> { - parameterProcessor.process(annotation, parameter, parameterIndex, method, metadata); - }); + .forEach(parameterProcessor -> { + parameterProcessor.process(annotation, parameter, parameterIndex, method, metadata); + }); }); } diff --git a/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/model/MetricsKey.java b/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/model/MetricsKey.java index adc558ef9c..578b0b89a5 100644 --- a/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/model/MetricsKey.java +++ b/dubbo-metrics/dubbo-metrics-api/src/main/java/org/apache/dubbo/metrics/model/MetricsKey.java @@ -132,6 +132,12 @@ public enum MetricsKey { return String.format(name, type); } + + public final MetricsKey formatName(String type) { + this.name = String.format(name, type); + return this; + } + public final String getDescription() { return this.description; } diff --git a/dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/filter/MetricsFilterTest.java b/dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/filter/MetricsFilterTest.java index be10b9fd7a..c94e8b2af8 100644 --- a/dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/filter/MetricsFilterTest.java +++ b/dubbo-metrics/dubbo-metrics-default/src/test/java/org/apache/dubbo/metrics/filter/MetricsFilterTest.java @@ -84,7 +84,7 @@ class MetricsFilterTest { filter = new MetricsFilter(); collector = applicationModel.getBeanFactory().getOrRegisterBean(DefaultMetricsCollector.class); - if (!initApplication.get()) { + if(!initApplication.get()) { collector.collectApplication(applicationModel); initApplication.set(true); } @@ -264,14 +264,14 @@ class MetricsFilterTest { } @Test - public void testErrors() { - testFilterError(RpcException.SERIALIZATION_EXCEPTION, - MetricsKey.METRIC_REQUESTS_CODEC_FAILED.getNameByType(side)); - testFilterError(RpcException.NETWORK_EXCEPTION, - MetricsKey.METRIC_REQUESTS_NETWORK_FAILED.getNameByType(side)); + public void testErrors(){ + testFilterError(RpcException.SERIALIZATION_EXCEPTION, MetricsKey.METRIC_REQUESTS_CODEC_FAILED.formatName(side)); + testFilterError(RpcException.NETWORK_EXCEPTION, MetricsKey.METRIC_REQUESTS_NETWORK_FAILED.formatName(side)); } - private void testFilterError(int errorCode, String name) { + + + private void testFilterError(int errorCode,MetricsKey metricsKey){ setup(); collector.setCollectEnabled(true); given(invoker.invoke(invocation)).willThrow(new RpcException(errorCode)); @@ -288,14 +288,14 @@ class MetricsFilterTest { } } Map metricsMap = getMetricsMap(); - Assertions.assertTrue(metricsMap.containsKey(name)); + Assertions.assertTrue(metricsMap.containsKey(metricsKey.getName())); - MetricSample sample = metricsMap.get(name); + MetricSample sample = metricsMap.get(metricsKey.getName()); Assertions.assertSame(((GaugeMetricSample) sample).applyAsLong(), count); - Assertions.assertTrue(metricsMap.containsKey(name)); + Assertions.assertTrue(metricsMap.containsKey(metricsKey.getName())); Map tags = sample.getTags(); Assertions.assertEquals(tags.get(TAG_INTERFACE_KEY), INTERFACE_NAME); diff --git a/dubbo-remoting/dubbo-remoting-http/pom.xml b/dubbo-remoting/dubbo-remoting-http/pom.xml index b2ef9653a4..e184f53eed 100644 --- a/dubbo-remoting/dubbo-remoting-http/pom.xml +++ b/dubbo-remoting/dubbo-remoting-http/pom.xml @@ -76,6 +76,5 @@ httpclient - diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/HttpHandler.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/HttpHandler.java index b05e065341..e6e9203c89 100644 --- a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/HttpHandler.java +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/HttpHandler.java @@ -16,15 +16,12 @@ */ package org.apache.dubbo.remoting.http; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * http invocation handler. */ -public interface HttpHandler { +public interface HttpHandler { /** * invoke. @@ -32,8 +29,7 @@ public interface HttpHandler { * @param request request. * @param response response. * @throws IOException - * @throws ServletException */ - void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException; + void handle(REQUEST request, RESPONSE response) throws IOException; } diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/RequestTemplate.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/RequestTemplate.java index e5d644c90b..6d19f1636b 100644 --- a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/RequestTemplate.java +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/RequestTemplate.java @@ -47,6 +47,7 @@ public class RequestTemplate implements Serializable { private String protocol = "http://"; private final Invocation invocation; private String contextPath = ""; + private Class bodyType; public RequestTemplate(Invocation invocation, String httpMethod, String address) { @@ -99,7 +100,7 @@ public class RequestTemplate implements Serializable { } } - return queryBuilder.toString(); + return queryBuilder.toString().replace("?&", "?"); } @@ -131,8 +132,9 @@ public class RequestTemplate implements Serializable { return getUnSerializedBody() == null; } - public RequestTemplate body(Object body) { + public RequestTemplate body(Object body,Class bodyType) { this.body = body; + setBodyType(bodyType); return this; } @@ -224,7 +226,7 @@ public class RequestTemplate implements Serializable { if (params == null) { params = new HashSet<>(); - this.headers.put(key, params); + this.queries.put(key, params); } params.addAll(values); } @@ -300,4 +302,12 @@ public class RequestTemplate implements Serializable { public void setContextPath(String contextPath) { this.contextPath = contextPath; } + + public Class getBodyType() { + return bodyType; + } + + public void setBodyType(Class bodyType) { + this.bodyType = bodyType; + } } diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/RestResult.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/RestResult.java index 1cca829291..bb057675df 100644 --- a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/RestResult.java +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/RestResult.java @@ -20,6 +20,9 @@ import java.io.IOException; import java.util.List; import java.util.Map; +/** + * rest response facade + */ public interface RestResult { String getContentType(); @@ -32,4 +35,8 @@ public interface RestResult { int getResponseCode() throws IOException; String getMessage() throws IOException; + + default String appendErrorMessage(String message, String errorInfo) { + return message + "\n error info is: " + errorInfo; + } } diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/restclient/HttpClientRestClient.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/restclient/HttpClientRestClient.java index 9895eab46a..b6d04cf2f5 100644 --- a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/restclient/HttpClientRestClient.java +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/restclient/HttpClientRestClient.java @@ -111,7 +111,8 @@ public class HttpClientRestClient implements RestClient { @Override public String getMessage() throws IOException { - return response.getStatusLine().getReasonPhrase(); + return appendErrorMessage(response.getStatusLine().getReasonPhrase(), + new String(getErrorResponse())); } }); } catch (IOException e) { diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/restclient/OKHttpRestClient.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/restclient/OKHttpRestClient.java index bb98aa32ec..975cc9d98b 100644 --- a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/restclient/OKHttpRestClient.java +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/restclient/OKHttpRestClient.java @@ -37,7 +37,7 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; - +// TODO add version 4.0 implements ,and default version is < 4.0,for dependency conflict public class OKHttpRestClient implements RestClient { private final OkHttpClient okHttpClient; private final HttpClientConfig httpClientConfig; @@ -109,7 +109,7 @@ public class OKHttpRestClient implements RestClient { @Override public String getMessage() throws IOException { - return response.message(); + return appendErrorMessage(response.message(), new String(getBody())); } }); } diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/restclient/URLConnectionRestClient.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/restclient/URLConnectionRestClient.java index b067228c44..9e1bea8b3c 100644 --- a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/restclient/URLConnectionRestClient.java +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/restclient/URLConnectionRestClient.java @@ -68,37 +68,7 @@ public class URLConnectionRestClient implements RestClient { 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> 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(); - } - }); + future.complete(getRestResultFromConnection(connection)); return future; } Integer contentLength = requestTemplate.getContentLength(); @@ -125,37 +95,7 @@ public class URLConnectionRestClient implements RestClient { } } - 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> 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(); - } - }); + future.complete(getRestResultFromConnection(connection)); } catch (Exception e) { future.completeExceptionally(e); } @@ -178,4 +118,41 @@ public class URLConnectionRestClient implements RestClient { return true; } + + private RestResult getRestResultFromConnection(HttpURLConnection connection) { + + return new RestResult() { + @Override + public String getContentType() { + return connection.getContentType(); + } + + @Override + public byte[] getBody() throws IOException { + return IOUtils.toByteArray(connection.getInputStream()); + } + + @Override + public Map> 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 appendErrorMessage(connection.getResponseMessage(), new String(getErrorResponse())); + + } + }; + } + } diff --git a/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/jetty/JettyHttpBinderTest.java b/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/jetty/JettyHttpBinderTest.java index 16f7bfd799..a00fbcd9c2 100644 --- a/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/jetty/JettyHttpBinderTest.java +++ b/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/jetty/JettyHttpBinderTest.java @@ -39,7 +39,7 @@ class JettyHttpBinderTest { int port = NetUtils.getAvailablePort(); URL url = new ServiceConfigURL("http", "localhost", port, new String[]{Constants.BIND_PORT_KEY, String.valueOf(port)}); - HttpServer httpServer = new JettyHttpServer(url, new HttpHandler() { + HttpServer httpServer = new JettyHttpServer(url, new HttpHandler() { @Override public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException { response.getWriter().write("Jetty"); diff --git a/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/jetty/JettyLoggerAdapterTest.java b/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/jetty/JettyLoggerAdapterTest.java index 8557f32e45..f2de8ccdef 100644 --- a/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/jetty/JettyLoggerAdapterTest.java +++ b/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/jetty/JettyLoggerAdapterTest.java @@ -49,7 +49,7 @@ class JettyLoggerAdapterTest { int port = NetUtils.getAvailablePort(); URL url = new ServiceConfigURL("http", "localhost", port, new String[]{Constants.BIND_PORT_KEY, String.valueOf(port)}); - HttpServer httpServer = new JettyHttpServer(url, new HttpHandler() { + HttpServer httpServer = new JettyHttpServer(url, new HttpHandler() { @Override public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException { response.getWriter().write("Jetty is using Dubbo's logger"); diff --git a/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/rest/RestClientTest.java b/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/rest/RestClientTest.java new file mode 100644 index 0000000000..04b4489b5b --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/rest/RestClientTest.java @@ -0,0 +1,199 @@ +/* + * 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.rest; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.url.component.ServiceConfigURL; +import org.apache.dubbo.common.utils.NetUtils; +import org.apache.dubbo.remoting.Constants; +import org.apache.dubbo.remoting.http.*; +import org.apache.dubbo.remoting.http.config.HttpClientConfig; +import org.apache.dubbo.remoting.http.jetty.JettyHttpServer; +import org.apache.dubbo.remoting.http.restclient.HttpClientRestClient; +import org.apache.dubbo.remoting.http.restclient.OKHttpRestClient; +import org.apache.dubbo.remoting.http.restclient.URLConnectionRestClient; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; + +public class RestClientTest { + + @Test + public void testRestClient() throws Exception { + int port = NetUtils.getAvailablePort(); + URL url = new ServiceConfigURL("http", "localhost", port, + new String[]{Constants.BIND_PORT_KEY, String.valueOf(port)}); + HttpServer httpServer = new JettyHttpServer(url, new HttpHandler() { + @Override + public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException { + response.getWriter().write("Jetty"); + } + }); + + RequestTemplate requestTemplate = new RequestTemplate(null, "POST", "localhost:" + port); + + requestTemplate.addParam("p1", "value1"); + requestTemplate.addParam("p2", "value2"); + + requestTemplate.addParams("p3", Arrays.asList("value3", "value3.1")); + requestTemplate.addHeader("test", "dubbo"); + requestTemplate.addKeepAliveHeader(60); + + requestTemplate.addHeaders("header", Arrays.asList("h1", "h2")); + + requestTemplate.path("/test"); + requestTemplate.serializeBody("test".getBytes(StandardCharsets.UTF_8)); + + RestClient restClient = new OKHttpRestClient(new HttpClientConfig()); + + CompletableFuture send = restClient.send(requestTemplate); + + RestResult restResult = send.get(); + + + assertThat(new String(restResult.getBody()), is("Jetty")); + + + restClient = new HttpClientRestClient(new HttpClientConfig()); + + send = restClient.send(requestTemplate); + + restResult = send.get(); + + assertThat(new String(restResult.getBody()), is("Jetty")); + + restClient = new URLConnectionRestClient(new HttpClientConfig()); + + send = restClient.send(requestTemplate); + + restResult = send.get(); + + assertThat(new String(restResult.getBody()), is("Jetty")); + + httpServer.close(); + } + + + @Test + public void testError() throws Exception { + int port = NetUtils.getAvailablePort(); + URL url = new ServiceConfigURL("http", "localhost", port, + new String[]{Constants.BIND_PORT_KEY, String.valueOf(port)}); + HttpServer httpServer = new JettyHttpServer(url, new HttpHandler() { + @Override + public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException { + response.setStatus(500); + response.getWriter().write("server error"); + response.addHeader("Content-Type", "text/html"); + } + }); + + RequestTemplate requestTemplate = new RequestTemplate(null, null, null); + + requestTemplate.httpMethod("POST"); + requestTemplate.setAddress("localhost:" + port); + requestTemplate.setProtocol("http://"); + requestTemplate.addHeader("test", "dubbo"); + requestTemplate.path("/test"); + requestTemplate.serializeBody("test".getBytes(StandardCharsets.UTF_8)); + + RestClient restClient = new OKHttpRestClient(new HttpClientConfig()); + + CompletableFuture send = restClient.send(requestTemplate); + + String error = "Server Error\n" + + " error info is: server error"; + RestResult restResult = send.get(); + + String contentType = "text/html;charset=iso-8859-1"; + + Assertions.assertEquals(500, restResult.getResponseCode()); + Assertions.assertEquals(error, restResult.getMessage()); + Assertions.assertEquals(contentType, restResult.getContentType()); + + Map> headers = restResult.headers(); + restClient.close(); + + restClient = new HttpClientRestClient(new HttpClientConfig()); + send = restClient.send(requestTemplate); + restResult = send.get(); + + + Assertions.assertEquals(500, restResult.getResponseCode()); + Assertions.assertEquals(error, restResult.getMessage()); + Assertions.assertEquals(contentType, restResult.getContentType()); + + restClient.close(); + + + restClient = new URLConnectionRestClient(new HttpClientConfig()); + send = restClient.send(requestTemplate); + restResult = send.get(); + + Assertions.assertEquals(500, restResult.getResponseCode()); + Assertions.assertEquals(error, restResult.getMessage()); + Assertions.assertEquals(contentType, restResult.getContentType()); + restClient.close(); + + + httpServer.close(); + } + + @Test + public void testMethod() { + + RequestTemplate requestTemplate = new RequestTemplate(null, null, null); + + requestTemplate.body(new Object(), Object.class); + + Assertions.assertEquals(requestTemplate.getBodyType(),Object.class); + + + requestTemplate.addHeader("Content-Length",1); + + Integer contentLength = requestTemplate.getContentLength(); + + Assertions.assertEquals(1,contentLength); + + List strings = Arrays.asList("h1", "h2"); + + requestTemplate.addHeaders("header",strings); + + + Assertions.assertArrayEquals(strings.toArray(new String[0]),requestTemplate.getHeaders("header").toArray(new String[0])); + + strings = Arrays.asList("p1", "p2"); + + requestTemplate.addParams("param",strings); + + Assertions.assertArrayEquals(strings.toArray(new String[0]),requestTemplate.getParam("param").toArray(new String[0])); + + + + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/tomcat/TomcatHttpBinderTest.java b/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/tomcat/TomcatHttpBinderTest.java index 10d6423963..ac100eef3f 100644 --- a/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/tomcat/TomcatHttpBinderTest.java +++ b/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/tomcat/TomcatHttpBinderTest.java @@ -40,7 +40,7 @@ class TomcatHttpBinderTest { URL url = new ServiceConfigURL("http", "localhost", port, new String[]{Constants.BIND_PORT_KEY, String.valueOf(port)}); - HttpServer httpServer = new TomcatHttpBinder().bind(url, new HttpHandler() { + HttpServer httpServer = new TomcatHttpBinder().bind(url, new HttpHandler() { @Override public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException { response.getWriter().write("Tomcat"); diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractProtocol.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractProtocol.java index 3781b2f382..fd49e60298 100644 --- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractProtocol.java +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractProtocol.java @@ -23,6 +23,7 @@ import org.apache.dubbo.common.logger.LoggerFactory; import org.apache.dubbo.common.serialize.support.SerializableClassRegistry; import org.apache.dubbo.common.serialize.support.SerializationOptimizer; import org.apache.dubbo.common.utils.ConcurrentHashSet; +import org.apache.dubbo.common.utils.NetUtils; import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.remoting.Constants; import org.apache.dubbo.rpc.Exporter; @@ -43,9 +44,11 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +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.DEFAULT_SERVER_SHUTDOWN_TIMEOUT; -import static org.apache.dubbo.common.constants.CommonConstants.SHUTDOWN_WAIT_KEY; import static org.apache.dubbo.common.constants.CommonConstants.OPTIMIZER_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.SHUTDOWN_WAIT_KEY; import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_FAILED_DESTROY_INVOKER; /** @@ -181,4 +184,13 @@ public abstract class AbstractProtocol implements Protocol, ScopeModelAware { } } + + protected String getAddr(URL url) { + String bindIp = url.getParameter(org.apache.dubbo.remoting.Constants.BIND_IP_KEY, url.getHost()); + if (url.getParameter(ANYHOST_KEY, false)) { + bindIp = ANYHOST_VALUE; + } + return NetUtils.getIpByHost(bindIp) + ":" + url.getParameter(org.apache.dubbo.remoting.Constants.BIND_PORT_KEY, url.getPort()); + } + } diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractProxyProtocol.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractProxyProtocol.java index 360e629c72..db55424ed6 100644 --- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractProxyProtocol.java +++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractProxyProtocol.java @@ -19,11 +19,9 @@ package org.apache.dubbo.rpc.protocol; import org.apache.dubbo.common.Parameters; import org.apache.dubbo.common.URL; -import org.apache.dubbo.common.utils.NetUtils; import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.remoting.Channel; import org.apache.dubbo.remoting.ChannelHandler; -import org.apache.dubbo.remoting.Constants; import org.apache.dubbo.remoting.RemotingException; import org.apache.dubbo.remoting.RemotingServer; import org.apache.dubbo.rpc.Exporter; @@ -43,8 +41,6 @@ import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; 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.LoggerCodeConstants.PROTOCOL_UNSUPPORTED; /** @@ -146,14 +142,6 @@ public abstract class AbstractProxyProtocol extends AbstractProtocol { return re; } - protected String getAddr(URL url) { - String bindIp = url.getParameter(Constants.BIND_IP_KEY, url.getHost()); - if (url.getParameter(ANYHOST_KEY, false)) { - bindIp = ANYHOST_VALUE; - } - return NetUtils.getIpByHost(bindIp) + ":" + url.getParameter(Constants.BIND_PORT_KEY, url.getPort()); - } - protected int getErrorCode(Throwable e) { return RpcException.UNKNOWN_EXCEPTION; } diff --git a/dubbo-rpc/dubbo-rpc-rest/pom.xml b/dubbo-rpc/dubbo-rpc-rest/pom.xml index dc5855b88a..e94f81d7e5 100644 --- a/dubbo-rpc/dubbo-rpc-rest/pom.xml +++ b/dubbo-rpc/dubbo-rpc-rest/pom.xml @@ -125,12 +125,6 @@ provided - - jakarta.servlet - jakarta.servlet-api - provided - - io.netty netty-codec-http diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/BaseRestProtocolServer.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/BaseRestProtocolServer.java deleted file mode 100644 index baa0fff9cc..0000000000 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/BaseRestProtocolServer.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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; - -import org.apache.dubbo.common.URL; -import org.apache.dubbo.common.utils.StringUtils; - -import org.jboss.resteasy.spi.ResteasyDeployment; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN; -import static org.apache.dubbo.rpc.protocol.rest.Constants.EXCEPTION_MAPPER_KEY; -import static org.apache.dubbo.rpc.protocol.rest.Constants.EXTENSION_KEY; - -public abstract class BaseRestProtocolServer implements RestProtocolServer { - - private String address; - - private final Map attributes = new ConcurrentHashMap<>(); - - @Override - public void start(URL url) { - getDeployment().getMediaTypeMappings().put("json", "application/json"); - getDeployment().getMediaTypeMappings().put("xml", "text/xml"); - getDeployment().getProviderClasses().add(RpcContextFilter.class.getName()); - - loadProviders(url.getParameter(EXCEPTION_MAPPER_KEY, RpcExceptionMapper.class.getName())); - - loadProviders(url.getParameter(EXTENSION_KEY, "")); - - doStart(url); - } - - @Override - public void deploy(Class resourceDef, Object resourceInstance, String contextPath) { - if (StringUtils.isEmpty(contextPath)) { - getDeployment().getRegistry().addResourceFactory(new DubboResourceFactory(resourceInstance, resourceDef)); - } else { - getDeployment().getRegistry().addResourceFactory(new DubboResourceFactory(resourceInstance, resourceDef), contextPath); - } - } - - @Override - public void undeploy(Class resourceDef) { - getDeployment().getRegistry().removeRegistrations(resourceDef); - } - - @Override - public String getAddress() { - return address; - } - - @Override - public void setAddress(String address) { - this.address = address; - } - - @Override - public Map getAttributes() { - return attributes; - } - - protected void loadProviders(String value) { - for (String clazz : COMMA_SPLIT_PATTERN.split(value)) { - if (!StringUtils.isEmpty(clazz)) { - getDeployment().getProviderClasses().add(clazz.trim()); - } - } - } - - protected abstract ResteasyDeployment getDeployment(); - - protected abstract void doStart(URL url); -} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/Constants.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/Constants.java index 8966315425..c2918062f4 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/Constants.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/Constants.java @@ -28,14 +28,13 @@ public interface Constants { String EXTENSION_KEY = "extension"; // http server - String SERVLET = "servlet"; - String JETTY = "jetty"; - - String TOMCAT = "tomcat"; - - String NETTY = "netty"; + String NETTY_HTTP = "netty_http"; // exception mapper String EXCEPTION_MAPPER_KEY = "exception.mapper"; + + + + } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/DubboHttpProtocolServer.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/DubboHttpProtocolServer.java deleted file mode 100644 index 475238d548..0000000000 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/DubboHttpProtocolServer.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * 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; - -import org.apache.dubbo.common.URL; -import org.apache.dubbo.remoting.http.HttpBinder; -import org.apache.dubbo.remoting.http.HttpHandler; -import org.apache.dubbo.remoting.http.HttpServer; -import org.apache.dubbo.remoting.http.servlet.BootstrapListener; -import org.apache.dubbo.remoting.http.servlet.ServletManager; -import org.apache.dubbo.rpc.RpcContext; -import org.apache.dubbo.rpc.RpcException; - -import org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher; -import org.jboss.resteasy.spi.ResteasyDeployment; - -import javax.servlet.ServletConfig; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Enumeration; - -public class DubboHttpProtocolServer extends BaseRestProtocolServer { - - private final HttpServletDispatcher dispatcher = new HttpServletDispatcher(); - private final ResteasyDeployment deployment = new ResteasyDeployment(); - private final HttpBinder httpBinder; - private HttpServer httpServer; - - public DubboHttpProtocolServer(HttpBinder httpBinder) { - this.httpBinder = httpBinder; - } - - @Override - protected void doStart(URL url) { - // TODO jetty will by default enable keepAlive so the xml config has no effect now - httpServer = httpBinder.bind(url, new RestHandler()); - - ServletContext servletContext = ServletManager.getInstance().getServletContext(url.getPort()); - if (servletContext == null) { - servletContext = ServletManager.getInstance().getServletContext(ServletManager.EXTERNAL_SERVER_PORT); - } - if (servletContext == null) { - throw new RpcException("No servlet context found. If you are using server='servlet', " + - "make sure that you've configured " + BootstrapListener.class.getName() + " in web.xml"); - } - - servletContext.setAttribute(ResteasyDeployment.class.getName(), deployment); - - try { - dispatcher.init(new SimpleServletConfig(servletContext)); - } catch (ServletException e) { - throw new RpcException(e); - } - } - - @Override - public void close() { - httpServer.close(); - } - - @Override - protected ResteasyDeployment getDeployment() { - return deployment; - } - - private class RestHandler implements HttpHandler { - - @Override - public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - RpcContext.getServiceContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort()); - dispatcher.service(request, response); - } - } - - private static class SimpleServletConfig implements ServletConfig { - - private final ServletContext servletContext; - - public SimpleServletConfig(ServletContext servletContext) { - this.servletContext = servletContext; - } - - @Override - public String getServletName() { - return "DispatcherServlet"; - } - - @Override - public ServletContext getServletContext() { - return servletContext; - } - - @Override - public String getInitParameter(String s) { - return null; - } - - @Override - public Enumeration getInitParameterNames() { - return new Enumeration() { - @Override - public boolean hasMoreElements() { - return false; - } - - @Override - public String nextElement() { - return null; - } - }; - } - } -} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/DubboResourceFactory.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/DubboResourceFactory.java deleted file mode 100644 index ac8695ccc4..0000000000 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/DubboResourceFactory.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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; - -import org.jboss.resteasy.spi.HttpRequest; -import org.jboss.resteasy.spi.HttpResponse; -import org.jboss.resteasy.spi.ResourceFactory; -import org.jboss.resteasy.spi.ResteasyProviderFactory; - -/** - * We don't support propertyInjector here since the resource impl should be singleton in dubbo - */ -public class DubboResourceFactory implements ResourceFactory { - - private final Object resourceInstance; - private final Class scannableClass; - - public DubboResourceFactory(Object resourceInstance, Class scannableClass) { - this.resourceInstance = resourceInstance; - this.scannableClass = scannableClass; - } - - @Override - public Object createResource(HttpRequest request, HttpResponse response, - ResteasyProviderFactory factory) { - return resourceInstance; - } - - @Override - public Class getScannableClass() { - return scannableClass; - } - - @Override - public void registered(ResteasyProviderFactory factory) { - } - - @Override - public void requestFinished(HttpRequest request, HttpResponse response, - Object resource) { - } - - @Override - public void unregistered() { - } -} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/NettyHttpRestServer.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/NettyHttpRestServer.java new file mode 100644 index 0000000000..f757fafced --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/NettyHttpRestServer.java @@ -0,0 +1,216 @@ +/* + * 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; + +import io.netty.buffer.PooledByteBufAllocator; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelOption; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.utils.NetUtils; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.metadata.rest.PathMatcher; +import org.apache.dubbo.metadata.rest.RestMethodMetadata; +import org.apache.dubbo.metadata.rest.ServiceRestMetadata; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.protocol.rest.constans.RestConstant; +import org.apache.dubbo.rpc.protocol.rest.exception.mapper.ExceptionMapper; +import org.apache.dubbo.rpc.protocol.rest.handler.NettyHttpHandler; +import org.apache.dubbo.rpc.protocol.rest.netty.NettyServer; +import org.apache.dubbo.rpc.protocol.rest.netty.RestHttpRequestDecoder; +import org.apache.dubbo.rpc.protocol.rest.netty.UnSharedHandlerCreator; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static org.apache.dubbo.common.constants.CommonConstants.BACKLOG_KEY; +import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN; +import static org.apache.dubbo.common.constants.CommonConstants.IO_THREADS_KEY; +import static org.apache.dubbo.remoting.Constants.BIND_IP_KEY; +import static org.apache.dubbo.remoting.Constants.BIND_PORT_KEY; +import static org.apache.dubbo.remoting.Constants.DEFAULT_IO_THREADS; +import static org.apache.dubbo.rpc.protocol.rest.Constants.DEFAULT_KEEP_ALIVE; +import static org.apache.dubbo.rpc.protocol.rest.Constants.EXCEPTION_MAPPER_KEY; +import static org.apache.dubbo.rpc.protocol.rest.Constants.KEEP_ALIVE_KEY; + +/** + * netty http server + */ +public class NettyHttpRestServer implements RestProtocolServer { + + private final PathAndInvokerMapper pathAndInvokerMapper = new PathAndInvokerMapper(); + private final ExceptionMapper exceptionMapper = new ExceptionMapper(); + private NettyServer server = getNettyServer(); + + /** + * for triple override + * + * @return + */ + protected NettyServer getNettyServer() { + return new NettyServer(); + } + + private String address; + + private final Map attributes = new ConcurrentHashMap<>(); + + + @Override + public String getAddress() { + return address; + } + + @Override + public void setAddress(String address) { + + this.address = address; + } + + @Override + public void close() { + server.stop(); + + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public void start(URL url) { + + registerExceptionMapper(url); + + String bindIp = url.getParameter(BIND_IP_KEY, url.getHost()); + if (!url.isAnyHost() && NetUtils.isValidLocalHost(bindIp)) { + server.setHostname(bindIp); + } + server.setPort(url.getParameter(BIND_PORT_KEY, url.getPort())); + + // child options + server.setChildChannelOptions(getChildChannelOptionMap(url)); + + // set options + server.setChannelOptions(getChannelOptionMap(url)); + // set unshared callback + server.setUnSharedHandlerCallBack(getUnSharedHttpChannelHandlers()); + // set channel handler and @Shared + server.setChannelHandlers(getChannelHandlers(url)); + server.setIoWorkerCount(url.getParameter(IO_THREADS_KEY, DEFAULT_IO_THREADS)); + server.start(url); + } + + private UnSharedHandlerCreator getUnSharedHttpChannelHandlers() { + return new UnSharedHandlerCreator() { + @Override + public List getUnSharedHandlers(URL url) { + return Arrays.asList( + // TODO add SslServerTlsHandler +// channelPipeline.addLast(ch.pipeline().addLast("negotiation", new SslServerTlsHandler(url))); + + new HttpRequestDecoder( + url.getParameter(RestConstant.MAX_INITIAL_LINE_LENGTH_PARAM, RestConstant.MAX_INITIAL_LINE_LENGTH), + url.getParameter(RestConstant.MAX_HEADER_SIZE_PARAM, RestConstant.MAX_HEADER_SIZE), + url.getParameter(RestConstant.MAX_CHUNK_SIZE_PARAM, RestConstant.MAX_CHUNK_SIZE)), + new HttpObjectAggregator(url.getParameter(RestConstant.MAX_REQUEST_SIZE_PARAM, RestConstant.MAX_REQUEST_SIZE)), + new HttpResponseEncoder(), new RestHttpRequestDecoder(new NettyHttpHandler(pathAndInvokerMapper, exceptionMapper), url)); + } + }; + } + + /** + * create child channel options map + * + * @param url + * @return + */ + protected Map getChildChannelOptionMap(URL url) { + Map channelOption = new HashMap<>(); + channelOption.put(ChannelOption.SO_KEEPALIVE, url.getParameter(KEEP_ALIVE_KEY, DEFAULT_KEEP_ALIVE)); + return channelOption; + } + + /** + * create channel options map + * + * @param url + * @return + */ + protected Map getChannelOptionMap(URL url) { + Map options = new HashMap<>(); + + options.put(ChannelOption.SO_REUSEADDR, Boolean.TRUE); + options.put(ChannelOption.TCP_NODELAY, Boolean.TRUE); + options.put(ChannelOption.SO_BACKLOG, url.getPositiveParameter(BACKLOG_KEY, org.apache.dubbo.remoting.Constants.DEFAULT_BACKLOG)); + options.put(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); + return options; + } + + /** + * create channel handler + * + * @param url + * @return + */ + protected List getChannelHandlers(URL url) { + List channelHandlers = new ArrayList<>(); + + return channelHandlers; + } + + @Override + public void deploy(ServiceRestMetadata serviceRestMetadata, Invoker invoker) { + Map pathToServiceMapContainPathVariable = + serviceRestMetadata.getPathContainPathVariableToServiceMap(); + pathAndInvokerMapper.addPathAndInvoker(pathToServiceMapContainPathVariable, invoker); + + Map pathToServiceMapUnContainPathVariable = + serviceRestMetadata.getPathUnContainPathVariableToServiceMap(); + pathAndInvokerMapper.addPathAndInvoker(pathToServiceMapUnContainPathVariable, invoker); + } + + @Override + public void undeploy(ServiceRestMetadata serviceRestMetadata) { + Map pathToServiceMapContainPathVariable = + serviceRestMetadata.getPathContainPathVariableToServiceMap(); + pathToServiceMapContainPathVariable.keySet().stream().forEach(pathAndInvokerMapper::removePath); + + Map pathToServiceMapUnContainPathVariable = + serviceRestMetadata.getPathUnContainPathVariableToServiceMap(); + pathToServiceMapUnContainPathVariable.keySet().stream().forEach(pathAndInvokerMapper::removePath); + + } + + private void registerExceptionMapper(URL url) { + + for (String clazz : COMMA_SPLIT_PATTERN.split(url.getParameter(EXCEPTION_MAPPER_KEY, RpcExceptionMapper.class.getName()))) { + if (!StringUtils.isEmpty(clazz)) { + exceptionMapper.registerMapper(clazz); + } + } + } + + +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/NettyRestProtocolServer.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/NettyRestProtocolServer.java deleted file mode 100644 index fbb9be782e..0000000000 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/NettyRestProtocolServer.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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; - -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; -import static org.apache.dubbo.remoting.Constants.BIND_IP_KEY; -import static org.apache.dubbo.remoting.Constants.BIND_PORT_KEY; -import static org.apache.dubbo.remoting.Constants.DEFAULT_IO_THREADS; -import static org.apache.dubbo.remoting.Constants.DEFAULT_PAYLOAD; -import static org.apache.dubbo.remoting.Constants.PAYLOAD_KEY; -import static org.apache.dubbo.rpc.protocol.rest.Constants.DEFAULT_KEEP_ALIVE; -import static org.apache.dubbo.rpc.protocol.rest.Constants.KEEP_ALIVE_KEY; - -/** - * Netty server can't support @Context injection of servlet objects since it's not a servlet container - * - */ -public class NettyRestProtocolServer extends BaseRestProtocolServer { - - private final NettyJaxrsServer server = new NettyJaxrsServer(); - - @Override - @SuppressWarnings("rawtypes") - protected void doStart(URL url) { - String bindIp = url.getParameter(BIND_IP_KEY, url.getHost()); - if (!url.isAnyHost() && NetUtils.isValidLocalHost(bindIp)) { - server.setHostname(bindIp); - } - server.setPort(url.getParameter(BIND_PORT_KEY, url.getPort())); - Map channelOption = new HashMap<>(); - channelOption.put(ChannelOption.SO_KEEPALIVE, url.getParameter(KEEP_ALIVE_KEY, DEFAULT_KEEP_ALIVE)); - server.setChildChannelOptions(channelOption); - server.setExecutorThreadCount(url.getParameter(THREADS_KEY, DEFAULT_THREADS)); - server.setIoWorkerCount(url.getParameter(IO_THREADS_KEY, DEFAULT_IO_THREADS)); - server.setMaxRequestSize(url.getParameter(PAYLOAD_KEY, DEFAULT_PAYLOAD)); - server.start(); - } - - @Override - public void close() { - server.stop(); - } - - @Override - protected ResteasyDeployment getDeployment() { - return server.getDeployment(); - } -} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/PathAndInvokerMapper.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/PathAndInvokerMapper.java new file mode 100644 index 0000000000..ce3a187e70 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/PathAndInvokerMapper.java @@ -0,0 +1,124 @@ +/* + * 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; + +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.metadata.rest.PathMatcher; +import org.apache.dubbo.metadata.rest.RestMethodMetadata; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.protocol.rest.exception.DoublePathCheckException; +import org.apache.dubbo.rpc.protocol.rest.exception.PathNoFoundException; +import org.apache.dubbo.rpc.protocol.rest.pair.InvokerAndRestMethodMetadataPair; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * save the path & metadata info mapping + */ +public class PathAndInvokerMapper { + private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(PathAndInvokerMapper.class); + + private final Map pathToServiceMapContainPathVariable = new ConcurrentHashMap<>(); + private final Map pathToServiceMapNoPathVariable = new ConcurrentHashMap<>(); + + + /** + * deploy path metadata + * + * @param metadataMap + * @param invoker + */ + public void addPathAndInvoker(Map metadataMap, Invoker invoker) { + + metadataMap.entrySet().stream().forEach(entry -> { + PathMatcher pathMatcher = entry.getKey(); + if (pathMatcher.hasPathVariable()) { + addPathMatcherToPathMap(pathMatcher, pathToServiceMapContainPathVariable, InvokerAndRestMethodMetadataPair.pair(invoker, entry.getValue())); + } else { + addPathMatcherToPathMap(pathMatcher, pathToServiceMapNoPathVariable, InvokerAndRestMethodMetadataPair.pair(invoker, entry.getValue())); + } + }); + } + + /** + * acquire metadata & invoker by service info + * + * @param path + * @param version + * @param group + * @param port + * @return + */ + public InvokerAndRestMethodMetadataPair getRestMethodMetadata(String path, String version, String group, Integer port) { + + + PathMatcher pathMather = PathMatcher.getInvokeCreatePathMatcher(path, version, group, port); + + // first search from pathToServiceMapNoPathVariable + if (pathToServiceMapNoPathVariable.containsKey(pathMather)) { + return pathToServiceMapNoPathVariable.get(pathMather); + } + + // second search from pathToServiceMapNoPathVariable + if (pathToServiceMapContainPathVariable.containsKey(pathMather)) { + return pathToServiceMapContainPathVariable.get(pathMather); + } + + throw new PathNoFoundException("rest service Path no found, current path info:" + pathMather); + } + + /** + * undeploy path metadata + * + * @param pathMatcher + */ + public void removePath(PathMatcher pathMatcher) { + + InvokerAndRestMethodMetadataPair containPathVariablePair = pathToServiceMapContainPathVariable.remove(pathMatcher); + + InvokerAndRestMethodMetadataPair unContainPathVariablePair = pathToServiceMapNoPathVariable.remove(pathMatcher); + logger.info("dubbo rest undeploy pathMatcher:" + pathMatcher + + ", and path variable method is :" + (containPathVariablePair == null ? null : containPathVariablePair.getRestMethodMetadata().getReflectMethod()) + + ", and no path variable method is :" + (unContainPathVariablePair == null ? null : unContainPathVariablePair.getRestMethodMetadata().getReflectMethod())); + + + } + + public void addPathMatcherToPathMap(PathMatcher pathMatcher, + Map pathMatcherPairMap, + InvokerAndRestMethodMetadataPair invokerRestMethodMetadataPair) { + + if (pathMatcherPairMap.containsKey(pathMatcher)) { + + InvokerAndRestMethodMetadataPair beforeMetadata = pathMatcherPairMap.get(pathMatcher); + + throw new DoublePathCheckException( + "dubbo rest double path check error, current path is: " + pathMatcher + + " ,and service method is: " + invokerRestMethodMetadataPair.getRestMethodMetadata().getReflectMethod() + + "before service method is: " + beforeMetadata.getRestMethodMetadata().getReflectMethod() + ); + } + + pathMatcherPairMap.put(pathMatcher, invokerRestMethodMetadataPair); + + + logger.info("dubbo rest deploy pathMatcher:" + pathMatcher + ", and service method is :" + invokerRestMethodMetadataPair.getRestMethodMetadata().getReflectMethod()); + } + +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestHeaderEnum.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestHeaderEnum.java new file mode 100644 index 0000000000..83ee81485c --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestHeaderEnum.java @@ -0,0 +1,42 @@ +/* + * 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; + +import org.apache.dubbo.rpc.protocol.rest.constans.RestConstant; + +public enum RestHeaderEnum { + CONTENT_TYPE(RestConstant.CONTENT_TYPE), + ACCEPT(RestConstant.ACCEPT), + GROUP(RestConstant.REST_HEADER_PREFIX + RestConstant.GROUP), + VERSION(RestConstant.REST_HEADER_PREFIX + RestConstant.VERSION), + PATH(RestConstant.REST_HEADER_PREFIX + RestConstant.PATH), + KEEP_ALIVE_HEADER(RestConstant.KEEP_ALIVE_HEADER), + CONNECTION(RestConstant.CONNECTION), + REST_HEADER_PREFIX(RestConstant.REST_HEADER_PREFIX), + + + ; + private final String header; + + RestHeaderEnum(String header) { + this.header = header; + } + + public String getHeader() { + return header; + } +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestInvoker.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestInvoker.java new file mode 100644 index 0000000000..09266c950a --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestInvoker.java @@ -0,0 +1,140 @@ +/* + * 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; + + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.metadata.ParameterTypesComparator; +import org.apache.dubbo.metadata.rest.RestMethodMetadata; +import org.apache.dubbo.metadata.rest.ServiceRestMetadata; +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.rpc.AppResponse; +import org.apache.dubbo.rpc.AsyncRpcResult; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.RpcException; +import org.apache.dubbo.rpc.protocol.AbstractInvoker; +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.exception.ParamParseException; +import org.apache.dubbo.rpc.protocol.rest.exception.PathNoFoundException; +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.HttpHeaderUtil; +import org.apache.dubbo.rpc.protocol.rest.util.MediaTypeUtil; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +public class RestInvoker extends AbstractInvoker { + private final ServiceRestMetadata serviceRestMetadata; + private final ReferenceCountedClient referenceCountedClient; + private final Set httpConnectionPreBuildIntercepts; + + + public RestInvoker(Class type, URL url, + ReferenceCountedClient referenceCountedClient, + Set httpConnectionPreBuildIntercepts, + ServiceRestMetadata serviceRestMetadata) { + super(type, url); + this.serviceRestMetadata = serviceRestMetadata; + this.referenceCountedClient = referenceCountedClient; + this.httpConnectionPreBuildIntercepts = httpConnectionPreBuildIntercepts; + } + + @Override + protected Result doInvoke(Invocation invocation) { + try { + + Map> metadataMap = serviceRestMetadata.getMethodToServiceMap(); + // get metadata + RestMethodMetadata restMethodMetadata = metadataMap.get(invocation.getMethodName()).get(ParameterTypesComparator.getInstance(invocation.getParameterTypes())); + + // create requestTemplate + RequestTemplate requestTemplate = new RequestTemplate(invocation, restMethodMetadata.getRequest().getMethod(), getUrl().getAddress()); + + HttpConnectionCreateContext httpConnectionCreateContext = + creatHttpConnectionCreateContext(invocation, serviceRestMetadata, restMethodMetadata, requestTemplate); + + // fill real data + for (HttpConnectionPreBuildIntercept intercept : httpConnectionPreBuildIntercepts) { + intercept.intercept(httpConnectionCreateContext); + } + + // TODO check rest client cannot be reused + CompletableFuture future = referenceCountedClient.getClient().send(requestTemplate); + CompletableFuture 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 (responseCode == 404) { + responseFuture.completeExceptionally(new PathNoFoundException(r.getMessage())); + } else if (400 <= responseCode && responseCode < 500) { + responseFuture.completeExceptionally(new ParamParseException(r.getMessage())); + // TODO add Exception Mapper + } else if (responseCode >= 500) { + responseFuture.completeExceptionally(new RemoteServerInternalException(r.getMessage())); + } else if (responseCode < 400) { + mediaType = MediaTypeUtil.convertMediaType(restMethodMetadata.getReflectMethod().getReturnType(), r.getContentType()); + Object value = HttpMessageCodecManager.httpMessageDecode(r.getBody(), + restMethodMetadata.getReflectMethod().getReturnType(), mediaType); + appResponse.setValue(value); + // resolve response attribute & attachment + HttpHeaderUtil.parseResponseHeader(appResponse, r); + responseFuture.complete(appResponse); + } + } catch (Exception e) { + responseFuture.completeExceptionally(e); + } + } + }); + return asyncRpcResult; + } catch (RpcException e) { + throw e; + } + } + + /** + * create intercept context + * + * @param invocation + * @param serviceRestMetadata + * @param restMethodMetadata + * @param requestTemplate + * @return + */ + private HttpConnectionCreateContext creatHttpConnectionCreateContext(Invocation invocation, ServiceRestMetadata serviceRestMetadata, RestMethodMetadata restMethodMetadata, RequestTemplate requestTemplate) { + HttpConnectionCreateContext httpConnectionCreateContext = new HttpConnectionCreateContext(); + httpConnectionCreateContext.setRequestTemplate(requestTemplate); + httpConnectionCreateContext.setRestMethodMetadata(restMethodMetadata); + httpConnectionCreateContext.setServiceRestMetadata(serviceRestMetadata); + httpConnectionCreateContext.setInvocation(invocation); + httpConnectionCreateContext.setUrl(getUrl()); + return httpConnectionCreateContext; + } +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestProtocol.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestProtocol.java index 6884aacc14..ad273efda3 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestProtocol.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestProtocol.java @@ -18,60 +18,36 @@ 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.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.metadata.rest.ServiceRestMetadata; 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.Exporter; 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.AbstractExporter; +import org.apache.dubbo.rpc.protocol.AbstractProtocol; 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.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.Objects; import java.util.Set; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.stream.Collectors; -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.LoggerCodeConstants.PROTOCOL_ERROR_CLOSE_CLIENT; import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_ERROR_CLOSE_SERVER; import static org.apache.dubbo.remoting.Constants.SERVER_KEY; -import static org.apache.dubbo.rpc.Constants.TOKEN_KEY; import static org.apache.dubbo.rpc.protocol.rest.constans.RestConstant.PATH_SEPARATOR; -public class RestProtocol extends AbstractProxyProtocol { +public class RestProtocol extends AbstractProtocol { private static final int DEFAULT_PORT = 80; - private static final String DEFAULT_CLIENT = org.apache.dubbo.remoting.Constants.OK_HTTP; - private static final String DEFAULT_SERVER = Constants.JETTY; + private static final String DEFAULT_SERVER = Constants.NETTY_HTTP; private final RestServerFactory serverFactory = new RestServerFactory(); @@ -82,7 +58,6 @@ public class RestProtocol extends AbstractProxyProtocol { private final Set httpConnectionPreBuildIntercepts; public RestProtocol(FrameworkModel frameworkModel) { - super(WebApplicationException.class, ProcessingException.class); this.clientFactory = frameworkModel.getExtensionLoader(RestClientFactory.class).getAdaptiveExtension(); this.httpConnectionPreBuildIntercepts = frameworkModel.getExtensionLoader(HttpConnectionPreBuildIntercept.class).getSupportedExtensionInstances(); } @@ -93,48 +68,49 @@ public class RestProtocol extends AbstractProxyProtocol { return DEFAULT_PORT; } + @Override - protected Runnable doExport(T impl, Class type, URL url) throws RpcException { - String addr = getAddr(url); - Class implClass = url.getServiceModel().getProxyObject().getClass(); - RestProtocolServer server = (RestProtocolServer) ConcurrentHashMapUtils.computeIfAbsent(serverMap, addr, restServer -> { + @SuppressWarnings("unchecked") + public Exporter export(final Invoker invoker) throws RpcException { + URL url = invoker.getUrl(); + final String uri = serviceKey(url); + Exporter exporter = (Exporter) exporterMap.get(uri); + if (exporter != null) { + // When modifying the configuration through override, you need to re-expose the newly modified service. + if (Objects.equals(exporter.getInvoker().getUrl(), invoker.getUrl())) { + return exporter; + } + } + + + // resolve metadata + ServiceRestMetadata serviceRestMetadata = + MetadataResolver.resolveProviderServiceMetadata(url.getServiceModel().getProxyObject().getClass(), + url, getContextPath(url)); + + + // TODO add Extension filter + // create rest server + RestProtocolServer server = (RestProtocolServer) ConcurrentHashMapUtils.computeIfAbsent(serverMap, getAddr(url), restServer -> { RestProtocolServer s = serverFactory.createServer(url.getParameter(SERVER_KEY, DEFAULT_SERVER)); s.setAddress(url.getAddress()); s.start(url); return s; }); - String contextPath = getContextPath(url); - if (Constants.SERVLET.equalsIgnoreCase(url.getParameter(SERVER_KEY, DEFAULT_SERVER))) { - ServletContext servletContext = ServletManager.getInstance().getServletContext(ServletManager.EXTERNAL_SERVER_PORT); - if (servletContext == null) { - throw new RpcException("No servlet context found. Since you are using server='servlet', " + - "make sure that you've configured " + BootstrapListener.class.getName() + " in web.xml"); + + server.deploy(serviceRestMetadata, invoker); + + exporter = new AbstractExporter(invoker) { + @Override + public void afterUnExport() { + destroyInternal(url); + exporterMap.remove(uri); + server.undeploy(serviceRestMetadata); } - String webappPath = servletContext.getContextPath(); - if (StringUtils.isNotEmpty(webappPath)) { - webappPath = webappPath.substring(1); - if (!contextPath.startsWith(webappPath)) { - throw new RpcException("Since you are using server='servlet', " + - "make sure that the 'contextpath' property starts with the path of external webapp"); - } - contextPath = contextPath.substring(webappPath.length()); - if (contextPath.startsWith(PATH_SEPARATOR)) { - contextPath = contextPath.substring(1); - } - } - } - - final Class resourceDef = GetRestful.getRootResourceClass(implClass) != null ? implClass : type; - - server.deploy(resourceDef, impl, contextPath); - - final RestProtocolServer s = server; - return () -> { - // TODO due to dubbo's current architecture, - // it will be called from registry protocol in the shutdown process and won't appear in logs - s.undeploy(resourceDef); }; + exporterMap.put(uri, exporter); + return exporter; } @@ -146,108 +122,42 @@ public class RestProtocol extends AbstractProxyProtocol { synchronized (clients) { refClient = clients.get(url.getAddress()); if (refClient == null || refClient.isDestroyed()) { - refClient = ConcurrentHashMapUtils.computeIfAbsent(clients, url.getAddress(), _key -> createReferenceCountedClient(url,clients)); + refClient = ConcurrentHashMapUtils.computeIfAbsent(clients, url.getAddress(), _key -> createReferenceCountedClient(url)); } } } refClient.retain(); + + String contextPathFromUrl = getContextPath(url); + // resolve metadata - Map> metadataMap = MetadataResolver.resolveConsumerServiceMetadata(type, url); + ServiceRestMetadata serviceRestMetadata = + MetadataResolver.resolveConsumerServiceMetadata(type, url, contextPathFromUrl); - ReferenceCountedClient finalRefClient = refClient; - Invoker invoker = new AbstractInvoker(type, url, new String[]{INTERFACE_KEY, GROUP_KEY, TOKEN_KEY}) { - @Override - protected Result doInvoke(Invocation invocation) { - try { - RestMethodMetadata restMethodMetadata = metadataMap.get(invocation.getMethodName()).get(ParameterTypesComparator.getInstance(invocation.getParameterTypes())); + Invoker invoker = new RestInvoker(type, url, + refClient, httpConnectionPreBuildIntercepts, serviceRestMetadata); - 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 future = finalRefClient.getClient().send(requestTemplate); - CompletableFuture 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 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; - } - } - - @Override - public void destroy() { - super.destroy(); - invokers.remove(this); - destroyInternal(url); - } - }; invokers.add(invoker); return invoker; } - private ReferenceCountedClient createReferenceCountedClient(URL url, ConcurrentMap> clients) throws RpcException { + /** + * create rest ReferenceCountedClient + * + * @param url + * @return + * @throws RpcException + */ + private ReferenceCountedClient createReferenceCountedClient(URL url) throws RpcException { // url -> RestClient RestClient restClient = clientFactory.createRestClient(url); - ReferenceCountedClient refClient = new ReferenceCountedClient(restClient, clients, clientFactory, url); - - return refClient; + return new ReferenceCountedClient<>(restClient, clients, clientFactory, url); } - @Override - protected int getErrorCode(Throwable e) { - // TODO - return super.getErrorCode(e); - } @Override public void destroy() { @@ -289,7 +199,7 @@ public class RestProtocol extends AbstractProxyProtocol { * * @return return path only if user has explicitly gave then a value. */ - protected String getContextPath(URL url) { + private String getContextPath(URL url) { String contextPath = url.getPath(); if (contextPath != null) { if (contextPath.equalsIgnoreCase(url.getParameter(INTERFACE_KEY))) { @@ -304,8 +214,8 @@ public class RestProtocol extends AbstractProxyProtocol { } } - @Override - protected void destroyInternal(URL url) { + + private void destroyInternal(URL url) { try { ReferenceCountedClient referenceCountedClient = clients.get(url.getAddress()); if (referenceCountedClient != null && referenceCountedClient.release()) { diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestProtocolServer.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestProtocolServer.java index ee03685662..4de74c937d 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestProtocolServer.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestProtocolServer.java @@ -17,17 +17,18 @@ package org.apache.dubbo.rpc.protocol.rest; import org.apache.dubbo.common.URL; +import org.apache.dubbo.metadata.rest.ServiceRestMetadata; +import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.ProtocolServer; + public interface RestProtocolServer extends ProtocolServer { void start(URL url); - /** - * @param resourceDef it could be either resource interface or resource impl - */ - void deploy(Class resourceDef, Object resourceInstance, String contextPath); - void undeploy(Class resourceDef); + void deploy(ServiceRestMetadata serviceRestMetadata, Invoker invoker); + + void undeploy(ServiceRestMetadata serviceRestMetadata); } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestRPCInvocationUtil.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestRPCInvocationUtil.java new file mode 100644 index 0000000000..8388e1fe88 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestRPCInvocationUtil.java @@ -0,0 +1,142 @@ +/* + * 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; + +import org.apache.dubbo.common.BaseServiceMetadata; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.metadata.rest.ArgInfo; +import org.apache.dubbo.metadata.rest.RestMethodMetadata; +import org.apache.dubbo.rpc.RpcInvocation; +import org.apache.dubbo.rpc.protocol.rest.annotation.ParamParserManager; +import org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.provider.ProviderParseContext; +import org.apache.dubbo.rpc.protocol.rest.exception.ParamParseException; +import org.apache.dubbo.rpc.protocol.rest.pair.InvokerAndRestMethodMetadataPair; +import org.apache.dubbo.rpc.protocol.rest.request.RequestFacade; +import org.apache.dubbo.rpc.protocol.rest.util.HttpHeaderUtil; + + +import java.util.Arrays; +import java.util.List; + + +public class RestRPCInvocationUtil { + + private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(RestRPCInvocationUtil.class); + + /** + * service method real args parse + * + * @param rpcInvocation + * @param request + * @param servletRequest + * @param servletResponse + * @param restMethodMetadata + */ + public static void parseMethodArgs(RpcInvocation rpcInvocation, RequestFacade request, Object servletRequest, + Object servletResponse, + RestMethodMetadata restMethodMetadata) { + + try { + ProviderParseContext parseContext = createParseContext(request, servletRequest, servletResponse, restMethodMetadata); + Object[] args = ParamParserManager.providerParamParse(parseContext); + + List argInfos = parseContext.getArgInfos(); + + for (ArgInfo argInfo : argInfos) { + // TODO set default value + if (argInfo.getParamType().isPrimitive() && args[argInfo.getIndex()] == null) { + throw new ParamParseException("\n dubbo provider primitive arg not exist in request, method is: " + + restMethodMetadata.getReflectMethod() + "\n type is: " + + argInfo.getParamType() + " \n and arg index is: " + argInfo.getIndex()); + } + } + + rpcInvocation.setArguments(args); + } catch (Exception e) { + logger.error("", e.getMessage(), "", "dubbo rest provider method args parse error: ", e); + throw new ParamParseException(e.getMessage()); + } + } + + /** + * create parseMethodArgs context + * + * @param request + * @param servletRequest + * @param servletResponse + * @param restMethodMetadata + * @return + */ + private static ProviderParseContext createParseContext(RequestFacade request, Object servletRequest, Object servletResponse, RestMethodMetadata restMethodMetadata) { + ProviderParseContext parseContext = new ProviderParseContext(request); + parseContext.setResponse(servletResponse); + parseContext.setRequest(servletRequest); + + Object[] objects = new Object[restMethodMetadata.getArgInfos().size()]; + parseContext.setArgs(Arrays.asList(objects)); + parseContext.setArgInfos(restMethodMetadata.getArgInfos()); + + + return parseContext; + } + + /** + * build RpcInvocation + * + * @param request + * @param restMethodMetadata + * @return + */ + public static RpcInvocation createBaseRpcInvocation(RequestFacade request, RestMethodMetadata restMethodMetadata) { + RpcInvocation rpcInvocation = new RpcInvocation(); + + rpcInvocation.setParameterTypes(restMethodMetadata.getReflectMethod().getParameterTypes()); + rpcInvocation.setReturnType(restMethodMetadata.getReflectMethod().getReturnType()); + rpcInvocation.setMethodName(restMethodMetadata.getMethod().getName()); + + // TODO set protocolServiceKey ,but no set method +// + + HttpHeaderUtil.parseRequest(rpcInvocation, request); + + String serviceKey = BaseServiceMetadata.buildServiceKey(request.getHeader(RestHeaderEnum.PATH.getHeader()), + request.getHeader(RestHeaderEnum.GROUP.getHeader()), + request.getHeader(RestHeaderEnum.VERSION.getHeader())); + rpcInvocation.setTargetServiceUniqueName(serviceKey); + + return rpcInvocation; + } + + + /** + * get path mapping + * + * @param request + * @param pathAndInvokerMapper + * @return + */ + public static InvokerAndRestMethodMetadataPair getRestMethodMetadata(RequestFacade request, PathAndInvokerMapper pathAndInvokerMapper) { + String path = request.getPath(); + String version = request.getHeader(RestHeaderEnum.VERSION.getHeader()); + String group = request.getHeader(RestHeaderEnum.GROUP.getHeader()); + + return pathAndInvokerMapper.getRestMethodMetadata(path, version, group, null); + } + + +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestServerFactory.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestServerFactory.java index 01c0df5aed..367c3a0195 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestServerFactory.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RestServerFactory.java @@ -16,8 +16,6 @@ */ 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 @@ -25,15 +23,8 @@ import org.apache.dubbo.rpc.model.FrameworkModel; */ public class RestServerFactory { - private static final HttpBinder httpBinder = FrameworkModel.defaultModel().getAdaptiveExtension(HttpBinder.class); public RestProtocolServer createServer(String name) { - if (Constants.SERVLET.equalsIgnoreCase(name) || Constants.JETTY.equalsIgnoreCase(name) || Constants.TOMCAT.equalsIgnoreCase(name)) { - return new DubboHttpProtocolServer(httpBinder); - } else if (Constants.NETTY.equalsIgnoreCase(name)) { - return new NettyRestProtocolServer(); - } else { - throw new IllegalArgumentException("Unrecognized server name: " + name); - } + return new NettyHttpRestServer(); } } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RpcContextFilter.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RpcContextFilter.java deleted file mode 100644 index 3572ea67e3..0000000000 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RpcContextFilter.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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; - -import org.apache.dubbo.common.constants.CommonConstants; -import org.apache.dubbo.common.utils.CollectionUtils; -import org.apache.dubbo.common.utils.StringUtils; -import org.apache.dubbo.rpc.Invocation; -import org.apache.dubbo.rpc.RpcContext; - -import org.jboss.resteasy.spi.ResteasyProviderFactory; - -import javax.annotation.Priority; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.client.ClientRequestContext; -import javax.ws.rs.client.ClientRequestFilter; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.container.ContainerRequestFilter; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; - -@Priority(Integer.MIN_VALUE + 1) -public class RpcContextFilter implements ContainerRequestFilter, ClientRequestFilter { - - private static final String DUBBO_ATTACHMENT_HEADER = "Dubbo-Attachments"; - - // currently, we use a single header to hold the attachments so that the total attachment size limit is about 8k - private static final int MAX_HEADER_SIZE = 8 * 1024; - - @Override - public void filter(ContainerRequestContext requestContext) throws IOException { - HttpServletRequest request = ResteasyProviderFactory.getContextData(HttpServletRequest.class); - RpcContext.getServiceContext().setRequest(request); - - // this only works for servlet containers - if (request != null && RpcContext.getServiceContext().getRemoteAddress() == null) { - RpcContext.getServiceContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort()); - } - - RpcContext.getServiceContext().setResponse(ResteasyProviderFactory.getContextData(HttpServletResponse.class)); - - String headers = requestContext.getHeaderString(DUBBO_ATTACHMENT_HEADER); - if (headers != null) { - for (String header : headers.split(CommonConstants.COMMA_SEPARATOR)) { - int index = header.indexOf("="); - if (index > 0) { - String key = header.substring(0, index); - String value = header.substring(index + 1); - if (!StringUtils.isEmpty(key)) { - RpcContext.getServerAttachment().setAttachment(key.trim(), value.trim()); - } - } - } - } - } - - @Override - public void filter(ClientRequestContext requestContext) throws IOException { - int size = 0; - Map objectAttachments = new HashMap<>(RpcContext.getClientAttachment().getObjectAttachments()); - Invocation invocation = RpcContext.getServiceContext().getInvocation(); - //should merge attachments from invocation and RpcContext.getClientAttachment() - if(invocation != null && CollectionUtils.isNotEmptyMap(invocation.getObjectAttachments()) ){ - objectAttachments.putAll(invocation.getObjectAttachments()); - } - for (Map.Entry entry : objectAttachments.entrySet()) { - String key = entry.getKey(); - String value = (String) entry.getValue(); - if (illegalHttpHeaderKey(key) || illegalHttpHeaderValue(value)) { - throw new IllegalArgumentException("The attachments of " + RpcContext.class.getSimpleName() + " must not contain ',' or '=' when using rest protocol"); - } - - // TODO for now we don't consider the differences of encoding and server limit - if (value != null) { - size += value.getBytes(StandardCharsets.UTF_8).length; - } - if (size > MAX_HEADER_SIZE) { - throw new IllegalArgumentException("The attachments of " + RpcContext.class.getSimpleName() + " is too big"); - } - - String attachments = key + "=" + value; - requestContext.getHeaders().add(DUBBO_ATTACHMENT_HEADER, attachments); - } - } - - private boolean illegalHttpHeaderKey(String key) { - if (StringUtils.isNotEmpty(key)) { - return key.contains(CommonConstants.COMMA_SEPARATOR) || key.contains("="); - } - return false; - } - - private boolean illegalHttpHeaderValue(String value) { - if (StringUtils.isNotEmpty(value)) { - return value.contains(CommonConstants.COMMA_SEPARATOR); - } - return false; - } -} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RpcExceptionMapper.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RpcExceptionMapper.java index de6d2e45a6..a50a2dda5a 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RpcExceptionMapper.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/RpcExceptionMapper.java @@ -17,25 +17,14 @@ package org.apache.dubbo.rpc.protocol.rest; import org.apache.dubbo.rpc.RpcException; -import org.apache.dubbo.rpc.protocol.rest.support.ContentType; +import org.apache.dubbo.rpc.protocol.rest.exception.mapper.ExceptionHandler; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; -import javax.ws.rs.core.Response; -import javax.ws.rs.ext.ExceptionMapper; -public class RpcExceptionMapper implements ExceptionMapper { +public class RpcExceptionMapper implements ExceptionHandler { - @Override - public Response toResponse(RpcException e) { - if (e.getCause() instanceof ConstraintViolationException) { - return handleConstraintViolationException((ConstraintViolationException) e.getCause()); - } - // we may want to avoid exposing the dubbo exception details to certain clients - return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("Internal server error: " + e.getMessage()).type(ContentType.TEXT_PLAIN_UTF_8).build(); - } - - protected Response handleConstraintViolationException(ConstraintViolationException cve) { + protected Object handleConstraintViolationException(ConstraintViolationException cve) { ViolationReport report = new ViolationReport(); for (ConstraintViolation cv : cve.getConstraintViolations()) { report.addConstraintViolation(new RestConstraintViolation( @@ -43,6 +32,14 @@ public class RpcExceptionMapper implements ExceptionMapper { cv.getMessage(), cv.getInvalidValue() == null ? "null" : cv.getInvalidValue().toString())); } - return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(report).type(ContentType.TEXT_XML_UTF_8).build(); + return report; + } + + @Override + public Object result(RpcException e) { + if (e.getCause() instanceof ConstraintViolationException) { + return handleConstraintViolationException((ConstraintViolationException) e.getCause()); + } + return "Internal server error: " + e.getMessage(); } } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/ParamParserManager.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/ParamParserManager.java index 476f7aaed0..fb865bf417 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/ParamParserManager.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/ParamParserManager.java @@ -21,6 +21,8 @@ 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 org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.provider.BaseProviderParamParser; +import org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.provider.ProviderParseContext; import java.util.List; import java.util.Set; @@ -31,6 +33,9 @@ public class ParamParserManager { private static final Set consumerParamParsers = FrameworkModel.defaultModel().getExtensionLoader(BaseConsumerParamParser.class).getSupportedExtensionInstances(); + private static final Set providerParamParsers = + FrameworkModel.defaultModel().getExtensionLoader(BaseProviderParamParser.class).getSupportedExtensionInstances(); + /** * provider Design Description: *

@@ -41,11 +46,35 @@ public class ParamParserManager { *

* args=toArray(new Object[0]); */ - public void consumerParamParse(ConsumerParseContext parseContext) { + public static Object[] providerParamParse(ProviderParseContext parseContext) { - List args = parseContext.getArgs(); + List args = parseContext.getArgInfos(); for (int i = 0; i < args.size(); i++) { + for (ParamParser paramParser : providerParamParsers) { + + paramParser.parse(parseContext, args.get(i)); + } + } + return parseContext.getArgs().toArray(new Object[0]); + } + + + /** + * consumer Design Description: + *

+ * Object[] args=new Object[0]; + * List argsList=new ArrayList<>; + *

+ * setValueByIndex(int index,Object value); + *

+ * args=toArray(new Object[0]); + */ + public static void consumerParamParse(ConsumerParseContext parseContext) { + + List argInfos = parseContext.getArgInfos(); + + for (int i = 0; i < argInfos.size(); i++) { for (BaseConsumerParamParser paramParser : consumerParamParsers) { ArgInfo argInfoByIndex = parseContext.getArgInfoByIndex(i); diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/HttpConnectionCreateContext.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/HttpConnectionCreateContext.java index 73e0635cc4..584b831835 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/HttpConnectionCreateContext.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/HttpConnectionCreateContext.java @@ -25,8 +25,8 @@ import org.apache.dubbo.rpc.Invocation; public class HttpConnectionCreateContext { private RequestTemplate requestTemplate; - private HttpConnectionConfig connectionConfig; private RestMethodMetadata restMethodMetadata; + private ServiceRestMetadata serviceRestMetadata; private Invocation invocation; private URL url; @@ -38,21 +38,13 @@ public class HttpConnectionCreateContext { 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(); + return serviceRestMetadata; } public RestMethodMetadata getRestMethodMetadata() { @@ -78,4 +70,8 @@ public class HttpConnectionCreateContext { public void setUrl(URL url) { this.url = url; } + + public void setServiceRestMetadata(ServiceRestMetadata serviceRestMetadata) { + this.serviceRestMetadata = serviceRestMetadata; + } } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/HttpConnectionPreBuildIntercept.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/HttpConnectionPreBuildIntercept.java index 3862e18314..a25466ed20 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/HttpConnectionPreBuildIntercept.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/HttpConnectionPreBuildIntercept.java @@ -19,6 +19,9 @@ package org.apache.dubbo.rpc.protocol.rest.annotation.consumer; import org.apache.dubbo.common.extension.ExtensionScope; import org.apache.dubbo.common.extension.SPI; +/** + * http request build intercept + */ @SPI(scope = ExtensionScope.FRAMEWORK) public interface HttpConnectionPreBuildIntercept { void intercept(HttpConnectionCreateContext connectionCreateContext); diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/AddMustAttachmentIntercept.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/AddMustAttachmentIntercept.java index 446cd4e6f3..a971390603 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/AddMustAttachmentIntercept.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/AddMustAttachmentIntercept.java @@ -20,11 +20,14 @@ 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.RestHeaderEnum; 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; +/** + * add some must attachment + */ @Activate(value = RestConstant.ADD_MUST_ATTTACHMENT,order = 1) public class AddMustAttachmentIntercept implements HttpConnectionPreBuildIntercept { @@ -33,13 +36,11 @@ public class AddMustAttachmentIntercept implements HttpConnectionPreBuildInterce 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()); + requestTemplate.addHeader(RestHeaderEnum.GROUP.getHeader(), serviceRestMetadata.getGroup()); + requestTemplate.addHeader(RestHeaderEnum.VERSION.getHeader(), serviceRestMetadata.getVersion()); + requestTemplate.addHeader(RestHeaderEnum.PATH.getHeader(), serviceRestMetadata.getServiceInterface()); } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/AttachmentIntercept.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/AttachmentIntercept.java index 3903aa654c..d1e1126f5d 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/AttachmentIntercept.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/AttachmentIntercept.java @@ -17,54 +17,24 @@ package org.apache.dubbo.rpc.protocol.rest.annotation.consumer.inercept; import org.apache.dubbo.common.extension.Activate; -import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.remoting.http.RequestTemplate; -import org.apache.dubbo.rpc.RpcContext; 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; +import org.apache.dubbo.rpc.protocol.rest.util.HttpHeaderUtil; -import java.nio.charset.StandardCharsets; -import java.util.Map; -@Activate(value = RestConstant.RPCCONTEXT_INTERCEPT,order = 3) +/** + * add client rpc context to request geader + */ +@Activate(value = RestConstant.RPCCONTEXT_INTERCEPT, order = 3) public class AttachmentIntercept implements HttpConnectionPreBuildIntercept { @Override public void intercept(HttpConnectionCreateContext connectionCreateContext) { RequestTemplate requestTemplate = connectionCreateContext.getRequestTemplate(); - int size = 0; - for (Map.Entry entry : connectionCreateContext.getInvocation().getObjectAttachments().entrySet()) { - String key = entry.getKey(); - String value = String.valueOf(entry.getValue()); - if (illegalHttpHeaderKey(key) || illegalHttpHeaderValue(value)) { - throw new IllegalArgumentException("The attachments of " + RpcContext.class.getSimpleName() + " must not contain ',' or '=' when using rest protocol"); - } - // TODO for now we don't consider the differences of encoding and server limit - if (value != null) { - size += value.getBytes(StandardCharsets.UTF_8).length; - } - if (size > RestConstant.MAX_HEADER_SIZE) { - throw new IllegalArgumentException("The attachments of " + RpcContext.class.getSimpleName() + " is too big"); - } - - String attachments = key + "=" + value; - requestTemplate.addHeader(RestConstant.DUBBO_ATTACHMENT_HEADER, attachments); - } + HttpHeaderUtil.addRequestAttachments(requestTemplate, connectionCreateContext.getInvocation().getObjectAttachments()); } - private boolean illegalHttpHeaderKey(String key) { - if (StringUtils.isNotEmpty(key)) { - return key.contains(",") || key.contains("="); - } - return false; - } - - private boolean illegalHttpHeaderValue(String value) { - if (StringUtils.isNotEmpty(value)) { - return value.contains(","); - } - return false; - } } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/ParamParseIntercept.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/ParamParseIntercept.java index 9babb8c605..951d19476b 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/ParamParseIntercept.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/ParamParseIntercept.java @@ -24,9 +24,11 @@ import org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.consumer.Consum import java.util.Arrays; +/** + * resolve method args by args info + */ @Activate(value = "paramparse",order = 5) public class ParamParseIntercept implements HttpConnectionPreBuildIntercept { - private static final ParamParserManager paramParser = new ParamParserManager(); @Override public void intercept(HttpConnectionCreateContext connectionCreateContext) { @@ -34,6 +36,6 @@ public class ParamParseIntercept implements HttpConnectionPreBuildIntercept { ConsumerParseContext consumerParseContext = new ConsumerParseContext(connectionCreateContext.getRequestTemplate()); consumerParseContext.setArgInfos(connectionCreateContext.getRestMethodMetadata().getArgInfos()); consumerParseContext.setArgs(Arrays.asList(connectionCreateContext.getInvocation().getArguments())); - paramParser.consumerParamParse(consumerParseContext); + ParamParserManager.consumerParamParse(consumerParseContext); } } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/PathVariableIntercept.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/PathVariableIntercept.java index 8329b086ad..9837d352b8 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/PathVariableIntercept.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/PathVariableIntercept.java @@ -29,6 +29,9 @@ import org.apache.dubbo.rpc.protocol.rest.constans.RestConstant; import java.util.Arrays; import java.util.List; +/** + * resolve method args from path + */ @Activate(value = RestConstant.PATH_INTERCEPT,order = 4) public class PathVariableIntercept implements HttpConnectionPreBuildIntercept { diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/RequestHeaderIntercept.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/RequestHeaderIntercept.java index e9ee687118..9ded1c14d3 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/RequestHeaderIntercept.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/RequestHeaderIntercept.java @@ -17,9 +17,11 @@ package org.apache.dubbo.rpc.protocol.rest.annotation.consumer.inercept; +import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.metadata.rest.RestMethodMetadata; import org.apache.dubbo.remoting.http.RequestTemplate; +import org.apache.dubbo.rpc.protocol.rest.RestHeaderEnum; 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; @@ -27,7 +29,10 @@ import org.apache.dubbo.rpc.protocol.rest.constans.RestConstant; import java.util.Collection; import java.util.Set; -@Activate(value = RestConstant.REQUEST_HEADER_INTERCEPT,order = 2) +/** + * resolve method args from header + */ +@Activate(value = RestConstant.REQUEST_HEADER_INTERCEPT, order = 2) public class RequestHeaderIntercept implements HttpConnectionPreBuildIntercept { @Override @@ -37,16 +42,21 @@ public class RequestHeaderIntercept implements HttpConnectionPreBuildIntercept { RequestTemplate requestTemplate = connectionCreateContext.getRequestTemplate(); - Set consumes = restMethodMetadata.getRequest().getConsumes(); - requestTemplate.addHeaders(RestConstant.CONTENT_TYPE, consumes); + requestTemplate.addHeaders(RestHeaderEnum.CONTENT_TYPE.getHeader(), consumes); - Collection headers = requestTemplate.getHeaders(RestConstant.ACCEPT); - if (headers == null || headers.isEmpty()) { - requestTemplate.addHeader(RestConstant.ACCEPT, RestConstant.DEFAULT_ACCEPT); + Collection produces = restMethodMetadata.getRequest().getProduces(); + if (produces == null || produces.isEmpty()) { + requestTemplate.addHeader(RestHeaderEnum.ACCEPT.getHeader(), RestConstant.DEFAULT_ACCEPT); + } else { + requestTemplate.addHeader(RestHeaderEnum.ACCEPT.getHeader(), produces); } + URL url = connectionCreateContext.getUrl(); + + requestTemplate.addKeepAliveHeader(url.getParameter(RestConstant.KEEP_ALIVE_TIMEOUT_PARAM,RestConstant.KEEP_ALIVE_TIMEOUT)); + } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/SerializeBodyIntercept.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/SerializeBodyIntercept.java index 6902cb987b..a29792be5b 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/SerializeBodyIntercept.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/inercept/SerializeBodyIntercept.java @@ -24,6 +24,7 @@ import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; import org.apache.dubbo.common.logger.LoggerFactory; import org.apache.dubbo.metadata.rest.media.MediaType; import org.apache.dubbo.remoting.http.RequestTemplate; +import org.apache.dubbo.rpc.RpcException; 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; @@ -33,6 +34,9 @@ import org.apache.dubbo.rpc.protocol.rest.util.MediaTypeUtil; import java.io.ByteArrayOutputStream; import java.util.Collection; +/** + * for request body Serialize + */ @Activate(value = RestConstant.SERIALIZE_INTERCEPT, order = Integer.MAX_VALUE) public class SerializeBodyIntercept implements HttpConnectionPreBuildIntercept { @@ -50,13 +54,21 @@ public class SerializeBodyIntercept implements HttpConnectionPreBuildIntercept { try { Object unSerializedBody = requestTemplate.getUnSerializedBody(); URL url = connectionCreateContext.getUrl(); + // TODO pool ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); Collection headers = requestTemplate.getHeaders(RestConstant.CONTENT_TYPE); - MediaType mediaType = MediaTypeUtil.convertMediaType(headers.toArray(new String[0])); - HttpMessageCodecManager.httpMessageEncode(outputStream, unSerializedBody, url, mediaType); + MediaType mediaType = MediaTypeUtil.convertMediaType(requestTemplate.getBodyType(), headers.toArray(new String[0])); + + // add mediaType by targetClass serialize + if (headers.isEmpty() && mediaType != null && !mediaType.equals(MediaType.ALL_VALUE)) { + headers.add(mediaType.value); + } + HttpMessageCodecManager.httpMessageEncode(outputStream, unSerializedBody, url, mediaType, requestTemplate.getBodyType()); requestTemplate.serializeBody(outputStream.toByteArray()); + outputStream.close(); } catch (Exception e) { logger.error(LoggerCodeConstants.PROTOCOL_ERROR_DESERIALIZE, "", "", "Rest SerializeBodyIntercept serialize error: {}", e); + throw new RpcException(e); } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/metadata/MetadataResolver.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/metadata/MetadataResolver.java index 69715715a1..f1521476a1 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/metadata/MetadataResolver.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/metadata/MetadataResolver.java @@ -18,13 +18,10 @@ package org.apache.dubbo.rpc.protocol.rest.annotation.metadata; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.ExtensionLoader; -import org.apache.dubbo.metadata.ParameterTypesComparator; -import org.apache.dubbo.metadata.rest.RestMethodMetadata; import org.apache.dubbo.metadata.rest.ServiceRestMetadata; import org.apache.dubbo.metadata.rest.ServiceRestMetadataResolver; import org.apache.dubbo.rpc.protocol.rest.exception.CodeStyleNotSupportException; -import java.util.Map; public class MetadataResolver { private MetadataResolver() { @@ -34,18 +31,19 @@ public class MetadataResolver { * for consumer * * @param targetClass target service class - * @param url consumer url + * @param url consumer url * @return rest metadata * @throws CodeStyleNotSupportException not support type */ - public static Map> resolveConsumerServiceMetadata(Class targetClass, URL url) { + public static ServiceRestMetadata resolveConsumerServiceMetadata(Class targetClass, URL url, String contextPathFromUrl) { ExtensionLoader extensionLoader = url.getOrDefaultApplicationModel().getExtensionLoader(ServiceRestMetadataResolver.class); for (ServiceRestMetadataResolver serviceRestMetadataResolver : extensionLoader.getSupportedExtensionInstances()) { if (serviceRestMetadataResolver.supports(targetClass, true)) { ServiceRestMetadata serviceRestMetadata = new ServiceRestMetadata(url.getServiceInterface(), url.getVersion(), url.getGroup(), true); + serviceRestMetadata.setContextPathFromUrl(contextPathFromUrl); ServiceRestMetadata resolve = serviceRestMetadataResolver.resolve(targetClass, serviceRestMetadata); - return resolve.getMethodToServiceMap(); + return resolve; } } @@ -54,4 +52,19 @@ public class MetadataResolver { } + public static ServiceRestMetadata resolveProviderServiceMetadata(Class serviceImpl, URL url, String contextPathFromUrl) { + ExtensionLoader extensionLoader = url.getOrDefaultApplicationModel().getExtensionLoader(ServiceRestMetadataResolver.class); + + for (ServiceRestMetadataResolver serviceRestMetadataResolver : extensionLoader.getSupportedExtensionInstances()) { + boolean supports = serviceRestMetadataResolver.supports(serviceImpl); + if (supports) { + ServiceRestMetadata serviceRestMetadata = new ServiceRestMetadata(url.getServiceInterface(), url.getVersion(), url.getGroup(), false); + serviceRestMetadata.setContextPathFromUrl(contextPathFromUrl); + ServiceRestMetadata resolve = serviceRestMetadataResolver.resolve(serviceImpl, serviceRestMetadata); + return resolve; + } + } + throw new CodeStyleNotSupportException("service is:" + serviceImpl + ",just support rest or spring-web annotation"); + } + } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/consumer/BodyConsumerParamParser.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/consumer/BodyConsumerParamParser.java index 0e9e02a9af..0632ca3474 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/consumer/BodyConsumerParamParser.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/consumer/BodyConsumerParamParser.java @@ -33,7 +33,7 @@ public class BodyConsumerParamParser implements BaseConsumerParamParser { RequestTemplate requestTemplate = parseContext.getRequestTemplate(); - requestTemplate.body(args.get(argInfo.getIndex())); + requestTemplate.body(args.get(argInfo.getIndex()),argInfo.getParamType()); } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/consumer/FormConsumerParamParser.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/consumer/FormConsumerParamParser.java index dba932b8de..6ecd7c2f16 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/consumer/FormConsumerParamParser.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/consumer/FormConsumerParamParser.java @@ -17,12 +17,17 @@ package org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.consumer; import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.utils.ReflectUtils; import org.apache.dubbo.metadata.rest.ArgInfo; import org.apache.dubbo.metadata.rest.ParamType; import org.apache.dubbo.remoting.http.RequestTemplate; -import org.apache.dubbo.rpc.protocol.rest.util.MultiValueCreator; +import org.apache.dubbo.rpc.protocol.rest.util.DataParseUtils; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Set; @Activate("consumer-form") public class FormConsumerParamParser implements BaseConsumerParamParser { @@ -35,14 +40,29 @@ public class FormConsumerParamParser implements BaseConsumerParamParser { RequestTemplate requestTemplate = parseContext.getRequestTemplate(); Object value = args.get(argInfo.getIndex()); - Object unSerializedBody = requestTemplate.getUnSerializedBody(); - if (unSerializedBody == null) { - unSerializedBody = MultiValueCreator.createMultiValueMap(); + if (value == null) { + return; } - MultiValueCreator.add(unSerializedBody, argInfo.getAnnotationNameAttribute(), String.valueOf(value)); - requestTemplate.body(unSerializedBody); + Map> tmp = new HashMap<>(); + if (DataParseUtils.isTextType(value.getClass())) { + tmp.put(argInfo.getAnnotationNameAttribute(), Arrays.asList(String.valueOf(value))); + requestTemplate.body(tmp, Map.class); + } else if (value instanceof Map) { + requestTemplate.body(value, Map.class); + } else { + Set allFieldNames = ReflectUtils.getAllFieldNames(value.getClass()); + + allFieldNames.stream().forEach(entry -> { + + Object fieldValue = ReflectUtils.getFieldValue(value, entry); + tmp.put(String.valueOf(entry), Arrays.asList(String.valueOf(fieldValue))); + } + ); + + requestTemplate.body(tmp, Map.class); + } } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/consumer/HeaderConsumerParamParser.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/consumer/HeaderConsumerParamParser.java index b53591e491..dd25756a4d 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/consumer/HeaderConsumerParamParser.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/consumer/HeaderConsumerParamParser.java @@ -22,6 +22,7 @@ import org.apache.dubbo.metadata.rest.ParamType; import org.apache.dubbo.remoting.http.RequestTemplate; import java.util.List; +import java.util.Map; @Activate("consumer-header") public class HeaderConsumerParamParser implements BaseConsumerParamParser { @@ -31,7 +32,25 @@ public class HeaderConsumerParamParser implements BaseConsumerParamParser { RequestTemplate requestTemplate = parseContext.getRequestTemplate(); - requestTemplate.addHeader(argInfo.getParamName(), args.get(argInfo.getIndex())); + Object headerValue = args.get(argInfo.getIndex()); + + if (headerValue == null) { + return; + } + + + // Map + if (Map.class.isAssignableFrom(argInfo.getParamType())) { + Map headerValues = (Map) headerValue; + for (Object name : headerValues.keySet()) { + requestTemplate.addHeader(String.valueOf(name), headerValues.get(name)); + } + } else { + // others + requestTemplate.addHeader(argInfo.getParamName(), headerValue); + + } + } @Override diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/consumer/ParameterConsumerParamParser.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/consumer/ParameterConsumerParamParser.java index eb2746f1bb..23e85ef239 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/consumer/ParameterConsumerParamParser.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/consumer/ParameterConsumerParamParser.java @@ -22,16 +22,34 @@ import org.apache.dubbo.metadata.rest.ParamType; import org.apache.dubbo.remoting.http.RequestTemplate; import java.util.List; +import java.util.Map; @Activate("consumer-parameter") -public class ParameterConsumerParamParser implements BaseConsumerParamParser{ +public class ParameterConsumerParamParser implements BaseConsumerParamParser { @Override public void parse(ConsumerParseContext parseContext, ArgInfo argInfo) { List args = parseContext.getArgs(); RequestTemplate requestTemplate = parseContext.getRequestTemplate(); - requestTemplate.addParam(argInfo.getParamName(), args.get(argInfo.getIndex())); + Object paramValue = args.get(argInfo.getIndex()); + + if (paramValue == null) { + return; + } + + + if (Map.class.isAssignableFrom(argInfo.getParamType())) { + Map paramValues = (Map) paramValue; + for (Object name : paramValues.keySet()) { + requestTemplate.addParam(String.valueOf(name), paramValues.get(name)); + } + } else { + requestTemplate.addParam(argInfo.getParamName(), paramValue); + + + } + } @Override diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/provider/BaseProviderParamParser.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/provider/BaseProviderParamParser.java new file mode 100644 index 0000000000..343efbbf62 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/provider/BaseProviderParamParser.java @@ -0,0 +1,27 @@ +/* + * 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.param.parse.provider; + + +import org.apache.dubbo.common.extension.ExtensionScope; +import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.rpc.protocol.rest.annotation.ParamParser; + +@SPI(scope = ExtensionScope.FRAMEWORK) +public interface BaseProviderParamParser extends ParamParser { + +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/provider/BodyProviderParamParser.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/provider/BodyProviderParamParser.java new file mode 100644 index 0000000000..ad3fadfae9 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/provider/BodyProviderParamParser.java @@ -0,0 +1,57 @@ +/* + * 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.param.parse.provider; + +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.metadata.rest.ArgInfo; +import org.apache.dubbo.metadata.rest.ParamType; +import org.apache.dubbo.metadata.rest.media.MediaType; +import org.apache.dubbo.rpc.protocol.rest.RestHeaderEnum; +import org.apache.dubbo.rpc.protocol.rest.constans.RestConstant; +import org.apache.dubbo.rpc.protocol.rest.exception.ParamParseException; +import org.apache.dubbo.rpc.protocol.rest.message.HttpMessageCodecManager; +import org.apache.dubbo.rpc.protocol.rest.request.RequestFacade; +import org.apache.dubbo.rpc.protocol.rest.util.MediaTypeUtil; + + + +/** + * body param parse + */ +@Activate(value = RestConstant.PROVIDER_BODY_PARSE) +public class BodyProviderParamParser extends ProviderParamParser { + + @Override + protected void doParse(ProviderParseContext parseContext, ArgInfo argInfo) { + + RequestFacade request = parseContext.getRequestFacade(); + + try { + String contentType = parseContext.getRequestFacade().getHeader(RestHeaderEnum.CONTENT_TYPE.getHeader()); + MediaType mediaType = MediaTypeUtil.convertMediaType(argInfo.getParamType(), contentType); + Object param = HttpMessageCodecManager.httpMessageDecode(request.getInputStream(), argInfo.getParamType(), mediaType); + parseContext.setValueByIndex(argInfo.getIndex(), param); + } catch (Throwable e) { + throw new ParamParseException("dubbo rest protocol provider body param parser error: " + e.getMessage()); + } + } + + @Override + protected ParamType getParamType() { + return ParamType.BODY; + } +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/provider/HeaderProviderParamParser.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/provider/HeaderProviderParamParser.java new file mode 100644 index 0000000000..9b87792bd9 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/provider/HeaderProviderParamParser.java @@ -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.rpc.protocol.rest.annotation.param.parse.provider; + +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.rpc.protocol.rest.constans.RestConstant; +import org.apache.dubbo.rpc.protocol.rest.request.RequestFacade; +import org.apache.dubbo.metadata.rest.ArgInfo; +import org.apache.dubbo.metadata.rest.ParamType; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * header param parse + */ +@Activate(value = RestConstant.PROVIDER_HEADER_PARSE) +public class HeaderProviderParamParser extends ProviderParamParser { + @Override + protected void doParse(ProviderParseContext parseContext, ArgInfo argInfo) { + + //TODO MAP convert + RequestFacade request = parseContext.getRequestFacade(); + if (Map.class.isAssignableFrom(argInfo.getParamType())) { + + Map headerMap = new LinkedHashMap<>(); + Enumeration headerNames = request.getHeaderNames(); + + while (headerNames.hasMoreElements()) { + String name = headerNames.nextElement(); + headerMap.put(name, request.getHeader(name)); + } + parseContext.setValueByIndex(argInfo.getIndex(), headerMap); + return; + + } + + + String header = request.getHeader(argInfo.getAnnotationNameAttribute()); + Object headerValue = paramTypeConvert(argInfo.getParamType(), header); + + + parseContext.setValueByIndex(argInfo.getIndex(), headerValue); + + } + + @Override + protected ParamType getParamType() { + return ParamType.HEADER; + } +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/provider/ParamProviderParamParser.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/provider/ParamProviderParamParser.java new file mode 100644 index 0000000000..5505ba4b80 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/provider/ParamProviderParamParser.java @@ -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.rpc.protocol.rest.annotation.param.parse.provider; + + +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.rpc.protocol.rest.constans.RestConstant; +import org.apache.dubbo.rpc.protocol.rest.request.RequestFacade; +import org.apache.dubbo.metadata.rest.ArgInfo; +import org.apache.dubbo.metadata.rest.ParamType; + +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Http Parameter param parse + */ +@Activate(value = RestConstant.PROVIDER_PARAM_PARSE) +public class ParamProviderParamParser extends ProviderParamParser { + @Override + protected void doParse(ProviderParseContext parseContext, ArgInfo argInfo) { + + //TODO MAP convert + RequestFacade request = parseContext.getRequestFacade(); + + if (Map.class.isAssignableFrom(argInfo.getParamType())) { + + Map paramMap = new LinkedHashMap<>(); + Enumeration parameterNames = request.getParameterNames(); + + while (parameterNames.hasMoreElements()) { + String name = parameterNames.nextElement(); + paramMap.put(name, request.getParameter(name)); + } + parseContext.setValueByIndex(argInfo.getIndex(), paramMap); + return; + + } + + String param = request.getParameter(argInfo.getAnnotationNameAttribute()); + + Object paramValue = paramTypeConvert(argInfo.getParamType(), param); + parseContext.setValueByIndex(argInfo.getIndex(), paramValue); + } + + @Override + protected ParamType getParamType() { + return ParamType.PARAM; + } +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/provider/PathProviderParamParser.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/provider/PathProviderParamParser.java new file mode 100644 index 0000000000..4addb20a53 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/provider/PathProviderParamParser.java @@ -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.rpc.protocol.rest.annotation.param.parse.provider; + + +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.rpc.protocol.rest.constans.RestConstant; +import org.apache.dubbo.metadata.rest.ArgInfo; +import org.apache.dubbo.metadata.rest.ParamType; + + +/** + * path param parse + */ +@Activate(value = RestConstant.PROVIDER_PATH_PARSE) +public class PathProviderParamParser extends ProviderParamParser { + @Override + protected void doParse(ProviderParseContext parseContext, ArgInfo argInfo) { + + String pathVariable = parseContext.getPathVariable(argInfo.getUrlSplitIndex()); + + Object pathVariableValue = paramTypeConvert(argInfo.getParamType(), pathVariable); + + parseContext.setValueByIndex(argInfo.getIndex(), pathVariableValue); + + } + + @Override + protected ParamType getParamType() { + return ParamType.PATH; + } +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/provider/ProviderParamParser.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/provider/ProviderParamParser.java new file mode 100644 index 0000000000..12606b0cf5 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/provider/ProviderParamParser.java @@ -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.rpc.protocol.rest.annotation.param.parse.provider; + + +import org.apache.dubbo.metadata.rest.ArgInfo; +import org.apache.dubbo.metadata.rest.ParamType; +import org.apache.dubbo.rpc.protocol.rest.util.DataParseUtils; + +public abstract class ProviderParamParser implements BaseProviderParamParser { + + public void parse(ProviderParseContext parseContext, ArgInfo argInfo) { + + if (!matchParseType(argInfo.getParamAnnotationType())) { + return; + } + + doParse(parseContext, argInfo); + } + + protected abstract void doParse(ProviderParseContext parseContext, ArgInfo argInfo); + + public boolean matchParseType(Class paramAnno) { + + ParamType paramAnnotType = getParamType(); + return paramAnnotType.supportAnno(paramAnno); + } + + protected abstract ParamType getParamType(); + + protected Object paramTypeConvert(Class targetType, String value) { + + + return DataParseUtils.stringTypeConvert(targetType, value); + + } + + +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/provider/ProviderParseContext.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/provider/ProviderParseContext.java new file mode 100644 index 0000000000..0e2be572a6 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/param/parse/provider/ProviderParseContext.java @@ -0,0 +1,70 @@ +/* + * 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.param.parse.provider; + + +import org.apache.dubbo.rpc.protocol.rest.annotation.BaseParseContext; +import org.apache.dubbo.rpc.protocol.rest.request.RequestFacade; + + +public class ProviderParseContext extends BaseParseContext { + + + private RequestFacade requestFacade; + private Object response; + private Object request; + + + public ProviderParseContext(RequestFacade request) { + this.requestFacade = request; + } + + public RequestFacade getRequestFacade() { + return requestFacade; + } + + public void setValueByIndex(int index, Object value) { + + this.args.set(index, value); + } + + public Object getResponse() { + return response; + } + + public void setResponse(Object response) { + this.response = response; + } + + public Object getRequest() { + return request; + } + + public void setRequest(Object request) { + this.request = request; + } + + public String getPathVariable(int urlSplitIndex) { + + String[] split = getRequestFacade().getRequestURI().split("/"); + + return split[urlSplitIndex]; + + } + + +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/constans/RestConstant.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/constans/RestConstant.java index 8fe65f03a1..189fefd9db 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/constans/RestConstant.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/constans/RestConstant.java @@ -17,44 +17,49 @@ package org.apache.dubbo.rpc.protocol.rest.constans; import org.apache.dubbo.common.constants.CommonConstants; -import org.apache.dubbo.remoting.Constants; public interface RestConstant { - String INTERFACE = CommonConstants.INTERFACE_KEY; - String METHOD = CommonConstants.METHOD_KEY; - String PARAMETER_TYPES_DESC = CommonConstants.GENERIC_PARAMETER_DESC; String VERSION = CommonConstants.VERSION_KEY; String GROUP = CommonConstants.GROUP_KEY; String PATH = CommonConstants.PATH_KEY; - String HOST = CommonConstants.HOST_KEY; String LOCAL_ADDR = "LOCAL_ADDR"; String REMOTE_ADDR = "REMOTE_ADDR"; String LOCAL_PORT = "LOCAL_PORT"; String REMOTE_PORT = "REMOTE_PORT"; - String SERIALIZATION_KEY = Constants.SERIALIZATION_KEY; String PROVIDER_BODY_PARSE = "body"; String PROVIDER_PARAM_PARSE = "param"; String PROVIDER_HEADER_PARSE = "header"; String PROVIDER_PATH_PARSE = "path"; - String PROVIDER_REQUEST_PARSE = "reuqest"; - String DUBBO_ATTACHMENT_HEADER = "Dubbo-Attachments"; - int MAX_HEADER_SIZE = 8 * 1024; String ADD_MUST_ATTTACHMENT = "must-intercept"; String RPCCONTEXT_INTERCEPT = "rpc-context"; String SERIALIZE_INTERCEPT = "serialize"; - String CONTENT_TYPE_INTERCEPT = "content-type"; String PATH_SEPARATOR = "/"; - String REQUEST_PARAM_INTERCEPT = "param"; String REQUEST_HEADER_INTERCEPT = "header"; String PATH_INTERCEPT = "path"; String KEEP_ALIVE_HEADER = "Keep-Alive"; String CONNECTION = "Connection"; - String KEEP_ALIVE = "keep-alive"; String CONTENT_TYPE = "Content-Type"; - String APPLICATION_JSON_VALUE = "application/json"; - String APPLICATION_FORM_URLENCODED_VALUE = "application/x-www-form-urlencoded"; String TEXT_PLAIN = "text/plain"; String ACCEPT = "Accept"; String DEFAULT_ACCEPT = "*/*"; + String REST_HEADER_PREFIX = "#rest#"; + + + // http + String MAX_INITIAL_LINE_LENGTH_PARAM = "max.initial.line.length"; + String MAX_HEADER_SIZE_PARAM = "max.header.size"; + String MAX_CHUNK_SIZE_PARAM = "max.chunk.size"; + String MAX_REQUEST_SIZE_PARAM = "max.request.size"; + String IDLE_TIMEOUT_PARAM = "idle.timeout"; + String KEEP_ALIVE_TIMEOUT_PARAM = "keep.alive.timeout"; + + int MAX_REQUEST_SIZE = 1024 * 1024 * 10; + int MAX_INITIAL_LINE_LENGTH = 4096; + int MAX_HEADER_SIZE = 8192; + int MAX_CHUNK_SIZE = 8192; + int IDLE_TIMEOUT = -1; + int KEEP_ALIVE_TIMEOUT = 60; + + } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/CodeStyleNotSupportException.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/CodeStyleNotSupportException.java index db68cf1ac7..d2c6c5849d 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/CodeStyleNotSupportException.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/CodeStyleNotSupportException.java @@ -16,7 +16,10 @@ */ package org.apache.dubbo.rpc.protocol.rest.exception; -public class CodeStyleNotSupportException extends RuntimeException{ +/** + * only support spring mvc & jaxrs annotation + */ +public class CodeStyleNotSupportException extends RestException{ public CodeStyleNotSupportException(String message) { super(message); diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/DoublePathCheckException.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/DoublePathCheckException.java new file mode 100644 index 0000000000..450f7a2da7 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/DoublePathCheckException.java @@ -0,0 +1,27 @@ +/* + * 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.exception; + +/** + * path mapper contains current path will throw + */ +public class DoublePathCheckException extends RuntimeException { + + public DoublePathCheckException(String message) { + super(message); + } +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/MediaTypeUnSupportException.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/MediaTypeUnSupportException.java new file mode 100644 index 0000000000..7c3d922e44 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/MediaTypeUnSupportException.java @@ -0,0 +1,23 @@ +/* + * 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.exception; + +public class MediaTypeUnSupportException extends RestException{ + public MediaTypeUnSupportException(String message) { + super(message); + } +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/HttpClientException.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/ParamParseException.java similarity index 83% rename from dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/HttpClientException.java rename to dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/ParamParseException.java index 709e12af1f..b0cd3d7507 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/HttpClientException.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/ParamParseException.java @@ -16,9 +16,9 @@ */ package org.apache.dubbo.rpc.protocol.rest.exception; -public class HttpClientException extends RuntimeException { +public class ParamParseException extends RestException { - public HttpClientException(String message) { - super("dubbo http rest protocol param error :"+message); + public ParamParseException(String message) { + super(message); } } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/PathNoFoundException.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/PathNoFoundException.java new file mode 100644 index 0000000000..a8b2751fb3 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/PathNoFoundException.java @@ -0,0 +1,27 @@ +/* + * 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.exception; + +/** + * response code : 404 path no found exception + */ +public class PathNoFoundException extends RestException{ + + public PathNoFoundException(String message) { + super(message); + } +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/RemoteServerInternalException.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/RemoteServerInternalException.java index 1d1219fdbf..a552562dee 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/RemoteServerInternalException.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/RemoteServerInternalException.java @@ -16,7 +16,10 @@ */ package org.apache.dubbo.rpc.protocol.rest.exception; -public class RemoteServerInternalException extends RuntimeException { +/** + * response status code : 500 + */ +public class RemoteServerInternalException extends RestException { public RemoteServerInternalException(String message) { super("dubbo http rest protocol remote error :"+message); diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/RestException.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/RestException.java new file mode 100644 index 0000000000..9956eb87f7 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/RestException.java @@ -0,0 +1,27 @@ +/* + * 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.exception; + +/** + * rest exception super + */ +public class RestException extends RuntimeException { + + public RestException(String message) { + super(message); + } +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/UnSupportContentTypeException.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/UnSupportContentTypeException.java index f895f6ec44..d4ad67b3b2 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/UnSupportContentTypeException.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/UnSupportContentTypeException.java @@ -18,7 +18,7 @@ package org.apache.dubbo.rpc.protocol.rest.exception; import org.apache.dubbo.metadata.rest.media.MediaType; -public class UnSupportContentTypeException extends RuntimeException { +public class UnSupportContentTypeException extends MediaTypeUnSupportException { public UnSupportContentTypeException(String message) { diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/mapper/ExceptionHandler.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/mapper/ExceptionHandler.java new file mode 100644 index 0000000000..792ee5d315 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/mapper/ExceptionHandler.java @@ -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.exception.mapper; + + +public interface ExceptionHandler { + + Object result(E exception); + +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/mapper/ExceptionMapper.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/mapper/ExceptionMapper.java new file mode 100644 index 0000000000..3ea8048042 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/exception/mapper/ExceptionMapper.java @@ -0,0 +1,113 @@ +/* + * 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.exception.mapper; + +import org.apache.dubbo.rpc.protocol.rest.util.ReflectUtils; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class ExceptionMapper { + + // TODO static or instance ? think about influence between difference url exception + private final Map, ExceptionHandler> exceptionHandlerMap = new ConcurrentHashMap<>(); + + public Object exceptionToResult(Object throwable) { + if (!hasExceptionMapper(throwable)) { + return throwable; + } + + return exceptionHandlerMap.get(throwable.getClass()).result((Throwable) throwable); + } + + public boolean hasExceptionMapper(Object throwable) { + if (throwable == null) { + return false; + } + return exceptionHandlerMap.containsKey(throwable.getClass()); + } + + + public void registerMapper(Class exceptionHandler) { + + try { + // resolve Java_Zulu_jdk/17.0.6-10/x64 param is not throwable + List methods = ReflectUtils.getMethodByNameList(exceptionHandler, "result"); + + + Set> exceptions = new HashSet<>(); + + for (Method method : methods) { + Class parameterType = method.getParameterTypes()[0]; + + // param type isAssignableFrom throwable + if (!Throwable.class.isAssignableFrom(parameterType)) { + continue; + } + + exceptions.add(parameterType); + } + + ArrayList> classes = new ArrayList<>(exceptions); + + // if size==1 so ,exception handler for Throwable + if (classes.size() != 1) { + // else remove throwable + exceptions.remove(Throwable.class); + } + + List> constructors = ReflectUtils.getConstructList(exceptionHandler); + + if (constructors.isEmpty()) { + throw new RuntimeException("dubbo rest exception mapper register mapper need exception handler exist no construct declare, current class is: " + exceptionHandler); + } + + // if exceptionHandler is inner class , no arg construct don`t appear , so newInstance don`t use noArgConstruct + Object handler = constructors.get(0).newInstance(new Object[constructors.get(0).getParameterCount()]); + + for (Class exception : exceptions) { + exceptionHandlerMap.put(exception, (ExceptionHandler) handler); + } + + } catch (Exception e) { + throw new RuntimeException("dubbo rest protocol exception mapper register error ", e); + } + + + } + + public void registerMapper(String exceptionMapper) { + try { + registerMapper(ReflectUtils.findClass(exceptionMapper)); + } catch (ClassNotFoundException e) { + throw new RuntimeException("dubbo rest protocol exception mapper register error ", e); + } + + } + + + public void unRegisterMapper(Class exception) { + exceptionHandlerMap.remove(exception); + } +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/handler/NettyHttpHandler.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/handler/NettyHttpHandler.java new file mode 100644 index 0000000000..2bf21d8261 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/handler/NettyHttpHandler.java @@ -0,0 +1,210 @@ +/* + * 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.handler; + +import io.netty.handler.codec.http.FullHttpRequest; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.metadata.rest.RestMethodMetadata; +import org.apache.dubbo.metadata.rest.media.MediaType; +import org.apache.dubbo.remoting.http.HttpHandler; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.RpcContext; +import org.apache.dubbo.rpc.RpcInvocation; +import org.apache.dubbo.rpc.protocol.rest.PathAndInvokerMapper; +import org.apache.dubbo.rpc.protocol.rest.RestRPCInvocationUtil; +import org.apache.dubbo.rpc.protocol.rest.RestHeaderEnum; +import org.apache.dubbo.rpc.protocol.rest.exception.MediaTypeUnSupportException; +import org.apache.dubbo.rpc.protocol.rest.exception.ParamParseException; +import org.apache.dubbo.rpc.protocol.rest.exception.PathNoFoundException; +import org.apache.dubbo.rpc.protocol.rest.exception.UnSupportContentTypeException; +import org.apache.dubbo.rpc.protocol.rest.exception.mapper.ExceptionMapper; +import org.apache.dubbo.rpc.protocol.rest.message.HttpMessageCodecManager; +import org.apache.dubbo.rpc.protocol.rest.netty.NettyHttpResponse; +import org.apache.dubbo.rpc.protocol.rest.pair.InvokerAndRestMethodMetadataPair; +import org.apache.dubbo.rpc.protocol.rest.pair.MessageCodecResultPair; +import org.apache.dubbo.rpc.protocol.rest.request.NettyRequestFacade; +import org.apache.dubbo.rpc.protocol.rest.request.RequestFacade; +import org.apache.dubbo.rpc.protocol.rest.util.MediaTypeUtil; + +import java.io.IOException; + +/** + * netty http request handler + */ +public class NettyHttpHandler implements HttpHandler { + private final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass()); + private final PathAndInvokerMapper pathAndInvokerMapper; + private final ExceptionMapper exceptionMapper; + + + public NettyHttpHandler(PathAndInvokerMapper pathAndInvokerMapper, ExceptionMapper exceptionMapper) { + this.pathAndInvokerMapper = pathAndInvokerMapper; + this.exceptionMapper = exceptionMapper; + } + + @Override + public void handle(NettyRequestFacade requestFacade, NettyHttpResponse nettyHttpResponse) throws IOException { + + // set remote address + RpcContext.getServiceContext().setRemoteAddress(requestFacade.getRemoteAddr(), requestFacade.getRemotePort()); + + // set local address + RpcContext.getServiceContext().setLocalAddress(requestFacade.getLocalAddr(), requestFacade.getLocalPort()); + + // set request + RpcContext.getServiceContext().setRequest(requestFacade.getRequest()); + + // set response + RpcContext.getServiceContext().setResponse(nettyHttpResponse); + // TODO add request filter chain + + FullHttpRequest nettyHttpRequest = requestFacade.getRequest(); + + try { + doHandler(nettyHttpRequest, nettyHttpResponse, requestFacade); + } catch (PathNoFoundException pathNoFoundException) { + logger.error("", pathNoFoundException.getMessage(), "", "dubbo rest protocol provider path no found ,raw request is :" + nettyHttpRequest, pathNoFoundException); + nettyHttpResponse.sendError(404, pathNoFoundException.getMessage()); + } catch (ParamParseException paramParseException) { + logger.error("", paramParseException.getMessage(), "", "dubbo rest protocol provider param parse error ,and raw request is :" + nettyHttpRequest, paramParseException); + nettyHttpResponse.sendError(400, paramParseException.getMessage()); + } catch (MediaTypeUnSupportException contentTypeException) { + logger.error("", contentTypeException.getMessage(), "", "dubbo rest protocol provider content-type un support" + nettyHttpRequest, contentTypeException); + nettyHttpResponse.sendError(415, contentTypeException.getMessage()); + } catch (Throwable throwable) { + logger.error("", throwable.getMessage(), "", "dubbo rest protocol provider error ,and raw request is " + nettyHttpRequest, throwable); + nettyHttpResponse.sendError(500, "dubbo rest invoke Internal error, message is " + throwable.getMessage() + + " , stacktrace is: " + stackTraceToString(throwable)); + } + + + } + + private void doHandler(FullHttpRequest nettyHttpRequest, NettyHttpResponse nettyHttpResponse, RequestFacade request) throws Exception { + // acquire metadata by request + InvokerAndRestMethodMetadataPair restMethodMetadataPair = RestRPCInvocationUtil.getRestMethodMetadata(request, pathAndInvokerMapper); + + Invoker invoker = restMethodMetadataPair.getInvoker(); + + RestMethodMetadata restMethodMetadata = restMethodMetadataPair.getRestMethodMetadata(); + + // content-type support judge,throw unSupportException + acceptSupportJudge(request, restMethodMetadata.getReflectMethod().getReturnType()); + + // build RpcInvocation + RpcInvocation rpcInvocation = RestRPCInvocationUtil.createBaseRpcInvocation(request, restMethodMetadata); + + // parse method real args + RestRPCInvocationUtil.parseMethodArgs(rpcInvocation, request, nettyHttpRequest, nettyHttpResponse, restMethodMetadata); + + // execute business method invoke + Result result = invoker.invoke(rpcInvocation); + + if (result.hasException()) { + Throwable exception = result.getException(); + logger.error("", exception.getMessage(), "", "dubbo rest protocol provider Invoker invoke error", exception); + + if (exceptionMapper.hasExceptionMapper(exception)) { + writeResult(nettyHttpResponse, request, invoker, exceptionMapper.exceptionToResult(result.getException()), rpcInvocation.getReturnType()); + nettyHttpResponse.setStatus(200); + } else { + nettyHttpResponse.sendError(500, + "\n dubbo rest business exception, error cause is: " + + result.getException().getCause() + + "\n message is: " + result.getException().getMessage() + + "\n stacktrace is: " + stackTraceToString(exception)); + } + } else { + Object value = result.getValue(); + writeResult(nettyHttpResponse, request, invoker, value, rpcInvocation.getReturnType()); + nettyHttpResponse.setStatus(200); + } + } + + + /** + * write return value by accept + * + * @param nettyHttpResponse + * @param request + * @param invoker + * @param value + * @param returnType + * @throws Exception + */ + private void writeResult(NettyHttpResponse nettyHttpResponse, RequestFacade request, Invoker invoker, Object value, Class returnType) throws Exception { + MediaType mediaType = getAcceptMediaType(request); + + MessageCodecResultPair booleanMediaTypePair = HttpMessageCodecManager.httpMessageEncode(nettyHttpResponse.getOutputStream(), value, invoker.getUrl(), mediaType, returnType); + + nettyHttpResponse.addOutputHeaders(RestHeaderEnum.CONTENT_TYPE.getHeader(), booleanMediaTypePair.getMediaType().value); + } + + /** + * return first match , if any multiple content-type + * + * @param request + * @return + */ + private MediaType getAcceptMediaType(RequestFacade request) { + String accept = request.getHeader(RestHeaderEnum.ACCEPT.getHeader()); + MediaType mediaType = MediaTypeUtil.convertMediaType(null, accept); + return mediaType; + } + + /** + * accept can not support will throw UnSupportAcceptException + * + * @param requestFacade + */ + private void acceptSupportJudge(RequestFacade requestFacade, Class returnType) { + try { + // media type judge + getAcceptMediaType(requestFacade); + } catch (UnSupportContentTypeException e) { + // return type judge + MediaType mediaType = HttpMessageCodecManager.typeSupport(returnType); + + String accept = requestFacade.getHeader(RestHeaderEnum.ACCEPT.getHeader()); + if (mediaType == null || accept == null) { + throw e; + } + + + if (!accept.contains(mediaType.value)) { + + throw e; + } + + } + } + + + public static String stackTraceToString(Throwable throwable) { + StackTraceElement[] stackTrace = throwable.getStackTrace(); + + StringBuilder stringBuilder = new StringBuilder("\n"); + for (StackTraceElement traceElement : stackTrace) { + stringBuilder.append("\tat " + traceElement).append("\n"); + } + + return stringBuilder.toString(); + } + +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/HttpMessageCodec.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/HttpMessageCodec.java index 1b6666b8f2..f38ee24a57 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/HttpMessageCodec.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/HttpMessageCodec.java @@ -21,10 +21,30 @@ import org.apache.dubbo.common.extension.ExtensionScope; import org.apache.dubbo.common.extension.SPI; import org.apache.dubbo.metadata.rest.media.MediaType; +/** + * for http body codec + * @param + * @param + */ @SPI(scope = ExtensionScope.FRAMEWORK) public interface HttpMessageCodec extends HttpMessageDecode, HttpMessageEncode { - boolean contentTypeSupport(MediaType mediaType, Class targetType); + /** + * content-type support judge + * @param mediaType + * @param targetType + * @return + */ + boolean contentTypeSupport(MediaType mediaType, Class targetType); + + /** + * class type support judge + * @param targetType + * @return + */ + boolean typeSupport(Class targetType); + + MediaType contentType(); } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/HttpMessageCodecManager.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/HttpMessageCodecManager.java index 019927ca36..9393f0969a 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/HttpMessageCodecManager.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/HttpMessageCodecManager.java @@ -20,6 +20,7 @@ import org.apache.dubbo.common.URL; import org.apache.dubbo.metadata.rest.media.MediaType; import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.protocol.rest.exception.UnSupportContentTypeException; +import org.apache.dubbo.rpc.protocol.rest.pair.MessageCodecResultPair; import java.io.OutputStream; import java.util.Set; @@ -29,23 +30,61 @@ public class HttpMessageCodecManager { FrameworkModel.defaultModel().getExtensionLoader(HttpMessageCodec.class).getSupportedExtensionInstances(); - public static Object httpMessageDecode(byte[] body, Class type, MediaType mediaType) throws Exception { + public static Object httpMessageDecode(byte[] body, Class type, MediaType mediaType) throws Exception { for (HttpMessageCodec httpMessageCodec : httpMessageCodecs) { - if (httpMessageCodec.contentTypeSupport(mediaType, type)) { + if (httpMessageCodec.contentTypeSupport(mediaType, type) || typeJudge(mediaType, type, httpMessageCodec)) { return httpMessageCodec.decode(body, type); } } throw new UnSupportContentTypeException("UnSupport content-type :" + mediaType.value); } - public static void httpMessageEncode(OutputStream outputStream, Object unSerializedBody, URL url, MediaType mediaType) throws Exception { - for (HttpMessageCodec httpMessageCodec : httpMessageCodecs) { - if (httpMessageCodec.contentTypeSupport(mediaType, unSerializedBody.getClass())) { - httpMessageCodec.encode(outputStream, unSerializedBody, url); - return; + public static MessageCodecResultPair httpMessageEncode(OutputStream outputStream, Object unSerializedBody, URL url, MediaType mediaType, Class bodyType) throws Exception { + + + if (unSerializedBody == null) { + for (HttpMessageCodec httpMessageCodec : httpMessageCodecs) { + if (httpMessageCodec.contentTypeSupport(mediaType, bodyType) || typeJudge(mediaType, bodyType, httpMessageCodec)) { + return MessageCodecResultPair.pair(false, httpMessageCodec.contentType()); + } } } + + for (HttpMessageCodec httpMessageCodec : httpMessageCodecs) { + if (httpMessageCodec.contentTypeSupport(mediaType, bodyType) || typeJudge(mediaType, bodyType, httpMessageCodec)) { + httpMessageCodec.encode(outputStream, unSerializedBody, url); + return MessageCodecResultPair.pair(true, httpMessageCodec.contentType()); + } + } + + throw new UnSupportContentTypeException("UnSupport content-type :" + mediaType.value); } + /** + * if content-type is null or all ,will judge media type by class type + * + * @param mediaType + * @param bodyType + * @param httpMessageCodec + * @return + */ + private static boolean typeJudge(MediaType mediaType, Class bodyType, HttpMessageCodec httpMessageCodec) { + return (MediaType.ALL_VALUE.equals(mediaType) || mediaType == null) + && bodyType != null && httpMessageCodec.typeSupport(bodyType); + } + + public static MediaType typeSupport(Class type) { + for (HttpMessageCodec httpMessageCodec : httpMessageCodecs) { + + if (httpMessageCodec.typeSupport(type)) { + return httpMessageCodec.contentType(); + } + + } + + return null; + } + + } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/HttpMessageDecode.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/HttpMessageDecode.java index 12ae381685..cf3b639b33 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/HttpMessageDecode.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/HttpMessageDecode.java @@ -19,6 +19,6 @@ package org.apache.dubbo.rpc.protocol.rest.message; public interface HttpMessageDecode{ - Object decode(InputStream body, Class targetType) throws Exception; + Object decode(InputStream body, Class targetType) throws Exception; } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/MediaTypeMatcher.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/MediaTypeMatcher.java index 2863f25893..6db52df861 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/MediaTypeMatcher.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/MediaTypeMatcher.java @@ -52,7 +52,6 @@ public enum MediaTypeMatcher { private static List getDefaultList() { List defaultList = new ArrayList<>(); - defaultList.add(MediaType.ALL_VALUE); return defaultList; } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/ByteArrayCodec.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/ByteArrayCodec.java index 4210e23569..85dea16e00 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/ByteArrayCodec.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/ByteArrayCodec.java @@ -23,20 +23,33 @@ import org.apache.dubbo.rpc.protocol.rest.message.HttpMessageCodec; import java.io.OutputStream; +/** + * body type is byte array + */ @Activate("byteArray") public class ByteArrayCodec implements HttpMessageCodec { @Override - public Object decode(byte[] body, Class targetType) throws Exception { + public Object decode(byte[] body, Class targetType) throws Exception { return body; } @Override - public boolean contentTypeSupport(MediaType mediaType, Class targetType) { + public boolean contentTypeSupport(MediaType mediaType, Class targetType) { return byte[].class.equals(targetType); } + @Override + public boolean typeSupport(Class targetType) { + return byte[].class.equals(targetType); + } + + @Override + public MediaType contentType() { + return MediaType.OCTET_STREAM; + } + @Override public void encode(OutputStream outputStream, Object unSerializedBody, URL url) throws Exception { diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/JsonCodec.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/JsonCodec.java index 095c33bdda..a134ec01c7 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/JsonCodec.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/JsonCodec.java @@ -29,6 +29,9 @@ import java.nio.charset.StandardCharsets; import java.util.HashSet; import java.util.Set; +/** + * body is json + */ @Activate("json") public class JsonCodec implements HttpMessageCodec { private static final Set unSupportClasses = new HashSet<>(); @@ -36,19 +39,29 @@ public class JsonCodec implements HttpMessageCodec { static { unSupportClasses.add(byte[].class); - unSupportClasses.add(String.class); + } @Override - public Object decode(byte[] body, Class targetType) throws Exception { + public Object decode(byte[] body, Class targetType) throws Exception { return DataParseUtils.jsonConvert(targetType, body); } @Override - public boolean contentTypeSupport(MediaType mediaType, Class targetType) { + public boolean contentTypeSupport(MediaType mediaType, Class targetType) { return MediaTypeMatcher.APPLICATION_JSON.mediaSupport(mediaType) && !unSupportClasses.contains(targetType); } + @Override + public boolean typeSupport(Class targetType) { + return !unSupportClasses.contains(targetType) && !DataParseUtils.isTextType(targetType); + } + + @Override + public MediaType contentType() { + return MediaType.APPLICATION_JSON_VALUE; + } + @Override public void encode(OutputStream outputStream, Object unSerializedBody, URL url) throws Exception { diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/MultiValueCodec.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/MultiValueCodec.java index 97fbb5bedb..b41955e8e9 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/MultiValueCodec.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/MultiValueCodec.java @@ -18,27 +18,82 @@ package org.apache.dubbo.rpc.protocol.rest.message.codec; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.utils.ReflectUtils; import org.apache.dubbo.metadata.rest.media.MediaType; import org.apache.dubbo.rpc.protocol.rest.message.HttpMessageCodec; import org.apache.dubbo.rpc.protocol.rest.message.MediaTypeMatcher; import org.apache.dubbo.rpc.protocol.rest.util.DataParseUtils; import java.io.OutputStream; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; import java.util.Map; +import java.util.Set; +/** + * body is form + */ @Activate("multiValue") -public class MultiValueCodec implements HttpMessageCodec { +public class MultiValueCodec implements HttpMessageCodec { @Override - public Object decode(byte[] body, Class targetType) throws Exception { + public Object decode(byte[] body, Class targetType) throws Exception { // TODO java bean get set convert - return DataParseUtils.multipartFormConvert(body); + Object map = DataParseUtils.multipartFormConvert(body,targetType); + Map valuesMap = (Map) map; + if (Map.class.isAssignableFrom(targetType)) { + return map; + } else if (DataParseUtils.isTextType(targetType)) { + + // only fetch first + Set set = valuesMap.keySet(); + ArrayList arrayList = new ArrayList<>(set); + Object key = arrayList.get(0); + Object value = valuesMap.get(key); + if (value == null) { + return null; + } + return DataParseUtils.stringTypeConvert(targetType, String.valueOf(((List) value).get(0))); + + + } else { + + + Map beanPropertyFields = ReflectUtils.getBeanPropertyFields(targetType); + + Object emptyObject = ReflectUtils.getEmptyObject(targetType); + + beanPropertyFields.entrySet().stream().forEach(entry -> { + try { + List values = (List) valuesMap.get(entry.getKey()); + String value = values == null ? null : String.valueOf(values.get(0)); + entry.getValue().set(emptyObject, DataParseUtils.stringTypeConvert(entry.getValue().getType(), value)); + } catch (IllegalAccessException e) { + + } + }); + + return emptyObject; + } + + } + + + @Override + public boolean contentTypeSupport(MediaType mediaType, Class targetType) { + return MediaTypeMatcher.MULTI_VALUE.mediaSupport(mediaType); } @Override - public boolean contentTypeSupport(MediaType mediaType,Class targetType) { - return MediaTypeMatcher.MULTI_VALUE.mediaSupport(mediaType); + public boolean typeSupport(Class targetType) { + return false; + } + + @Override + public MediaType contentType() { + return MediaType.APPLICATION_FORM_URLENCODED_VALUE; } @Override diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/StringCodec.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/StringCodec.java index 0deffe32b1..6324b3325f 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/StringCodec.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/StringCodec.java @@ -24,20 +24,33 @@ import org.apache.dubbo.rpc.protocol.rest.message.HttpMessageCodec; import java.io.OutputStream; import java.nio.charset.StandardCharsets; +/** + * body is string + */ @Activate("string") public class StringCodec implements HttpMessageCodec { @Override - public Object decode(byte[] body, Class targetType) throws Exception { + public Object decode(byte[] body, Class targetType) throws Exception { return new String(body); } @Override - public boolean contentTypeSupport(MediaType mediaType,Class targetType) { + public boolean contentTypeSupport(MediaType mediaType,Class targetType) { return String.class.equals(targetType); } + @Override + public boolean typeSupport(Class targetType) { + return String.class.equals(targetType); + } + + @Override + public MediaType contentType() { + return MediaType.TEXT_PLAIN; + } + @Override public void encode(OutputStream outputStream, Object unSerializedBody, URL url) throws Exception { diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/TextCodec.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/TextCodec.java index c4722d01b3..63c06876f8 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/TextCodec.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/TextCodec.java @@ -26,20 +26,33 @@ import org.apache.dubbo.rpc.protocol.rest.util.DataParseUtils; import java.io.OutputStream; import java.nio.charset.StandardCharsets; +/** + * content-type is text/html + */ @Activate("text") public class TextCodec implements HttpMessageCodec { @Override - public Object decode(byte[] body, Class targetType) throws Exception { + public Object decode(byte[] body, Class targetType) throws Exception { return DataParseUtils.stringTypeConvert(targetType, new String(body, StandardCharsets.UTF_8)); } @Override - public boolean contentTypeSupport(MediaType mediaType, Class targetType) { + public boolean contentTypeSupport(MediaType mediaType, Class targetType) { return MediaTypeMatcher.TEXT_PLAIN.mediaSupport(mediaType); } + @Override + public boolean typeSupport(Class targetType) { + return DataParseUtils.isTextType(targetType); + } + + @Override + public MediaType contentType() { + return MediaType.TEXT_PLAIN; + } + @Override public void encode(OutputStream outputStream, Object unSerializedBody, URL url) throws Exception { DataParseUtils.writeTextContent(unSerializedBody, outputStream); diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/XMLCodec.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/XMLCodec.java index 766ea8064b..d58fd9e8de 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/XMLCodec.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/message/codec/XMLCodec.java @@ -33,12 +33,15 @@ import javax.xml.transform.sax.SAXSource; import java.io.OutputStream; import java.io.StringReader; +/** + * body content-type is xml + */ @Activate("xml") public class XMLCodec implements HttpMessageCodec { @Override - public Object decode(byte[] body, Class targetType) throws Exception { + public Object decode(byte[] body, Class targetType) throws Exception { SAXParserFactory spf = SAXParserFactory.newInstance(); @@ -56,16 +59,25 @@ public class XMLCodec implements HttpMessageCodec { } @Override - public boolean contentTypeSupport(MediaType mediaType, Class targetType) { + public boolean contentTypeSupport(MediaType mediaType, Class targetType) { return MediaTypeMatcher.TEXT_XML.mediaSupport(mediaType); } + @Override + public boolean typeSupport(Class targetType) { + return false; + } + + @Override + public MediaType contentType() { + return MediaType.TEXT_XML; + } + @Override public void encode(OutputStream outputStream, Object unSerializedBody, URL url) throws Exception { Marshaller marshaller = JAXBContext.newInstance(unSerializedBody.getClass()).createMarshaller(); marshaller.marshal(unSerializedBody, outputStream); - outputStream.write((byte[]) unSerializedBody); } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/ChunkOutputStream.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/ChunkOutputStream.java new file mode 100644 index 0000000000..53db275b7d --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/ChunkOutputStream.java @@ -0,0 +1,91 @@ +/* + * 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.netty; + + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.DefaultHttpContent; + +import java.io.IOException; +import java.io.OutputStream; + +public class ChunkOutputStream extends OutputStream { + final ByteBuf buffer; + final ChannelHandlerContext ctx; + final NettyHttpResponse response; + + ChunkOutputStream(final NettyHttpResponse response, final ChannelHandlerContext ctx, final int chunksize) { + this.response = response; + if (chunksize < 1) { + throw new IllegalArgumentException(); + } + // TODO buffer pool + this.buffer = Unpooled.buffer(0, chunksize); + this.ctx = ctx; + } + + @Override + public void write(int b) throws IOException { + if (buffer.maxWritableBytes() < 1) { + flush(); + } + buffer.writeByte(b); + } + + public void reset() + { + if (response.isCommitted()) throw new IllegalStateException(); + buffer.clear(); + } + + @Override + public void close() throws IOException { + flush(); + super.close(); + } + + + @Override + public void write(byte[] b, int off, int len) throws IOException { + int dataLengthLeftToWrite = len; + int dataToWriteOffset = off; + int spaceLeftInCurrentChunk; + while ((spaceLeftInCurrentChunk = buffer.maxWritableBytes()) < dataLengthLeftToWrite) { + buffer.writeBytes(b, dataToWriteOffset, spaceLeftInCurrentChunk); + dataToWriteOffset = dataToWriteOffset + spaceLeftInCurrentChunk; + dataLengthLeftToWrite = dataLengthLeftToWrite - spaceLeftInCurrentChunk; + flush(); + } + if (dataLengthLeftToWrite > 0) { + buffer.writeBytes(b, dataToWriteOffset, dataLengthLeftToWrite); + } + } + + @Override + public void flush() throws IOException { + int readable = buffer.readableBytes(); + if (readable == 0) return; + if (!response.isCommitted()) response.prepareChunkStream(); + ctx.writeAndFlush(new DefaultHttpContent(buffer.copy())); + buffer.clear(); + super.flush(); + } + +} + diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/StreamUtils.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/HttpResponse.java similarity index 53% rename from dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/StreamUtils.java rename to dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/HttpResponse.java index 5b597c1724..890fe039b9 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/StreamUtils.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/HttpResponse.java @@ -14,28 +14,42 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.rpc.protocol.rest.util; +package org.apache.dubbo.rpc.protocol.rest.netty; + import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.Charset; +import java.io.OutputStream; +import java.util.List; +import java.util.Map; -public class StreamUtils { - public static String copyToString(InputStream in, Charset charset) throws IOException { - if (in == null) { - return ""; - } else { - StringBuilder out = new StringBuilder(); - InputStreamReader reader = new InputStreamReader(in, charset); - char[] buffer = new char[4096]; - int bytesRead; - while ((bytesRead = reader.read(buffer)) != -1) { - out.append(buffer, 0, bytesRead); - } +public interface HttpResponse { + int getStatus(); + + void setStatus(int status); + + Map> getOutputHeaders(); + + OutputStream getOutputStream() throws IOException; + + void setOutputStream(OutputStream os); + + + void sendError(int status) throws IOException; + + void sendError(int status, String message) throws IOException; + + boolean isCommitted(); + + /** + * reset status and headers. Will fail if response is committed + */ + void reset(); + + void flushBuffer() throws IOException; + + + void addOutputHeaders(String name, String value); - return out.toString(); - } - } } + diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/NettyHttpResponse.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/NettyHttpResponse.java new file mode 100644 index 0000000000..074b087996 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/NettyHttpResponse.java @@ -0,0 +1,214 @@ +/* + * 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.netty; + + +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.DefaultHttpResponse; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpHeaders.Names; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.LastHttpContent; +import org.apache.dubbo.metadata.rest.media.MediaType; +import org.apache.dubbo.rpc.protocol.rest.RestHeaderEnum; + + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; + + +/** + * netty http response + */ +public class NettyHttpResponse implements HttpResponse { + private static final int EMPTY_CONTENT_LENGTH = 0; + private int status = 200; + private OutputStream os; + private Map> outputHeaders; + private final ChannelHandlerContext ctx; + private boolean committed; + private boolean keepAlive; + private HttpMethod method; + + public NettyHttpResponse(final ChannelHandlerContext ctx, final boolean keepAlive) { + this(ctx, keepAlive, null); + } + + public NettyHttpResponse(final ChannelHandlerContext ctx, final boolean keepAlive, final HttpMethod method) { + outputHeaders = new HashMap<>(); + this.method = method; + // TODO chunk size to config + os = new ChunkOutputStream(this, ctx, 1000); + this.ctx = ctx; + this.keepAlive = keepAlive; + } + + + public void setOutputStream(OutputStream os) { + this.os = os; + } + + @Override + public int getStatus() { + return status; + } + + @Override + public void setStatus(int status) { + if (status > 200) { + addOutputHeaders(RestHeaderEnum.CONTENT_TYPE.getHeader(), MediaType.TEXT_PLAIN.value); + } + this.status = status; + } + + @Override + public Map> getOutputHeaders() { + return outputHeaders; + } + + @Override + public OutputStream getOutputStream() throws IOException { + return os; + } + + + @Override + public void sendError(int status) throws IOException { + sendError(status, null); + } + + @Override + public void sendError(int status, String message) throws IOException { + setStatus(status); + if (message != null) { + getOutputStream().write(message.getBytes(StandardCharsets.UTF_8)); + } + + } + + @Override + public boolean isCommitted() { + return committed; + } + + @Override + public void reset() { + if (committed) { + throw new IllegalStateException("Messages.MESSAGES.alreadyCommitted()"); + } + outputHeaders.clear(); + outputHeaders.clear(); + } + + public boolean isKeepAlive() { + return keepAlive; + } + + public DefaultHttpResponse getDefaultHttpResponse() { + DefaultHttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(getStatus())); + transformResponseHeaders(res); + return res; + } + + public DefaultHttpResponse getEmptyHttpResponse() { + DefaultFullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(getStatus())); + if (method == null || !method.equals(HttpMethod.HEAD)) { + res.headers().add(Names.CONTENT_LENGTH, EMPTY_CONTENT_LENGTH); + } + transformResponseHeaders(res); + + return res; + } + + private void transformResponseHeaders(io.netty.handler.codec.http.HttpResponse res) { + transformHeaders(this, res); + } + + + public void prepareChunkStream() { + committed = true; + DefaultHttpResponse response = getDefaultHttpResponse(); + HttpHeaders.setTransferEncodingChunked(response); + ctx.write(response); + } + + public void finish() throws IOException { + if (os != null) + os.flush(); + ChannelFuture future; + if (isCommitted()) { + // if committed this means the output stream was used. + future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); + } else { + future = ctx.writeAndFlush(getEmptyHttpResponse()); + } + + if (!isKeepAlive()) { + future.addListener(ChannelFutureListener.CLOSE); + } + + getOutputStream().close(); + } + + @Override + public void flushBuffer() throws IOException { + if (os != null) + os.flush(); + ctx.flush(); + } + + @Override + public void addOutputHeaders(String name, String value) { + + List values = outputHeaders.get(name); + + if (values == null) { + values = new ArrayList<>(); + outputHeaders.put(name, values); + } + + values.add(value); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public static void transformHeaders(NettyHttpResponse nettyResponse, io.netty.handler.codec.http.HttpResponse response) { + if (nettyResponse.isKeepAlive()) { + response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE); + } else { + response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE); + } + + for (Map.Entry> entry : nettyResponse.getOutputHeaders().entrySet()) { + String key = entry.getKey(); + for (String value : entry.getValue()) { + response.headers().set(key, value); + } + } + + } +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/NettyServer.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/NettyServer.java new file mode 100644 index 0000000000..782072f6cc --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/NettyServer.java @@ -0,0 +1,184 @@ +/* + * 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.netty; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.timeout.IdleStateHandler; +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.utils.NamedThreadFactory; +import org.apache.dubbo.rpc.protocol.rest.constans.RestConstant; + +import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static org.apache.dubbo.remoting.Constants.EVENT_LOOP_BOSS_POOL_NAME; +import static org.apache.dubbo.remoting.Constants.EVENT_LOOP_WORKER_POOL_NAME; + + +public class NettyServer { + + protected ServerBootstrap bootstrap = new ServerBootstrap(); + protected String hostname = null; + protected int configuredPort = 8080; + protected int runtimePort = -1; + + private EventLoopGroup eventLoopGroup; + private EventLoopGroup workerLoopGroup; + private int ioWorkerCount = Runtime.getRuntime().availableProcessors() * 2; + + private List channelHandlers = Collections.emptyList(); + private Map channelOptions = Collections.emptyMap(); + private Map childChannelOptions = Collections.emptyMap(); + private UnSharedHandlerCreator unSharedHandlerCallBack; + + public NettyServer() { + } + + + /** + * Specify the worker count to use. For more information about this please see the javadocs of {@link EventLoopGroup} + * + * @param ioWorkerCount worker count + */ + public void setIoWorkerCount(int ioWorkerCount) { + this.ioWorkerCount = ioWorkerCount; + } + + public String getHostname() { + return hostname; + } + + public void setHostname(String hostname) { + this.hostname = hostname; + } + + public int getPort() { + return runtimePort > 0 ? runtimePort : configuredPort; + } + + public void setPort(int port) { + this.configuredPort = port; + } + + + /** + * Add additional {@link io.netty.channel.ChannelHandler}s to the {@link io.netty.bootstrap.ServerBootstrap}. + *

The additional channel handlers are being added before the HTTP handling.

+ * + * @param channelHandlers the additional {@link io.netty.channel.ChannelHandler}s. + */ + public void setChannelHandlers(final List channelHandlers) { + this.channelHandlers = channelHandlers == null ? Collections.emptyList() : channelHandlers; + } + + /** + * Add Netty {@link io.netty.channel.ChannelOption}s to the {@link io.netty.bootstrap.ServerBootstrap}. + * + * @param channelOptions the additional {@link io.netty.channel.ChannelOption}s. + * @see io.netty.bootstrap.ServerBootstrap#option(io.netty.channel.ChannelOption, Object) + */ + public void setChannelOptions(final Map channelOptions) { + this.channelOptions = channelOptions == null ? Collections.emptyMap() : channelOptions; + } + + /** + * Add child options to the {@link io.netty.bootstrap.ServerBootstrap}. + * + * @param channelOptions the additional child {@link io.netty.channel.ChannelOption}s. + * @see io.netty.bootstrap.ServerBootstrap#childOption(io.netty.channel.ChannelOption, Object) + */ + public void setChildChannelOptions(final Map channelOptions) { + this.childChannelOptions = channelOptions == null ? Collections.emptyMap() : channelOptions; + } + + public void setUnSharedHandlerCallBack(UnSharedHandlerCreator unSharedHandlerCallBack) { + this.unSharedHandlerCallBack = unSharedHandlerCallBack; + } + + public void start(URL url) { + eventLoopGroup = new NioEventLoopGroup(1, new NamedThreadFactory(EVENT_LOOP_BOSS_POOL_NAME)); + workerLoopGroup = new NioEventLoopGroup(ioWorkerCount, new NamedThreadFactory(EVENT_LOOP_WORKER_POOL_NAME)); + + // Configure the server. + bootstrap.group(eventLoopGroup, workerLoopGroup) + .channel(NioServerSocketChannel.class) + .childHandler(setupHandlers(url)); + + + for (Map.Entry entry : channelOptions.entrySet()) { + bootstrap.option(entry.getKey(), entry.getValue()); + } + + for (Map.Entry entry : childChannelOptions.entrySet()) { + bootstrap.childOption(entry.getKey(), entry.getValue()); + } + + final InetSocketAddress socketAddress; + if (null == getHostname() || getHostname().isEmpty()) { + socketAddress = new InetSocketAddress(configuredPort); + } else { + socketAddress = new InetSocketAddress(hostname, configuredPort); + } + + Channel channel = bootstrap.bind(socketAddress).syncUninterruptibly().channel(); + runtimePort = ((InetSocketAddress) channel.localAddress()).getPort(); + } + + + protected ChannelHandler setupHandlers(URL url) { + + return new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) throws Exception { + ChannelPipeline channelPipeline = ch.pipeline(); + + int idleTimeout = url.getParameter(RestConstant.IDLE_TIMEOUT_PARAM, RestConstant.IDLE_TIMEOUT); + if (idleTimeout > 0) { + channelPipeline.addLast(new IdleStateHandler(0, 0, idleTimeout)); + } + + channelPipeline.addLast(channelHandlers.toArray(new ChannelHandler[channelHandlers.size()])); + + List unSharedHandlers = unSharedHandlerCallBack.getUnSharedHandlers(url); + + for (ChannelHandler unSharedHandler : unSharedHandlers) { + channelPipeline.addLast(unSharedHandler); + } + + } + }; + + } + + + public void stop() { + runtimePort = -1; + eventLoopGroup.shutdownGracefully(); + } +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/RestHttpRequestDecoder.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/RestHttpRequestDecoder.java new file mode 100644 index 0000000000..c2ddc65931 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/RestHttpRequestDecoder.java @@ -0,0 +1,77 @@ +/* + * 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.netty; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageDecoder; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.Executor; + +import io.netty.handler.codec.http.HttpHeaders; +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.threadpool.ThreadPool; +import org.apache.dubbo.rpc.protocol.rest.handler.NettyHttpHandler; +import org.apache.dubbo.rpc.protocol.rest.request.NettyRequestFacade; + + +public class RestHttpRequestDecoder extends MessageToMessageDecoder { + private final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass()); + + private final NettyHttpHandler handler; + private final Executor executor; + + + public RestHttpRequestDecoder(NettyHttpHandler handler, URL url) { + this.handler = handler; + executor = url.getOrDefaultFrameworkModel().getExtensionLoader(ThreadPool.class).getAdaptiveExtension().getExecutor(url); + } + + + @Override + protected void decode(ChannelHandlerContext ctx, io.netty.handler.codec.http.FullHttpRequest request, List out) throws Exception { + boolean keepAlive = HttpHeaders.isKeepAlive(request); + + NettyHttpResponse nettyHttpResponse = new NettyHttpResponse(ctx, keepAlive); + NettyRequestFacade requestFacade = new NettyRequestFacade(request, ctx); + + executor.execute(() -> { + + // business handler + try { + handler.handle(requestFacade, nettyHttpResponse); + + } catch (IOException e) { + logger.error("", e.getCause().getMessage(), "dubbo rest rest http request handler error", e.getMessage(), e); + } + + // write response + try { + nettyHttpResponse.finish(); + } catch (IOException e) { + logger.error("", e.getCause().getMessage(), "dubbo rest rest http response flush error", e.getMessage(), e); + } + }); + + + } +} + diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/UnSharedHandlerCreator.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/UnSharedHandlerCreator.java new file mode 100644 index 0000000000..24fce27ab6 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/netty/UnSharedHandlerCreator.java @@ -0,0 +1,30 @@ +/* + * 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.netty; + +import io.netty.channel.ChannelHandler; +import org.apache.dubbo.common.URL; + +import java.util.List; + +/** + * FOR create netty un shared (no @Shared) handler + */ +public interface UnSharedHandlerCreator { + + List getUnSharedHandlers(URL url); +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/pair/InvokerAndRestMethodMetadataPair.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/pair/InvokerAndRestMethodMetadataPair.java new file mode 100644 index 0000000000..0075275e49 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/pair/InvokerAndRestMethodMetadataPair.java @@ -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.rpc.protocol.rest.pair; + +import org.apache.dubbo.metadata.rest.RestMethodMetadata; +import org.apache.dubbo.rpc.Invoker; + +/** + * for invoker & restMethodMetadata pair + */ +public class InvokerAndRestMethodMetadataPair { + + Invoker invoker; + RestMethodMetadata restMethodMetadata; + + public InvokerAndRestMethodMetadataPair(Invoker invoker, RestMethodMetadata restMethodMetadata) { + this.invoker = invoker; + this.restMethodMetadata = restMethodMetadata; + } + + public Invoker getInvoker() { + return invoker; + } + + public RestMethodMetadata getRestMethodMetadata() { + return restMethodMetadata; + } + + + public static InvokerAndRestMethodMetadataPair pair(Invoker invoker, RestMethodMetadata restMethodMetadata) { + return new InvokerAndRestMethodMetadataPair(invoker, restMethodMetadata); + } + + +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/HttpConnectionConfig.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/pair/MessageCodecResultPair.java similarity index 54% rename from dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/HttpConnectionConfig.java rename to dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/pair/MessageCodecResultPair.java index e9a9931c46..00b3154c0a 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/annotation/consumer/HttpConnectionConfig.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/pair/MessageCodecResultPair.java @@ -14,38 +14,40 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.rpc.protocol.rest.annotation.consumer; +package org.apache.dubbo.rpc.protocol.rest.pair; + +import org.apache.dubbo.metadata.rest.media.MediaType; + +/** + * for http message codec result + */ +public class MessageCodecResultPair { + /** + * has coded + */ + boolean coded; + + /** + * codec type + */ + MediaType mediaType; -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 MessageCodecResultPair(boolean coded, MediaType mediaType) { + this.coded = coded; + this.mediaType = mediaType; } - public int getReadTimeout() { - return readTimeout; + + public boolean isCoded() { + return coded; } - public int getChunkLength() { - return chunkLength; + public MediaType getMediaType() { + return mediaType; } - public byte getSerialization() { - return serialization; - } - - public int getKeepAlive() { - return keepAlive; + public static MessageCodecResultPair pair(boolean coded, MediaType mediaType) { + return new MessageCodecResultPair(coded, mediaType); } } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/request/NettyRequestFacade.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/request/NettyRequestFacade.java new file mode 100644 index 0000000000..b14178bf72 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/request/NettyRequestFacade.java @@ -0,0 +1,253 @@ +/* + * 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.request; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpContent; +import org.apache.dubbo.common.utils.IOUtils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +/** + * netty request facade + */ +public class NettyRequestFacade extends RequestFacade { + + + private ChannelHandlerContext context; + + + public NettyRequestFacade(Object request, ChannelHandlerContext context) { + super((FullHttpRequest) request); + this.context = context; + + } + + + protected void initHeaders() { + for (Map.Entry header : request.headers()) { + + String key = header.getKey(); + + ArrayList tmpHeaders = headers.get(key); + + if (tmpHeaders == null) { + tmpHeaders = new ArrayList<>(); + headers.put(key, tmpHeaders); + } + + tmpHeaders.add(header.getValue()); + } + } + + @Override + public String getHeader(String name) { + + List values = headers.get(name); + + if (values == null || values.isEmpty()) { + return null; + } else { + return values.get(0); + } + + } + + @Override + public Enumeration getHeaders(String name) { + + List list = headers.get(name); + + if (list == null) { + list = new ArrayList<>(); + } + + + ListIterator stringListIterator = list.listIterator(); + + return new Enumeration() { + @Override + public boolean hasMoreElements() { + return stringListIterator.hasNext(); + } + + @Override + public String nextElement() { + return stringListIterator.next(); + } + }; + } + + + @Override + public Enumeration getHeaderNames() { + + Iterator strings = headers.keySet().iterator(); + + return new Enumeration() { + @Override + public boolean hasMoreElements() { + return strings.hasNext(); + } + + @Override + public String nextElement() { + return strings.next(); + } + }; + } + + @Override + public String getMethod() { + return request.method().name(); + } + + @Override + public String getPath() { + return path; + } + + @Override + public String getContextPath() { + // TODO add ContextPath + return null; + } + + + @Override + public String getRequestURI() { + return request.uri(); + } + + + @Override + public String getParameter(String name) { + ArrayList strings = parameters.get(name); + + String value = null; + if (strings != null && !strings.isEmpty()) { + value = strings.get(0); + + } + return value; + } + + @Override + public Enumeration getParameterNames() { + + Iterator iterator = parameters.keySet().iterator(); + + return new Enumeration() { + @Override + public boolean hasMoreElements() { + return iterator.hasNext(); + } + + @Override + public String nextElement() { + return iterator.next(); + } + }; + + } + + @Override + public String[] getParameterValues(String name) { + + if (!parameters.containsKey(name)) { + + return null; + } + return parameters.get(name).toArray(new String[0]); + } + + @Override + public Map getParameterMap() { + HashMap map = new HashMap<>(); + parameters.entrySet().forEach(entry -> { + map.put(entry.getKey(), entry.getValue().toArray(new String[0])); + }); + return map; + + } + + + @Override + public String getRemoteAddr() { + return getChannel().remoteAddress().getHostString(); + } + + @Override + public String getRemoteHost() { + return getRemoteAddr() + ":" + getRemotePort(); + } + + @Override + public int getRemotePort() { + return getChannel().remoteAddress().getPort(); + } + + @Override + public String getLocalAddr() { + return getChannel().localAddress().getHostString(); + } + + @Override + public String getLocalHost() { + return getRemoteAddr() + ":" + getLocalPort(); + } + + private NioSocketChannel getChannel() { + return (NioSocketChannel) context.channel(); + } + + @Override + public int getLocalPort() { + return getChannel().localAddress().getPort(); + } + + @Override + public byte[] getInputStream() throws IOException { + + return body; + } + + protected void parseBody() { + ByteBuf byteBuf = ((HttpContent) request).content(); + + if (byteBuf.readableBytes() > 0) { + + try { + body = IOUtils.toByteArray(new ByteBufInputStream(byteBuf)); + } catch (IOException e) { + + } + } + + } +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/request/RequestFacade.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/request/RequestFacade.java new file mode 100644 index 0000000000..8d95ac60cb --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/request/RequestFacade.java @@ -0,0 +1,135 @@ +/* + * 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.request; + + +import org.apache.dubbo.common.utils.StringUtils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +/** + * request facade for different request + * + * @param + */ +public abstract class RequestFacade { + protected Map> headers = new HashMap<>(); + protected Map> parameters = new HashMap<>(); + + protected String path; + protected T request; + protected byte[] body = new byte[0]; + + public RequestFacade(T request) { + this.request = request; + initHeaders(); + initParameters(); + parseBody(); + } + + protected void initHeaders() { + + } + + + protected void initParameters() { + String requestURI = getRequestURI(); + + if (requestURI != null && requestURI.contains("?")) { + + String queryString = requestURI.substring(requestURI.indexOf("?") + 1); + path = requestURI.substring(0, requestURI.indexOf("?")); + + String[] split = queryString.split("&"); + + for (String params : split) { + // key a= ;value b==c + int index = params.indexOf("="); + if (index <= 0) { + continue; + } + + String name = params.substring(0, index); + String value = params.substring(index + 1); + if (!StringUtils.isEmpty(name)) { + ArrayList values = parameters.get(name); + + if (values == null) { + values = new ArrayList<>(); + parameters.put(name, values); + } + values.add(value); + + } + } + } else { + path = requestURI; + } + } + + + public T getRequest() { + return request; + } + + public abstract String getHeader(String name); + + + public abstract Enumeration getHeaders(String name); + + + public abstract Enumeration getHeaderNames(); + + public abstract String getMethod(); + + + public abstract String getPath(); + + public abstract String getContextPath(); + + public abstract String getRequestURI(); + + public abstract String getParameter(String name); + + public abstract Enumeration getParameterNames(); + + public abstract String[] getParameterValues(String name); + + public abstract Map getParameterMap(); + + public abstract String getRemoteAddr(); + + public abstract String getRemoteHost(); + + public abstract int getRemotePort(); + + public abstract String getLocalAddr(); + + public abstract String getLocalHost(); + + public abstract int getLocalPort(); + + public abstract byte[] getInputStream() throws IOException; + + protected abstract void parseBody(); + + +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/support/LoggingFilter.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/support/LoggingFilter.java deleted file mode 100644 index 37d42b1c3c..0000000000 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/support/LoggingFilter.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * 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.support; - -import org.apache.dubbo.common.logger.Logger; -import org.apache.dubbo.common.logger.LoggerFactory; - -import org.apache.commons.io.IOUtils; - -import javax.annotation.Priority; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.client.ClientRequestContext; -import javax.ws.rs.client.ClientRequestFilter; -import javax.ws.rs.client.ClientResponseContext; -import javax.ws.rs.client.ClientResponseFilter; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.container.ContainerRequestFilter; -import javax.ws.rs.container.ContainerResponseContext; -import javax.ws.rs.container.ContainerResponseFilter; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.ext.ReaderInterceptor; -import javax.ws.rs.ext.ReaderInterceptorContext; -import javax.ws.rs.ext.WriterInterceptor; -import javax.ws.rs.ext.WriterInterceptorContext; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Map; - -/** - * This logging filter is not highly optimized for now - * - */ -@Priority(Integer.MIN_VALUE) -public class LoggingFilter implements ContainerRequestFilter, ClientRequestFilter, ContainerResponseFilter, ClientResponseFilter, WriterInterceptor, ReaderInterceptor { - - private static final Logger logger = LoggerFactory.getLogger(LoggingFilter.class); - - @Override - public void filter(ClientRequestContext context) throws IOException { - logHttpHeaders(context.getStringHeaders()); - } - - @Override - public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException { - logHttpHeaders(responseContext.getHeaders()); - } - - @Override - public void filter(ContainerRequestContext context) throws IOException { - logHttpHeaders(context.getHeaders()); - } - - @Override - public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { - logHttpHeaders(responseContext.getStringHeaders()); - } - - @Override - public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException, WebApplicationException { - byte[] buffer = IOUtils.toByteArray(context.getInputStream()); - logger.info("The contents of request body is: \n" + new String(buffer, StandardCharsets.UTF_8) + "\n"); - context.setInputStream(new ByteArrayInputStream(buffer)); - return context.proceed(); - } - - @Override - public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { - OutputStreamWrapper wrapper = new OutputStreamWrapper(context.getOutputStream()); - context.setOutputStream(wrapper); - context.proceed(); - logger.info("The contents of response body is: \n" + new String(wrapper.getBytes(), StandardCharsets.UTF_8) + "\n"); - } - - protected void logHttpHeaders(MultivaluedMap headers) { - StringBuilder msg = new StringBuilder("The HTTP headers are: \n"); - for (Map.Entry> entry : headers.entrySet()) { - msg.append(entry.getKey()).append(": "); - for (int i = 0; i < entry.getValue().size(); i++) { - msg.append(entry.getValue().get(i)); - if (i < entry.getValue().size() - 1) { - msg.append(", "); - } - } - msg.append('\n'); - } - logger.info(msg.toString()); - } - - protected static class OutputStreamWrapper extends OutputStream { - - private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - private final OutputStream output; - - private OutputStreamWrapper(OutputStream output) { - this.output = output; - } - - @Override - public void write(int i) throws IOException { - buffer.write(i); - output.write(i); - } - - @Override - public void write(byte[] b) throws IOException { - buffer.write(b); - output.write(b); - } - - @Override - public void write(byte[] b, int off, int len) throws IOException { - buffer.write(b, off, len); - output.write(b, off, len); - } - - @Override - public void flush() throws IOException { - output.flush(); - } - - @Override - public void close() throws IOException { - output.close(); - } - - public byte[] getBytes() { - return buffer.toByteArray(); - } - } -} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/DataParseUtils.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/DataParseUtils.java index d5e587cd93..b2beeaa8e1 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/DataParseUtils.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/DataParseUtils.java @@ -17,6 +17,7 @@ package org.apache.dubbo.rpc.protocol.rest.util; import org.apache.dubbo.common.utils.JsonUtils; +import org.apache.dubbo.common.utils.StringUtils; import java.io.IOException; import java.io.OutputStream; @@ -33,10 +34,13 @@ import java.util.StringTokenizer; public class DataParseUtils { - public static Object stringTypeConvert(Class targetType, String value) { + public static Object stringTypeConvert(Class targetType, String value) { + if (StringUtils.isEmpty(value)) { + return null; + } - if (targetType == Boolean.class) { + if (targetType == Boolean.class || targetType == boolean.class) { return Boolean.valueOf(value); } @@ -48,10 +52,24 @@ public class DataParseUtils { return NumberUtils.parseNumber(value, targetType); } + if (targetType != null && targetType.isPrimitive()) { + return NumberUtils.parseNumber(value, targetType); + } + return value; } + public static boolean isTextType(Class targetType) { + if (targetType == null) { + return false; + } + + return targetType == Boolean.class || targetType == boolean.class || + targetType == String.class || + Number.class.isAssignableFrom(targetType) || targetType.isPrimitive(); + } + /** * content-type text @@ -117,7 +135,7 @@ public class DataParseUtils { public static byte[] objectTextConvertToByteArray(Object object) { Class objectClass = object.getClass(); - if (objectClass == Boolean.class) { + if (objectClass == Boolean.class || objectClass == boolean.class) { return object.toString().getBytes(); } @@ -125,26 +143,7 @@ public class DataParseUtils { return ((String) object).getBytes(); } - if (objectClass.isAssignableFrom(Number.class)) { - return (byte[]) NumberUtils.numberToBytes((Number) object); - } - - return object.toString().getBytes(); - - } - - public static byte[] objectJsonConvertToByteArray(Object object) { - Class objectClass = object.getClass(); - - if (objectClass == Boolean.class) { - return object.toString().getBytes(); - } - - if (objectClass == String.class) { - return ((String) object).getBytes(); - } - - if (objectClass.isAssignableFrom(Number.class)) { + if (objectClass.isAssignableFrom(Number.class) || objectClass.isPrimitive()) { return (byte[]) NumberUtils.numberToBytes((Number) object); } @@ -157,9 +156,9 @@ public class DataParseUtils { } - public static Object multipartFormConvert(byte[] body, Charset charset) throws Exception { + public static Object multipartFormConvert(byte[] body, Charset charset, Class targetType) throws Exception { String[] pairs = tokenizeToStringArray(new String(body, StandardCharsets.UTF_8), "&"); - Object result = MultiValueCreator.createMultiValueMap(); + Object result = MultiValueCreator.providerCreateMultiValueMap(targetType); for (String pair : pairs) { int idx = pair.indexOf('='); if (idx == -1) { @@ -174,8 +173,8 @@ public class DataParseUtils { return result; } - public static Object multipartFormConvert(byte[] body) throws Exception { - return multipartFormConvert(body, Charset.defaultCharset()); + public static Object multipartFormConvert(byte[] body, Class targetType) throws Exception { + return multipartFormConvert(body, Charset.defaultCharset(), targetType); } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/HttpHeaderUtil.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/HttpHeaderUtil.java new file mode 100644 index 0000000000..194d7de464 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/HttpHeaderUtil.java @@ -0,0 +1,217 @@ +/* + * 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.util; + +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.remoting.http.RequestTemplate; +import org.apache.dubbo.remoting.http.RestResult; +import org.apache.dubbo.rpc.AppResponse; +import org.apache.dubbo.rpc.RpcContext; +import org.apache.dubbo.rpc.RpcInvocation; +import org.apache.dubbo.rpc.protocol.rest.RestHeaderEnum; +import org.apache.dubbo.rpc.protocol.rest.constans.RestConstant; +import org.apache.dubbo.rpc.protocol.rest.netty.NettyHttpResponse; +import org.apache.dubbo.rpc.protocol.rest.request.RequestFacade; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class HttpHeaderUtil { + + + /** + * convert attachment to Map> + * + * @param attachmentMap + * @return + */ + public static Map> createAttachments(Map attachmentMap) { + Map> attachments = new HashMap<>(); + int size = 0; + for (Map.Entry entry : attachmentMap.entrySet()) { + String key = entry.getKey(); + String value = String.valueOf(entry.getValue()); + + if (value != null) { + size += value.getBytes(StandardCharsets.UTF_8).length; + } + + List strings = attachments.get(key); + if (strings == null) { + strings = new ArrayList<>(); + attachments.put(key, strings); + } + strings.add(value); + } + + return attachments; + } + + + /** + * add consumer attachment to request + * + * @param requestTemplate + * @param attachmentMap + */ + public static void addRequestAttachments(RequestTemplate requestTemplate, Map attachmentMap) { + Map> attachments = createAttachments(attachmentMap); + + attachments.entrySet().forEach(attachment -> { + requestTemplate.addHeaders(appendPrefixToAttachRealHeader(attachment.getKey()), attachment.getValue()); + }); + + } + + + /** + * add provider attachment to response + * + * @param nettyHttpResponse + */ + public static void addResponseAttachments(NettyHttpResponse nettyHttpResponse) { + Map> attachments = createAttachments(RpcContext.getServerContext().getObjectAttachments()); + + attachments.entrySet().stream().forEach(attachment -> { + nettyHttpResponse.getOutputHeaders().put(appendPrefixToAttachRealHeader(attachment.getKey()), attachment.getValue()); + }); + } + + + /** + * parse rest request header attachment & header + * + * @param rpcInvocation + * @param requestFacade + */ + public static void parseRequestHeader(RpcInvocation rpcInvocation, RequestFacade requestFacade) { + + Enumeration headerNames = requestFacade.getHeaderNames(); + + while (headerNames.hasMoreElements()) { + String header = headerNames.nextElement(); + + if (!isRestAttachHeader(header)) { + // attribute + rpcInvocation.put(header, requestFacade.getHeader(header)); + continue; + } + + // attachment + rpcInvocation.setAttachment(subRestAttachRealHeaderPrefix(header.trim()), requestFacade.getHeader(header)); + + } + } + + + /** + * for judge rest header or rest attachment + * + * @param header + * @return + */ + public static boolean isRestAttachHeader(String header) { + + if (StringUtils.isEmpty(header) || !header.startsWith(RestHeaderEnum.REST_HEADER_PREFIX.getHeader())) { + return false; + } + + return true; + } + + /** + * for substring attachment prefix + * + * @param header + * @return + */ + public static String subRestAttachRealHeaderPrefix(String header) { + + return header.substring(RestHeaderEnum.REST_HEADER_PREFIX.getHeader().length()); + } + + /** + * append prefix to rest header distinguish from normal header + * + * @param header + * @return + */ + public static String appendPrefixToAttachRealHeader(String header) { + + return RestHeaderEnum.REST_HEADER_PREFIX.getHeader() + header; + } + + + /** + * parse request attribute + * @param rpcInvocation + * @param request + */ + public static void parseRequestAttribute(RpcInvocation rpcInvocation, RequestFacade request) { + int localPort = request.getLocalPort(); + String localAddr = request.getLocalAddr(); + int remotePort = request.getRemotePort(); + String remoteAddr = request.getRemoteAddr(); + + rpcInvocation.put(RestConstant.REMOTE_ADDR, remoteAddr); + rpcInvocation.put(RestConstant.LOCAL_ADDR, localAddr); + rpcInvocation.put(RestConstant.REMOTE_PORT, remotePort); + rpcInvocation.put(RestConstant.LOCAL_PORT, localPort); + } + + + /** + * parse request + * @param rpcInvocation + * @param request + */ + public static void parseRequest(RpcInvocation rpcInvocation, RequestFacade request) { + parseRequestHeader(rpcInvocation, request); + parseRequestAttribute(rpcInvocation, request); + } + + /** + * parse rest response header to appResponse attribute & attachment + * @param appResponse + * @param restResult + */ + public static void parseResponseHeader(AppResponse appResponse, RestResult restResult) { + + Map> headers = restResult.headers(); + if (headers == null || headers.isEmpty()) { + return; + } + + headers.entrySet().stream().forEach(entry -> { + String header = entry.getKey(); + if (isRestAttachHeader(header)) { + // attachment + appResponse.setAttachment(subRestAttachRealHeaderPrefix(header), entry.getValue()); + } else { + // attribute + appResponse.setAttribute(header, entry.getValue()); + } + }); + + + } +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/MediaTypeUtil.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/MediaTypeUtil.java index 4a16ea5861..55a9783b12 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/MediaTypeUtil.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/MediaTypeUtil.java @@ -18,14 +18,27 @@ package org.apache.dubbo.rpc.protocol.rest.util; import org.apache.dubbo.metadata.rest.media.MediaType; import org.apache.dubbo.rpc.protocol.rest.exception.UnSupportContentTypeException; +import org.apache.dubbo.rpc.protocol.rest.message.HttpMessageCodecManager; import java.util.Arrays; import java.util.List; public class MediaTypeUtil { - private static final List mediaTypes = Arrays.asList(MediaType.values()); - public static MediaType convertMediaType(String... contentTypes) { + private static final List mediaTypes = MediaType.getSupportMediaTypes(); + + + /** + * return first match , if any multiple content-type ,acquire mediaType by targetClass type .if contentTypes is empty + * + * @param contentTypes + * @return + */ + public static MediaType convertMediaType(Class targetType, String... contentTypes) { + + if (contentTypes == null || contentTypes.length == 0) { + return HttpMessageCodecManager.typeSupport(targetType); + } for (String contentType : contentTypes) { for (MediaType mediaType : mediaTypes) { @@ -34,6 +47,10 @@ public class MediaTypeUtil { return mediaType; } } + + if (contentType != null && contentType.contains(MediaType.ALL_VALUE.value)) { + return HttpMessageCodecManager.typeSupport(targetType); + } } throw new UnSupportContentTypeException(Arrays.toString(contentTypes)); diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/MultiValueCreator.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/MultiValueCreator.java index e64f47af73..8a391b9955 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/MultiValueCreator.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/MultiValueCreator.java @@ -17,37 +17,86 @@ package org.apache.dubbo.rpc.protocol.rest.util; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; + import java.lang.reflect.Method; +import java.util.Map; public class MultiValueCreator { - private final static String SPRING_MultiValueMap = "org.springframework.util.LinkedMultiValueMap"; - private final static String JAVAX_MultiValueMap = "org.jboss.resteasy.specimpl.MultivaluedMapImpl"; + private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(MultiValueCreator.class); - private static Class multiValueMapClass = null; - private static Method multiValueMapAdd = null; + private final static String SPRING_MultiValueMapImpl = "org.springframework.util.LinkedMultiValueMap"; + private final static String SPRING_MultiValueMap = "org.springframework.util.MultiValueMap"; + private final static String JAVAX_MultiValueMapImpl = "org.jboss.resteasy.specimpl.MultivaluedMapImpl"; + private final static String JAVAX_MultiValueMap = "javax.ws.rs.core.MultivaluedMap"; + + private static Class springMultiValueMapImplClass = null; + private static Class springMultiValueMapClass = null; + private static Method springMultiValueMapAdd = null; + + private static Class jaxrsMultiValueMapImplClass = null; + private static Class jaxrsMultiValueMapClass = null; + + private static Method jaxrsMultiValueMapAdd = null; static { - multiValueMapClass = ReflectUtils.findClassTryException(SPRING_MultiValueMap, JAVAX_MultiValueMap); - multiValueMapAdd = ReflectUtils.getMethodAndTryCatch(multiValueMapClass, "add", new Class[]{Object.class, Object.class}); + springMultiValueMapClass = ReflectUtils.findClassTryException(SPRING_MultiValueMap); + springMultiValueMapImplClass = ReflectUtils.findClassTryException(SPRING_MultiValueMapImpl); + springMultiValueMapAdd = ReflectUtils.getMethodByName(springMultiValueMapImplClass, "add"); + + jaxrsMultiValueMapClass = ReflectUtils.findClassTryException(JAVAX_MultiValueMap); + jaxrsMultiValueMapImplClass = ReflectUtils.findClassTryException(JAVAX_MultiValueMapImpl); + jaxrsMultiValueMapAdd = ReflectUtils.getMethodByName(jaxrsMultiValueMapImplClass, "add"); + } - public static Object createMultiValueMap() { + public static Object providerCreateMultiValueMap(Class targetType) { try { - return multiValueMapClass.getDeclaredConstructor().newInstance(); + if (typeJudge(springMultiValueMapClass, targetType)) { + return springMultiValueMapImplClass.getDeclaredConstructor().newInstance(); + } else if (typeJudge(jaxrsMultiValueMapClass, targetType)) { + return jaxrsMultiValueMapImplClass.getDeclaredConstructor().newInstance(); + } } catch (Exception e) { - + logger.error("", e.getMessage(), "current param type is: " + targetType + "and support type is : " + springMultiValueMapClass + "or" + jaxrsMultiValueMapClass, + "dubbo rest form content-type param construct error,un support param type: ", e); } return null; } - public static void add(Object multiValueMap, String key, String value) { - try { - ReflectUtils.invokeAndTryCatch(multiValueMap, multiValueMapAdd, new String[]{key, value}); - } catch (Exception e) { + private static boolean typeJudge(Class parent, Class targetType) { + if (parent == null) { + return false; + } + + if (!Map.class.isAssignableFrom(targetType)) { + return true; + } + + return parent.isAssignableFrom(targetType) || parent.equals(targetType); + } + + public static void add(Object multiValueMap, String key, Object value) { + try { + if (multiValueMap == null) { + return; + } + + Method multiValueMapAdd = null; + if (springMultiValueMapImplClass.equals(multiValueMap.getClass())) { + multiValueMapAdd = springMultiValueMapAdd; + } else if (jaxrsMultiValueMapImplClass.equals(multiValueMap.getClass())) { + multiValueMapAdd = jaxrsMultiValueMapAdd; + } + + ReflectUtils.invokeAndTryCatch(multiValueMap, multiValueMapAdd, new Object[]{key, value}); + } catch (Exception e) { + logger.error("", e.getMessage(), "", "dubbo rest form content-type param add data error: ", e); } } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/NumberUtils.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/NumberUtils.java index 47aa746bbb..77a87b7308 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/NumberUtils.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/NumberUtils.java @@ -21,47 +21,27 @@ import org.apache.dubbo.common.utils.StringUtils; import java.math.BigDecimal; import java.math.BigInteger; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; public class NumberUtils { - - public static final Set> STANDARD_NUMBER_TYPES; - - static { - Set> numberTypes = new HashSet<>(8); - numberTypes.add(Byte.class); - numberTypes.add(Short.class); - numberTypes.add(Integer.class); - numberTypes.add(Long.class); - numberTypes.add(BigInteger.class); - numberTypes.add(Float.class); - numberTypes.add(Double.class); - numberTypes.add(BigDecimal.class); - STANDARD_NUMBER_TYPES = Collections.unmodifiableSet(numberTypes); - } - - public static T parseNumber(String text, Class targetClass) { Assert.notNull(text, "Text must not be null"); Assert.notNull(targetClass, "Target class must not be null"); String trimmed = trimAllWhitespace(text); - if (Byte.class == targetClass) { + if (Byte.class == targetClass || byte.class == targetClass) { return (T) (isHexNumber(trimmed) ? Byte.decode(trimmed) : Byte.valueOf(trimmed)); - } else if (Short.class == targetClass) { + } else if (Short.class == targetClass || short.class == targetClass) { return (T) (isHexNumber(trimmed) ? Short.decode(trimmed) : Short.valueOf(trimmed)); - } else if (Integer.class == targetClass) { + } else if (Integer.class == targetClass || int.class == targetClass) { return (T) (isHexNumber(trimmed) ? Integer.decode(trimmed) : Integer.valueOf(trimmed)); - } else if (Long.class == targetClass) { + } else if (Long.class == targetClass || long.class == targetClass) { return (T) (isHexNumber(trimmed) ? Long.decode(trimmed) : Long.valueOf(trimmed)); } else if (BigInteger.class == targetClass) { return (T) (isHexNumber(trimmed) ? decodeBigInteger(trimmed) : new BigInteger(trimmed)); - } else if (Float.class == targetClass) { + } else if (Float.class == targetClass || float.class == targetClass) { return (T) Float.valueOf(trimmed); - } else if (Double.class == targetClass) { + } else if (Double.class == targetClass || double.class == targetClass) { return (T) Double.valueOf(trimmed); } else if (BigDecimal.class == targetClass || Number.class == targetClass) { return (T) new BigDecimal(trimmed); diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/ReflectUtils.java b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/ReflectUtils.java index 23b71540e7..4da7589e99 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/ReflectUtils.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/java/org/apache/dubbo/rpc/protocol/rest/util/ReflectUtils.java @@ -17,49 +17,20 @@ package org.apache.dubbo.rpc.protocol.rest.util; -import java.lang.reflect.Array; -import java.lang.reflect.Field; +import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; public class ReflectUtils { - private final static Class[] EMPTY_CLASS_ARRAY = new Class[0]; - private final static Object[] EMPTY_OBJECT_ARRAY = new Object[0]; - public static Field getField(Class clazz, String field) throws IllegalAccessException { - Field[] fields = clazz.getDeclaredFields(); + public static Class findClass(String name, ClassLoader classLoader) throws ClassNotFoundException { - for (Field field1 : fields) { - if (field1.getName().equals(field)) { - return setModifiersUnFinal(field1); - } - } + return classLoader.loadClass(name); - fields = clazz.getFields(); - - for (Field field1 : fields) { - if (field1.getName().equals(field)) { - return setModifiersUnFinal(field1); - } - } - - return null; - } - - - public static Method getMethod(Class clazz, String method, Class[] paramTypes) throws NoSuchMethodException { - Method declaredMethod = clazz.getDeclaredMethod(method, paramTypes); - declaredMethod.setAccessible(true); - return declaredMethod; - } - - private static Field setModifiersUnFinal(Field field) throws IllegalAccessException { - // public -// MODIFIERS.set(field, 1); - field.setAccessible(true); - return field; } public static Class findClass(String name) throws ClassNotFoundException { @@ -68,12 +39,6 @@ public class ReflectUtils { } - public static Class findClass(String name, ClassLoader classLoader) throws ClassNotFoundException { - - return classLoader.loadClass(name); - - } - public static Class findClassAndTryCatch(String name, ClassLoader classLoader) { try { @@ -85,12 +50,6 @@ public class ReflectUtils { } - public static Class findClass(String... name) throws ClassNotFoundException { - - return findClass(Thread.currentThread().getContextClassLoader(), name); - } - - public static Class findClass(ClassLoader classLoader, String... name) throws ClassNotFoundException { String[] names = name; @@ -119,79 +78,79 @@ public class ReflectUtils { } + public static List getMethodByNameList(Class clazz, String name) { + // prevent duplicate method + Set methods = new HashSet<>(); + + try { + filterMethod(name, methods, clazz.getDeclaredMethods()); + + } catch (Exception e) { + + } + + try { + filterMethod(name, methods, clazz.getMethods()); + } catch (Exception e) { + + } + return new ArrayList<>(methods); + + + } + + public static List> getConstructList(Class clazz) { + // prevent duplicate method + Set> methods = new HashSet<>(); + + try { + filterConstructMethod(methods, clazz.getDeclaredConstructors()); + } catch (Exception e) { + } + + try { + filterConstructMethod(methods, clazz.getConstructors()); + } catch (Exception e) { + + } + return new ArrayList>(methods); + + + } + + private static void filterConstructMethod(Set> methods, Constructor[] declaredMethods) { + for (Constructor constructor : declaredMethods) { + methods.add(constructor); + } + + } + + private static void filterMethod(String name, Set methodList, Method[] methods) { + for (Method declaredMethod : methods) { + if (!name.equals(declaredMethod.getName())) { + continue; + } + declaredMethod.setAccessible(true); + methodList.add(declaredMethod); + } + } + + public static Method getMethodByName(Class clazz, String name) { + + List methodByNameList = getMethodByNameList(clazz, name); + if (methodByNameList.isEmpty()) { + return null; + } else { + return methodByNameList.get(0); + } + } + public static Class findClassTryException(String... name) { return findClassTryException(Thread.currentThread().getContextClassLoader(), name); } - - public static Object getArrayElement(Object obj, int index) { - return Array.get(obj, index); - } - - public static List getArrayElements(Object obj) { - - List objects = new ArrayList(); - int length = Array.getLength(obj); - - if (length == 0) { - return objects; - } - - for (int i = 0; i < length; i++) { - objects.add(Array.get(obj, i)); - } - - return objects; - } - - - public static Field getFieldAndTryCatch(Class clazz, String field) { - - try { - return getField(clazz, field); - } catch (Exception e) { - - } - return null; - } - - public static Method getMethod(Class clazz, String method) throws NoSuchMethodException { - Method declaredMethod = clazz.getDeclaredMethod(method, new Class[]{}); - declaredMethod.setAccessible(true); - return declaredMethod; - } - - public static Method getMethodAndTry(Class clazz, String method) { - Method declaredMethod = null; - try { - declaredMethod = clazz.getDeclaredMethod(method, EMPTY_CLASS_ARRAY); - declaredMethod.setAccessible(true); - } catch (Exception e) { - - } - - if (declaredMethod == null) { - try { - declaredMethod = getMethodByName(clazz, method); - } catch (Exception e) { - - } - } - - return declaredMethod; - } - - public static Object invoke(Object object, Method method) { - try { - return method.invoke(object, EMPTY_OBJECT_ARRAY); - } catch (Exception e) { - return null; - } - } - public static Object invoke(Object object, Method method, Object[] params) throws InvocationTargetException, IllegalAccessException { return method.invoke(object, params); - } public static Object invokeAndTryCatch(Object object, Method method, Object[] params) { @@ -204,66 +163,5 @@ public class ReflectUtils { return null; } - public static Class findClassAndTry(String name) { - - try { - return findClass(name); - } catch (Exception e) { - return null; - } - - } - - public static Method getMethodByName(Class clazz, String name) { - Method[] declaredMethods = clazz.getMethods(); - - for (Method declaredMethod : declaredMethods) { - if (name.equals(declaredMethod.getName())) { - - return declaredMethod; - } - } - - return null; - - } - - - public static Object getValueByFields(Object obj, Field... fields) { - for (Field field : fields) { - - try { - Object o = field.get(obj); - if (o != null) { - return o; - } - } catch (Exception e) { - - } - - } - - return null; - } - - public static Method getMethodAndTryCatch(Class clazz, String method, Class[] paramTypes) { - try { - return getMethod(clazz, method, paramTypes); - } catch (Throwable e) { - - } - return null; - - } - - - public static Object getFieldValueAndTryCatch(Object obj, String field) { - try { - return ReflectUtils.getValueByFields(obj, ReflectUtils.getFieldAndTryCatch(obj.getClass(), field)); - } catch (Exception e) { - return null; - } - } - } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.provider.BaseProviderParamParser b/dubbo-rpc/dubbo-rpc-rest/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.provider.BaseProviderParamParser new file mode 100644 index 0000000000..0f62f51f54 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.provider.BaseProviderParamParser @@ -0,0 +1,4 @@ +body=org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.provider.BodyProviderParamParser +header=org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.provider.HeaderProviderParamParser +path=org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.provider.PathProviderParamParser +param=org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.provider.ParamProviderParamParser diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/DataParseUtilsTest.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/DataParseUtilsTest.java new file mode 100644 index 0000000000..ae29327019 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/DataParseUtilsTest.java @@ -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.rpc.protocol.rest; + +import org.apache.dubbo.rpc.protocol.rest.util.DataParseUtils; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; + +public class DataParseUtilsTest { + @Test + void testJsonConvert() throws Exception { + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + DataParseUtils.writeJsonContent(User.getInstance(), byteArrayOutputStream); + + Assertions.assertEquals("{\"age\":18,\"id\":404,\"name\":\"dubbo\"}", + new String(byteArrayOutputStream.toByteArray())); + + + } + + @Test + void testStr() { + Object convert = DataParseUtils.stringTypeConvert(boolean.class, "true"); + + Assertions.assertEquals(Boolean.TRUE, convert); + + convert = DataParseUtils.stringTypeConvert(Boolean.class, "true"); + + Assertions.assertEquals(Boolean.TRUE, convert); + + convert = DataParseUtils.stringTypeConvert(String.class, "true"); + + Assertions.assertEquals("true", convert); + + convert = DataParseUtils.stringTypeConvert(int.class, "1"); + + Assertions.assertEquals(1, convert); + + convert = DataParseUtils.stringTypeConvert(Integer.class, "1"); + + Assertions.assertEquals(1, convert); + + + } +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/DemoService.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/DemoService.java index 265c6ce382..980355844e 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/DemoService.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/DemoService.java @@ -17,22 +17,117 @@ package org.apache.dubbo.rpc.protocol.rest; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.RequestBody; -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 io.netty.handler.codec.http.DefaultFullHttpRequest; -@RestController() -@RequestMapping("/demoService") +import javax.ws.rs.Consumes; +import javax.ws.rs.FormParam; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import java.util.List; +import java.util.Map; + +@Path("/demoService") public interface DemoService { - @RequestMapping(value = "/hello", method = RequestMethod.GET) - Integer hello(@RequestParam Integer a, @RequestParam Integer b); + @GET + @Path("/hello") + Integer hello(@QueryParam("a") Integer a, @QueryParam("b") Integer b); - @RequestMapping(value = "/error", method = RequestMethod.GET) + @GET + @Path("/error") + @Consumes({javax.ws.rs.core.MediaType.TEXT_PLAIN}) + @Produces({javax.ws.rs.core.MediaType.TEXT_PLAIN}) String error(); - @RequestMapping(value = "/say", method = RequestMethod.POST, consumes = MediaType.TEXT_PLAIN_VALUE) - String sayHello(@RequestBody String name); + @POST + @Path("/say") + @Consumes({javax.ws.rs.core.MediaType.TEXT_PLAIN}) + String sayHello(String name); + + @POST + @Path("number") + @Produces({MediaType.TEXT_PLAIN}) + @Consumes({MediaType.APPLICATION_FORM_URLENCODED}) + Long testFormBody(@FormParam("number") Long number); + + boolean isCalled(); + + @GET + @Path("/primitive") + int primitiveInt(@QueryParam("a") int a, @QueryParam("b") int b); + + @GET + @Path("/primitiveLong") + long primitiveLong(@QueryParam("a") long a, @QueryParam("b") Long b); + + @GET + @Path("/primitiveByte") + long primitiveByte(@QueryParam("a") byte a, @QueryParam("b") Long b); + + @POST + @Path("/primitiveShort") + long primitiveShort(@QueryParam("a") short a, @QueryParam("b") Long b, int c); + + @GET + @Path("/request") + void request(DefaultFullHttpRequest defaultFullHttpRequest); + + @GET + @Path("testMapParam") + @Produces({MediaType.TEXT_PLAIN}) + @Consumes({MediaType.TEXT_PLAIN}) + String testMapParam(@QueryParam("test") Map params); + + @GET + @Path("testMapHeader") + @Produces({MediaType.TEXT_PLAIN}) + @Consumes({MediaType.TEXT_PLAIN}) + String testMapHeader(@HeaderParam("test") Map headers); + + @POST + @Path("testMapForm") + @Produces({MediaType.APPLICATION_JSON}) + @Consumes({MediaType.APPLICATION_FORM_URLENCODED}) + List testMapForm(MultivaluedMap params); + + @POST + @Path("/header") + @Consumes({javax.ws.rs.core.MediaType.TEXT_PLAIN}) + String header(@HeaderParam("header") String header); + + @POST + @Path("/headerInt") + @Consumes({javax.ws.rs.core.MediaType.TEXT_PLAIN}) + int headerInt(@HeaderParam("header") int header); + + @POST + @Path("/noStringParam") + @Consumes({javax.ws.rs.core.MediaType.TEXT_PLAIN}) + String noStringParam(@QueryParam("param") String param); + + @POST + @Path("/noStringHeader") + @Consumes({javax.ws.rs.core.MediaType.TEXT_PLAIN}) + String noStringHeader(@HeaderParam("header") String header); + + @POST + @Path("/noIntHeader") + @Consumes({javax.ws.rs.core.MediaType.TEXT_PLAIN}) + int noIntHeader(int header); + + @POST + @Path("/noIntParam") + @Consumes({javax.ws.rs.core.MediaType.TEXT_PLAIN}) + int noIntParam(int header); + + @POST + @Path("/noBodyArg") + @Consumes({MediaType.APPLICATION_JSON}) + User noBodyArg(User user); + } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/DemoServiceImpl.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/DemoServiceImpl.java index a71fb9f45b..7a908d93ad 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/DemoServiceImpl.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/DemoServiceImpl.java @@ -17,15 +17,21 @@ package org.apache.dubbo.rpc.protocol.rest; +import io.netty.handler.codec.http.DefaultFullHttpRequest; import org.apache.dubbo.rpc.RpcContext; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; +import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.HeaderParam; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import java.util.List; import java.util.Map; + @Path("/demoService") public class DemoServiceImpl implements DemoService { private static Map context; @@ -40,11 +46,102 @@ public class DemoServiceImpl implements DemoService { return "Hello, " + name; } + @POST + @Path("number") + @Produces({javax.ws.rs.core.MediaType.APPLICATION_FORM_URLENCODED}) + @Consumes({MediaType.APPLICATION_FORM_URLENCODED}) + @Override + public Long testFormBody(Long number) { + return number; + } + public boolean isCalled() { return called; } + @Override + public int primitiveInt(int a, int b) { + return a + b; + } + + @Override + public long primitiveLong(long a, Long b) { + return a + b; + } + + @Override + public long primitiveByte(byte a, Long b) { + return a + b; + } + + @Override + public long primitiveShort(short a, Long b, int c) { + return a + b; + } + + @Override + public void request(DefaultFullHttpRequest defaultFullHttpRequest) { + + } + + @Override + public String testMapParam(Map params) { + return params.get("param"); + } + + @Override + public String testMapHeader(Map headers) { + return headers.get("header"); + } + + @Override + public List testMapForm(MultivaluedMap params) { + return params.get("form"); + } + + @Override + public String header(String header) { + return header; + } + + @Override + public int headerInt(int header) { + return header; + } + + @Override + public String noStringParam(String param) { + return param; + } + + + @Override + public String noStringHeader(String header) { + return header; + } + + @POST + @Path("/noIntHeader") + @Consumes({javax.ws.rs.core.MediaType.TEXT_PLAIN}) + @Override + public int noIntHeader(@HeaderParam("header")int header) { + return header; + } + + @POST + @Path("/noIntParam") + @Consumes({javax.ws.rs.core.MediaType.TEXT_PLAIN}) + @Override + public int noIntParam(@QueryParam("header")int header) { + return header; + } + + @Override + public User noBodyArg(User user) { + return user; + } + @GET @Path("/hello") @Override @@ -58,7 +155,7 @@ public class DemoServiceImpl implements DemoService { @Path("/error") @Override public String error() { - throw new RuntimeException(); + throw new RuntimeException("test error"); } public static Map getAttachments() { diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/ExceptionMapperTest.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/ExceptionMapperTest.java new file mode 100644 index 0000000000..a10bbad0df --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/ExceptionMapperTest.java @@ -0,0 +1,75 @@ +/* + * 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; + +import org.apache.dubbo.rpc.protocol.rest.exception.mapper.ExceptionHandler; +import org.apache.dubbo.rpc.protocol.rest.exception.mapper.ExceptionMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + + +public class ExceptionMapperTest { + private final ExceptionMapper exceptionMapper = new ExceptionMapper(); + + @Test + void testRegister() { + + + exceptionMapper.registerMapper(TestExceptionHandler.class); + + + Object result = exceptionMapper.exceptionToResult(new RuntimeException("test")); + + + Assertions.assertEquals("test", result); + + + } + + @Test + void testExceptionNoArgConstruct() { + + + Assertions.assertThrows(RuntimeException.class, () -> { + exceptionMapper.registerMapper(TestExceptionHandlerException.class); + + }); + + + } + + + public class TestExceptionHandler implements ExceptionHandler { + + + @Override + public Object result(RuntimeException exception) { + return exception.getMessage(); + } + } + + class TestExceptionHandlerException implements ExceptionHandler { + + + @Override + public Object result(RuntimeException exception) { + return exception.getMessage(); + } + } + + +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/HttpMessageCodecManagerTest.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/HttpMessageCodecManagerTest.java new file mode 100644 index 0000000000..1f514ba8e5 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/HttpMessageCodecManagerTest.java @@ -0,0 +1,57 @@ +/* + * 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; + +import org.apache.dubbo.metadata.rest.media.MediaType; +import org.apache.dubbo.rpc.protocol.rest.message.HttpMessageCodecManager; +import org.apache.dubbo.rpc.protocol.rest.message.codec.XMLCodec; +import org.apache.dubbo.rpc.protocol.rest.pair.MessageCodecResultPair; +import org.apache.dubbo.rpc.protocol.rest.rest.RegistrationResult; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; + +public class HttpMessageCodecManagerTest { + + @Test + void testCodec() throws Exception { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + + RegistrationResult registrationResult = new RegistrationResult(); + registrationResult.setId(1l); + HttpMessageCodecManager.httpMessageEncode(byteArrayOutputStream, + registrationResult, null, MediaType.TEXT_XML, null); + + Object o = HttpMessageCodecManager.httpMessageDecode(byteArrayOutputStream.toByteArray(), RegistrationResult.class, MediaType.TEXT_XML); + + Assertions.assertEquals(registrationResult, o); + + byteArrayOutputStream = new ByteArrayOutputStream(); + MessageCodecResultPair messageCodecResultPair = HttpMessageCodecManager.httpMessageEncode(byteArrayOutputStream, null, null, null, RegistrationResult.class); + + MediaType mediaType = messageCodecResultPair.getMediaType(); + + Assertions.assertEquals(MediaType.APPLICATION_JSON_VALUE, mediaType); + + XMLCodec xmlCodec = new XMLCodec(); + + Assertions.assertEquals(false, xmlCodec.typeSupport(null)); + + + } +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/JaxrsRestProtocolTest.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/JaxrsRestProtocolTest.java index cff2d73222..9658f7f89f 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/JaxrsRestProtocolTest.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/JaxrsRestProtocolTest.java @@ -19,6 +19,9 @@ package org.apache.dubbo.rpc.protocol.rest; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.ExtensionLoader; import org.apache.dubbo.common.utils.NetUtils; +import org.apache.dubbo.metadata.rest.PathMatcher; +import org.apache.dubbo.metadata.rest.RestMethodMetadata; +import org.apache.dubbo.metadata.rest.ServiceRestMetadata; import org.apache.dubbo.rpc.Exporter; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.Protocol; @@ -33,17 +36,23 @@ import org.apache.dubbo.rpc.model.ModuleServiceRepository; import org.apache.dubbo.rpc.model.ProviderModel; import org.apache.dubbo.rpc.model.ServiceDescriptor; +import org.apache.dubbo.rpc.protocol.rest.annotation.metadata.MetadataResolver; +import org.apache.dubbo.rpc.protocol.rest.exception.DoublePathCheckException; +import org.apache.dubbo.rpc.protocol.rest.exception.mapper.ExceptionHandler; +import org.apache.dubbo.rpc.protocol.rest.exception.mapper.ExceptionMapper; import org.apache.dubbo.rpc.protocol.rest.rest.AnotherUserRestService; import org.apache.dubbo.rpc.protocol.rest.rest.AnotherUserRestServiceImpl; +import org.apache.dubbo.rpc.protocol.rest.rest.RestDemoForTestException; import org.hamcrest.CoreMatchers; +import org.jboss.resteasy.specimpl.MultivaluedMapImpl; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import javax.ws.rs.core.Response; -import javax.ws.rs.ext.ExceptionMapper; import java.util.Arrays; +import java.util.HashMap; import java.util.Map; import static org.apache.dubbo.remoting.Constants.SERVER_KEY; @@ -60,6 +69,7 @@ class JaxrsRestProtocolTest { private final int availablePort = NetUtils.getAvailablePort(); private final URL exportUrl = URL.valueOf("rest://127.0.0.1:" + availablePort + "/rest?interface=org.apache.dubbo.rpc.protocol.rest.DemoService"); private final ModuleServiceRepository repository = ApplicationModel.defaultModel().getDefaultModule().getServiceRepository(); + private final ExceptionMapper exceptionMapper = new ExceptionMapper(); @AfterEach public void tearDown() { @@ -83,14 +93,28 @@ class JaxrsRestProtocolTest { String result = client.sayHello("haha"); Assertions.assertTrue(server.isCalled()); Assertions.assertEquals("Hello, haha", result); + + String header = client.header("header test"); + Assertions.assertEquals("header test", header); + + Assertions.assertEquals(1, client.headerInt(1)); invoker.destroy(); exporter.unexport(); } @Test - void testAnotherUserRestProtocol() { - URL url = URL.valueOf("rest://127.0.0.1:" + NetUtils.getAvailablePort() + "/?version=1.0.0&interface=org.apache.dubbo.rpc.protocol.rest.rest.AnotherUserRestService"); + void testAnotherUserRestProtocolByDifferentRestClient() { + testAnotherUserRestProtocol(org.apache.dubbo.remoting.Constants.OK_HTTP); + testAnotherUserRestProtocol(org.apache.dubbo.remoting.Constants.APACHE_HTTP_CLIENT); + testAnotherUserRestProtocol(org.apache.dubbo.remoting.Constants.URL_CONNECTION); + } + + + void testAnotherUserRestProtocol(String restClient) { + URL url = URL.valueOf("rest://127.0.0.1:" + NetUtils.getAvailablePort() + + "/?version=1.0.0&interface=org.apache.dubbo.rpc.protocol.rest.rest.AnotherUserRestService&" + + org.apache.dubbo.remoting.Constants.CLIENT_KEY + "=" + restClient); AnotherUserRestServiceImpl server = new AnotherUserRestServiceImpl(); @@ -115,6 +139,12 @@ class JaxrsRestProtocolTest { Assertions.assertEquals(1l, client.number(1l)); + HashMap map = new HashMap<>(); + map.put("headers", "h1"); + Assertions.assertEquals("h1", client.headerMap(map)); + Assertions.assertEquals(null, client.headerMap(null)); + + invoker.destroy(); exporter.unexport(); } @@ -146,7 +176,7 @@ class JaxrsRestProtocolTest { URL url = this.registerProvider(exportUrl, server, DemoService.class); - RpcContext.getClientAttachment().setAttachment("timeout", "200"); + RpcContext.getClientAttachment().setAttachment("timeout", "20000"); Exporter exporter = protocol.export(proxy.getInvoker(server, DemoService.class, url)); DemoService demoService = this.proxy.getProxy(protocol.refer(DemoService.class, url)); @@ -174,6 +204,7 @@ class JaxrsRestProtocolTest { exporter.unexport(); } + @Disabled @Test void testServletWithoutWebConfig() { Assertions.assertThrows(RpcException.class, () -> { @@ -190,6 +221,7 @@ class JaxrsRestProtocolTest { @Test void testErrorHandler() { Assertions.assertThrows(RpcException.class, () -> { + exceptionMapper.unRegisterMapper(RuntimeException.class); DemoService server = new DemoServiceImpl(); URL url = this.registerProvider(exportUrl, server, DemoService.class); @@ -269,6 +301,7 @@ class JaxrsRestProtocolTest { exporter.unexport(); } + @Disabled @Test void testRegFail() { Assertions.assertThrows(RuntimeException.class, () -> { @@ -288,6 +321,7 @@ class JaxrsRestProtocolTest { @Test void testExceptionMapper() { + DemoService server = new DemoServiceImpl(); URL url = this.registerProvider(exportUrl, server, DemoService.class); @@ -301,11 +335,178 @@ class JaxrsRestProtocolTest { Assertions.assertEquals("test-exception", referDemoService.error()); } - public static class TestExceptionMapper implements ExceptionMapper { + @Test + void testFormConsumerParser() { + DemoService server = new DemoServiceImpl(); + URL nettyUrl = this.registerProvider(exportUrl, server, DemoService.class); + + + Exporter exporter = protocol.export(proxy.getInvoker(server, DemoService.class, nettyUrl)); + + DemoService demoService = this.proxy.getProxy(protocol.refer(DemoService.class, nettyUrl)); + + + Long number = demoService.testFormBody(18l); + Assertions.assertEquals(18l, number); + + exporter.unexport(); + } + + @Test + void test404() { + Assertions.assertThrows(RpcException.class, () -> { + DemoService server = new DemoServiceImpl(); + URL nettyUrl = this.registerProvider(exportUrl, server, DemoService.class); + + + Exporter exporter = protocol.export(proxy.getInvoker(server, DemoService.class, nettyUrl)); + + URL referUrl = URL.valueOf("rest://127.0.0.1:" + availablePort + "/rest?interface=org.apache.dubbo.rpc.protocol.rest.rest.RestDemoForTestException"); + + RestDemoForTestException restDemoForTestException = this.proxy.getProxy(protocol.refer(RestDemoForTestException.class, referUrl)); + + restDemoForTestException.test404(); + + exporter.unexport(); + }); + + } + + @Test + void test400() { + Assertions.assertThrows(RpcException.class, () -> { + DemoService server = new DemoServiceImpl(); + URL nettyUrl = this.registerProvider(exportUrl, server, DemoService.class); + + + Exporter exporter = protocol.export(proxy.getInvoker(server, DemoService.class, nettyUrl)); + + URL referUrl = URL.valueOf("rest://127.0.0.1:" + availablePort + "/rest?interface=org.apache.dubbo.rpc.protocol.rest.rest.RestDemoForTestException"); + + RestDemoForTestException restDemoForTestException = this.proxy.getProxy(protocol.refer(RestDemoForTestException.class, referUrl)); + + restDemoForTestException.test400("abc", "edf"); + + exporter.unexport(); + }); + + } + + @Test + void testPrimitive() { + DemoService server = new DemoServiceImpl(); + + URL url = this.registerProvider(exportUrl, server, DemoService.class); + + URL nettyUrl = url.addParameter(SERVER_KEY, "netty") + .addParameter(EXTENSION_KEY, "org.apache.dubbo.rpc.protocol.rest.support.LoggingFilter"); + Exporter exporter = protocol.export(proxy.getInvoker(server, DemoService.class, nettyUrl)); + + DemoService demoService = this.proxy.getProxy(protocol.refer(DemoService.class, nettyUrl)); + + Integer result = demoService.primitiveInt(1, 2); + Long resultLong = demoService.primitiveLong(1, 2l); + long resultByte = demoService.primitiveByte((byte) 1, 2l); + long resultShort = demoService.primitiveShort((short) 1, 2l, 1); + + assertThat(result, is(3)); + assertThat(resultShort, is(3l)); + assertThat(resultLong, is(3l)); + assertThat(resultByte, is(3l)); + + exporter.unexport(); + } + + + @Test + void testDoubleCheckException() { + + + Assertions.assertThrows(DoublePathCheckException.class, () -> { + + DemoService server = new DemoServiceImpl(); + + + Invoker invoker = proxy.getInvoker(server, DemoService.class, exportUrl); + + PathAndInvokerMapper pathAndInvokerMapper = new PathAndInvokerMapper(); + + ServiceRestMetadata serviceRestMetadata = MetadataResolver.resolveConsumerServiceMetadata(DemoService.class, exportUrl, ""); + + Map pathContainPathVariableToServiceMap = serviceRestMetadata.getPathUnContainPathVariableToServiceMap(); + + + pathAndInvokerMapper.addPathAndInvoker(pathContainPathVariableToServiceMap, invoker); + pathAndInvokerMapper.addPathAndInvoker(pathContainPathVariableToServiceMap, invoker); + }); + + + } + + @Test + void testMapParam() { + DemoService server = new DemoServiceImpl(); + + URL url = this.registerProvider(exportUrl, server, DemoService.class); + + URL nettyUrl = url.addParameter(SERVER_KEY, "netty") + .addParameter(EXTENSION_KEY, "org.apache.dubbo.rpc.protocol.rest.support.LoggingFilter"); + Exporter exporter = protocol.export(proxy.getInvoker(server, DemoService.class, nettyUrl)); + + DemoService demoService = this.proxy.getProxy(protocol.refer(DemoService.class, nettyUrl)); + + Map params = new HashMap<>(); + params.put("param", "P1"); + ; + + + Map headers = new HashMap<>(); + headers.put("header", "H1"); + + + Assertions.assertEquals("P1", demoService.testMapParam(params)); + Assertions.assertEquals("H1", demoService.testMapHeader(headers)); + + MultivaluedMapImpl forms = new MultivaluedMapImpl<>(); + forms.put("form", Arrays.asList("F1")); + + Assertions.assertEquals(Arrays.asList("F1"), demoService.testMapForm(forms)); + exporter.unexport(); + } + + + @Test + void testNoArgParam() { + DemoService server = new DemoServiceImpl(); + + URL url = this.registerProvider(exportUrl, server, DemoService.class); + + URL nettyUrl = url.addParameter(SERVER_KEY, "netty") + .addParameter(EXTENSION_KEY, "org.apache.dubbo.rpc.protocol.rest.support.LoggingFilter"); + Exporter exporter = protocol.export(proxy.getInvoker(server, DemoService.class, nettyUrl)); + + DemoService demoService = this.proxy.getProxy(protocol.refer(DemoService.class, nettyUrl)); + + + Assertions.assertEquals(null, demoService.noStringHeader(null)); + Assertions.assertEquals(null, demoService.noStringParam(null)); + Assertions.assertThrows(RpcException.class, () -> { + demoService.noIntHeader(1); + }); + + Assertions.assertThrows(RpcException.class, () -> { + demoService.noIntParam(1); + }); + + Assertions.assertEquals(null, demoService.noBodyArg(null)); + exporter.unexport(); + } + + public static class TestExceptionMapper implements ExceptionHandler { @Override - public Response toResponse(RuntimeException e) { - return Response.ok("test-exception").build(); + public String result(RuntimeException e) { + return "test-exception"; } } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/MediaTypeUtilTest.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/MediaTypeUtilTest.java new file mode 100644 index 0000000000..bfb2e0005d --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/MediaTypeUtilTest.java @@ -0,0 +1,58 @@ +/* + * 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; + +import org.apache.dubbo.metadata.rest.media.MediaType; +import org.apache.dubbo.rpc.protocol.rest.exception.UnSupportContentTypeException; +import org.apache.dubbo.rpc.protocol.rest.util.MediaTypeUtil; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class MediaTypeUtilTest { + + @Test + void testException() { + + Assertions.assertThrows(UnSupportContentTypeException.class, () -> { + MediaTypeUtil.convertMediaType(null, "aaaaa"); + + }); + + + } + + @Test + void testConvertMediaType() { + MediaType mediaType = MediaTypeUtil.convertMediaType(null, new String[]{MediaType.APPLICATION_JSON_VALUE.value}); + + Assertions.assertEquals(MediaType.APPLICATION_JSON_VALUE, mediaType); + + + mediaType = MediaTypeUtil.convertMediaType(int.class, null); + + Assertions.assertEquals(MediaType.TEXT_PLAIN, mediaType); + + mediaType = MediaTypeUtil.convertMediaType(null, new String[]{MediaType.ALL_VALUE.value}); + + Assertions.assertEquals(MediaType.APPLICATION_JSON_VALUE, mediaType); + + mediaType = MediaTypeUtil.convertMediaType(String.class, new String[]{MediaType.TEXT_XML.value}); + + Assertions.assertEquals(MediaType.TEXT_XML, mediaType); + + } +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/NettyRequestFacadeTest.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/NettyRequestFacadeTest.java new file mode 100644 index 0000000000..de5f70776f --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/NettyRequestFacadeTest.java @@ -0,0 +1,102 @@ +/* + * 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; + +import io.netty.handler.codec.http.DefaultFullHttpRequest; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpVersion; +import org.apache.dubbo.rpc.protocol.rest.request.NettyRequestFacade; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; + +public class NettyRequestFacadeTest { + + @Test + void testMethod() { + + String uri = "/a/b?c=c&d=d"; + DefaultFullHttpRequest defaultFullHttpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri); + + defaultFullHttpRequest.headers().add("h1", "a"); + defaultFullHttpRequest.headers().add("h1", "b"); + defaultFullHttpRequest.headers().add("h2", "c"); + NettyRequestFacade nettyRequestFacade = new NettyRequestFacade(defaultFullHttpRequest, null); + + + Assertions.assertArrayEquals(new String[]{"c"}, nettyRequestFacade.getParameterValues("c")); + Enumeration parameterNames = nettyRequestFacade.getParameterNames(); + + List names = new ArrayList<>(); + while (parameterNames.hasMoreElements()) { + + names.add(parameterNames.nextElement()); + } + + Assertions.assertArrayEquals(Arrays.asList("c", "d").toArray(), names.toArray()); + + Enumeration headerNames = nettyRequestFacade.getHeaderNames(); + + List heads = new ArrayList<>(); + while (headerNames.hasMoreElements()) { + + heads.add(headerNames.nextElement()); + } + + Assertions.assertArrayEquals(Arrays.asList("h1", "h2").toArray(), heads.toArray()); + + + Assertions.assertEquals(uri, nettyRequestFacade.getRequestURI()); + + + Assertions.assertEquals("c", nettyRequestFacade.getHeader("h2")); + + Assertions.assertEquals("d", nettyRequestFacade.getParameter("d")); + + Assertions.assertEquals("/a/b", nettyRequestFacade.getPath()); + + Assertions.assertEquals(null, nettyRequestFacade.getParameterValues("e")); + + Assertions.assertArrayEquals(new String[]{"d"}, nettyRequestFacade.getParameterValues("d")); + + Enumeration h1s = nettyRequestFacade.getHeaders("h1"); + + + heads = new ArrayList<>(); + + while (h1s.hasMoreElements()) { + + heads.add(h1s.nextElement()); + } + + Assertions.assertArrayEquals(new String[]{"a","b"}, heads.toArray()); + + Map parameterMap = nettyRequestFacade.getParameterMap(); + + + Assertions.assertArrayEquals(new String[]{"c"},parameterMap.get("c")); + Assertions.assertArrayEquals(new String[]{"d"},parameterMap.get("d")); + + Assertions.assertEquals("GET",nettyRequestFacade.getMethod()); + + } +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/NumberUtilsTest.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/NumberUtilsTest.java new file mode 100644 index 0000000000..a726e38cc5 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/NumberUtilsTest.java @@ -0,0 +1,177 @@ +/* + * 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; + +import org.apache.dubbo.rpc.protocol.rest.util.DataParseUtils; +import org.apache.dubbo.rpc.protocol.rest.util.NumberUtils; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; + +public class NumberUtilsTest { + void testParseNumber(String numberStr) { + int integer = NumberUtils.parseNumber(numberStr, Integer.class); + + Assertions.assertEquals(1, integer); + + integer = NumberUtils.parseNumber(numberStr, int.class); + + Assertions.assertEquals(1, integer); + + long a = NumberUtils.parseNumber(numberStr, Long.class); + + Assertions.assertEquals(1, a); + + a = NumberUtils.parseNumber(numberStr, long.class); + + Assertions.assertEquals(1, a); + + byte b = NumberUtils.parseNumber(numberStr, Byte.class); + + Assertions.assertEquals(1, b); + + b = NumberUtils.parseNumber(numberStr, byte.class); + + Assertions.assertEquals(1, b); + + short c = NumberUtils.parseNumber(numberStr, Short.class); + + Assertions.assertEquals(1, c); + + c = NumberUtils.parseNumber(numberStr, short.class); + + Assertions.assertEquals(1, c); + + BigInteger f = NumberUtils.parseNumber(numberStr, BigInteger.class); + + Assertions.assertEquals(1, f.intValue()); + + + } + + @Test + void testNumberToBytes() { + byte[] except = {49}; + byte[] bytes = (byte[]) DataParseUtils.objectTextConvertToByteArray(Integer.valueOf("1")); + + Assertions.assertArrayEquals(except, bytes); + + bytes = (byte[]) DataParseUtils.objectTextConvertToByteArray(NumberUtils.parseNumber("1", int.class)); + + Assertions.assertArrayEquals(except, bytes); + + except = new byte[]{49}; + bytes = (byte[]) DataParseUtils.objectTextConvertToByteArray(Byte.valueOf("1")); + + Assertions.assertArrayEquals(except, bytes); + + except = new byte[]{49}; + bytes = (byte[]) DataParseUtils.objectTextConvertToByteArray(Short.valueOf("1")); + + Assertions.assertArrayEquals(except, bytes); + + except = new byte[]{49}; + bytes = (byte[]) DataParseUtils.objectTextConvertToByteArray(Long.valueOf("1")); + + Assertions.assertArrayEquals(except, bytes); + + except = new byte[]{49}; + bytes = (byte[]) DataParseUtils.objectTextConvertToByteArray(BigDecimal.valueOf(1)); + + Assertions.assertArrayEquals(except, bytes); + + except = new byte[]{116, 114, 117, 101}; + bytes = (byte[]) DataParseUtils.objectTextConvertToByteArray(Boolean.TRUE); + + Assertions.assertArrayEquals(except, bytes); + + except = new byte[]{116, 114, 117, 101}; + bytes = (byte[]) DataParseUtils.objectTextConvertToByteArray(true); + + Assertions.assertArrayEquals(except, bytes); + + bytes = (byte[]) DataParseUtils.objectTextConvertToByteArray(User.getInstance()); + + except = User.getInstance().toString().getBytes(StandardCharsets.UTF_8); + Assertions.assertArrayEquals(except, bytes); + + + } + + @Test + void testNumberStr() { + testParseNumber("1"); + testParseNumber("0X0001"); + testParseNumber("0x0001"); + testParseNumber("#1"); + + } + + + @Test + void testUnHexNumber() { + String numberStr = "1"; + double e = NumberUtils.parseNumber(numberStr, Double.class); + + Assertions.assertEquals(1.0, e); + + e = NumberUtils.parseNumber(numberStr, double.class); + + Assertions.assertEquals(1.0, e); + + BigDecimal g = NumberUtils.parseNumber(numberStr, BigDecimal.class); + + Assertions.assertEquals(1, g.intValue()); + + int integer = NumberUtils.parseNumber(numberStr, int.class); + + Assertions.assertEquals(1, integer); + } + + @Test + void testNegative() { + + Integer integer = NumberUtils.parseNumber("-0X1", int.class); + Assertions.assertEquals(-1, integer); + + BigInteger bigInteger = NumberUtils.parseNumber("-0X1", BigInteger.class); + Assertions.assertEquals(-1, bigInteger.intValue()); + } + + @Test + void testException() { + + Assertions.assertThrowsExactly(IllegalArgumentException.class, () -> { + Object abc = NumberUtils.parseNumber("abc", Object.class); + + }); + + Assertions.assertThrowsExactly(IllegalArgumentException.class, () -> { + Object abc = NumberUtils.parseNumber(null, Object.class); + + }); + + Assertions.assertThrowsExactly(IllegalArgumentException.class, () -> { + Object abc = NumberUtils.parseNumber("1", null); + + }); + } + +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/RpcExceptionMapperTest.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/RpcExceptionMapperTest.java index b8a983c055..2749282ea7 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/RpcExceptionMapperTest.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/RpcExceptionMapperTest.java @@ -18,6 +18,8 @@ package org.apache.dubbo.rpc.protocol.rest; import org.apache.dubbo.rpc.RpcException; +import org.apache.dubbo.rpc.protocol.rest.exception.mapper.ExceptionHandler; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Answers; @@ -25,7 +27,8 @@ import org.mockito.internal.util.collections.Sets; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; -import javax.ws.rs.core.Response; + +import java.util.LinkedList; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.not; @@ -36,7 +39,7 @@ import static org.mockito.Mockito.mock; class RpcExceptionMapperTest { - private RpcExceptionMapper exceptionMapper; + private ExceptionHandler exceptionMapper; @BeforeEach public void setUp() { @@ -50,19 +53,60 @@ class RpcExceptionMapperTest { given(violationException.getConstraintViolations()).willReturn(Sets.newSet(violation)); RpcException rpcException = new RpcException("violation", violationException); - Response response = exceptionMapper.toResponse(rpcException); + Object response = exceptionMapper.result(rpcException); assertThat(response, not(nullValue())); - assertThat(response.getEntity(), instanceOf(ViolationReport.class)); + assertThat(response, instanceOf(ViolationReport.class)); } @Test void testNormalException() { RpcException rpcException = new RpcException(); - Response response = exceptionMapper.toResponse(rpcException); + Object response = exceptionMapper.result(rpcException); assertThat(response, not(nullValue())); - assertThat(response.getEntity(), instanceOf(String.class)); + assertThat(response, instanceOf(String.class)); + } + + @Test + void testBuildException() { + + RestConstraintViolation restConstraintViolation = new RestConstraintViolation(); + String message = "message"; + restConstraintViolation.setMessage(message); + String path = "path"; + restConstraintViolation.setPath(path); + String value = "value"; + restConstraintViolation.setValue(value); + + + Assertions.assertEquals(message, restConstraintViolation.getMessage()); + Assertions.assertEquals(path, restConstraintViolation.getPath()); + Assertions.assertEquals(value, restConstraintViolation.getValue()); + + + } + + @Test + public void testViolationReport() { + + ViolationReport violationReport = new ViolationReport(); + + + RestConstraintViolation restConstraintViolation = new RestConstraintViolation("path", "message", "value"); + + violationReport.addConstraintViolation(restConstraintViolation); + + Assertions.assertEquals(1, violationReport.getConstraintViolations().size()); + + + violationReport = new ViolationReport(); + + violationReport.setConstraintViolations(new LinkedList<>()); + + Assertions.assertEquals(0, violationReport.getConstraintViolations().size()); + + } } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/SpringMvcRestProtocolTest.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/SpringMvcRestProtocolTest.java index fb63dea8db..a5f4cb0c6d 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/SpringMvcRestProtocolTest.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/SpringMvcRestProtocolTest.java @@ -32,17 +32,25 @@ import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.model.ModuleServiceRepository; import org.apache.dubbo.rpc.model.ProviderModel; import org.apache.dubbo.rpc.model.ServiceDescriptor; -import org.apache.dubbo.rpc.protocol.rest.rest.RestDemoService; -import org.apache.dubbo.rpc.protocol.rest.rest.RestDemoServiceImpl; +import org.apache.dubbo.rpc.protocol.rest.exception.mapper.ExceptionHandler; +import org.apache.dubbo.rpc.protocol.rest.exception.mapper.ExceptionMapper; +import org.apache.dubbo.rpc.protocol.rest.mvc.SpringDemoServiceImpl; +import org.apache.dubbo.rpc.protocol.rest.mvc.SpringRestDemoService; +import org.apache.dubbo.rpc.protocol.rest.rest.AnotherUserRestService; +import org.apache.dubbo.rpc.protocol.rest.rest.AnotherUserRestServiceImpl; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.springframework.util.LinkedMultiValueMap; +import java.util.Arrays; import java.util.Map; import static org.apache.dubbo.remoting.Constants.SERVER_KEY; +import static org.apache.dubbo.rpc.protocol.rest.Constants.EXCEPTION_MAPPER_KEY; import static org.apache.dubbo.rpc.protocol.rest.Constants.EXTENSION_KEY; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; @@ -52,64 +60,113 @@ import static org.hamcrest.MatcherAssert.assertThat; public class SpringMvcRestProtocolTest { private Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("rest"); private ProxyFactory proxy = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); - private final int availablePort = NetUtils.getAvailablePort(); - private final URL exportUrl = URL.valueOf("rest://127.0.0.1:" + availablePort + "/rest?interface=org.apache.dubbo.rpc.protocol.rest.DemoService"); + private final static int availablePort = NetUtils.getAvailablePort(); + private final static URL exportUrl = URL.valueOf("rest://127.0.0.1:" + availablePort + "/rest?interface=org.apache.dubbo.rpc.protocol.rest.mvc.SpringRestDemoService"); + + private final ModuleServiceRepository repository = ApplicationModel.defaultModel().getDefaultModule().getServiceRepository(); + private final ExceptionMapper exceptionMapper = new ExceptionMapper(); + @AfterEach public void tearDown() { protocol.destroy(); FrameworkModel.destroyAll(); } - public RestDemoService getServerImpl() { - return new RestDemoServiceImpl(); + public SpringRestDemoService getServerImpl() { + return new SpringDemoServiceImpl(); } - public Class getServerClass() { - return RestDemoService.class; + public Class getServerClass() { + return SpringRestDemoService.class; } - public Exporter getExport(URL url, RestDemoService server) { + public Exporter getExport(URL url, SpringRestDemoService server) { + url = url.addParameter(SERVER_KEY, Constants.NETTY_HTTP); + return protocol.export(proxy.getInvoker(server, getServerClass(), url)); + } + + public Exporter getExceptionHandlerExport(URL url, SpringRestDemoService server) { + url = url.addParameter(SERVER_KEY, Constants.NETTY_HTTP); + url = url.addParameter(EXCEPTION_MAPPER_KEY, TestExceptionMapper.class.getName()); return protocol.export(proxy.getInvoker(server, getServerClass(), url)); } @Test void testRestProtocol() { - URL url = URL.valueOf("rest://127.0.0.1:" + NetUtils.getAvailablePort() + "/?version=1.0.0&interface=org.apache.dubbo.rpc.protocol.rest.DemoService"); + URL url = URL.valueOf("rest://127.0.0.1:" + NetUtils.getAvailablePort() + "/?version=1.0.0&interface=org.apache.dubbo.rpc.protocol.rest.mvc.SpringRestDemoService"); - RestDemoService server = getServerImpl(); + SpringRestDemoService server = getServerImpl(); url = this.registerProvider(url, server, getServerClass()); - Exporter exporter = getExport(url, server); - Invoker invoker = protocol.refer(DemoService.class, url); + Exporter exporter = getExport(url, server); + Invoker invoker = protocol.refer(SpringRestDemoService.class, url); Assertions.assertFalse(server.isCalled()); - DemoService client = proxy.getProxy(invoker); + SpringRestDemoService client = proxy.getProxy(invoker); String result = client.sayHello("haha"); Assertions.assertTrue(server.isCalled()); Assertions.assertEquals("Hello, haha", result); + + String header = client.testHeader("header"); + Assertions.assertEquals("header", header); + + String headerInt = client.testHeaderInt(1); + Assertions.assertEquals("1", headerInt); + invoker.destroy(); + exporter.unexport(); + } + + + @Test + void testAnotherUserRestProtocol() { + URL url = URL.valueOf("rest://127.0.0.1:" + NetUtils.getAvailablePort() + "/?version=1.0.0&interface=org.apache.dubbo.rpc.protocol.rest.rest.AnotherUserRestService"); + + AnotherUserRestServiceImpl server = new AnotherUserRestServiceImpl(); + + url = this.registerProvider(url, server, SpringRestDemoService.class); + + Exporter exporter = protocol.export(proxy.getInvoker(server, AnotherUserRestService.class, url)); + Invoker invoker = protocol.refer(AnotherUserRestService.class, url); + + + AnotherUserRestService client = proxy.getProxy(invoker); + User result = client.getUser(123l); + + Assertions.assertEquals(123l, result.getId()); + + result.setName("dubbo"); + Assertions.assertEquals(123l, client.registerUser(result).getId()); + + Assertions.assertEquals("context", client.getContext()); + + byte[] bytes = {1, 2, 3, 4}; + Assertions.assertTrue(Arrays.equals(bytes, client.bytes(bytes))); + + Assertions.assertEquals(1l, client.number(1l)); + invoker.destroy(); exporter.unexport(); } @Test void testRestProtocolWithContextPath() { - RestDemoService server = getServerImpl(); + SpringRestDemoService server = getServerImpl(); Assertions.assertFalse(server.isCalled()); int port = NetUtils.getAvailablePort(); - URL url = URL.valueOf("rest://127.0.0.1:" + port + "/a/b/c?version=1.0.0&interface=org.apache.dubbo.rpc.protocol.rest.DemoService"); + URL url = URL.valueOf("rest://127.0.0.1:" + port + "/a/b/c?version=1.0.0&interface=org.apache.dubbo.rpc.protocol.rest.mvc.SpringRestDemoService"); - url = this.registerProvider(url, server, DemoService.class); + url = this.registerProvider(url, server, SpringRestDemoService.class); - Exporter exporter = getExport(url, server); + Exporter exporter = getExport(url, server); - url = URL.valueOf("rest://127.0.0.1:" + port + "/a/b/c/?version=1.0.0&interface=org.apache.dubbo.rpc.protocol.rest.DemoService"); - Invoker invoker = protocol.refer(DemoService.class, url); - DemoService client = proxy.getProxy(invoker); + url = URL.valueOf("rest://127.0.0.1:" + port + "/a/b/c/?version=1.0.0&interface=org.apache.dubbo.rpc.protocol.rest.mvc.SpringRestDemoService"); + Invoker invoker = protocol.refer(SpringRestDemoService.class, url); + SpringRestDemoService client = proxy.getProxy(invoker); String result = client.sayHello("haha"); Assertions.assertTrue(server.isCalled()); Assertions.assertEquals("Hello, haha", result); @@ -119,14 +176,14 @@ public class SpringMvcRestProtocolTest { @Test void testExport() { - RestDemoService server = getServerImpl(); + SpringRestDemoService server = getServerImpl(); - URL url = this.registerProvider(exportUrl, server, DemoService.class); + URL url = this.registerProvider(exportUrl, server, SpringRestDemoService.class); RpcContext.getClientAttachment().setAttachment("timeout", "200"); - Exporter exporter = getExport(url, server); + Exporter exporter = getExport(url, server); - DemoService demoService = this.proxy.getProxy(protocol.refer(DemoService.class, url)); + SpringRestDemoService demoService = this.proxy.getProxy(protocol.refer(SpringRestDemoService.class, url)); Integer echoString = demoService.hello(1, 2); assertThat(echoString, is(3)); @@ -136,15 +193,14 @@ public class SpringMvcRestProtocolTest { @Test void testNettyServer() { - RestDemoService server = getServerImpl(); + SpringRestDemoService server = getServerImpl(); - URL url = this.registerProvider(exportUrl, server, DemoService.class); + URL nettyUrl = this.registerProvider(exportUrl, server, SpringRestDemoService.class); - URL nettyUrl = url.addParameter(SERVER_KEY, "netty"); - Exporter exporter = protocol.export(proxy.getInvoker(server, RestDemoService.class, nettyUrl)); + Exporter exporter = getExport(nettyUrl, server); - DemoService demoService = this.proxy.getProxy(protocol.refer(DemoService.class, nettyUrl)); + SpringRestDemoService demoService = this.proxy.getProxy(protocol.refer(SpringRestDemoService.class, nettyUrl)); Integer echoString = demoService.hello(10, 10); assertThat(echoString, is(20)); @@ -152,12 +208,13 @@ public class SpringMvcRestProtocolTest { exporter.unexport(); } + @Disabled @Test void testServletWithoutWebConfig() { Assertions.assertThrows(RpcException.class, () -> { - RestDemoService server = getServerImpl(); + SpringRestDemoService server = getServerImpl(); - URL url = this.registerProvider(exportUrl, server, DemoService.class); + URL url = this.registerProvider(exportUrl, server, SpringRestDemoService.class); URL servletUrl = url.addParameter(SERVER_KEY, "servlet"); @@ -168,14 +225,14 @@ public class SpringMvcRestProtocolTest { @Test void testErrorHandler() { Assertions.assertThrows(RpcException.class, () -> { - RestDemoService server = getServerImpl(); + exceptionMapper.unRegisterMapper(RuntimeException.class); + SpringRestDemoService server = getServerImpl(); - URL url = this.registerProvider(exportUrl, server, DemoService.class); + URL nettyUrl = this.registerProvider(exportUrl, server, SpringRestDemoService.class); - URL nettyUrl = url.addParameter(SERVER_KEY, "netty"); - Exporter exporter = getExport(nettyUrl, server); + Exporter exporter = getExport(nettyUrl, server); - DemoService demoService = this.proxy.getProxy(protocol.refer(DemoService.class, nettyUrl)); + SpringRestDemoService demoService = this.proxy.getProxy(protocol.refer(SpringRestDemoService.class, nettyUrl)); demoService.error(); }); @@ -183,13 +240,13 @@ public class SpringMvcRestProtocolTest { @Test void testInvoke() { - RestDemoService server = getServerImpl(); + SpringRestDemoService server = getServerImpl(); - URL url = this.registerProvider(exportUrl, server, DemoService.class); + URL url = this.registerProvider(exportUrl, server, SpringRestDemoService.class); - Exporter exporter = getExport(url, server); + Exporter exporter = getExport(url, server); - RpcInvocation rpcInvocation = new RpcInvocation("hello", DemoService.class.getName(), "", new Class[]{Integer.class, Integer.class}, new Integer[]{2, 3}); + RpcInvocation rpcInvocation = new RpcInvocation("hello", SpringRestDemoService.class.getName(), "", new Class[]{Integer.class, Integer.class}, new Integer[]{2, 3}); Result result = exporter.getInvoker().invoke(rpcInvocation); assertThat(result.getValue(), CoreMatchers.is(5)); @@ -197,15 +254,13 @@ public class SpringMvcRestProtocolTest { @Test void testFilter() { - RestDemoService server = getServerImpl(); + SpringRestDemoService server = getServerImpl(); - URL url = this.registerProvider(exportUrl, server, DemoService.class); + URL url = this.registerProvider(exportUrl, server, SpringRestDemoService.class); - URL nettyUrl = url.addParameter(SERVER_KEY, "netty") - .addParameter(EXTENSION_KEY, "org.apache.dubbo.rpc.protocol.rest.support.LoggingFilter"); - Exporter exporter = getExport(nettyUrl, server); + Exporter exporter = getExport(url, server); - DemoService demoService = this.proxy.getProxy(protocol.refer(DemoService.class, nettyUrl)); + SpringRestDemoService demoService = this.proxy.getProxy(protocol.refer(SpringRestDemoService.class, url)); Integer result = demoService.hello(1, 2); @@ -216,16 +271,16 @@ public class SpringMvcRestProtocolTest { @Test void testRpcContextFilter() { - RestDemoService server = getServerImpl(); + SpringRestDemoService server = getServerImpl(); - URL url = this.registerProvider(exportUrl, server, DemoService.class); + URL nettyUrl = this.registerProvider(exportUrl, server, SpringRestDemoService.class); // use RpcContextFilter - URL nettyUrl = url.addParameter(SERVER_KEY, "netty") - .addParameter(EXTENSION_KEY, "org.apache.dubbo.rpc.protocol.rest.RpcContextFilter"); - Exporter exporter = protocol.export(proxy.getInvoker(server, RestDemoService.class, nettyUrl)); +// URL nettyUrl = url.addParameter(SERVER_KEY, "netty") +// .addParameter(EXTENSION_KEY, "org.apache.dubbo.rpc.protocol.rest.RpcContextFilter"); + Exporter exporter = getExport(nettyUrl, server); - DemoService demoService = this.proxy.getProxy(protocol.refer(DemoService.class, nettyUrl)); + SpringRestDemoService demoService = this.proxy.getProxy(protocol.refer(SpringRestDemoService.class, nettyUrl)); // make sure null and base64 encoded string can work RpcContext.getClientAttachment().setAttachment("key1", null); @@ -237,7 +292,7 @@ public class SpringMvcRestProtocolTest { assertThat(result, is(3)); - Map attachment = RestDemoServiceImpl.getAttachments(); + Map attachment = org.apache.dubbo.rpc.protocol.rest.mvc.SpringDemoServiceImpl.getAttachments(); assertThat(attachment.get("key1"), nullValue()); assertThat(attachment.get("key2"), equalTo("value")); assertThat(attachment.get("key3"), equalTo("=value")); @@ -247,15 +302,16 @@ public class SpringMvcRestProtocolTest { exporter.unexport(); } + @Disabled @Test void testRegFail() { Assertions.assertThrows(RuntimeException.class, () -> { - RestDemoService server = getServerImpl(); + SpringRestDemoService server = getServerImpl(); - URL url = this.registerProvider(exportUrl, server, DemoService.class); + URL url = this.registerProvider(exportUrl, server, SpringRestDemoService.class); URL nettyUrl = url.addParameter(EXTENSION_KEY, "com.not.existing.Filter"); - Exporter exporter = getExport(nettyUrl, server); + Exporter exporter = getExport(nettyUrl, server); }); } @@ -264,6 +320,59 @@ public class SpringMvcRestProtocolTest { assertThat(protocol.getDefaultPort(), is(80)); } + + @Test + void testExceptionMapper() { + + SpringRestDemoService server = getServerImpl(); + + URL exceptionUrl = this.registerProvider(exportUrl, server, SpringRestDemoService.class); + + Exporter exporter = getExceptionHandlerExport(exceptionUrl, server); + + + SpringRestDemoService referDemoService = this.proxy.getProxy(protocol.refer(SpringRestDemoService.class, exceptionUrl)); + + Assertions.assertEquals("test-exception", referDemoService.error()); + + exporter.unexport(); + } + + + @Test + void testFormConsumerParser() { + SpringRestDemoService server = getServerImpl(); + + URL nettyUrl = this.registerProvider(exportUrl, server, SpringRestDemoService.class); + + + Exporter exporter = getExport(nettyUrl, server); + + SpringRestDemoService demoService = this.proxy.getProxy(protocol.refer(SpringRestDemoService.class, nettyUrl)); + + User user = new User(); + user.setAge(18); + user.setName("dubbo"); + user.setId(404l); + String name = demoService.testFormBody(user); + Assertions.assertEquals("dubbo", name); + + LinkedMultiValueMap forms = new LinkedMultiValueMap<>(); + forms.put("form", Arrays.asList("F1")); + + Assertions.assertEquals(Arrays.asList("F1"), demoService.testFormMapBody(forms)); + + exporter.unexport(); + } + + public static class TestExceptionMapper implements ExceptionHandler { + + @Override + public String result(RuntimeException e) { + return "test-exception"; + } + } + private URL registerProvider(URL url, Object impl, Class interfaceClass) { ServiceDescriptor serviceDescriptor = repository.registerService(interfaceClass); ProviderModel providerModel = new ProviderModel( diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/User.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/User.java index 668d8fcafd..bb16caea7d 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/User.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/User.java @@ -55,6 +55,14 @@ public class User implements Serializable { this.age = age; } + public static User getInstance() { + User user = new User(); + user.setAge(18); + user.setName("dubbo"); + user.setId(404l); + return user; + } + @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/mvc/SpringDemoServiceImpl.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/mvc/SpringDemoServiceImpl.java index 72bf284cf0..f06897b076 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/mvc/SpringDemoServiceImpl.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/mvc/SpringDemoServiceImpl.java @@ -18,10 +18,13 @@ package org.apache.dubbo.rpc.protocol.rest.mvc; import org.apache.dubbo.rpc.RpcContext; +import org.apache.dubbo.rpc.protocol.rest.User; +import org.springframework.util.LinkedMultiValueMap; +import java.util.List; import java.util.Map; -public class SpringDemoServiceImpl implements DemoService { +public class SpringDemoServiceImpl implements SpringRestDemoService { private static Map context; private boolean called; @@ -33,10 +36,31 @@ public class SpringDemoServiceImpl implements DemoService { } + @Override public boolean isCalled() { return called; } + @Override + public String testFormBody(User user) { + return user.getName(); + } + + @Override + public List testFormMapBody(LinkedMultiValueMap map) { + return map.get("form"); + } + + @Override + public String testHeader(String header) { + return header; + } + + @Override + public String testHeaderInt(int header) { + return String.valueOf(header); + } + @Override public Integer hello(Integer a, Integer b) { context = RpcContext.getServerAttachment().getObjectAttachments(); @@ -52,4 +76,6 @@ public class SpringDemoServiceImpl implements DemoService { public static Map getAttachments() { return context; } + + } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/mvc/DemoService.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/mvc/SpringRestDemoService.java similarity index 59% rename from dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/mvc/DemoService.java rename to dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/mvc/SpringRestDemoService.java index 346ae90184..5aca5f06bd 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/mvc/DemoService.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/mvc/SpringRestDemoService.java @@ -17,14 +17,20 @@ package org.apache.dubbo.rpc.protocol.rest.mvc; +import org.apache.dubbo.rpc.protocol.rest.User; import org.springframework.http.MediaType; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; 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 java.util.List; + @RestController("/demoService") -public interface DemoService { +public interface SpringRestDemoService { @RequestMapping(value = "/hello", method = RequestMethod.GET) Integer hello(@RequestParam Integer a, @RequestParam Integer b); @@ -33,4 +39,18 @@ public interface DemoService { @RequestMapping(value = "/sayHello", method = RequestMethod.POST, consumes = MediaType.TEXT_PLAIN_VALUE) String sayHello(String name); + + boolean isCalled(); + + @RequestMapping(value = "/testFormBody", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + String testFormBody(@RequestBody User user); + + @RequestMapping(value = "/testFormMapBody", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + List testFormMapBody(@RequestBody LinkedMultiValueMap map); + + @RequestMapping(value = "/testHeader", method = RequestMethod.POST, consumes = MediaType.TEXT_PLAIN_VALUE) + String testHeader(@RequestHeader String header); + + @RequestMapping(value = "/testHeaderInt", method = RequestMethod.GET, consumes = MediaType.TEXT_PLAIN_VALUE) + String testHeaderInt(@RequestHeader int header); } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/rest/AnotherUserRestService.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/rest/AnotherUserRestService.java index 837fdf27c8..a2ecc36808 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/rest/AnotherUserRestService.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/rest/AnotherUserRestService.java @@ -20,11 +20,13 @@ import org.apache.dubbo.rpc.protocol.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.core.MediaType; +import java.util.Map; @Path("u") @Consumes({MediaType.APPLICATION_JSON, MediaType.TEXT_XML}) @@ -54,4 +56,9 @@ public interface AnotherUserRestService { @Path("number") @Produces({MediaType.APPLICATION_JSON}) Long number(Long number); + + @POST + @Path("headerMap") + @Produces({MediaType.APPLICATION_JSON}) + String headerMap(@HeaderParam("headers") Map headers); } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/rest/AnotherUserRestServiceImpl.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/rest/AnotherUserRestServiceImpl.java index 42223ebdda..7a81f071fe 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/rest/AnotherUserRestServiceImpl.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/rest/AnotherUserRestServiceImpl.java @@ -18,6 +18,8 @@ package org.apache.dubbo.rpc.protocol.rest.rest; import org.apache.dubbo.rpc.protocol.rest.User; +import java.util.Map; + public class AnotherUserRestServiceImpl implements AnotherUserRestService { @@ -50,4 +52,11 @@ public class AnotherUserRestServiceImpl implements AnotherUserRestService { public Long number(Long number) { return number; } + + @Override + public String headerMap(Map headers) { + return headers.get("headers"); + } + + } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/rest/RegistrationResult.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/rest/RegistrationResult.java index 7f48b95c71..ff48474d16 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/rest/RegistrationResult.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/rest/RegistrationResult.java @@ -18,6 +18,7 @@ package org.apache.dubbo.rpc.protocol.rest.rest; import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; +import java.util.Objects; /** * DTO to customize the returned message @@ -41,4 +42,17 @@ public class RegistrationResult implements Serializable { public void setId(Long id) { this.id = id; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RegistrationResult that = (RegistrationResult) o; + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/rest/RestDemoForTestException.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/rest/RestDemoForTestException.java new file mode 100644 index 0000000000..718b665ce6 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/rest/RestDemoForTestException.java @@ -0,0 +1,39 @@ +/* + * 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.rest; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; + +@Path("/demoService") +public interface RestDemoForTestException { + + @POST + @Path("/noFound") + @Produces(MediaType.TEXT_PLAIN) + String test404(); + + @GET + @Consumes({MediaType.TEXT_PLAIN}) + @Path("/hello") + Integer test400(@QueryParam("a")String a,@QueryParam("b") String b); +} diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/rest/RestDemoService.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/rest/RestDemoService.java index 56384d0725..4f04bd3894 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/rest/RestDemoService.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/rest/RestDemoService.java @@ -16,11 +16,7 @@ */ package org.apache.dubbo.rpc.protocol.rest.rest; -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.QueryParam; +import javax.ws.rs.*; import javax.ws.rs.core.MediaType; @@ -39,5 +35,11 @@ public interface RestDemoService { @Consumes({MediaType.TEXT_PLAIN}) String sayHello(String name); + @POST + @Path("number") + @Produces({MediaType.APPLICATION_FORM_URLENCODED}) + @Consumes({MediaType.APPLICATION_FORM_URLENCODED}) + Long testFormBody(@FormParam("number") Long number); + boolean isCalled(); } diff --git a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/rest/RestDemoServiceImpl.java b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/rest/RestDemoServiceImpl.java index e9819e5db6..bed70b7657 100644 --- a/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/rest/RestDemoServiceImpl.java +++ b/dubbo-rpc/dubbo-rpc-rest/src/test/java/org/apache/dubbo/rpc/protocol/rest/rest/RestDemoServiceImpl.java @@ -32,6 +32,11 @@ public class RestDemoServiceImpl implements RestDemoService { return "Hello, " + name; } + @Override + public Long testFormBody(Long number) { + return number; + } + public boolean isCalled() { return called;