This is an automated email from the ASF dual-hosted git repository. albumenj pushed a commit to branch revert-14082-delete_remoting_http in repository https://gitbox.apache.org/repos/asf/dubbo.git
commit b287491ed29d47e6cc551867ca7ca42401ea068f Author: Albumen Kevin <jhq0...@gmail.com> AuthorDate: Wed May 8 10:48:05 2024 +0800 Revert "remove the remoting-http (#14082)" This reverts commit 583b57be2b9d4f0ccbc9be2bc6e4a190fd5e27c8. --- .artifacts | 1 + .../dubbo-demo-native-consumer/pom.xml | 5 + .../dubbo-demo-native-provider/pom.xml | 6 + dubbo-distribution/dubbo-all-shaded/pom.xml | 6 + dubbo-distribution/dubbo-all/pom.xml | 14 + dubbo-distribution/dubbo-core-spi/pom.xml | 6 + dubbo-remoting/dubbo-remoting-http/pom.xml | 85 ++++++ .../apache/dubbo/remoting/http/BaseRestClient.java | 41 +++ .../org/apache/dubbo/remoting/http/HttpBinder.java | 39 +++ .../apache/dubbo/remoting/http/HttpHandler.java | 34 +++ .../org/apache/dubbo/remoting/http/HttpServer.java | 71 +++++ .../dubbo/remoting/http/RequestTemplate.java | 299 +++++++++++++++++++++ .../org/apache/dubbo/remoting/http/RestClient.java | 48 ++++ .../org/apache/dubbo/remoting/http/RestResult.java | 42 +++ .../remoting/http/config/HttpClientConfig.java | 60 +++++ .../http/factory/AbstractHttpClientFactory.java | 63 +++++ .../remoting/http/factory/RestClientFactory.java | 35 +++ .../http/factory/impl/ApacheHttpClientFactory.java | 35 +++ .../http/factory/impl/OkHttpClientFactory.java | 35 +++ .../factory/impl/URLConnectionClientFactory.java | 35 +++ .../dubbo/remoting/http/jetty/JettyHttpBinder.java | 33 +++ .../dubbo/remoting/http/jetty/JettyHttpServer.java | 113 ++++++++ .../remoting/http/jetty/JettyLoggerAdapter.java | 161 +++++++++++ .../http/restclient/HttpClientRestClient.java | 188 +++++++++++++ .../remoting/http/restclient/OKHttpRestClient.java | 150 +++++++++++ .../http/restclient/URLConnectionRestClient.java | 170 ++++++++++++ .../remoting/http/servlet/BootstrapListener.java | 37 +++ .../remoting/http/servlet/DispatcherServlet.java | 65 +++++ .../remoting/http/servlet/ServletHttpBinder.java | 33 +++ .../remoting/http/servlet/ServletHttpServer.java | 30 +++ .../remoting/http/servlet/ServletManager.java | 50 ++++ .../remoting/http/support/AbstractHttpServer.java | 124 +++++++++ .../remoting/http/tomcat/TomcatHttpBinder.java | 30 +++ .../remoting/http/tomcat/TomcatHttpServer.java | 100 +++++++ .../org.apache.dubbo.remoting.http.HttpBinder | 3 + ...e.dubbo.remoting.http.factory.RestClientFactory | 4 + .../remoting/http/jetty/JettyHttpBinderTest.java | 57 ++++ .../http/jetty/JettyLoggerAdapterTest.java | 127 +++++++++ .../dubbo/remoting/http/rest/RestClientTest.java | 240 +++++++++++++++++ .../remoting/http/tomcat/TomcatHttpBinderTest.java | 60 +++++ .../src/test/resources/log4j2-test.xml | 29 ++ dubbo-remoting/pom.xml | 1 + dubbo-test/dubbo-dependencies-all/pom.xml | 5 + 43 files changed, 2770 insertions(+) diff --git a/.artifacts b/.artifacts index ba6f6896e5..cd6334693d 100644 --- a/.artifacts +++ b/.artifacts @@ -76,6 +76,7 @@ dubbo-registry-nacos dubbo-registry-zookeeper dubbo-remoting dubbo-remoting-api +dubbo-remoting-http dubbo-remoting-http12 dubbo-remoting-netty dubbo-remoting-netty4 diff --git a/dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer/pom.xml b/dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer/pom.xml index ab13f35f2d..1b2d730c85 100644 --- a/dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer/pom.xml +++ b/dubbo-demo/dubbo-demo-native/dubbo-demo-native-consumer/pom.xml @@ -133,6 +133,11 @@ <artifactId>dubbo-filter-validation</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>org.apache.dubbo</groupId> + <artifactId>dubbo-remoting-http</artifactId> + <version>${project.version}</version> + </dependency> <dependency> <groupId>com.alibaba</groupId> diff --git a/dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider/pom.xml b/dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider/pom.xml index 39471b6dbd..da5c091b11 100644 --- a/dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider/pom.xml +++ b/dubbo-demo/dubbo-demo-native/dubbo-demo-native-provider/pom.xml @@ -133,6 +133,12 @@ <artifactId>dubbo-filter-validation</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>org.apache.dubbo</groupId> + <artifactId>dubbo-remoting-http</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> diff --git a/dubbo-distribution/dubbo-all-shaded/pom.xml b/dubbo-distribution/dubbo-all-shaded/pom.xml index 8fa70f3390..b059115692 100644 --- a/dubbo-distribution/dubbo-all-shaded/pom.xml +++ b/dubbo-distribution/dubbo-all-shaded/pom.xml @@ -729,6 +729,9 @@ <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.consumer.BaseConsumerParamParser</resource> </transformer> + <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> + <resource>META-INF/dubbo/internal/org.apache.dubbo.remoting.http.factory.RestClientFactory</resource> + </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/dubbo/internal/org.apache.dubbo.remoting.ChannelHandler</resource> </transformer> @@ -759,6 +762,9 @@ <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/dubbo/internal/org.apache.dubbo.remoting.exchange.Exchanger</resource> </transformer> + <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> + <resource>META-INF/dubbo/internal/org.apache.dubbo.remoting.http.HttpBinder</resource> + </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory</resource> </transformer> diff --git a/dubbo-distribution/dubbo-all/pom.xml b/dubbo-distribution/dubbo-all/pom.xml index 6e60992f60..19fd7dbbfc 100644 --- a/dubbo-distribution/dubbo-all/pom.xml +++ b/dubbo-distribution/dubbo-all/pom.xml @@ -343,6 +343,13 @@ <scope>compile</scope> <optional>true</optional> </dependency> + <dependency> + <groupId>org.apache.dubbo</groupId> + <artifactId>dubbo-remoting-http</artifactId> + <version>${project.version}</version> + <scope>compile</scope> + <optional>true</optional> + </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-remoting-http12</artifactId> @@ -546,6 +553,7 @@ <include>org.apache.dubbo:dubbo-registry-nacos</include> <include>org.apache.dubbo:dubbo-registry-zookeeper</include> <include>org.apache.dubbo:dubbo-remoting-api</include> + <include>org.apache.dubbo:dubbo-remoting-http</include> <include>org.apache.dubbo:dubbo-remoting-http12</include> <include>org.apache.dubbo:dubbo-remoting-netty4</include> <include>org.apache.dubbo:dubbo-remoting-netty</include> @@ -776,6 +784,9 @@ <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.consumer.BaseConsumerParamParser</resource> </transformer> + <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> + <resource>META-INF/dubbo/internal/org.apache.dubbo.remoting.http.factory.RestClientFactory</resource> + </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/dubbo/internal/org.apache.dubbo.remoting.ChannelHandler</resource> </transformer> @@ -806,6 +817,9 @@ <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/dubbo/internal/org.apache.dubbo.remoting.exchange.Exchanger</resource> </transformer> + <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> + <resource>META-INF/dubbo/internal/org.apache.dubbo.remoting.http.HttpBinder</resource> + </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory</resource> </transformer> diff --git a/dubbo-distribution/dubbo-core-spi/pom.xml b/dubbo-distribution/dubbo-core-spi/pom.xml index 9ffea1d76c..6ef9c5d928 100644 --- a/dubbo-distribution/dubbo-core-spi/pom.xml +++ b/dubbo-distribution/dubbo-core-spi/pom.xml @@ -345,6 +345,9 @@ <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.annotation.param.parse.consumer.BaseConsumerParamParser</resource> </transformer> + <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> + <resource>META-INF/dubbo/internal/org.apache.dubbo.remoting.http.factory.RestClientFactory</resource> + </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/dubbo/internal/org.apache.dubbo.remoting.ChannelHandler</resource> </transformer> @@ -375,6 +378,9 @@ <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/dubbo/internal/org.apache.dubbo.remoting.exchange.Exchanger</resource> </transformer> + <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> + <resource>META-INF/dubbo/internal/org.apache.dubbo.remoting.http.HttpBinder</resource> + </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory</resource> </transformer> diff --git a/dubbo-remoting/dubbo-remoting-http/pom.xml b/dubbo-remoting/dubbo-remoting-http/pom.xml new file mode 100644 index 0000000000..91834389bb --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/pom.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.dubbo</groupId> + <artifactId>dubbo-remoting</artifactId> + <version>${revision}</version> + <relativePath>../pom.xml</relativePath> + </parent> + <artifactId>dubbo-remoting-http</artifactId> + <packaging>jar</packaging> + <name>${project.artifactId}</name> + <description>The http remoting module of dubbo project</description> + <properties> + <skip_maven_deploy>false</skip_maven_deploy> + </properties> + <dependencies> + <dependency> + <groupId>org.apache.dubbo</groupId> + <artifactId>dubbo-common</artifactId> + <version>${project.parent.version}</version> + </dependency> + <dependency> + <groupId>org.apache.dubbo</groupId> + <artifactId>dubbo-remoting-api</artifactId> + <version>${project.parent.version}</version> + </dependency> + <dependency> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-server</artifactId> + </dependency> + <dependency> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-servlet</artifactId> + </dependency> + <dependency> + <groupId>org.apache.tomcat.embed</groupId> + <artifactId>tomcat-embed-core</artifactId> + </dependency> + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>fluent-hc</artifactId> + <version>4.5.14</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-slf4j-impl</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>com.squareup.okhttp3</groupId> + <artifactId>okhttp</artifactId> + </dependency> + + <dependency> + <groupId>org.apache.dubbo</groupId> + <artifactId>dubbo-rpc-api</artifactId> + <version>${project.parent.version}</version> + </dependency> + + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpclient</artifactId> + </dependency> + + </dependencies> +</project> diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/BaseRestClient.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/BaseRestClient.java new file mode 100644 index 0000000000..23c78419ad --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/BaseRestClient.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http; + +import org.apache.dubbo.remoting.http.config.HttpClientConfig; + +public abstract class BaseRestClient<CLIENT> implements RestClient { + + protected CLIENT client; + + protected HttpClientConfig clientConfig; + + public BaseRestClient(HttpClientConfig clientConfig) { + this.clientConfig = clientConfig; + client = createHttpClient(clientConfig); + } + + protected abstract CLIENT createHttpClient(HttpClientConfig clientConfig); + + public HttpClientConfig getClientConfig() { + return clientConfig; + } + + public CLIENT getClient() { + return client; + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/HttpBinder.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/HttpBinder.java new file mode 100644 index 0000000000..26db36d92c --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/HttpBinder.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Adaptive; +import org.apache.dubbo.common.extension.ExtensionScope; +import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.remoting.Constants; + +/** + * HttpBinder + */ +@SPI(value = "jetty", scope = ExtensionScope.FRAMEWORK) +public interface HttpBinder { + + /** + * bind the server. + * + * @param url server url. + * @return server. + */ + @Adaptive({Constants.SERVER_KEY}) + HttpServer bind(URL url, HttpHandler handler); +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/HttpHandler.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/HttpHandler.java new file mode 100644 index 0000000000..27085f67bc --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/HttpHandler.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http; + +import java.io.IOException; + +/** + * http invocation handler. + */ +public interface HttpHandler<REQUEST, RESPONSE> { + + /** + * invoke. + * + * @param request request. + * @param response response. + * @throws IOException + */ + void handle(REQUEST request, RESPONSE response) throws IOException; +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/HttpServer.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/HttpServer.java new file mode 100644 index 0000000000..d85582eb76 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/HttpServer.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http; + +import org.apache.dubbo.common.Resetable; +import org.apache.dubbo.common.URL; +import org.apache.dubbo.remoting.RemotingServer; + +import java.net.InetSocketAddress; + +public interface HttpServer extends Resetable, RemotingServer { + + /** + * get http handler. + * + * @return http handler. + */ + HttpHandler getHttpHandler(); + + /** + * get url. + * + * @return url + */ + URL getUrl(); + + /** + * get local address. + * + * @return local address. + */ + InetSocketAddress getLocalAddress(); + + /** + * close the channel. + */ + void close(); + + /** + * Graceful close the channel. + */ + void close(int timeout); + + /** + * is bound. + * + * @return bound + */ + boolean isBound(); + + /** + * is closed. + * + * @return closed + */ + boolean isClosed(); +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/RequestTemplate.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/RequestTemplate.java new file mode 100644 index 0000000000..5536e6c83e --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/RequestTemplate.java @@ -0,0 +1,299 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http; + +import org.apache.dubbo.remoting.Constants; +import org.apache.dubbo.rpc.Invocation; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class RequestTemplate implements Serializable { + private static final long serialVersionUID = 1L; + public static final String CONTENT_ENCODING = "Content-Encoding"; + private static final String CONTENT_LENGTH = "Content-Length"; + public static final String ENCODING_GZIP = "gzip"; + public static final String ENCODING_DEFLATE = "deflate"; + private static final List<String> EMPTY_ARRAYLIST = new ArrayList<>(); + + private final Map<String, Collection<String>> queries = new LinkedHashMap<>(); + private final Map<String, Collection<String>> headers = new LinkedHashMap<>(); + private String httpMethod; + private String path; + private String address; + private Object body; + private byte[] byteBody = new byte[0]; + private String protocol = "http://"; + private final Invocation invocation; + private String contextPath = ""; + private Class<?> bodyType; + + public RequestTemplate(Invocation invocation, String httpMethod, String address) { + this(invocation, httpMethod, address, ""); + } + + public RequestTemplate(Invocation invocation, String httpMethod, String address, String contextPath) { + this.httpMethod = httpMethod; + this.address = address; + this.invocation = invocation; + this.contextPath = contextPath; + } + + public String getURL() { + StringBuilder stringBuilder = new StringBuilder(getProtocol() + address); + + stringBuilder.append(getUri()); + return stringBuilder.toString(); + } + + public String getUri() { + StringBuilder stringBuilder = new StringBuilder(getContextPath() + path); + return stringBuilder.append(getQueryString()).toString(); + } + + public String getQueryString() { + + if (queries.isEmpty()) { + return ""; + } + + StringBuilder queryBuilder = new StringBuilder("?"); + for (String field : queries.keySet()) { + + Collection<String> queryValues = queries.get(field); + + if (queryValues == null || queryValues.isEmpty()) { + continue; + } + + for (String value : queryValues) { + queryBuilder.append('&'); + queryBuilder.append(field); + if (value == null) { + continue; + } + + queryBuilder.append('='); + queryBuilder.append(value); + } + } + + return queryBuilder.toString().replace("?&", "?"); + } + + public RequestTemplate path(String path) { + this.path = path; + return this; + } + + public String getHttpMethod() { + return httpMethod; + } + + public RequestTemplate httpMethod(String httpMethod) { + this.httpMethod = httpMethod; + return this; + } + + public byte[] getSerializedBody() { + return byteBody; + } + + public void serializeBody(byte[] body) { + addHeader(CONTENT_LENGTH, body.length); // must header + this.byteBody = body; + } + + public boolean isBodyEmpty() { + return getUnSerializedBody() == null; + } + + public RequestTemplate body(Object body, Class bodyType) { + this.body = body; + setBodyType(bodyType); + return this; + } + + public Object getUnSerializedBody() { + return body; + } + + public Map<String, Collection<String>> getAllHeaders() { + return headers; + } + + public Collection<String> getHeaders(String name) { + return headers.get(name); + } + + public String getHeader(String name) { + if (headers.containsKey(name)) { + + Collection<String> headers = getHeaders(name); + + if (headers.isEmpty()) { + return null; + } + String[] strings = headers.toArray(new String[0]); + return strings[0]; + + } else { + return null; + } + } + + public Collection<String> getEncodingValues() { + if (headers.containsKey(CONTENT_ENCODING)) { + return headers.get(CONTENT_ENCODING); + } + return EMPTY_ARRAYLIST; + } + + public boolean isGzipEncodedRequest() { + return getEncodingValues().contains(ENCODING_GZIP); + } + + public boolean isDeflateEncodedRequest() { + return getEncodingValues().contains(ENCODING_DEFLATE); + } + + public void addHeader(String key, String value) { + addValueByKey(key, value, this.headers); + } + + public void addHeader(String key, Object value) { + addValueByKey(key, String.valueOf(value), this.headers); + } + + public void addKeepAliveHeader(int time) { + addHeader(Constants.KEEP_ALIVE_HEADER, time); + addHeader(Constants.CONNECTION, Constants.KEEP_ALIVE); + } + + public void addHeaders(String key, Collection<String> values) { + Collection<String> header = getHeaders(key); + + if (header == null) { + header = new HashSet<>(); + this.headers.put(key, header); + } + header.addAll(values); + } + + public void addParam(String key, String value) { + addValueByKey(key, value, this.queries); + } + + public void addParam(String key, Object value) { + addParam(key, String.valueOf(value)); + } + + public Map<String, Collection<String>> getQueries() { + return queries; + } + + public Collection<String> getParam(String key) { + return getQueries().get(key); + } + + public void addParams(String key, Collection<String> values) { + Collection<String> params = getParam(key); + + if (params == null) { + params = new HashSet<>(); + this.queries.put(key, params); + } + params.addAll(values); + } + + public void addValueByKey(String key, String value, Map<String, Collection<String>> maps) { + + if (value == null) { + return; + } + + Collection<String> values = null; + if (!maps.containsKey(key)) { + values = new HashSet<>(); + maps.put(key, values); + } + values = maps.get(key); + + values.add(value); + } + + public Integer getContentLength() { + + if (!getAllHeaders().containsKey(CONTENT_LENGTH)) { + return null; + } + + HashSet<String> strings = (HashSet<String>) getAllHeaders().get(CONTENT_LENGTH); + + return Integer.parseInt(new ArrayList<>(strings).get(0)); + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + addHeader("Host", address); // must header + this.address = address; + } + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public Invocation getInvocation() { + return invocation; + } + + public String getContextPath() { + if (contextPath == null || contextPath.length() == 0) { + return ""; + } + + if (contextPath.startsWith("/")) { + return contextPath; + } else { + return "/" + contextPath; + } + } + + public void setContextPath(String contextPath) { + this.contextPath = contextPath; + } + + public Class<?> getBodyType() { + return bodyType; + } + + public void setBodyType(Class<?> bodyType) { + this.bodyType = bodyType; + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/RestClient.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/RestClient.java new file mode 100644 index 0000000000..e5b8cecf7d --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/RestClient.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http; + +import org.apache.dubbo.remoting.RemotingException; + +import java.util.concurrent.CompletableFuture; + +public interface RestClient { + /** + * send message. + * + * @param message + * @throws RemotingException + */ + CompletableFuture<RestResult> send(RequestTemplate message); + + /** + * close the channel. + */ + void close(); + + /** + * Graceful close the channel. + */ + void close(int timeout); + + /** + * is closed. + * + * @return closed + */ + boolean isClosed(); +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/RestResult.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/RestResult.java new file mode 100644 index 0000000000..bb057675df --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/RestResult.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * rest response facade + */ +public interface RestResult { + String getContentType(); + + byte[] getBody() throws IOException; + + Map<String, List<String>> headers(); + + byte[] getErrorResponse() throws IOException; + + int getResponseCode() throws IOException; + + String getMessage() throws IOException; + + default String appendErrorMessage(String message, String errorInfo) { + return message + "\n error info is: " + errorInfo; + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/config/HttpClientConfig.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/config/HttpClientConfig.java new file mode 100644 index 0000000000..09e0ab92a1 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/config/HttpClientConfig.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http.config; + +public class HttpClientConfig { + private int readTimeout = 6 * 1000; + private int writeTimeout = 6 * 1000; + private int connectTimeout = 6 * 1000; + private int chunkLength = 8196; + + private int HTTP_CLIENT_CONNECTION_MANAGER_MAX_PER_ROUTE = 20; + private int HTTP_CLIENT_CONNECTION_MANAGER_MAX_TOTAL = 20; + private int HTTPCLIENT_KEEP_ALIVE_DURATION = 30 * 1000; + private int HTTP_CLIENT_CONNECTION_MANAGER_CLOSE_WAIT_TIME_MS = 1000; + private int HTTP_CLIENT_CONNECTION_MANAGER_CLOSE_IDLE_TIME_S = 30; + + public HttpClientConfig() {} + + public int getReadTimeout() { + return readTimeout; + } + + public void setReadTimeout(int readTimeout) { + this.readTimeout = readTimeout; + } + + public int getWriteTimeout() { + return writeTimeout; + } + + public void setWriteTimeout(int writeTimeout) { + this.writeTimeout = writeTimeout; + } + + public int getConnectTimeout() { + return connectTimeout; + } + + public void setConnectTimeout(int connectTimeout) { + this.connectTimeout = connectTimeout; + } + + public int getChunkLength() { + return chunkLength; + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/factory/AbstractHttpClientFactory.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/factory/AbstractHttpClientFactory.java new file mode 100644 index 0000000000..ad9e7f7821 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/factory/AbstractHttpClientFactory.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http.factory; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.remoting.http.RestClient; +import org.apache.dubbo.remoting.http.config.HttpClientConfig; +import org.apache.dubbo.rpc.RpcException; + +/** + * AbstractHttpClientFactory + */ +public abstract class AbstractHttpClientFactory implements RestClientFactory { + + protected final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass()); + + // TODO load config + protected HttpClientConfig httpClientConfig = new HttpClientConfig(); + + //////////////////////////////////////// implements start /////////////////////////////////////////////// + @Override + public RestClient createRestClient(URL url) throws RpcException { + + beforeCreated(url); + + // create a raw client + RestClient restClient = doCreateRestClient(url); + + // postprocessor + afterCreated(restClient); + + return restClient; + } + + //////////////////////////////////////// implements end /////////////////////////////////////////////// + + //////////////////////////////////////// inner methods /////////////////////////////////////////////// + + protected void beforeCreated(URL url) {} + + protected abstract RestClient doCreateRestClient(URL url) throws RpcException; + + protected void afterCreated(RestClient client) {} + + //////////////////////////////////////// inner methods /////////////////////////////////////////////// + +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/factory/RestClientFactory.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/factory/RestClientFactory.java new file mode 100644 index 0000000000..c64453dbda --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/factory/RestClientFactory.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http.factory; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Adaptive; +import org.apache.dubbo.common.extension.ExtensionScope; +import org.apache.dubbo.common.extension.SPI; +import org.apache.dubbo.remoting.Constants; +import org.apache.dubbo.remoting.http.RestClient; +import org.apache.dubbo.rpc.RpcException; + +/** + * RestClientFactory. (API/SPI, Singleton, ThreadSafe) + */ +@SPI(value = Constants.OK_HTTP, scope = ExtensionScope.FRAMEWORK) +public interface RestClientFactory { + + @Adaptive({Constants.CLIENT_KEY}) + RestClient createRestClient(URL url) throws RpcException; +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/factory/impl/ApacheHttpClientFactory.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/factory/impl/ApacheHttpClientFactory.java new file mode 100644 index 0000000000..89d9c36f6f --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/factory/impl/ApacheHttpClientFactory.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http.factory.impl; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.remoting.Constants; +import org.apache.dubbo.remoting.http.RestClient; +import org.apache.dubbo.remoting.http.factory.AbstractHttpClientFactory; +import org.apache.dubbo.remoting.http.restclient.HttpClientRestClient; +import org.apache.dubbo.rpc.RpcException; + +@Activate(Constants.APACHE_HTTP_CLIENT) +public class ApacheHttpClientFactory extends AbstractHttpClientFactory { + + @Override + protected RestClient doCreateRestClient(URL url) throws RpcException { + + return new HttpClientRestClient(httpClientConfig); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/factory/impl/OkHttpClientFactory.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/factory/impl/OkHttpClientFactory.java new file mode 100644 index 0000000000..523df3caf8 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/factory/impl/OkHttpClientFactory.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http.factory.impl; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.remoting.Constants; +import org.apache.dubbo.remoting.http.RestClient; +import org.apache.dubbo.remoting.http.factory.AbstractHttpClientFactory; +import org.apache.dubbo.remoting.http.restclient.OKHttpRestClient; +import org.apache.dubbo.rpc.RpcException; + +@Activate(Constants.OK_HTTP) +public class OkHttpClientFactory extends AbstractHttpClientFactory { + + @Override + protected RestClient doCreateRestClient(URL url) throws RpcException { + + return new OKHttpRestClient(httpClientConfig); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/factory/impl/URLConnectionClientFactory.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/factory/impl/URLConnectionClientFactory.java new file mode 100644 index 0000000000..40c2a82a66 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/factory/impl/URLConnectionClientFactory.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http.factory.impl; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.remoting.Constants; +import org.apache.dubbo.remoting.http.RestClient; +import org.apache.dubbo.remoting.http.factory.AbstractHttpClientFactory; +import org.apache.dubbo.remoting.http.restclient.URLConnectionRestClient; +import org.apache.dubbo.rpc.RpcException; + +@Activate(Constants.URL_CONNECTION) +public class URLConnectionClientFactory extends AbstractHttpClientFactory { + + @Override + protected RestClient doCreateRestClient(URL url) throws RpcException { + + return new URLConnectionRestClient(httpClientConfig); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/jetty/JettyHttpBinder.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/jetty/JettyHttpBinder.java new file mode 100644 index 0000000000..05e5dc5a35 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/jetty/JettyHttpBinder.java @@ -0,0 +1,33 @@ +/* + * 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.jetty; + +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; + +/** + * JettyHttpTransporter + */ +public class JettyHttpBinder implements HttpBinder { + + @Override + public HttpServer bind(URL url, HttpHandler handler) { + return new JettyHttpServer(url, handler); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/jetty/JettyHttpServer.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/jetty/JettyHttpServer.java new file mode 100644 index 0000000000..0139a5d79f --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/jetty/JettyHttpServer.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http.jetty; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.NetUtils; +import org.apache.dubbo.remoting.Constants; +import org.apache.dubbo.remoting.http.HttpHandler; +import org.apache.dubbo.remoting.http.servlet.DispatcherServlet; +import org.apache.dubbo.remoting.http.servlet.ServletManager; +import org.apache.dubbo.remoting.http.support.AbstractHttpServer; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.thread.QueuedThreadPool; + +import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_THREADS; +import static org.apache.dubbo.common.constants.CommonConstants.THREADS_KEY; +import static org.apache.dubbo.common.constants.LoggerCodeConstants.COMMON_FAILED_STOP_HTTP_SERVER; + +public class JettyHttpServer extends AbstractHttpServer { + + private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(JettyHttpServer.class); + + private Server server; + + private URL url; + + public JettyHttpServer(URL url, final HttpHandler handler) { + super(url, handler); + this.url = url; + + // set dubbo's logger + System.setProperty("org.eclipse.jetty.util.log.class", JettyLoggerAdapter.class.getName()); + + DispatcherServlet.addHttpHandler(url.getParameter(Constants.BIND_PORT_KEY, url.getPort()), handler); + + int threads = url.getParameter(THREADS_KEY, DEFAULT_THREADS); + QueuedThreadPool threadPool = new QueuedThreadPool(); + threadPool.setDaemon(true); + threadPool.setMaxThreads(threads); + threadPool.setMinThreads(threads); + + server = new Server(threadPool); + + ServerConnector connector = new ServerConnector(server); + + String bindIp = url.getParameter(Constants.BIND_IP_KEY, url.getHost()); + if (!url.isAnyHost() && NetUtils.isValidLocalHost(bindIp)) { + connector.setHost(bindIp); + } + connector.setPort(url.getParameter(Constants.BIND_PORT_KEY, url.getPort())); + + server.addConnector(connector); + + ServletHandler servletHandler = new ServletHandler(); + ServletHolder servletHolder = servletHandler.addServletWithMapping(DispatcherServlet.class, "/*"); + servletHolder.setInitOrder(2); + + // dubbo's original impl can't support the use of ServletContext + // server.addHandler(servletHandler); + // TODO Context.SESSIONS is the best option here? (In jetty 9.x, it becomes ServletContextHandler.SESSIONS) + ServletContextHandler context = new ServletContextHandler(server, "/", ServletContextHandler.SESSIONS); + context.setServletHandler(servletHandler); + ServletManager.getInstance() + .addServletContext( + url.getParameter(Constants.BIND_PORT_KEY, url.getPort()), context.getServletContext()); + + try { + server.start(); + } catch (Exception e) { + throw new IllegalStateException( + "Failed to start jetty server on " + url.getParameter(Constants.BIND_IP_KEY) + ":" + + url.getParameter(Constants.BIND_PORT_KEY) + ", cause: " + e.getMessage(), + e); + } + } + + @Override + public void close() { + super.close(); + + // + ServletManager.getInstance().removeServletContext(url.getParameter(Constants.BIND_PORT_KEY, url.getPort())); + + if (server != null) { + try { + server.stop(); + } catch (Exception e) { + logger.warn(COMMON_FAILED_STOP_HTTP_SERVER, "", "", e.getMessage(), e); + } + } + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/jetty/JettyLoggerAdapter.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/jetty/JettyLoggerAdapter.java new file mode 100644 index 0000000000..b93cb3f2ce --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/jetty/JettyLoggerAdapter.java @@ -0,0 +1,161 @@ +/* + * 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.jetty; + +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; + +import org.eclipse.jetty.util.log.AbstractLogger; +import org.eclipse.jetty.util.log.Logger; + +import static org.apache.dubbo.common.constants.LoggerCodeConstants.COMMON_UNEXPECTED_EXCEPTION; + +/** + * logger adapter for jetty + */ +public class JettyLoggerAdapter extends AbstractLogger { + protected String name; + + private final ErrorTypeAwareLogger logger; + + private static boolean debugEnabled = false; + + public JettyLoggerAdapter() { + this("org.apache.dubbo.remoting.http.jetty"); + } + + public JettyLoggerAdapter(Class<?> clazz) { + this(clazz.getName()); + } + + public JettyLoggerAdapter(String name) { + this.name = name; + this.logger = LoggerFactory.getErrorTypeAwareLogger(name); + } + + @Override + protected Logger newLogger(String name) { + return new JettyLoggerAdapter(name); + } + + @Override + public String getName() { + return this.name; + } + + @Override + public void warn(String msg, Object... objects) { + if (logger.isWarnEnabled()) { + logger.warn(COMMON_UNEXPECTED_EXCEPTION, "", "", this.format(msg, objects)); + } + } + + @Override + public void warn(Throwable throwable) { + if (logger.isWarnEnabled()) { + logger.warn(COMMON_UNEXPECTED_EXCEPTION, "", "", throwable.getMessage(), throwable); + } + } + + @Override + public void warn(String msg, Throwable throwable) { + if (logger.isWarnEnabled()) { + logger.warn(COMMON_UNEXPECTED_EXCEPTION, "", "", msg, throwable); + } + } + + @Override + public void info(String msg, Object... objects) { + if (logger.isInfoEnabled()) { + logger.info(this.format(msg, objects)); + } + } + + @Override + public void info(Throwable throwable) { + if (logger.isInfoEnabled()) { + logger.info(throwable); + } + } + + @Override + public void info(String msg, Throwable throwable) { + if (logger.isInfoEnabled()) { + logger.info(msg, throwable); + } + } + + @Override + public boolean isDebugEnabled() { + return debugEnabled; + } + + @Override + public void setDebugEnabled(boolean enabled) { + debugEnabled = enabled; + } + + @Override + public void debug(String msg, Object... objects) { + if (debugEnabled && logger.isDebugEnabled()) { + logger.debug(this.format(msg, objects)); + } + } + + @Override + public void debug(Throwable throwable) { + if (debugEnabled && logger.isDebugEnabled()) { + logger.debug(throwable); + } + } + + @Override + public void debug(String msg, Throwable throwable) { + if (debugEnabled && logger.isDebugEnabled()) { + logger.debug(msg, throwable); + } + } + + @Override + public void ignore(Throwable throwable) { + if (logger.isWarnEnabled()) { + logger.warn(COMMON_UNEXPECTED_EXCEPTION, "", "", "IGNORED EXCEPTION ", throwable); + } + } + + private String format(String msg, Object... args) { + msg = String.valueOf(msg); // Avoids NPE + String braces = "{}"; + StringBuilder builder = new StringBuilder(); + int start = 0; + for (Object arg : args) { + int bracesIndex = msg.indexOf(braces, start); + if (bracesIndex < 0) { + builder.append(msg.substring(start)); + builder.append(' '); + builder.append(arg); + start = msg.length(); + } else { + builder.append(msg, start, bracesIndex); + builder.append(arg); + start = bracesIndex + braces.length(); + } + } + builder.append(msg.substring(start)); + return builder.toString(); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/restclient/HttpClientRestClient.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/restclient/HttpClientRestClient.java new file mode 100644 index 0000000000..f09b288c5f --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/restclient/HttpClientRestClient.java @@ -0,0 +1,188 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http.restclient; + +import org.apache.dubbo.remoting.http.RequestTemplate; +import org.apache.dubbo.remoting.http.RestClient; +import org.apache.dubbo.remoting.http.RestResult; +import org.apache.dubbo.remoting.http.config.HttpClientConfig; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import org.apache.commons.io.IOUtils; +import org.apache.http.Header; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpHead; +import org.apache.http.client.methods.HttpOptions; +import org.apache.http.client.methods.HttpPatch; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.client.methods.HttpTrace; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; + +public class HttpClientRestClient implements RestClient { + private final CloseableHttpClient closeableHttpClient; + private final HttpClientConfig httpClientConfig; + + public HttpClientRestClient(HttpClientConfig clientConfig) { + closeableHttpClient = createHttpClient(); + httpClientConfig = clientConfig; + } + + @Override + public CompletableFuture<RestResult> send(RequestTemplate requestTemplate) { + + HttpRequestBase httpRequest = null; + String httpMethod = requestTemplate.getHttpMethod(); + + httpRequest = createHttpUriRequest(httpMethod, requestTemplate); + + if (httpRequest instanceof HttpEntityEnclosingRequest) { + ((HttpEntityEnclosingRequestBase) httpRequest) + .setEntity(new ByteArrayEntity(requestTemplate.getSerializedBody())); + } + + Map<String, Collection<String>> allHeaders = requestTemplate.getAllHeaders(); + + allHeaders.remove("Content-Length"); + // header + for (String headerName : allHeaders.keySet()) { + Collection<String> headerValues = allHeaders.get(headerName); + + for (String headerValue : headerValues) { + httpRequest.addHeader(headerName, headerValue); + } + } + + httpRequest.setConfig(getRequestConfig(httpClientConfig)); + + CompletableFuture<RestResult> future = new CompletableFuture<>(); + try { + CloseableHttpResponse response = closeableHttpClient.execute(httpRequest); + future.complete(new RestResult() { + @Override + public String getContentType() { + Header header = response.getFirstHeader("Content-Type"); + return header == null ? null : header.getValue(); + } + + @Override + public byte[] getBody() throws IOException { + if (response.getEntity() == null) { + return new byte[0]; + } + return IOUtils.toByteArray(response.getEntity().getContent()); + } + + @Override + public Map<String, List<String>> headers() { + return Arrays.stream(response.getAllHeaders()) + .collect(Collectors.toMap(Header::getName, h -> Collections.singletonList(h.getValue()))); + } + + @Override + public byte[] getErrorResponse() throws IOException { + return getBody(); + } + + @Override + public int getResponseCode() { + return response.getStatusLine().getStatusCode(); + } + + @Override + public String getMessage() throws IOException { + return appendErrorMessage( + response.getStatusLine().getReasonPhrase(), new String(getErrorResponse())); + } + }); + } catch (IOException e) { + future.completeExceptionally(e); + } + return future; + } + + private RequestConfig getRequestConfig(HttpClientConfig clientConfig) { + + // TODO config + return RequestConfig.custom().build(); + } + + @Override + public void close() { + try { + closeableHttpClient.close(); + } catch (IOException e) { + + } + } + + @Override + public void close(int timeout) {} + + @Override + public boolean isClosed() { + // TODO close judge + return true; + } + + public CloseableHttpClient createHttpClient() { + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); + return HttpClients.custom().setConnectionManager(connectionManager).build(); + } + + protected HttpRequestBase createHttpUriRequest(String httpMethod, RequestTemplate requestTemplate) { + String uri = requestTemplate.getURL(); + HttpRequestBase httpUriRequest = null; + if (HttpGet.METHOD_NAME.equals(httpMethod)) { + httpUriRequest = new HttpGet(uri); + } else if (HttpHead.METHOD_NAME.equals(httpMethod)) { + httpUriRequest = new HttpHead(uri); + } else if (HttpPost.METHOD_NAME.equals(httpMethod)) { + httpUriRequest = new HttpPost(uri); + } else if (HttpPut.METHOD_NAME.equals(httpMethod)) { + httpUriRequest = new HttpPut(uri); + } else if (HttpPatch.METHOD_NAME.equals(httpMethod)) { + httpUriRequest = new HttpPatch(uri); + } else if (HttpDelete.METHOD_NAME.equals(httpMethod)) { + httpUriRequest = new HttpDelete(uri); + } else if (HttpOptions.METHOD_NAME.equals(httpMethod)) { + httpUriRequest = new HttpOptions(uri); + } else if (HttpTrace.METHOD_NAME.equals(httpMethod)) { + httpUriRequest = new HttpTrace(uri); + } else { + throw new IllegalArgumentException("Invalid HTTP method: " + httpMethod); + } + return httpUriRequest; + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/restclient/OKHttpRestClient.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/restclient/OKHttpRestClient.java new file mode 100644 index 0000000000..36ba2e6d18 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/restclient/OKHttpRestClient.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http.restclient; + +import org.apache.dubbo.remoting.http.RequestTemplate; +import org.apache.dubbo.remoting.http.RestClient; +import org.apache.dubbo.remoting.http.RestResult; +import org.apache.dubbo.remoting.http.config.HttpClientConfig; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okhttp3.internal.http.HttpMethod; + +// 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; + + public OKHttpRestClient(HttpClientConfig clientConfig) { + this.okHttpClient = createHttpClient(clientConfig); + this.httpClientConfig = clientConfig; + } + + @Override + public CompletableFuture<RestResult> send(RequestTemplate requestTemplate) { + + Request.Builder builder = new Request.Builder(); + // url + builder.url(requestTemplate.getURL()); + + Map<String, Collection<String>> allHeaders = requestTemplate.getAllHeaders(); + + boolean hasBody = false; + RequestBody requestBody = null; + // GET & HEAD body is forbidden + if (HttpMethod.permitsRequestBody(requestTemplate.getHttpMethod())) { + requestBody = RequestBody.create(null, requestTemplate.getSerializedBody()); + hasBody = true; + } + + // header + for (String headerName : allHeaders.keySet()) { + Collection<String> headerValues = allHeaders.get(headerName); + if (!hasBody && "Content-Length".equals(headerName)) { + continue; + } + for (String headerValue : headerValues) { + + builder.addHeader(headerName, headerValue); + } + } + + builder.method(requestTemplate.getHttpMethod(), requestBody); + + CompletableFuture<RestResult> future = new CompletableFuture<>(); + + okHttpClient.newCall(builder.build()).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + future.completeExceptionally(e); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + future.complete(new RestResult() { + @Override + public String getContentType() { + return response.header("Content-Type"); + } + + @Override + public byte[] getBody() throws IOException { + ResponseBody body = response.body(); + return body == null ? null : body.bytes(); + } + + @Override + public Map<String, List<String>> headers() { + return response.headers().toMultimap(); + } + + @Override + public byte[] getErrorResponse() throws IOException { + return getBody(); + } + + @Override + public int getResponseCode() throws IOException { + return response.code(); + } + + @Override + public String getMessage() throws IOException { + return appendErrorMessage(response.message(), new String(getBody())); + } + }); + } + }); + + return future; + } + + @Override + public void close() { + okHttpClient.connectionPool().evictAll(); + } + + @Override + public void close(int timeout) {} + + @Override + public boolean isClosed() { + return okHttpClient.retryOnConnectionFailure(); + } + + public OkHttpClient createHttpClient(HttpClientConfig httpClientConfig) { + OkHttpClient client = new OkHttpClient.Builder() + .readTimeout(httpClientConfig.getReadTimeout(), TimeUnit.SECONDS) + .writeTimeout(httpClientConfig.getWriteTimeout(), TimeUnit.SECONDS) + .connectTimeout(httpClientConfig.getConnectTimeout(), TimeUnit.SECONDS) + .build(); + return client; + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/restclient/URLConnectionRestClient.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/restclient/URLConnectionRestClient.java new file mode 100644 index 0000000000..65f20a19f2 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/restclient/URLConnectionRestClient.java @@ -0,0 +1,170 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http.restclient; + +import org.apache.dubbo.remoting.http.RequestTemplate; +import org.apache.dubbo.remoting.http.RestClient; +import org.apache.dubbo.remoting.http.RestResult; +import org.apache.dubbo.remoting.http.config.HttpClientConfig; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.GZIPOutputStream; + +import org.apache.commons.io.IOUtils; + +public class URLConnectionRestClient implements RestClient { + private final HttpClientConfig clientConfig; + + public URLConnectionRestClient(HttpClientConfig clientConfig) { + this.clientConfig = clientConfig; + } + + @Override + public CompletableFuture<RestResult> send(RequestTemplate requestTemplate) { + + CompletableFuture<RestResult> future = new CompletableFuture<>(); + + try { + HttpURLConnection connection = (HttpURLConnection) new URL(requestTemplate.getURL()).openConnection(); + connection.setConnectTimeout(clientConfig.getConnectTimeout()); + connection.setReadTimeout(clientConfig.getReadTimeout()); + connection.setRequestMethod(requestTemplate.getHttpMethod()); + + prepareConnection(connection, requestTemplate.getHttpMethod()); + + // writeHeaders + + for (String field : requestTemplate.getAllHeaders().keySet()) { + for (String value : requestTemplate.getHeaders(field)) { + connection.addRequestProperty(field, value); + } + } + + // writeBody + + boolean gzipEncodedRequest = requestTemplate.isGzipEncodedRequest(); + boolean deflateEncodedRequest = requestTemplate.isDeflateEncodedRequest(); + if (requestTemplate.isBodyEmpty()) { + future.complete(getRestResultFromConnection(connection)); + return future; + } + Integer contentLength = requestTemplate.getContentLength(); + + if (contentLength != null) { + connection.setFixedLengthStreamingMode(contentLength); + } else { + connection.setChunkedStreamingMode(clientConfig.getChunkLength()); + } + + OutputStream out = connection.getOutputStream(); + if (gzipEncodedRequest) { + out = new GZIPOutputStream(out); + } else if (deflateEncodedRequest) { + out = new DeflaterOutputStream(out); + } + try { + out.write(requestTemplate.getSerializedBody()); + } finally { + try { + out.close(); + } catch (IOException suppressed) { + } + } + + future.complete(getRestResultFromConnection(connection)); + } catch (Exception e) { + future.completeExceptionally(e); + } + + return future; + } + + @Override + public void close() {} + + @Override + public void close(int timeout) {} + + @Override + public boolean isClosed() { + 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())); + } + }; + } + + private void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException { + + connection.setDoInput(true); + + if ("GET".equals(httpMethod)) { + connection.setInstanceFollowRedirects(true); + } else { + connection.setInstanceFollowRedirects(false); + } + + if ("POST".equals(httpMethod) + || "PUT".equals(httpMethod) + || "PATCH".equals(httpMethod) + || "DELETE".equals(httpMethod)) { + connection.setDoOutput(true); + } else { + connection.setDoOutput(false); + } + + connection.setRequestMethod(httpMethod); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/servlet/BootstrapListener.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/servlet/BootstrapListener.java new file mode 100644 index 0000000000..0a902f292f --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/servlet/BootstrapListener.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http.servlet; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +/** + * This class must be defined before something like spring's ContextLoaderListener in web.xml + */ +public class BootstrapListener implements ServletContextListener { + + @Override + public void contextInitialized(ServletContextEvent servletContextEvent) { + ServletManager.getInstance() + .addServletContext(ServletManager.EXTERNAL_SERVER_PORT, servletContextEvent.getServletContext()); + } + + @Override + public void contextDestroyed(ServletContextEvent servletContextEvent) { + ServletManager.getInstance().removeServletContext(ServletManager.EXTERNAL_SERVER_PORT); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/servlet/DispatcherServlet.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/servlet/DispatcherServlet.java new file mode 100644 index 0000000000..0f2358cee0 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/servlet/DispatcherServlet.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http.servlet; + +import org.apache.dubbo.remoting.http.HttpHandler; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Service dispatcher Servlet. + */ +public class DispatcherServlet extends HttpServlet { + + private static final long serialVersionUID = 5766349180380479888L; + private static final Map<Integer, HttpHandler> HANDLERS = new ConcurrentHashMap<>(); + private static DispatcherServlet INSTANCE; + + public DispatcherServlet() { + DispatcherServlet.INSTANCE = this; + } + + public static void addHttpHandler(int port, HttpHandler processor) { + HANDLERS.put(port, processor); + } + + public static void removeHttpHandler(int port) { + HANDLERS.remove(port); + } + + public static DispatcherServlet getInstance() { + return INSTANCE; + } + + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + HttpHandler handler = HANDLERS.get(request.getLocalPort()); + if (handler == null) { // service not found. + response.sendError(HttpServletResponse.SC_NOT_FOUND, "Service not found."); + } else { + handler.handle(request, response); + } + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/servlet/ServletHttpBinder.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/servlet/ServletHttpBinder.java new file mode 100644 index 0000000000..33aecc5aff --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/servlet/ServletHttpBinder.java @@ -0,0 +1,33 @@ +/* + * 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.servlet; + +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; + +/** + * ServletHttpTransporter + */ +public class ServletHttpBinder implements HttpBinder { + + @Override + public HttpServer bind(URL url, HttpHandler handler) { + return new ServletHttpServer(url, handler); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/servlet/ServletHttpServer.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/servlet/ServletHttpServer.java new file mode 100644 index 0000000000..bf0b01457e --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/servlet/ServletHttpServer.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http.servlet; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.remoting.Constants; +import org.apache.dubbo.remoting.http.HttpHandler; +import org.apache.dubbo.remoting.http.support.AbstractHttpServer; + +public class ServletHttpServer extends AbstractHttpServer { + + public ServletHttpServer(URL url, HttpHandler handler) { + super(url, handler); + DispatcherServlet.addHttpHandler(url.getParameter(Constants.BIND_PORT_KEY, 8080), handler); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/servlet/ServletManager.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/servlet/ServletManager.java new file mode 100644 index 0000000000..dc71cd862d --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/servlet/ServletManager.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http.servlet; + +import javax.servlet.ServletContext; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * TODO this may not be a pretty elegant solution, + */ +public class ServletManager { + + public static final int EXTERNAL_SERVER_PORT = -1234; + + private static final ServletManager INSTANCE = new ServletManager(); + + private final Map<Integer, ServletContext> contextMap = new ConcurrentHashMap<>(); + + public static ServletManager getInstance() { + return INSTANCE; + } + + public void addServletContext(int port, ServletContext servletContext) { + contextMap.put(port, servletContext); + } + + public void removeServletContext(int port) { + contextMap.remove(port); + } + + public ServletContext getServletContext(int port) { + return contextMap.get(port); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/support/AbstractHttpServer.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/support/AbstractHttpServer.java new file mode 100644 index 0000000000..cc01b1e064 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/support/AbstractHttpServer.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http.support; + +import org.apache.dubbo.common.Parameters; +import org.apache.dubbo.common.URL; +import org.apache.dubbo.remoting.Channel; +import org.apache.dubbo.remoting.ChannelHandler; +import org.apache.dubbo.remoting.RemotingException; +import org.apache.dubbo.remoting.http.HttpHandler; +import org.apache.dubbo.remoting.http.HttpServer; + +import java.net.InetSocketAddress; +import java.util.Collection; + +/** + * AbstractHttpServer + */ +public abstract class AbstractHttpServer implements HttpServer { + + private final URL url; + + private final HttpHandler handler; + + private volatile boolean closed; + + public AbstractHttpServer(URL url, HttpHandler handler) { + if (url == null) { + throw new IllegalArgumentException("url == null"); + } + if (handler == null) { + throw new IllegalArgumentException("handler == null"); + } + this.url = url; + this.handler = handler; + } + + @Override + public HttpHandler getHttpHandler() { + return handler; + } + + @Override + public URL getUrl() { + return url; + } + + @Override + public void reset(URL url) {} + + @Override + public boolean isBound() { + return true; + } + + @Override + public InetSocketAddress getLocalAddress() { + return url.toInetSocketAddress(); + } + + @Override + public void close() { + closed = true; + } + + @Override + public void close(int timeout) { + close(); + } + + @Override + public boolean isClosed() { + return closed; + } + + /** + * Following methods are extended from RemotingServer, useless for http servers + */ + @Override + public boolean canHandleIdle() { + return false; + } + + @Override + public Collection<Channel> getChannels() { + return null; + } + + @Override + public Channel getChannel(InetSocketAddress remoteAddress) { + return null; + } + + @Override + public void reset(Parameters parameters) {} + + @Override + public ChannelHandler getChannelHandler() { + return null; + } + + @Override + public void send(Object message) throws RemotingException {} + + @Override + public void send(Object message, boolean sent) throws RemotingException {} + + @Override + public void startClose() {} +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/tomcat/TomcatHttpBinder.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/tomcat/TomcatHttpBinder.java new file mode 100755 index 0000000000..92b4bf1c70 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/tomcat/TomcatHttpBinder.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http.tomcat; + +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; + +public class TomcatHttpBinder implements HttpBinder { + + @Override + public HttpServer bind(URL url, HttpHandler handler) { + return new TomcatHttpServer(url, handler); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/tomcat/TomcatHttpServer.java b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/tomcat/TomcatHttpServer.java new file mode 100755 index 0000000000..002f963c35 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/java/org/apache/dubbo/remoting/http/tomcat/TomcatHttpServer.java @@ -0,0 +1,100 @@ +/* + * 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.tomcat; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.logger.ErrorTypeAwareLogger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.SystemPropertyConfigUtils; +import org.apache.dubbo.remoting.http.HttpHandler; +import org.apache.dubbo.remoting.http.servlet.DispatcherServlet; +import org.apache.dubbo.remoting.http.servlet.ServletManager; +import org.apache.dubbo.remoting.http.support.AbstractHttpServer; + +import java.io.File; + +import org.apache.catalina.Context; +import org.apache.catalina.LifecycleException; +import org.apache.catalina.connector.Connector; +import org.apache.catalina.startup.Tomcat; + +import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_THREADS; +import static org.apache.dubbo.common.constants.CommonConstants.SystemProperty.SYSTEM_JAVA_IO_TMPDIR; +import static org.apache.dubbo.common.constants.CommonConstants.THREADS_KEY; +import static org.apache.dubbo.common.constants.LoggerCodeConstants.COMMON_FAILED_STOP_HTTP_SERVER; +import static org.apache.dubbo.remoting.Constants.ACCEPTS_KEY; + +public class TomcatHttpServer extends AbstractHttpServer { + + private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(TomcatHttpServer.class); + + private final Tomcat tomcat; + + private final URL url; + + public TomcatHttpServer(URL url, final HttpHandler handler) { + super(url, handler); + + this.url = url; + DispatcherServlet.addHttpHandler(url.getPort(), handler); + String baseDir = new File(SystemPropertyConfigUtils.getSystemProperty(SYSTEM_JAVA_IO_TMPDIR)).getAbsolutePath(); + tomcat = new Tomcat(); + + Connector connector = tomcat.getConnector(); + connector.setPort(url.getPort()); + connector.setProperty("maxThreads", String.valueOf(url.getParameter(THREADS_KEY, DEFAULT_THREADS))); + connector.setProperty("maxConnections", String.valueOf(url.getParameter(ACCEPTS_KEY, -1))); + connector.setProperty("URIEncoding", "UTF-8"); + connector.setProperty("connectionTimeout", "60000"); + connector.setProperty("maxKeepAliveRequests", "-1"); + + tomcat.setBaseDir(baseDir); + tomcat.setPort(url.getPort()); + + Context context = tomcat.addContext("/", baseDir); + Tomcat.addServlet(context, "dispatcher", new DispatcherServlet()); + // Issue : https://github.com/apache/dubbo/issues/6418 + // addServletMapping method will be removed since Tomcat 9 + // context.addServletMapping("/*", "dispatcher"); + context.addServletMappingDecoded("/*", "dispatcher"); + ServletManager.getInstance().addServletContext(url.getPort(), context.getServletContext()); + + // tell tomcat to fail on startup failures. + System.setProperty("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE", "true"); + + try { + tomcat.start(); + } catch (LifecycleException e) { + throw new IllegalStateException("Failed to start tomcat server at " + url.getAddress(), e); + } + } + + @Override + public void close() { + super.close(); + + ServletManager.getInstance().removeServletContext(url.getPort()); + + try { + tomcat.stop(); + // close port by destroy() + tomcat.destroy(); + } catch (Exception e) { + logger.warn(COMMON_FAILED_STOP_HTTP_SERVER, "", "", e.getMessage(), e); + } + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http.HttpBinder b/dubbo-remoting/dubbo-remoting-http/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http.HttpBinder new file mode 100644 index 0000000000..845124bb43 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http.HttpBinder @@ -0,0 +1,3 @@ +servlet=org.apache.dubbo.remoting.http.servlet.ServletHttpBinder +jetty=org.apache.dubbo.remoting.http.jetty.JettyHttpBinder +tomcat=org.apache.dubbo.remoting.http.tomcat.TomcatHttpBinder \ No newline at end of file diff --git a/dubbo-remoting/dubbo-remoting-http/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http.factory.RestClientFactory b/dubbo-remoting/dubbo-remoting-http/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http.factory.RestClientFactory new file mode 100644 index 0000000000..95dd12e072 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http.factory.RestClientFactory @@ -0,0 +1,4 @@ +ok-http=org.apache.dubbo.remoting.http.factory.impl.OkHttpClientFactory +url-connection=org.apache.dubbo.remoting.http.factory.impl.URLConnectionClientFactory +apache-http-client=org.apache.dubbo.remoting.http.factory.impl.ApacheHttpClientFactory + diff --git a/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/jetty/JettyHttpBinderTest.java b/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/jetty/JettyHttpBinderTest.java new file mode 100644 index 0000000000..04fc431863 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/jetty/JettyHttpBinderTest.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.dubbo.remoting.http.jetty; + +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.HttpHandler; +import org.apache.dubbo.remoting.http.HttpServer; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.io.IOException; + +import org.apache.http.client.fluent.Request; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; + +class JettyHttpBinderTest { + @Test + void shouldAbleHandleRequestForJettyBinder() 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"); + } + }); + + String response = + Request.Get(url.toJavaURL().toURI()).execute().returnContent().asString(); + + assertThat(response, is("Jetty")); + + httpServer.close(); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/jetty/JettyLoggerAdapterTest.java b/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/jetty/JettyLoggerAdapterTest.java new file mode 100644 index 0000000000..e4cbbfd539 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/jetty/JettyLoggerAdapterTest.java @@ -0,0 +1,127 @@ +/* + * 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.jetty; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.support.FailsafeErrorTypeAwareLogger; +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.HttpHandler; +import org.apache.dubbo.remoting.http.HttpServer; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import org.apache.http.client.fluent.Request; +import org.eclipse.jetty.util.log.Log; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class JettyLoggerAdapterTest { + + @Test + void testJettyUseDubboLogger() 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 is using Dubbo's logger"); + } + }); + Request.Get(url.toJavaURL().toURI()).execute().returnContent().asString(); + + assertThat(Log.getLog().getClass().isAssignableFrom(JettyLoggerAdapter.class), is(true)); + + httpServer.close(); + } + + @Test + void testSuccessLogger() throws Exception { + Logger successLogger = mock(Logger.class); + Class<?> clazz = Class.forName("org.apache.dubbo.remoting.http.jetty.JettyLoggerAdapter"); + JettyLoggerAdapter jettyLoggerAdapter = + (JettyLoggerAdapter) clazz.getDeclaredConstructor().newInstance(); + + Field loggerField = clazz.getDeclaredField("logger"); + loggerField.setAccessible(true); + loggerField.set(jettyLoggerAdapter, new FailsafeErrorTypeAwareLogger(successLogger)); + jettyLoggerAdapter.setDebugEnabled(true); + + when(successLogger.isDebugEnabled()).thenReturn(true); + when(successLogger.isWarnEnabled()).thenReturn(true); + when(successLogger.isInfoEnabled()).thenReturn(true); + + jettyLoggerAdapter.warn("warn"); + jettyLoggerAdapter.info("info"); + jettyLoggerAdapter.debug("debug"); + + verify(successLogger).warn(anyString()); + verify(successLogger).info(anyString()); + verify(successLogger).debug(anyString()); + + jettyLoggerAdapter.warn(new Exception("warn")); + jettyLoggerAdapter.info(new Exception("info")); + jettyLoggerAdapter.debug(new Exception("debug")); + jettyLoggerAdapter.ignore(new Exception("ignore")); + + jettyLoggerAdapter.warn("warn", new Exception("warn")); + jettyLoggerAdapter.info("info", new Exception("info")); + jettyLoggerAdapter.debug("debug", new Exception("debug")); + } + + @Test + void testNewLogger() { + JettyLoggerAdapter loggerAdapter = new JettyLoggerAdapter(); + org.eclipse.jetty.util.log.Logger logger = + loggerAdapter.newLogger(this.getClass().getName()); + assertThat(logger.getClass().isAssignableFrom(JettyLoggerAdapter.class), is(true)); + } + + @Test + void testDebugEnabled() { + JettyLoggerAdapter loggerAdapter = new JettyLoggerAdapter(); + loggerAdapter.setDebugEnabled(true); + assertThat(loggerAdapter.isDebugEnabled(), is(true)); + } + + @Test + void testLoggerFormat() throws Exception { + Class<?> clazz = Class.forName("org.apache.dubbo.remoting.http.jetty.JettyLoggerAdapter"); + Object newInstance = clazz.getDeclaredConstructor().newInstance(); + + Method method = clazz.getDeclaredMethod("format", String.class, Object[].class); + method.setAccessible(true); + + String print = (String) method.invoke(newInstance, "Hello,{}! I'am {}", new String[] {"World", "Jetty"}); + + assertThat(print, is("Hello,World! I'am Jetty")); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/rest/RestClientTest.java b/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/rest/RestClientTest.java new file mode 100644 index 0000000000..571861ae3d --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/rest/RestClientTest.java @@ -0,0 +1,240 @@ +/* + * 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 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 org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +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])); + } + + @Test + void testBuildURL() 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.setCharacterEncoding("UTF-8"); + response.getWriter().write(request.getQueryString()); + } + }); + + RequestTemplate requestTemplate = new RequestTemplate(null, "POST", "localhost:" + port); + + requestTemplate.addParam("name", "李强"); + requestTemplate.addParam("age", "18"); + requestTemplate.path("/hello/world"); + + // When using the OKHttpRestClient, parameters will be encoded with UTF-8 and appended to the URL + RestClient restClient = new OKHttpRestClient(new HttpClientConfig()); + + CompletableFuture<RestResult> send = restClient.send(requestTemplate); + + RestResult restResult = send.get(); + + assertThat(new String(restResult.getBody()), is("name=%E6%9D%8E%E5%BC%BA&age=18")); + + // When using the HttpClientRestClient, parameters will be encoded with UTF-8 and appended to the URL + restClient = new HttpClientRestClient(new HttpClientConfig()); + + send = restClient.send(requestTemplate); + + restResult = send.get(); + + assertThat(new String(restResult.getBody()), is("name=%E6%9D%8E%E5%BC%BA&age=18")); + + // When using the URLConnectionRestClient, parameters won't be encoded and still appended to the URL + restClient = new URLConnectionRestClient(new HttpClientConfig()); + + send = restClient.send(requestTemplate); + + restResult = send.get(); + + assertThat(new String(restResult.getBody(), StandardCharsets.UTF_8), is("name=李强&age=18")); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/tomcat/TomcatHttpBinderTest.java b/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/tomcat/TomcatHttpBinderTest.java new file mode 100644 index 0000000000..75efc28b41 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/test/java/org/apache/dubbo/remoting/http/tomcat/TomcatHttpBinderTest.java @@ -0,0 +1,60 @@ +/* + * 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.tomcat; + +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.HttpHandler; +import org.apache.dubbo.remoting.http.HttpServer; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.io.IOException; + +import org.apache.http.client.fluent.Request; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; + +class TomcatHttpBinderTest { + @Test + void shouldAbleHandleRequestForTomcatBinder() 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 TomcatHttpBinder() + .bind(url, new HttpHandler<HttpServletRequest, HttpServletResponse>() { + @Override + public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException { + response.getWriter().write("Tomcat"); + } + }); + + String response = + Request.Get(url.toJavaURL().toURI()).execute().returnContent().asString(); + + assertThat(response, is("Tomcat")); + + httpServer.close(); + assertThat(NetUtils.isPortInUsed(port), is(false)); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http/src/test/resources/log4j2-test.xml b/dubbo-remoting/dubbo-remoting-http/src/test/resources/log4j2-test.xml new file mode 100644 index 0000000000..ba99f52cc2 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http/src/test/resources/log4j2-test.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> +<Configuration status="WARN"> + <Appenders> + <Console name="Console" target="SYSTEM_OUT" follow="true"> + <PatternLayout pattern="%d{HH:mm:ss.SSS} |-%highlight{%-5p} [%t] %40.40c:%-3L -| %m%n%rEx{filters(jdk.internal.reflect,java.lang.reflect,sun.reflect,org.junit,org.mockito)}" charset="UTF-8"/> + </Console> + </Appenders> + <Loggers> + <Root level="info"> + <AppenderRef ref="Console"/> + </Root> + </Loggers> +</Configuration> diff --git a/dubbo-remoting/pom.xml b/dubbo-remoting/pom.xml index e7aca3685e..1a1d0cd0c4 100644 --- a/dubbo-remoting/pom.xml +++ b/dubbo-remoting/pom.xml @@ -29,6 +29,7 @@ <description>The remoting module of dubbo project</description> <modules> <module>dubbo-remoting-api</module> + <module>dubbo-remoting-http</module> <module>dubbo-remoting-netty</module> <module>dubbo-remoting-zookeeper-api</module> <module>dubbo-remoting-zookeeper</module> diff --git a/dubbo-test/dubbo-dependencies-all/pom.xml b/dubbo-test/dubbo-dependencies-all/pom.xml index 2c802671c7..8bfc74434b 100644 --- a/dubbo-test/dubbo-dependencies-all/pom.xml +++ b/dubbo-test/dubbo-dependencies-all/pom.xml @@ -300,6 +300,11 @@ <artifactId>dubbo-remoting-api</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>org.apache.dubbo</groupId> + <artifactId>dubbo-remoting-http</artifactId> + <version>${project.version}</version> + </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-remoting-http12</artifactId>