diff --git a/dubbo-all/pom.xml b/dubbo-all/pom.xml
index e0e6e11ccc9d..585332512e00 100644
--- a/dubbo-all/pom.xml
+++ b/dubbo-all/pom.xml
@@ -143,6 +143,13 @@
compile
true
+
+ org.apache.dubbo
+ dubbo-rpc-jsonrpc
+ ${project.version}
+ compile
+ true
+
org.apache.dubbo
dubbo-rpc-rmi
@@ -491,6 +498,7 @@
org.apache.dubbo:dubbo-rpc-api
org.apache.dubbo:dubbo-rpc-dubbo
org.apache.dubbo:dubbo-rpc-injvm
+ org.apache.dubbo:dubbo-rpc-jsonrpc
org.apache.dubbo:dubbo-rpc-rmi
org.apache.dubbo:dubbo-rpc-hessian
org.apache.dubbo:dubbo-rpc-http
diff --git a/dubbo-bom/pom.xml b/dubbo-bom/pom.xml
index 85fcbae576db..b7e9f4ed460e 100644
--- a/dubbo-bom/pom.xml
+++ b/dubbo-bom/pom.xml
@@ -158,6 +158,11 @@
dubbo-rpc-injvm
${project.version}
+
+ org.apache.dubbo
+ dubbo-rpc-jsonrpc
+ ${project.version}
+
org.apache.dubbo
dubbo-rpc-rmi
diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ApplicationConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ApplicationConfig.java
index d73e3e209e15..984733f293f5 100644
--- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ApplicationConfig.java
+++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ApplicationConfig.java
@@ -124,6 +124,9 @@ public class ApplicationConfig extends AbstractConfig {
private String shutwait;
+ private Boolean preferPublicIp;
+
+
public ApplicationConfig() {
}
@@ -328,4 +331,11 @@ public boolean isValid() {
return !StringUtils.isEmpty(name);
}
+ public Boolean getPreferPublicIp() {
+ return preferPublicIp;
+ }
+
+ public void setPreferPublicIp(Boolean preferPublicIp) {
+ this.preferPublicIp = preferPublicIp;
+ }
}
\ No newline at end of file
diff --git a/dubbo-dependencies-bom/pom.xml b/dubbo-dependencies-bom/pom.xml
index d2e604dad244..33fe9ee1ecbb 100644
--- a/dubbo-dependencies-bom/pom.xml
+++ b/dubbo-dependencies-bom/pom.xml
@@ -143,6 +143,8 @@
2.0.1
2.8.5
+ 1.2.0
+ 2.0
@@ -528,12 +530,21 @@
${spring_test_version}
test
-
com.google.code.gson
gson
${gson_version}
+
+ com.github.briandilley.jsonrpc4j
+ jsonrpc4j
+ ${jsonrpc_version}
+
+
+ javax.portlet
+ portlet-api
+ ${portlet_version}
+
diff --git a/dubbo-distribution/pom.xml b/dubbo-distribution/pom.xml
index d60548d3b179..126ec47618ca 100644
--- a/dubbo-distribution/pom.xml
+++ b/dubbo-distribution/pom.xml
@@ -110,6 +110,11 @@
dubbo-rpc-injvm
${project.version}
+
+ org.apache.dubbo
+ dubbo-rpc-jsonrpc
+ ${project.version}
+
org.apache.dubbo
dubbo-rpc-rmi
diff --git a/dubbo-rpc/dubbo-rpc-jsonrpc/pom.xml b/dubbo-rpc/dubbo-rpc-jsonrpc/pom.xml
new file mode 100644
index 000000000000..4a367328fe84
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-jsonrpc/pom.xml
@@ -0,0 +1,61 @@
+
+
+
+ dubbo-rpc
+ org.apache.dubbo
+ 2.7.2-SNAPSHOT
+
+ 4.0.0
+
+ dubbo-rpc-jsonrpc
+
+ The JSON-RPC module of dubbo project
+
+
+ false
+
+
+
+
+ org.apache.dubbo
+ dubbo-rpc-api
+ ${project.parent.version}
+
+
+ org.apache.dubbo
+ dubbo-remoting-http
+ ${project.parent.version}
+
+
+ org.springframework
+ spring-context
+
+
+ com.github.briandilley.jsonrpc4j
+ jsonrpc4j
+
+
+ javax.portlet
+ portlet-api
+
+
+
+
+
\ No newline at end of file
diff --git a/dubbo-rpc/dubbo-rpc-jsonrpc/src/main/java/org/apache/dubbo/rpc/protocol/jsonrpc/JsonRpcProtocol.java b/dubbo-rpc/dubbo-rpc-jsonrpc/src/main/java/org/apache/dubbo/rpc/protocol/jsonrpc/JsonRpcProtocol.java
new file mode 100644
index 000000000000..aaba802cd4ec
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-jsonrpc/src/main/java/org/apache/dubbo/rpc/protocol/jsonrpc/JsonRpcProtocol.java
@@ -0,0 +1,163 @@
+/*
+ * 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.jsonrpc;
+
+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.rpc.RpcContext;
+import org.apache.dubbo.rpc.RpcException;
+import org.apache.dubbo.rpc.protocol.AbstractProxyProtocol;
+
+import com.googlecode.jsonrpc4j.HttpException;
+import com.googlecode.jsonrpc4j.JsonRpcClientException;
+import com.googlecode.jsonrpc4j.JsonRpcServer;
+import com.googlecode.jsonrpc4j.spring.JsonProxyFactoryBean;
+import org.springframework.remoting.RemoteAccessException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class JsonRpcProtocol extends AbstractProxyProtocol {
+
+ public static final String ACCESS_CONTROL_ALLOW_ORIGIN_HEADER = "Access-Control-Allow-Origin";
+ public static final String ACCESS_CONTROL_ALLOW_METHODS_HEADER = "Access-Control-Allow-Methods";
+ public static final String ACCESS_CONTROL_ALLOW_HEADERS_HEADER = "Access-Control-Allow-Headers";
+
+ private final Map serverMap = new ConcurrentHashMap<>();
+
+ private final Map skeletonMap = new ConcurrentHashMap<>();
+
+ private HttpBinder httpBinder;
+
+ public JsonRpcProtocol() {
+ super(HttpException.class, JsonRpcClientException.class);
+ }
+
+ public void setHttpBinder(HttpBinder httpBinder) {
+ this.httpBinder = httpBinder;
+ }
+
+ @Override
+ public int getDefaultPort() {
+ return 80;
+ }
+
+ private class InternalHandler implements HttpHandler {
+
+ private boolean cors;
+
+ public InternalHandler(boolean cors) {
+ this.cors = cors;
+ }
+
+ @Override
+ public void handle(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException {
+ String uri = request.getRequestURI();
+ JsonRpcServer skeleton = skeletonMap.get(uri);
+ if (cors) {
+ response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, "*");
+ response.setHeader(ACCESS_CONTROL_ALLOW_METHODS_HEADER, "POST");
+ response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS_HEADER, "*");
+ }
+ if (request.getMethod().equalsIgnoreCase("OPTIONS")) {
+ response.setStatus(200);
+ } else if (request.getMethod().equalsIgnoreCase("POST")) {
+
+ RpcContext.getContext().setRemoteAddress(request.getRemoteAddr(), request.getRemotePort());
+ try {
+ skeleton.handle(request.getInputStream(), response.getOutputStream());
+ } catch (Throwable e) {
+ throw new ServletException(e);
+ }
+ } else {
+ response.setStatus(500);
+ }
+ }
+
+ }
+
+ @Override
+ protected Runnable doExport(T impl, Class type, URL url) throws RpcException {
+ String addr = url.getIp() + ":" + url.getPort();
+ HttpServer server = serverMap.get(addr);
+ if (server == null) {
+ server = httpBinder.bind(url, new InternalHandler(url.getParameter("cors", false)));
+ serverMap.put(addr, server);
+ }
+ final String path = url.getAbsolutePath();
+ JsonRpcServer skeleton = new JsonRpcServer(impl, type);
+ skeletonMap.put(path, skeleton);
+ return () -> skeletonMap.remove(path);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected T doRefer(final Class serviceType, URL url) throws RpcException {
+ JsonProxyFactoryBean jsonProxyFactoryBean = new JsonProxyFactoryBean();
+ jsonProxyFactoryBean.setServiceUrl(url.setProtocol("http").toIdentityString());
+ jsonProxyFactoryBean.setServiceInterface(serviceType);
+
+ jsonProxyFactoryBean.afterPropertiesSet();
+ return (T) jsonProxyFactoryBean.getObject();
+ }
+
+ @Override
+ protected int getErrorCode(Throwable e) {
+ if (e instanceof RemoteAccessException) {
+ e = e.getCause();
+ }
+ if (e != null) {
+ Class> cls = e.getClass();
+ if (SocketTimeoutException.class.equals(cls)) {
+ return RpcException.TIMEOUT_EXCEPTION;
+ } else if (IOException.class.isAssignableFrom(cls)) {
+ return RpcException.NETWORK_EXCEPTION;
+ } else if (ClassNotFoundException.class.isAssignableFrom(cls)) {
+ return RpcException.SERIALIZATION_EXCEPTION;
+ }
+ }
+ return super.getErrorCode(e);
+ }
+
+ @Override
+ public void destroy() {
+ super.destroy();
+ for (String key : new ArrayList<>(serverMap.keySet())) {
+ HttpServer server = serverMap.remove(key);
+ if (server != null) {
+ try {
+ if (logger.isInfoEnabled()) {
+ logger.info("Close jsonrpc server " + server.getUrl());
+ }
+ server.close();
+ } catch (Throwable t) {
+ logger.warn(t.getMessage(), t);
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/dubbo-rpc/dubbo-rpc-jsonrpc/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol b/dubbo-rpc/dubbo-rpc-jsonrpc/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol
new file mode 100644
index 000000000000..0ce276fc4bdb
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-jsonrpc/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol
@@ -0,0 +1 @@
+jsonrpc=org.apache.dubbo.rpc.protocol.jsonrpc.JsonRpcProtocol
\ No newline at end of file
diff --git a/dubbo-rpc/dubbo-rpc-jsonrpc/src/test/java/org/apache/dubbo/rpc/protocol/jsonrpc/JsonRpcProtocolTest.java b/dubbo-rpc/dubbo-rpc-jsonrpc/src/test/java/org/apache/dubbo/rpc/protocol/jsonrpc/JsonRpcProtocolTest.java
new file mode 100644
index 000000000000..5f6e791e9b1a
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-jsonrpc/src/test/java/org/apache/dubbo/rpc/protocol/jsonrpc/JsonRpcProtocolTest.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.jsonrpc;
+
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.extension.ExtensionLoader;
+import org.apache.dubbo.rpc.Exporter;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.Protocol;
+import org.apache.dubbo.rpc.ProxyFactory;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class JsonRpcProtocolTest {
+
+ @Test
+ public void testJsonrpcProtocol() {
+ JsonRpcServiceImpl server = new JsonRpcServiceImpl();
+ assertFalse(server.isCalled());
+ ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
+ Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
+ URL url = URL.valueOf("jsonrpc://127.0.0.1:5342/" + JsonRpcService.class.getName() + "?version=1.0.0");
+ Exporter exporter = protocol.export(proxyFactory.getInvoker(server, JsonRpcService.class, url));
+ Invoker invoker = protocol.refer(JsonRpcService.class, url);
+ JsonRpcService client = proxyFactory.getProxy(invoker);
+ String result = client.sayHello("haha");
+ assertTrue(server.isCalled());
+ assertEquals("Hello, haha", result);
+ invoker.destroy();
+ exporter.unexport();
+ }
+
+ @Test
+ public void testJsonrpcProtocolForServerJetty9() {
+ JsonRpcServiceImpl server = new JsonRpcServiceImpl();
+ assertFalse(server.isCalled());
+ ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
+ Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
+ URL url = URL.valueOf("jsonrpc://127.0.0.1:5342/" + JsonRpcService.class.getName() + "?version=1.0.0&server=jetty9");
+ Exporter exporter = protocol.export(proxyFactory.getInvoker(server, JsonRpcService.class, url));
+ Invoker invoker = protocol.refer(JsonRpcService.class, url);
+ JsonRpcService client = proxyFactory.getProxy(invoker);
+ String result = client.sayHello("haha");
+ assertTrue(server.isCalled());
+ assertEquals("Hello, haha", result);
+ invoker.destroy();
+ exporter.unexport();
+ }
+
+}
\ No newline at end of file
diff --git a/dubbo-rpc/dubbo-rpc-jsonrpc/src/test/java/org/apache/dubbo/rpc/protocol/jsonrpc/JsonRpcService.java b/dubbo-rpc/dubbo-rpc-jsonrpc/src/test/java/org/apache/dubbo/rpc/protocol/jsonrpc/JsonRpcService.java
new file mode 100644
index 000000000000..0bcbc8d1ade1
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-jsonrpc/src/test/java/org/apache/dubbo/rpc/protocol/jsonrpc/JsonRpcService.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.jsonrpc;
+
+public interface JsonRpcService {
+ String sayHello(String name);
+
+ void timeOut(int millis);
+
+ String customException();
+}
diff --git a/dubbo-rpc/dubbo-rpc-jsonrpc/src/test/java/org/apache/dubbo/rpc/protocol/jsonrpc/JsonRpcServiceImpl.java b/dubbo-rpc/dubbo-rpc-jsonrpc/src/test/java/org/apache/dubbo/rpc/protocol/jsonrpc/JsonRpcServiceImpl.java
new file mode 100644
index 000000000000..01f32d28abcf
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-jsonrpc/src/test/java/org/apache/dubbo/rpc/protocol/jsonrpc/JsonRpcServiceImpl.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.jsonrpc;
+
+public class JsonRpcServiceImpl implements JsonRpcService {
+ private boolean called;
+
+ public String sayHello(String name) {
+ called = true;
+ return "Hello, " + name;
+ }
+
+ public boolean isCalled() {
+ return called;
+ }
+
+ public void timeOut(int millis) {
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public String customException() {
+ throw new MyException("custom exception");
+ }
+
+ static class MyException extends RuntimeException{
+
+ private static final long serialVersionUID = -3051041116483629056L;
+
+ public MyException(String message) {
+ super(message);
+ }
+ }
+}
diff --git a/dubbo-rpc/pom.xml b/dubbo-rpc/pom.xml
index dfd9be38c881..342c193fe9d4 100644
--- a/dubbo-rpc/pom.xml
+++ b/dubbo-rpc/pom.xml
@@ -32,6 +32,7 @@
dubbo-rpc-api
dubbo-rpc-dubbo
dubbo-rpc-injvm
+ dubbo-rpc-jsonrpc
dubbo-rpc-rmi
dubbo-rpc-hessian
dubbo-rpc-http
diff --git a/dubbo-test/pom.xml b/dubbo-test/pom.xml
index 0cdf2a68fab5..51e97b6963c0 100644
--- a/dubbo-test/pom.xml
+++ b/dubbo-test/pom.xml
@@ -94,6 +94,10 @@
org.apache.dubbo
dubbo-remoting-http
+
+ org.apache.dubbo
+ dubbo-rpc-jsonrpc
+
org.apache.dubbo
dubbo-rpc-dubbo