Feature/3.2 rest protocol provider (#11640)

This commit is contained in:
suncairong163 2023-04-05 14:10:53 +08:00 committed by GitHub
parent d974a5499e
commit 912deb69a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
146 changed files with 5666 additions and 1581 deletions

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -1129,7 +1129,6 @@ public class DefaultApplicationDeployer extends AbstractDeployer<ApplicationMode
return;
}
setStarted();
startMetricsCollector();
if (logger.isInfoEnabled()) {
logger.info(getIdentifier() + " is ready.");
}
@ -1147,12 +1146,6 @@ public class DefaultApplicationDeployer extends AbstractDeployer<ApplicationMode
}
}
private void startMetricsCollector() {
DefaultMetricsCollector collector = applicationModel.getBeanFactory().getBean(DefaultMetricsCollector.class);
if (Objects.nonNull(collector) && collector.isThreadpoolCollectEnabled()) {
collector.registryDefaultSample();
}
}
private void completeStartFuture(boolean success) {
if (startFuture != null) {

View File

@ -435,7 +435,7 @@ class ReferenceConfigTest {
Invoker invoker = originalInvoker.getInvoker();
Assertions.assertTrue(invoker instanceof InjvmInvoker);
}
URL url = withCount.getUrl();
URL url = withFilter.getUrl();
Assertions.assertEquals("application1", url.getParameter("application"));
Assertions.assertEquals("value1", url.getParameter("key1"));
Assertions.assertEquals("value2", url.getParameter("key2"));

View File

@ -117,7 +117,6 @@
<protobuf-java_version>3.22.2</protobuf-java_version>
<javax_annotation-api_version>1.3.2</javax_annotation-api_version>
<servlet_version>3.1.0</servlet_version>
<jakarta.servlet_version>5.0.0</jakarta.servlet_version>
<jetty_version>9.4.51.v20230217</jetty_version>
<validation_new_version>3.0.2</validation_new_version>
<validation_version>1.1.0.Final</validation_version>
@ -423,13 +422,6 @@
<version>${servlet_version}</version>
</dependency>
<!-- support higher tomcat -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>${jakarta.servlet_version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>

View File

@ -1308,6 +1308,13 @@
</resource>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>
META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.provider.BaseProviderParamParser
</resource>
</transformer>
</transformers>
<filters>
<filter>

View File

@ -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";
}

View File

@ -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;
}

View File

@ -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<RestMethodMetadata> metadataToProcess) {
Consumer<RestMethodMetadata> 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) {

View File

@ -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 +
'}';
}
}

View File

@ -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);
}

View File

@ -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<Class> 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
*

View File

@ -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 + '\'' +
'}';
}
}

View File

@ -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<String, List<String>> 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) {

View File

@ -57,8 +57,6 @@ public class RestMethodMetadata implements Serializable {
private Map<Integer, Boolean> indexToEncoded;
private ServiceRestMetadata serviceRestMetadata;
private List<ArgInfo> 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<ArgInfo> 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 +
'}';
}
}

View File

@ -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<PathMatcher, RestMethodMetadata> pathToServiceMap = new HashMap<>();
private Map<PathMatcher, RestMethodMetadata> pathToServiceMapContainPathVariable = new HashMap<>();
private Map<PathMatcher, RestMethodMetadata> pathToServiceMapUnContainPathVariable = new HashMap<>();
private Map<String, Map<ParameterTypesComparator, RestMethodMetadata>> 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<PathMatcher, RestMethodMetadata> getPathToServiceMap() {
return pathToServiceMap;
public Map<PathMatcher, RestMethodMetadata> getPathContainPathVariableToServiceMap() {
return pathToServiceMapContainPathVariable;
}
public Map<PathMatcher, RestMethodMetadata> 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<PathMatcher, RestMethodMetadata> 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<PathMatcher, RestMethodMetadata> pathToServiceMap = getPathToServiceMap();
for (PathMatcher pathMather : pathToServiceMap.keySet()) {
setPort(port, getPathContainPathVariableToServiceMap());
setPort(port, getPathUnContainPathVariableToServiceMap());
}
private void setPort(Integer port, Map<PathMatcher, RestMethodMetadata> 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) {

View File

@ -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<MediaType> getSupportMediaTypes() {
return Arrays.asList(APPLICATION_JSON_VALUE,
APPLICATION_FORM_URLENCODED_VALUE,
TEXT_PLAIN,TEXT_XML,OCTET_STREAM);
}
}

View File

@ -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

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -16,5 +16,8 @@
*/
package org.apache.dubbo.metadata.rest.tag;
public class BodyTag {
/**
* for @RequestBody class no found
*/
public interface BodyTag {
}

View File

@ -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 {
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

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

View File

@ -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);
}

View File

@ -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);
}

View File

@ -47,4 +47,9 @@ public class JaxrsRestServiceImpl implements JaxrsRestService {
public String pathVariable(String a) {
return a;
}
@Override
public String noAnno(String a) {
return a;
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -95,14 +95,13 @@ class JAXRSServiceRestMetadataResolverTest {
// Generated by "dubbo-metadata-processor"
List<String> 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<String> 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)));
}
}

View File

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

View File

@ -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<String> 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<String> 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<PathMatcher, RestMethodMetadata> pathContainPathVariableToServiceMap = springMetadata.getPathContainPathVariableToServiceMap();
for (PathMatcher pathMatcher : pathContainPathVariableToServiceMap.keySet()) {
Assertions.assertTrue(pathMatcher.hasPathVariable());
Assertions.assertEquals(404, pathMatcher.getPort());
}
Map<PathMatcher, RestMethodMetadata> pathUnContainPathVariableToServiceMap = springMetadata.getPathUnContainPathVariableToServiceMap();
for (PathMatcher pathMatcher : pathUnContainPathVariableToServiceMap.keySet()) {
Assertions.assertFalse(pathMatcher.hasPathVariable());
Assertions.assertEquals(404, pathMatcher.getPort());
}
}
}

View File

@ -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<RestMethodMetadata> 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);
});
});
}

View File

@ -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;
}

View File

@ -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<String, MetricSample> 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<String, String> tags = sample.getTags();
Assertions.assertEquals(tags.get(TAG_INTERFACE_KEY), INTERFACE_NAME);

View File

@ -76,6 +76,5 @@
<artifactId>httpclient</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -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<REQUEST, RESPONSE> {
/**
* 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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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()));
}
});
}

View File

@ -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<String, List<String>> headers() {
return connection.getHeaderFields();
}
@Override
public byte[] getErrorResponse() throws IOException {
return IOUtils.toByteArray(connection.getErrorStream());
}
@Override
public int getResponseCode() throws IOException {
return connection.getResponseCode();
}
@Override
public String getMessage() throws IOException {
return connection.getResponseMessage();
}
});
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<String, List<String>> headers() {
return connection.getHeaderFields();
}
@Override
public byte[] getErrorResponse() throws IOException {
return IOUtils.toByteArray(connection.getErrorStream());
}
@Override
public int getResponseCode() throws IOException {
return connection.getResponseCode();
}
@Override
public String getMessage() throws IOException {
return connection.getResponseMessage();
}
});
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<String, List<String>> headers() {
return connection.getHeaderFields();
}
@Override
public byte[] getErrorResponse() throws IOException {
return IOUtils.toByteArray(connection.getErrorStream());
}
@Override
public int getResponseCode() throws IOException {
return connection.getResponseCode();
}
@Override
public String getMessage() throws IOException {
return appendErrorMessage(connection.getResponseMessage(), new String(getErrorResponse()));
}
};
}
}

View File

@ -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<HttpServletRequest,HttpServletResponse>() {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.getWriter().write("Jetty");

View File

@ -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<HttpServletRequest,HttpServletResponse>() {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.getWriter().write("Jetty is using Dubbo's logger");

View File

@ -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<HttpServletRequest, HttpServletResponse>() {
@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<RestResult> 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<HttpServletRequest, HttpServletResponse>() {
@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<RestResult> 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<String, List<String>> 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<String> 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]));
}
}

View File

@ -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<HttpServletRequest,HttpServletResponse>() {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.getWriter().write("Tomcat");

View File

@ -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());
}
}

View File

@ -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;
}

View File

@ -125,12 +125,6 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http</artifactId>

View File

@ -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<String, Object> 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<String, Object> 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);
}

View File

@ -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";
}

View File

@ -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<String> getInitParameterNames() {
return new Enumeration<String>() {
@Override
public boolean hasMoreElements() {
return false;
}
@Override
public String nextElement() {
return null;
}
};
}
}
}

View File

@ -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() {
}
}

View File

@ -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<String, Object> 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<String, Object> 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<ChannelHandler> 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<ChannelOption, Object> getChildChannelOptionMap(URL url) {
Map<ChannelOption, Object> 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<ChannelOption, Object> getChannelOptionMap(URL url) {
Map<ChannelOption, Object> 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<ChannelHandler> getChannelHandlers(URL url) {
List<ChannelHandler> channelHandlers = new ArrayList<>();
return channelHandlers;
}
@Override
public void deploy(ServiceRestMetadata serviceRestMetadata, Invoker invoker) {
Map<PathMatcher, RestMethodMetadata> pathToServiceMapContainPathVariable =
serviceRestMetadata.getPathContainPathVariableToServiceMap();
pathAndInvokerMapper.addPathAndInvoker(pathToServiceMapContainPathVariable, invoker);
Map<PathMatcher, RestMethodMetadata> pathToServiceMapUnContainPathVariable =
serviceRestMetadata.getPathUnContainPathVariableToServiceMap();
pathAndInvokerMapper.addPathAndInvoker(pathToServiceMapUnContainPathVariable, invoker);
}
@Override
public void undeploy(ServiceRestMetadata serviceRestMetadata) {
Map<PathMatcher, RestMethodMetadata> pathToServiceMapContainPathVariable =
serviceRestMetadata.getPathContainPathVariableToServiceMap();
pathToServiceMapContainPathVariable.keySet().stream().forEach(pathAndInvokerMapper::removePath);
Map<PathMatcher, RestMethodMetadata> 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);
}
}
}
}

View File

@ -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, Object> 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();
}
}

View File

@ -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<PathMatcher, InvokerAndRestMethodMetadataPair> pathToServiceMapContainPathVariable = new ConcurrentHashMap<>();
private final Map<PathMatcher, InvokerAndRestMethodMetadataPair> pathToServiceMapNoPathVariable = new ConcurrentHashMap<>();
/**
* deploy path metadata
*
* @param metadataMap
* @param invoker
*/
public void addPathAndInvoker(Map<PathMatcher, RestMethodMetadata> 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<PathMatcher, InvokerAndRestMethodMetadataPair> 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());
}
}

View File

@ -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;
}
}

View File

@ -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<T> extends AbstractInvoker<T> {
private final ServiceRestMetadata serviceRestMetadata;
private final ReferenceCountedClient<? extends RestClient> referenceCountedClient;
private final Set<HttpConnectionPreBuildIntercept> httpConnectionPreBuildIntercepts;
public RestInvoker(Class type, URL url,
ReferenceCountedClient<? extends RestClient> referenceCountedClient,
Set<HttpConnectionPreBuildIntercept> httpConnectionPreBuildIntercepts,
ServiceRestMetadata serviceRestMetadata) {
super(type, url);
this.serviceRestMetadata = serviceRestMetadata;
this.referenceCountedClient = referenceCountedClient;
this.httpConnectionPreBuildIntercepts = httpConnectionPreBuildIntercepts;
}
@Override
protected Result doInvoke(Invocation invocation) {
try {
Map<String, Map<ParameterTypesComparator, RestMethodMetadata>> 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<RestResult> future = referenceCountedClient.getClient().send(requestTemplate);
CompletableFuture<AppResponse> responseFuture = new CompletableFuture<>();
AsyncRpcResult asyncRpcResult = new AsyncRpcResult(responseFuture, invocation);
future.whenComplete((r, t) -> {
if (t != null) {
responseFuture.completeExceptionally(t);
} else {
AppResponse appResponse = new AppResponse();
try {
int responseCode = r.getResponseCode();
MediaType mediaType = MediaType.TEXT_PLAIN;
if (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;
}
}

View File

@ -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<HttpConnectionPreBuildIntercept> httpConnectionPreBuildIntercepts;
public RestProtocol(FrameworkModel frameworkModel) {
super(WebApplicationException.class, ProcessingException.class);
this.clientFactory = frameworkModel.getExtensionLoader(RestClientFactory.class).getAdaptiveExtension();
this.httpConnectionPreBuildIntercepts = frameworkModel.getExtensionLoader(HttpConnectionPreBuildIntercept.class).getSupportedExtensionInstances();
}
@ -93,48 +68,49 @@ public class RestProtocol extends AbstractProxyProtocol {
return DEFAULT_PORT;
}
@Override
protected <T> Runnable doExport(T impl, Class<T> 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 <T> Exporter<T> export(final Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();
final String uri = serviceKey(url);
Exporter<T> exporter = (Exporter<T>) 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<T>(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<String, Map<ParameterTypesComparator, RestMethodMetadata>> metadataMap = MetadataResolver.resolveConsumerServiceMetadata(type, url);
ServiceRestMetadata serviceRestMetadata =
MetadataResolver.resolveConsumerServiceMetadata(type, url, contextPathFromUrl);
ReferenceCountedClient<? extends RestClient> finalRefClient = refClient;
Invoker<T> invoker = new AbstractInvoker<T>(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<T> invoker = new RestInvoker<T>(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<RestResult> future = finalRefClient.getClient().send(requestTemplate);
CompletableFuture<AppResponse> responseFuture = new CompletableFuture<>();
AsyncRpcResult asyncRpcResult = new AsyncRpcResult(responseFuture, invocation);
future.whenComplete((r, t) -> {
if (t != null) {
responseFuture.completeExceptionally(t);
} else {
AppResponse appResponse = new AppResponse();
try {
int responseCode = r.getResponseCode();
MediaType mediaType = MediaType.TEXT_PLAIN;
if (400 < responseCode && responseCode < 500) {
throw new HttpClientException(r.getMessage());
} else if (responseCode >= 500) {
throw new RemoteServerInternalException(r.getMessage());
} else if (responseCode < 400) {
mediaType = MediaTypeUtil.convertMediaType(r.getContentType());
}
Object value = HttpMessageCodecManager.httpMessageDecode(r.getBody(),
restMethodMetadata.getReflectMethod().getReturnType(), mediaType);
appResponse.setValue(value);
Map<String, String> headers = r.headers()
.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
appResponse.setAttachments(headers);
responseFuture.complete(appResponse);
} catch (Exception e) {
responseFuture.completeExceptionally(e);
}
}
});
return asyncRpcResult;
} catch (RpcException e) {
if (e.getCode() == RpcException.UNKNOWN_EXCEPTION) {
e.setCode(getErrorCode(e.getCause()));
}
throw e;
}
}
@Override
public void destroy() {
super.destroy();
invokers.remove(this);
destroyInternal(url);
}
};
invokers.add(invoker);
return invoker;
}
private ReferenceCountedClient<? extends RestClient> createReferenceCountedClient(URL url, ConcurrentMap<String, ReferenceCountedClient<? extends RestClient>> clients) throws RpcException {
/**
* create rest ReferenceCountedClient
*
* @param url
* @return
* @throws RpcException
*/
private ReferenceCountedClient<? extends RestClient> 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()) {

View File

@ -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);
}

View File

@ -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<ArgInfo> 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);
}
}

View File

@ -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();
}
}

View File

@ -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<String, Object> 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<String, Object> 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;
}
}

View File

@ -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<RpcException> {
public class RpcExceptionMapper implements ExceptionHandler<RpcException> {
@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<RpcException> {
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();
}
}

View File

@ -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<BaseConsumerParamParser> consumerParamParsers =
FrameworkModel.defaultModel().getExtensionLoader(BaseConsumerParamParser.class).getSupportedExtensionInstances();
private static final Set<BaseProviderParamParser> providerParamParsers =
FrameworkModel.defaultModel().getExtensionLoader(BaseProviderParamParser.class).getSupportedExtensionInstances();
/**
* provider Design Description:
* <p>
@ -41,11 +46,35 @@ public class ParamParserManager {
* <p>
* args=toArray(new Object[0]);
*/
public void consumerParamParse(ConsumerParseContext parseContext) {
public static Object[] providerParamParse(ProviderParseContext parseContext) {
List<Object> args = parseContext.getArgs();
List<ArgInfo> 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:
* <p>
* Object[] args=new Object[0];
* List<Object> argsList=new ArrayList<>;</>
* <p>
* setValueByIndex(int index,Object value);
* <p>
* args=toArray(new Object[0]);
*/
public static void consumerParamParse(ConsumerParseContext parseContext) {
List<ArgInfo> argInfos = parseContext.getArgInfos();
for (int i = 0; i < argInfos.size(); i++) {
for (BaseConsumerParamParser paramParser : consumerParamParsers) {
ArgInfo argInfoByIndex = parseContext.getArgInfoByIndex(i);

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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());
}

View File

@ -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<String, Object> 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;
}
}

View File

@ -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);
}
}

View File

@ -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 {

View File

@ -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<String> consumes = restMethodMetadata.getRequest().getConsumes();
requestTemplate.addHeaders(RestConstant.CONTENT_TYPE, consumes);
requestTemplate.addHeaders(RestHeaderEnum.CONTENT_TYPE.getHeader(), consumes);
Collection<String> headers = requestTemplate.getHeaders(RestConstant.ACCEPT);
if (headers == null || headers.isEmpty()) {
requestTemplate.addHeader(RestConstant.ACCEPT, RestConstant.DEFAULT_ACCEPT);
Collection<String> 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));
}

View File

@ -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<String> 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);
}

View File

@ -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<String, Map<ParameterTypesComparator, RestMethodMetadata>> resolveConsumerServiceMetadata(Class<?> targetClass, URL url) {
public static ServiceRestMetadata resolveConsumerServiceMetadata(Class<?> targetClass, URL url, String contextPathFromUrl) {
ExtensionLoader<ServiceRestMetadataResolver> 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<ServiceRestMetadataResolver> 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");
}
}

View File

@ -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());
}

View File

@ -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<String, List<String>> 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<String> 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);
}
}

View File

@ -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<String,String>
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

View File

@ -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<Object> 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

View File

@ -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<ProviderParseContext> {
}

View File

@ -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;
}
}

View File

@ -0,0 +1,65 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.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<String,String> convert
RequestFacade request = parseContext.getRequestFacade();
if (Map.class.isAssignableFrom(argInfo.getParamType())) {
Map<String, String> headerMap = new LinkedHashMap<>();
Enumeration<String> 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;
}
}

View File

@ -0,0 +1,65 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.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<String,String> convert
RequestFacade request = parseContext.getRequestFacade();
if (Map.class.isAssignableFrom(argInfo.getParamType())) {
Map<String, String> paramMap = new LinkedHashMap<>();
Enumeration<String> 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;
}
}

View File

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

View File

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

View File

@ -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];
}
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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) {

View File

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

View File

@ -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<Class<?>, 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<Method> methods = ReflectUtils.getMethodByNameList(exceptionHandler, "result");
Set<Class<?>> 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<Class<?>> classes = new ArrayList<>(exceptions);
// if size==1 so ,exception handler for Throwable
if (classes.size() != 1) {
// else remove throwable
exceptions.remove(Throwable.class);
}
List<Constructor<?>> 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);
}
}

View File

@ -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<NettyRequestFacade, NettyHttpResponse> {
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();
}
}

View File

@ -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 <InputStream>
* @param <OutputStream>
*/
@SPI(scope = ExtensionScope.FRAMEWORK)
public interface HttpMessageCodec<InputStream, OutputStream> extends HttpMessageDecode<InputStream>, HttpMessageEncode<OutputStream> {
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();
}

View File

@ -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;
}
}

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