Extend rpc http service (#37)

* http 测试

* 测试异常 Not found method $invoke 解决

* 修改pom文件

* fix cli build error

* delete log4j properties
diff --git a/dubbo-rpc-extensions/dubbo-rpc-http/pom.xml b/dubbo-rpc-extensions/dubbo-rpc-http/pom.xml
new file mode 100644
index 0000000..fecae4f
--- /dev/null
+++ b/dubbo-rpc-extensions/dubbo-rpc-http/pom.xml
@@ -0,0 +1,62 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>dubbo-rpc-extensions</artifactId>
+        <groupId>org.apache.dubbo.extensions</groupId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>dubbo-rpc-http</artifactId>
+
+    <description>The JSON-RPC module of dubbo project</description>
+
+    <properties>
+        <skip_maven_deploy>false</skip_maven_deploy>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-rpc-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-remoting-http</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-cluster</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.briandilley.jsonrpc4j</groupId>
+            <artifactId>jsonrpc4j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.portlet</groupId>
+            <artifactId>portlet-api</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/dubbo-rpc-extensions/dubbo-rpc-http/src/main/java/org/apache/dubbo/rpc/protocol/http/HttpProtocol.java b/dubbo-rpc-extensions/dubbo-rpc-http/src/main/java/org/apache/dubbo/rpc/protocol/http/HttpProtocol.java
new file mode 100644
index 0000000..22d4434
--- /dev/null
+++ b/dubbo-rpc-extensions/dubbo-rpc-http/src/main/java/org/apache/dubbo/rpc/protocol/http/HttpProtocol.java
@@ -0,0 +1,195 @@
+/*
+ * 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.http;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.remoting.RemotingServer;
+import org.apache.dubbo.remoting.http.HttpBinder;
+import org.apache.dubbo.remoting.http.HttpHandler;
+import org.apache.dubbo.rpc.ProtocolServer;
+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.apache.dubbo.rpc.service.GenericService;
+import org.apache.dubbo.rpc.support.ProtocolUtils;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.remoting.RemoteAccessException;
+import org.springframework.remoting.support.RemoteInvocation;
+
+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;
+
+import static org.apache.dubbo.rpc.Constants.GENERIC_KEY;
+
+public class HttpProtocol 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<String, JsonRpcServer> skeletonMap = new ConcurrentHashMap<>();
+
+    private HttpBinder httpBinder;
+
+    public HttpProtocol() {
+        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 <T> Runnable doExport(final T impl, Class<T> type, URL url) throws RpcException {
+        String addr = getAddr(url);
+        ProtocolServer protocolServer = serverMap.get(addr);
+        if (protocolServer == null) {
+            RemotingServer remotingServer = httpBinder.bind(url, new InternalHandler(url.getParameter("cors", false)));
+            serverMap.put(addr, new ProxyProtocolServer(remotingServer));
+        }
+        final String path = url.getAbsolutePath();
+        final String genericPath = path + "/" + GENERIC_KEY;
+        ObjectMapper mapper = new ObjectMapper();
+        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+        JsonRpcServer skeleton = new JsonRpcServer(mapper, impl, type);
+        JsonRpcServer genericServer = new JsonRpcServer(mapper, impl, GenericService.class);
+        skeletonMap.put(path, skeleton);
+        skeletonMap.put(genericPath, genericServer);
+        return () -> {
+            skeletonMap.remove(path);
+            skeletonMap.remove(genericPath);
+        };
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    protected <T> T doRefer(final Class<T> serviceType, URL url) throws RpcException {
+        final String generic = url.getParameter(GENERIC_KEY);
+        final boolean isGeneric = ProtocolUtils.isGeneric(generic) || serviceType.equals(GenericService.class);
+        JsonProxyFactoryBean jsonProxyFactoryBean = new JsonProxyFactoryBean();
+        JsonRpcProxyFactoryBean jsonRpcProxyFactoryBean = new JsonRpcProxyFactoryBean(jsonProxyFactoryBean);
+        jsonRpcProxyFactoryBean.setRemoteInvocationFactory((methodInvocation) -> {
+            RemoteInvocation invocation = new JsonRemoteInvocation(methodInvocation);
+            if (isGeneric) {
+                invocation.addAttribute(GENERIC_KEY, generic);
+            }
+            return invocation;
+        });
+        String key = url.setProtocol("http").toIdentityString();
+        if (isGeneric) {
+            key = key + "/" + GENERIC_KEY;
+        }
+
+        jsonRpcProxyFactoryBean.setServiceUrl(key);
+        jsonRpcProxyFactoryBean.setServiceInterface(serviceType);
+
+        jsonProxyFactoryBean.afterPropertiesSet();
+        return (T) jsonProxyFactoryBean.getObject();
+    }
+
+    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;
+            }
+
+            if (e instanceof HttpProtocolErrorCode) {
+                return ((HttpProtocolErrorCode) e).getErrorCode();
+            }
+        }
+        return super.getErrorCode(e);
+    }
+
+    @Override
+    public void destroy() {
+        super.destroy();
+        for (String key : new ArrayList<>(serverMap.keySet())) {
+            ProtocolServer 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);
+                }
+            }
+        }
+    }
+
+
+}
diff --git a/dubbo-rpc-extensions/dubbo-rpc-http/src/main/java/org/apache/dubbo/rpc/protocol/http/HttpProtocolErrorCode.java b/dubbo-rpc-extensions/dubbo-rpc-http/src/main/java/org/apache/dubbo/rpc/protocol/http/HttpProtocolErrorCode.java
new file mode 100644
index 0000000..75b4103
--- /dev/null
+++ b/dubbo-rpc-extensions/dubbo-rpc-http/src/main/java/org/apache/dubbo/rpc/protocol/http/HttpProtocolErrorCode.java
@@ -0,0 +1,29 @@
+/*
+ * 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.http;
+
+/**
+ * Custom exception error code for http protocol.
+ */
+public interface HttpProtocolErrorCode {
+
+    /**
+     * @return custom error code of exception
+     */
+    int getErrorCode();
+
+}
diff --git a/dubbo-rpc-extensions/dubbo-rpc-http/src/main/java/org/apache/dubbo/rpc/protocol/http/JsonRemoteInvocation.java b/dubbo-rpc-extensions/dubbo-rpc-http/src/main/java/org/apache/dubbo/rpc/protocol/http/JsonRemoteInvocation.java
new file mode 100644
index 0000000..b5437f6
--- /dev/null
+++ b/dubbo-rpc-extensions/dubbo-rpc-http/src/main/java/org/apache/dubbo/rpc/protocol/http/JsonRemoteInvocation.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dubbo.rpc.protocol.http;
+
+import org.apache.dubbo.common.utils.StringUtils;
+import org.apache.dubbo.rpc.RpcContext;
+
+import org.aopalliance.intercept.MethodInvocation;
+import org.springframework.remoting.support.RemoteInvocation;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.apache.dubbo.rpc.Constants.GENERIC_KEY;
+
+/**
+ * JsonRemoteInvocation
+ */
+public class JsonRemoteInvocation extends RemoteInvocation {
+    private static final long serialVersionUID = 1L;
+    private static final String DUBBO_ATTACHMENTS_ATTR_NAME = "dubbo.attachments";
+
+    public JsonRemoteInvocation(MethodInvocation methodInvocation) {
+        super(methodInvocation);
+        addAttribute(DUBBO_ATTACHMENTS_ATTR_NAME, new HashMap<>(RpcContext.getContext().getAttachments()));
+    }
+
+    @Override
+    public Object invoke(Object targetObject) throws NoSuchMethodException, IllegalAccessException,
+            InvocationTargetException {
+        RpcContext context = RpcContext.getContext();
+        context.setAttachments((Map<String, String>) getAttribute(DUBBO_ATTACHMENTS_ATTR_NAME));
+
+        String generic = (String) getAttribute(GENERIC_KEY);
+        if (StringUtils.isNotEmpty(generic)) {
+            context.setAttachment(GENERIC_KEY, generic);
+        }
+        try {
+            return super.invoke(targetObject);
+        } finally {
+            context.setAttachments(null);
+
+        }
+    }
+}
diff --git a/dubbo-rpc-extensions/dubbo-rpc-http/src/main/java/org/apache/dubbo/rpc/protocol/http/JsonRpcProxyFactoryBean.java b/dubbo-rpc-extensions/dubbo-rpc-http/src/main/java/org/apache/dubbo/rpc/protocol/http/JsonRpcProxyFactoryBean.java
new file mode 100644
index 0000000..aff81d2
--- /dev/null
+++ b/dubbo-rpc-extensions/dubbo-rpc-http/src/main/java/org/apache/dubbo/rpc/protocol/http/JsonRpcProxyFactoryBean.java
@@ -0,0 +1,86 @@
+/*
+ * 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.http;
+
+import com.googlecode.jsonrpc4j.spring.JsonProxyFactoryBean;
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.remoting.support.RemoteInvocationBasedAccessor;
+
+/**
+ * JsonRpcProxyFactoryBean
+ */
+public class JsonRpcProxyFactoryBean extends RemoteInvocationBasedAccessor
+        implements MethodInterceptor,
+        InitializingBean,
+        FactoryBean<Object>,
+        ApplicationContextAware {
+    private final JsonProxyFactoryBean jsonProxyFactoryBean;
+
+    public JsonRpcProxyFactoryBean(JsonProxyFactoryBean factoryBean) {
+        this.jsonProxyFactoryBean = factoryBean;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void afterPropertiesSet() {
+        jsonProxyFactoryBean.afterPropertiesSet();
+    }
+
+    @Override
+    public Object invoke(MethodInvocation invocation)
+            throws Throwable {
+
+        return jsonProxyFactoryBean.invoke(invocation);
+    }
+
+    @Override
+    public Object getObject() {
+        return jsonProxyFactoryBean.getObject();
+    }
+
+    @Override
+    public Class<?> getObjectType() {
+        return jsonProxyFactoryBean.getObjectType();
+    }
+
+    @Override
+    public boolean isSingleton() {
+        return jsonProxyFactoryBean.isSingleton();
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) {
+        jsonProxyFactoryBean.setApplicationContext(applicationContext);
+    }
+
+    @Override
+    public void setServiceUrl(String serviceUrl) {
+        jsonProxyFactoryBean.setServiceUrl(serviceUrl);
+    }
+
+    @Override
+    public void setServiceInterface(Class<?> serviceInterface) {
+        jsonProxyFactoryBean.setServiceInterface(serviceInterface);
+    }
+
+}
diff --git a/dubbo-rpc-extensions/dubbo-rpc-http/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol b/dubbo-rpc-extensions/dubbo-rpc-http/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol
new file mode 100644
index 0000000..46f47ba
--- /dev/null
+++ b/dubbo-rpc-extensions/dubbo-rpc-http/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol
@@ -0,0 +1 @@
+http=org.apache.dubbo.rpc.protocol.http.HttpProtocol
\ No newline at end of file
diff --git a/dubbo-rpc-extensions/dubbo-rpc-http/src/test/java/org/apache/dubbo/rpc/protocol/http/HttpProtocolTest.java b/dubbo-rpc-extensions/dubbo-rpc-http/src/test/java/org/apache/dubbo/rpc/protocol/http/HttpProtocolTest.java
new file mode 100644
index 0000000..8b23d1e
--- /dev/null
+++ b/dubbo-rpc-extensions/dubbo-rpc-http/src/test/java/org/apache/dubbo/rpc/protocol/http/HttpProtocolTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.http;
+
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.extension.ExtensionLoader;
+import org.apache.dubbo.common.utils.NetUtils;
+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.apache.dubbo.rpc.service.GenericService;
+import org.junit.jupiter.api.Assertions;
+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 HttpProtocolTest {
+
+    @Test
+    public void testJsonrpcProtocol() {
+        HttpServiceImpl server = new HttpServiceImpl();
+        assertFalse(server.isCalled());
+        ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
+        Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
+        int port = NetUtils.getAvailablePort();
+        URL url = URL.valueOf("http://127.0.0.1:" + port + "/" + HttpService.class.getName() + "?version=1.0.0");
+        Exporter<HttpService> exporter = protocol.export(proxyFactory.getInvoker(server, HttpService.class, url));
+        Invoker<HttpService> invoker = protocol.refer(HttpService.class, url);
+        HttpService client = proxyFactory.getProxy(invoker);
+        String result = client.sayHello("haha");
+        assertTrue(server.isCalled());
+        assertEquals("Hello, haha", result);
+        invoker.destroy();
+        exporter.unexport();
+    }
+
+    @Test
+    public void testJsonrpcProtocolForServerJetty() {
+        HttpServiceImpl server = new HttpServiceImpl();
+        assertFalse(server.isCalled());
+        ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
+        Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
+        int port = NetUtils.getAvailablePort();
+        URL url = URL.valueOf("http://127.0.0.1:" + port + "/" + HttpService.class.getName() + "?version=1.0.0&server=jetty");
+        Exporter<HttpService> exporter = protocol.export(proxyFactory.getInvoker(server, HttpService.class, url));
+        Invoker<HttpService> invoker = protocol.refer(HttpService.class, url);
+        HttpService client = proxyFactory.getProxy(invoker);
+        String result = client.sayHello("haha");
+        assertTrue(server.isCalled());
+        assertEquals("Hello, haha", result);
+        invoker.destroy();
+        exporter.unexport();
+    }
+
+    @Test
+    public void testGenericInvoke() {
+        HttpServiceImpl server = new HttpServiceImpl();
+        Assertions.assertFalse(server.isCalled());
+        ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
+        Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
+        int port = NetUtils.getAvailablePort();
+        URL url = URL.valueOf("http://127.0.0.1:" + port + "/" + HttpService.class.getName() + "?release=2.7.2");
+        Exporter<HttpService> exporter = protocol.export(proxyFactory.getInvoker(server, HttpService.class, url));
+        Invoker<GenericService> invoker = protocol.refer(GenericService.class, url);
+        GenericService client = proxyFactory.getProxy(invoker, true);
+        String result = (String) client.$invoke("sayHello", new String[]{"java.lang.String"}, new Object[]{"haha"});
+        Assertions.assertTrue(server.isCalled());
+        Assertions.assertEquals("Hello, haha", result);
+        invoker.destroy();
+        exporter.unexport();
+    }
+
+}
diff --git a/dubbo-rpc-extensions/dubbo-rpc-http/src/test/java/org/apache/dubbo/rpc/protocol/http/HttpService.java b/dubbo-rpc-extensions/dubbo-rpc-http/src/test/java/org/apache/dubbo/rpc/protocol/http/HttpService.java
new file mode 100644
index 0000000..f05994e
--- /dev/null
+++ b/dubbo-rpc-extensions/dubbo-rpc-http/src/test/java/org/apache/dubbo/rpc/protocol/http/HttpService.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.http;
+
+public interface HttpService {
+    String sayHello(String name);
+
+    void timeOut(int millis);
+
+    String customException();
+
+    String getRemoteApplicationName();
+}
diff --git a/dubbo-rpc-extensions/dubbo-rpc-http/src/test/java/org/apache/dubbo/rpc/protocol/http/HttpServiceImpl.java b/dubbo-rpc-extensions/dubbo-rpc-http/src/test/java/org/apache/dubbo/rpc/protocol/http/HttpServiceImpl.java
new file mode 100644
index 0000000..44fb4f2
--- /dev/null
+++ b/dubbo-rpc-extensions/dubbo-rpc-http/src/test/java/org/apache/dubbo/rpc/protocol/http/HttpServiceImpl.java
@@ -0,0 +1,66 @@
+/*
+ * 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.http;
+
+import org.apache.dubbo.rpc.RpcContext;
+import org.apache.dubbo.rpc.service.GenericException;
+import org.apache.dubbo.rpc.service.GenericService;
+
+public class HttpServiceImpl implements HttpService {
+    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");
+    }
+
+//    @Override
+//    public Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException {
+//        called = true;
+//        return "Hello, " + args[0];
+//    }
+
+    static class MyException extends RuntimeException{
+
+        private static final long serialVersionUID = -3051041116483629056L;
+
+        public MyException(String message) {
+            super(message);
+        }
+    }
+
+    @Override
+    public String getRemoteApplicationName() {
+        return RpcContext.getContext().getRemoteApplicationName();
+    }
+}
diff --git a/dubbo-rpc-extensions/pom.xml b/dubbo-rpc-extensions/pom.xml
index 09fd505..9a75355 100644
--- a/dubbo-rpc-extensions/pom.xml
+++ b/dubbo-rpc-extensions/pom.xml
@@ -24,8 +24,12 @@
         <version>${revision}</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
+    <packaging>pom</packaging>
+
     <modelVersion>4.0.0</modelVersion>
 
     <artifactId>dubbo-rpc-extensions</artifactId>
-
+    <modules>
+        <module>dubbo-rpc-http</module>
+    </modules>
 </project>