Return-Path: X-Original-To: apmail-cloudstack-commits-archive@www.apache.org Delivered-To: apmail-cloudstack-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 07E84183EC for ; Tue, 25 Aug 2015 18:49:43 +0000 (UTC) Received: (qmail 68635 invoked by uid 500); 25 Aug 2015 18:49:28 -0000 Delivered-To: apmail-cloudstack-commits-archive@cloudstack.apache.org Received: (qmail 68578 invoked by uid 500); 25 Aug 2015 18:49:28 -0000 Mailing-List: contact commits-help@cloudstack.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cloudstack.apache.org Delivered-To: mailing list commits@cloudstack.apache.org Received: (qmail 66982 invoked by uid 99); 25 Aug 2015 18:49:27 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 25 Aug 2015 18:49:27 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id EFDA2E715C; Tue, 25 Aug 2015 18:49:26 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: remi@apache.org To: commits@cloudstack.apache.org Date: Tue, 25 Aug 2015 18:50:03 -0000 Message-Id: In-Reply-To: <4dd6a75df8c6474683ffca09cf957425@git.apache.org> References: <4dd6a75df8c6474683ffca09cf957425@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [39/45] git commit: updated refs/heads/master to 44ba14d Add basic RestClient implentation based on HTTP Components 4.5 - Upgrade version of HTTP Components to 4.5 - Add helper class to create Http clients - Add helper class to build http requests - Add enum with the different Http Methods - Add constants class for HTTP related values Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/8a93bb8d Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/8a93bb8d Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/8a93bb8d Branch: refs/heads/master Commit: 8a93bb8d2dc85dc6382e373744f8e7c6a9593fb1 Parents: 6e74ef8 Author: Miguel Ferreira Authored: Fri Aug 21 17:44:57 2015 +0200 Committer: Miguel Ferreira Committed: Tue Aug 25 17:36:13 2015 +0200 ---------------------------------------------------------------------- pom.xml | 2 +- .../com/cloud/utils/rest/BasicRestClient.java | 118 ++++++++++++++++ .../com/cloud/utils/rest/HttpClientHelper.java | 71 ++++++++++ .../com/cloud/utils/rest/HttpConstants.java | 34 +++++ .../java/com/cloud/utils/rest/HttpMethods.java | 41 ++++++ .../cloud/utils/rest/HttpUriRequestBuilder.java | 119 ++++++++++++++++ .../java/com/cloud/utils/rest/RestClient.java | 31 ++++ .../cloud/utils/rest/BasicRestClientTest.java | 106 ++++++++++++++ .../cloud/utils/rest/HttpClientHelperTest.java | 38 +++++ .../cloud/utils/rest/HttpRequestMatcher.java | 141 +++++++++++++++++++ .../utils/rest/HttpUriRequestBuilderTest.java | 115 +++++++++++++++ .../utils/rest/HttpUriRequestQueryMatcher.java | 48 +++++++ 12 files changed, 863 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 95b90db..b5e380e 100644 --- a/pom.xml +++ b/pom.xml @@ -77,7 +77,7 @@ 18.0 18.0 6.2.0-3.1 - 4.3.6 + 4.5 4.4 3.1 5.1.34 http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/utils/src/main/java/com/cloud/utils/rest/BasicRestClient.java ---------------------------------------------------------------------- diff --git a/utils/src/main/java/com/cloud/utils/rest/BasicRestClient.java b/utils/src/main/java/com/cloud/utils/rest/BasicRestClient.java new file mode 100644 index 0000000..5c29155 --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/rest/BasicRestClient.java @@ -0,0 +1,118 @@ +// +// 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 com.cloud.utils.rest; + +import java.io.IOException; +import java.net.URI; + +import org.apache.http.HttpHost; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.log4j.Logger; + +public class BasicRestClient implements RestClient { + + private static final Logger s_logger = Logger.getLogger(BasicRestClient.class); + + private static final String HTTPS = HttpConstants.HTTPS; + private static final int HTTPS_PORT = HttpConstants.HTTPS_PORT; + + private final CloseableHttpClient client; + private final HttpClientContext clientContext; + + private BasicRestClient(final Builder builder) { + client = builder.client; + clientContext = builder.clientContext; + clientContext.setTargetHost(buildHttpHost(builder.host)); + } + + protected BasicRestClient(final CloseableHttpClient client, final HttpClientContext clientContex, final String host) { + this.client = client; + clientContext = clientContex; + clientContext.setTargetHost(buildHttpHost(host)); + } + + private static HttpHost buildHttpHost(final String host) { + return new HttpHost(host, HTTPS_PORT, HTTPS); + } + + @SuppressWarnings("rawtypes") + public static Builder create() { + return new Builder(); + } + + @Override + public CloseableHttpResponse execute(final HttpUriRequest request) throws CloudstackRESTException { + logRequestExecution(request); + try { + return client.execute(clientContext.getTargetHost(), request, clientContext); + } catch (final IOException e) { + throw new CloudstackRESTException("Could not execute request " + request, e); + } + } + + private void logRequestExecution(final HttpUriRequest request) { + final URI uri = request.getURI(); + String query = uri.getQuery(); + query = query != null ? "?" + query : ""; + s_logger.debug("Executig " + request.getMethod() + " request on " + clientContext.getTargetHost() + uri.getPath() + query); + } + + @Override + public void closeResponse(final CloseableHttpResponse response) throws CloudstackRESTException { + try { + s_logger.debug("Closing HTTP connection"); + response.close(); + } catch (final IOException e) { + final StringBuilder sb = new StringBuilder(); + sb.append("Failed to close response object for request.\nResponse: ").append(response); + throw new CloudstackRESTException(sb.toString(), e); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected static class Builder { + private CloseableHttpClient client; + private HttpClientContext clientContext = HttpClientContext.create(); + private String host; + + public T client(final CloseableHttpClient client) { + this.client = client; + return (T) this; + } + + public T clientContext(final HttpClientContext clientContext) { + this.clientContext = clientContext; + return (T) this; + } + + public T host(final String host) { + this.host = host; + return (T) this; + } + + public BasicRestClient build() { + return new BasicRestClient(this); + } + } + +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/utils/src/main/java/com/cloud/utils/rest/HttpClientHelper.java ---------------------------------------------------------------------- diff --git a/utils/src/main/java/com/cloud/utils/rest/HttpClientHelper.java b/utils/src/main/java/com/cloud/utils/rest/HttpClientHelper.java new file mode 100644 index 0000000..1c6f564 --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/rest/HttpClientHelper.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 com.cloud.utils.rest; + +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; + +import javax.net.ssl.SSLContext; + +import org.apache.http.client.config.CookieSpecs; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.TrustSelfSignedStrategy; +import org.apache.http.impl.client.BasicCookieStore; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.LaxRedirectStrategy; +import org.apache.http.impl.client.StandardHttpRequestRetryHandler; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.ssl.SSLContexts; + +public class HttpClientHelper { + + private static final String HTTPS = HttpConstants.HTTPS; + + public static CloseableHttpClient createHttpClient(final int maxRedirects) throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException { + final Registry socketFactoryRegistry = createSocketFactoryConfigration(); + final BasicCookieStore cookieStore = new BasicCookieStore(); + return HttpClientBuilder.create() + .setConnectionManager(new PoolingHttpClientConnectionManager(socketFactoryRegistry)) + .setRedirectStrategy(new LaxRedirectStrategy()) + .setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.DEFAULT).setMaxRedirects(maxRedirects).build()) + .setDefaultCookieStore(cookieStore) + .setRetryHandler(new StandardHttpRequestRetryHandler()) + .build(); + } + + private static Registry createSocketFactoryConfigration() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException { + Registry socketFactoryRegistry; + final SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(new TrustSelfSignedStrategy()).build(); + final SSLConnectionSocketFactory cnnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); + socketFactoryRegistry = RegistryBuilder. create() + .register(HTTPS, cnnectionSocketFactory) + .build(); + + return socketFactoryRegistry; + } + +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/utils/src/main/java/com/cloud/utils/rest/HttpConstants.java ---------------------------------------------------------------------- diff --git a/utils/src/main/java/com/cloud/utils/rest/HttpConstants.java b/utils/src/main/java/com/cloud/utils/rest/HttpConstants.java new file mode 100644 index 0000000..93502a5 --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/rest/HttpConstants.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 com.cloud.utils.rest; + +public class HttpConstants { + + public static final int HTTPS_PORT = 443; + public static final String HTTPS = "https"; + public static final String GET_METHOD_TYPE = "get"; + public static final String DELETE_METHOD_TYPE = "delete"; + public static final String PUT_METHOD_TYPE = "put"; + public static final String POST_METHOD_TYPE = "post"; + public static final String TEXT_HTML_CONTENT_TYPE = "text/html"; + public static final String JSON_CONTENT_TYPE = "application/json"; + public static final String CONTENT_TYPE = "Content-Type"; + +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/utils/src/main/java/com/cloud/utils/rest/HttpMethods.java ---------------------------------------------------------------------- diff --git a/utils/src/main/java/com/cloud/utils/rest/HttpMethods.java b/utils/src/main/java/com/cloud/utils/rest/HttpMethods.java new file mode 100644 index 0000000..7fecad0 --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/rest/HttpMethods.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 com.cloud.utils.rest; + +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; + +public enum HttpMethods { + + GET(HttpGet.METHOD_NAME), POST(HttpPost.METHOD_NAME), PUT(HttpPut.METHOD_NAME), DELETE(HttpDelete.METHOD_NAME); + + private final String name; + + private HttpMethods(final String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/utils/src/main/java/com/cloud/utils/rest/HttpUriRequestBuilder.java ---------------------------------------------------------------------- diff --git a/utils/src/main/java/com/cloud/utils/rest/HttpUriRequestBuilder.java b/utils/src/main/java/com/cloud/utils/rest/HttpUriRequestBuilder.java new file mode 100644 index 0000000..4fb3a1d --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/rest/HttpUriRequestBuilder.java @@ -0,0 +1,119 @@ +// +// 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 com.cloud.utils.rest; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.http.Consts; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.message.BasicHeader; +import org.springframework.util.Assert; + +import com.google.common.base.Optional; + +public class HttpUriRequestBuilder { + + private static final String CONTENT_TYPE = HttpConstants.CONTENT_TYPE; + private static final String JSON_CONTENT_TYPE = HttpConstants.JSON_CONTENT_TYPE; + + private static final Optional ABSENT = Optional.absent(); + + private HttpMethods method; + private String path; + private Optional jsonPayload = ABSENT; + private final Map parameters = new HashMap(); + private final Map methodParameters = new HashMap(); + + private HttpUriRequestBuilder() { + + } + + public static HttpUriRequestBuilder create() { + return new HttpUriRequestBuilder(); + } + + public HttpUriRequestBuilder method(final HttpMethods method) { + this.method = method; + return this; + } + + public HttpUriRequestBuilder path(final String path) { + this.path = path; + return this; + } + + public HttpUriRequestBuilder jsonPayload(final Optional jsonPayload) { + this.jsonPayload = jsonPayload; + return this; + } + + public HttpUriRequestBuilder parameters(final Map parameters) { + this.parameters.clear(); + this.parameters.putAll(parameters); + return this; + } + + public HttpUriRequestBuilder methodParameters(final Map methodParameters) { + this.methodParameters.clear(); + this.methodParameters.putAll(methodParameters); + return this; + } + + public HttpUriRequest build() { + validate(); + final RequestBuilder builder = RequestBuilder.create(method.toString()).setUri(buildUri()); + if (!methodParameters.isEmpty()) { + for (final Entry entry : methodParameters.entrySet()) { + builder.addParameter(entry.getKey(), entry.getValue()); + } + } + if (jsonPayload.isPresent()) { + builder.addHeader(new BasicHeader(CONTENT_TYPE, JSON_CONTENT_TYPE)) + .setEntity(new StringEntity(jsonPayload.get(), ContentType.create(JSON_CONTENT_TYPE, Consts.UTF_8))); + } + return builder.build(); + } + + private void validate() { + Assert.notNull(method, "HTTP Method cannot be null"); + Assert.hasText(path, "target path must be defined"); + Assert.isTrue(path.startsWith("/"), "targte path must start with a '/' character"); + } + + private URI buildUri() { + try { + final URIBuilder builder = new URIBuilder().setPath(path); + for (final Map.Entry entry : parameters.entrySet()) { + builder.addParameter(entry.getKey(), entry.getValue()); + } + return builder.build(); + } catch (final URISyntaxException e) { + throw new IllegalArgumentException("Unable to build REST Service URI", e); + } + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/utils/src/main/java/com/cloud/utils/rest/RestClient.java ---------------------------------------------------------------------- diff --git a/utils/src/main/java/com/cloud/utils/rest/RestClient.java b/utils/src/main/java/com/cloud/utils/rest/RestClient.java new file mode 100644 index 0000000..104f09b --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/rest/RestClient.java @@ -0,0 +1,31 @@ +// +// 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 com.cloud.utils.rest; + +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpUriRequest; + +public interface RestClient { + + public CloseableHttpResponse execute(final HttpUriRequest request) throws CloudstackRESTException; + + public void closeResponse(final CloseableHttpResponse response) throws CloudstackRESTException; + +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/utils/src/test/java/com/cloud/utils/rest/BasicRestClientTest.java ---------------------------------------------------------------------- diff --git a/utils/src/test/java/com/cloud/utils/rest/BasicRestClientTest.java b/utils/src/test/java/com/cloud/utils/rest/BasicRestClientTest.java new file mode 100644 index 0000000..c30103e --- /dev/null +++ b/utils/src/test/java/com/cloud/utils/rest/BasicRestClientTest.java @@ -0,0 +1,106 @@ +// +// 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 com.cloud.utils.rest; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.sameInstance; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import org.apache.http.HttpHost; +import org.apache.http.ProtocolVersion; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.message.BasicStatusLine; +import org.junit.BeforeClass; +import org.junit.Test; + +public class BasicRestClientTest { + + private static final String LOCALHOST = "localhost"; + private static final String HTTPS = HttpConstants.HTTPS; + + private static final StatusLine HTTP_200_REPSONSE = new BasicStatusLine(new ProtocolVersion(HTTPS, 1, 1), 200, "OK"); + private static final StatusLine HTTP_503_STATUSLINE = new BasicStatusLine(new ProtocolVersion(HTTPS, 1, 1), 503, "Service unavailable"); + + private static final CloseableHttpResponse mockResponse = mock(CloseableHttpResponse.class); + + private static CloseableHttpClient httpClient; + private static HttpUriRequest request; + + @BeforeClass + public static void setupClass() throws Exception { + request = HttpUriRequestBuilder.create() + .method(HttpMethods.GET) + .path("/path") + .build(); + httpClient = spy(HttpClientHelper.createHttpClient(2)); + } + + @Test + public void testExecuteRequest() throws Exception { + when(mockResponse.getStatusLine()).thenReturn(HTTP_200_REPSONSE); + doReturn(mockResponse).when(httpClient).execute(any(HttpHost.class), HttpRequestMatcher.eq(request), any(HttpClientContext.class)); + final BasicRestClient restClient = BasicRestClient.create() + .host(LOCALHOST) + .client(httpClient) + .build(); + + final CloseableHttpResponse response = restClient.execute(request); + + assertThat(response, notNullValue()); + assertThat(response, sameInstance(mockResponse)); + assertThat(response.getStatusLine(), sameInstance(HTTP_200_REPSONSE)); + } + + @Test + public void testExecuteRequestStatusCodeIsNotOk() throws Exception { + when(mockResponse.getStatusLine()).thenReturn(HTTP_503_STATUSLINE); + doReturn(mockResponse).when(httpClient).execute(any(HttpHost.class), HttpRequestMatcher.eq(request), any(HttpClientContext.class)); + final BasicRestClient restClient = BasicRestClient.create() + .host(LOCALHOST) + .client(httpClient) + .build(); + + final CloseableHttpResponse response = restClient.execute(request); + + assertThat(response, notNullValue()); + assertThat(response, sameInstance(mockResponse)); + assertThat(response.getStatusLine(), sameInstance(HTTP_503_STATUSLINE)); + } + + @Test(expected = CloudstackRESTException.class) + public void testExecuteRequestWhenClientThrowsIOException() throws Exception { + final BasicRestClient restClient = BasicRestClient.create() + .host(LOCALHOST) + .client(HttpClientHelper.createHttpClient(5)) + .build(); + + restClient.execute(request); + } + +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/utils/src/test/java/com/cloud/utils/rest/HttpClientHelperTest.java ---------------------------------------------------------------------- diff --git a/utils/src/test/java/com/cloud/utils/rest/HttpClientHelperTest.java b/utils/src/test/java/com/cloud/utils/rest/HttpClientHelperTest.java new file mode 100644 index 0000000..20b32dd --- /dev/null +++ b/utils/src/test/java/com/cloud/utils/rest/HttpClientHelperTest.java @@ -0,0 +1,38 @@ +// +// 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 com.cloud.utils.rest; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.notNullValue; + +import org.apache.http.impl.client.CloseableHttpClient; +import org.junit.Test; + +public class HttpClientHelperTest { + + @Test + public void testCreateClient() throws Exception { + int maxRedirects = 5; + final CloseableHttpClient client = HttpClientHelper.createHttpClient(maxRedirects); + + assertThat(client, notNullValue()); + } + +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/utils/src/test/java/com/cloud/utils/rest/HttpRequestMatcher.java ---------------------------------------------------------------------- diff --git a/utils/src/test/java/com/cloud/utils/rest/HttpRequestMatcher.java b/utils/src/test/java/com/cloud/utils/rest/HttpRequestMatcher.java new file mode 100644 index 0000000..effec79 --- /dev/null +++ b/utils/src/test/java/com/cloud/utils/rest/HttpRequestMatcher.java @@ -0,0 +1,141 @@ +// +// 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 com.cloud.utils.rest; + +import static org.mockito.Matchers.argThat; + +import java.io.IOException; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpRequest; +import org.apache.http.ParseException; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.util.EntityUtils; +import org.hamcrest.Description; +import org.hamcrest.SelfDescribing; +import org.mockito.ArgumentMatcher; + +public class HttpRequestMatcher extends ArgumentMatcher { + private final HttpRequest wanted; + + public HttpRequestMatcher(final HttpRequest wanted) { + this.wanted = wanted; + } + + public static HttpRequest eq(final HttpRequest request) { + return argThat(new HttpRequestMatcher(request)); + } + + @Override + public boolean matches(final Object actual) { + if (actual instanceof HttpUriRequest) { + final HttpUriRequest converted = (HttpUriRequest) actual; + return checkMethod(converted) && checkUri(converted) && checkPayload(converted); + } else { + return wanted == actual; + } + } + + private boolean checkPayload(final HttpUriRequest actual) { + final String wantedPayload = getPayload(wanted); + final String actualPayload = getPayload(actual); + return equalsString(wantedPayload, actualPayload); + } + + private static String getPayload(final HttpRequest request) { + String payload = ""; + if (request instanceof HttpEntityEnclosingRequest) { + try { + payload = EntityUtils.toString(((HttpEntityEnclosingRequest) request).getEntity()); + } catch (final ParseException e) { + throw new IllegalArgumentException("Couldn't read request's entity payload.", e); + } catch (final IOException e) { + throw new IllegalArgumentException("Couldn't read request's entity payload.", e); + } + } + return payload; + } + + private boolean checkUri(final HttpUriRequest actual) { + if (wanted instanceof HttpUriRequest) { + final String wantedQuery = ((HttpUriRequest) wanted).getURI().getQuery(); + final String actualQuery = actual.getURI().getQuery(); + return equalsString(wantedQuery, actualQuery); + } else { + return wanted == actual; + } + } + + private boolean checkMethod(final HttpUriRequest actual) { + if (wanted instanceof HttpUriRequest) { + final String wantedMethod = ((HttpUriRequest) wanted).getMethod(); + final String actualMethod = actual.getMethod(); + return equalsString(wantedMethod, actualMethod); + } else { + return wanted == actual; + } + } + + private static boolean equalsString(final String a, final String b) { + return a == b || a != null && a.equals(b); + } + + @Override + public void describeTo(final Description description) { + description.appendText(describe(wanted)); + } + + public String describe(final HttpRequest object) { + final StringBuilder sb = new StringBuilder(); + if (object instanceof HttpUriRequest) { + final HttpUriRequest converted = (HttpUriRequest) object; + sb.append("method = ").append(converted.getMethod()); + sb.append(", query = ").append(converted.getURI().getQuery()); + sb.append(", payload = ").append(getPayload(object)); + } + return sb.toString(); + } + + @Override + public boolean equals(final Object o) { + return EqualsBuilder.reflectionEquals(this, o); + } + + @Override + public int hashCode() { + return HashCodeBuilder.reflectionHashCode(this); + } + + public SelfDescribing withExtraTypeInfo() { + return new SelfDescribing() { + @Override + public void describeTo(final Description description) { + description.appendText("(" + wanted.getClass().getSimpleName() + ") ").appendText(describe(wanted)); + } + }; + } + + public boolean typeMatches(final Object object) { + return wanted != null && object != null && object.getClass() == wanted.getClass(); + } + +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestBuilderTest.java ---------------------------------------------------------------------- diff --git a/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestBuilderTest.java b/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestBuilderTest.java new file mode 100644 index 0000000..470f563 --- /dev/null +++ b/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestBuilderTest.java @@ -0,0 +1,115 @@ +// +// 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 com.cloud.utils.rest; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; + +import java.util.HashMap; + +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpUriRequest; +import org.junit.Test; + +import com.google.common.base.Optional; + +public class HttpUriRequestBuilderTest { + + @Test(expected = IllegalArgumentException.class) + public void testBuildWithNullMethod() throws Exception { + HttpUriRequestBuilder.create().path("/path").build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testBuildWithNullPath() throws Exception { + HttpUriRequestBuilder.create().method(HttpMethods.GET).build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testBuildWithEmptyPath() throws Exception { + HttpUriRequestBuilder.create() + .method(HttpMethods.GET) + .path("") + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testBuildWithIlegalPath() throws Exception { + HttpUriRequestBuilder.create() + .method(HttpMethods.GET) + .path("path") + .build(); + } + + @Test + public void testBuildSimpleRequest() throws Exception { + final HttpUriRequest request = HttpUriRequestBuilder.create() + .method(HttpMethods.GET) + .path("/path") + .build(); + + assertThat(request, notNullValue()); + assertThat(request.getURI().getPath(), equalTo("/path")); + assertThat(request.getURI().getScheme(), nullValue()); + assertThat(request.getURI().getQuery(), nullValue()); + assertThat(request.getURI().getHost(), nullValue()); + assertThat(request.getMethod(), equalTo(HttpGet.METHOD_NAME)); + } + + @Test + public void testBuildRequestWithParameters() throws Exception { + final HashMap parameters = new HashMap(); + parameters.put("key1", "value1"); + final HttpUriRequest request = HttpUriRequestBuilder.create() + .method(HttpMethods.GET) + .path("/path") + .parameters(parameters) + .build(); + + assertThat(request, notNullValue()); + assertThat(request.getURI().getPath(), equalTo("/path")); + assertThat(request.getURI().getQuery(), equalTo("key1=value1")); + assertThat(request.getURI().getScheme(), nullValue()); + assertThat(request.getURI().getHost(), nullValue()); + assertThat(request.getMethod(), equalTo(HttpGet.METHOD_NAME)); + } + + @Test + public void testBuildRequestWithJsonPayload() throws Exception { + final HttpUriRequest request = HttpUriRequestBuilder.create() + .method(HttpMethods.GET) + .path("/path") + .jsonPayload(Optional.of("{'key1':'value1'}")) + .build(); + + assertThat(request, notNullValue()); + assertThat(request.getURI().getPath(), equalTo("/path")); + assertThat(request.getURI().getScheme(), nullValue()); + assertThat(request.getURI().getQuery(), nullValue()); + assertThat(request.getURI().getHost(), nullValue()); + assertThat(request.getMethod(), equalTo(HttpGet.METHOD_NAME)); + assertThat(request.containsHeader(HttpConstants.CONTENT_TYPE), equalTo(true)); + assertThat(request.getFirstHeader(HttpConstants.CONTENT_TYPE).getValue(), equalTo(HttpConstants.JSON_CONTENT_TYPE)); + assertThat(request, HttpUriRequestPayloadMatcher.hasPayload("{'key1':'value1'}")); + } + +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/8a93bb8d/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestQueryMatcher.java ---------------------------------------------------------------------- diff --git a/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestQueryMatcher.java b/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestQueryMatcher.java new file mode 100644 index 0000000..e73641b --- /dev/null +++ b/utils/src/test/java/com/cloud/utils/rest/HttpUriRequestQueryMatcher.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 com.cloud.utils.rest; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Matchers.argThat; + +import org.apache.http.client.methods.HttpUriRequest; +import org.hamcrest.FeatureMatcher; +import org.hamcrest.Matcher; + +public class HttpUriRequestQueryMatcher extends FeatureMatcher { + + public static HttpUriRequest aQuery(final String query) { + return argThat(new HttpUriRequestQueryMatcher(equalTo(query), "query", "query")); + } + + public static HttpUriRequest aQueryThatContains(final String query) { + return argThat(new HttpUriRequestQueryMatcher(containsString(query), "query", "query")); + } + + public HttpUriRequestQueryMatcher(final Matcher subMatcher, final String featureDescription, final String featureName) { + super(subMatcher, featureDescription, featureName); + } + + @Override + protected String featureValueOf(final HttpUriRequest actual) { + return actual.getURI().getQuery(); + } +}