| /* |
| * 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.brooklyn.test; |
| |
| import static org.testng.Assert.assertTrue; |
| import static org.testng.Assert.fail; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.HttpURLConnection; |
| import java.net.URL; |
| import java.net.URLConnection; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.Future; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import javax.net.ssl.HostnameVerifier; |
| import javax.net.ssl.HttpsURLConnection; |
| import javax.net.ssl.SSLSession; |
| |
| import org.apache.brooklyn.util.collections.MutableMap; |
| import org.apache.brooklyn.util.crypto.SslTrustUtils; |
| import org.apache.brooklyn.util.exceptions.Exceptions; |
| import org.apache.brooklyn.util.http.HttpTool; |
| import org.apache.brooklyn.util.http.TrustingSslSocketFactory; |
| import org.apache.brooklyn.util.stream.Streams; |
| import org.apache.brooklyn.util.time.Time; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.testng.Assert; |
| |
| import com.google.common.base.Throwables; |
| import com.google.common.collect.Lists; |
| import com.google.common.util.concurrent.ListenableFuture; |
| import com.google.common.util.concurrent.ListeningExecutorService; |
| |
| /** |
| * Utility methods to aid testing HTTP. |
| * |
| * @author aled |
| * |
| * @deprecated since 0.9.0. Prefer {@link org.apache.brooklyn.util.http.HttpAsserts} which has no TestNG dependencies |
| * (or {@link HttpTool} for some utility methods). |
| */ |
| @Deprecated |
| public class HttpTestUtils { |
| |
| private static final Logger LOG = LoggerFactory.getLogger(HttpTestUtils.class); |
| |
| static final ExecutorService executor = Executors.newCachedThreadPool(); |
| |
| /** |
| * Connects to the given url and returns the connection. |
| * Caller should {@code connection.getInputStream().close()} the result of this |
| * (especially if they are making heavy use of this method). |
| */ |
| public static URLConnection connectToUrl(String u) throws Exception { |
| final URL url = new URL(u); |
| final AtomicReference<Exception> exception = new AtomicReference<Exception>(); |
| |
| // sometimes openConnection hangs, so run in background |
| Future<URLConnection> f = executor.submit(new Callable<URLConnection>() { |
| public URLConnection call() { |
| try { |
| HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { |
| @Override public boolean verify(String s, SSLSession sslSession) { |
| return true; |
| } |
| }); |
| URLConnection connection = url.openConnection(); |
| TrustingSslSocketFactory.configure(connection); |
| connection.connect(); |
| |
| connection.getContentLength(); // Make sure the connection is made. |
| return connection; |
| } catch (Exception e) { |
| exception.set(e); |
| LOG.debug("Error connecting to url "+url+" (propagating): "+e, e); |
| } |
| return null; |
| } |
| }); |
| try { |
| URLConnection result = null; |
| try { |
| result = f.get(60, TimeUnit.SECONDS); |
| } catch (InterruptedException e) { |
| throw e; |
| } catch (Exception e) { |
| LOG.debug("Error connecting to url "+url+", probably timed out (rethrowing): "+e); |
| throw new IllegalStateException("Connect to URL not complete within 60 seconds, for url "+url+": "+e); |
| } |
| if (exception.get() != null) { |
| LOG.debug("Error connecting to url "+url+", thread caller of "+exception, new Throwable("source of rethrown error "+exception)); |
| throw exception.get(); |
| } else { |
| return result; |
| } |
| } finally { |
| f.cancel(true); |
| } |
| } |
| |
| public static void assertHealthyStatusCode(int code) { |
| if (code>=200 && code<=299) return; |
| Assert.fail("Wrong status code: "+code); |
| } |
| |
| public static int getHttpStatusCode(String url) throws Exception { |
| URLConnection connection = connectToUrl(url); |
| long startTime = System.currentTimeMillis(); |
| int status = ((HttpURLConnection) connection).getResponseCode(); |
| |
| // read fully if possible, then close everything, trying to prevent cached threads at server |
| consumeAndCloseQuietly((HttpURLConnection) connection); |
| |
| if (LOG.isDebugEnabled()) |
| LOG.debug("connection to {} ({}ms) gives {}", new Object[] { url, (System.currentTimeMillis()-startTime), status }); |
| return status; |
| } |
| |
| /** |
| * Asserts that gets back any "valid" response - i.e. not an exception. This could be an unauthorized, |
| * a redirect, a 404, or anything else that implies there is web-server listening on that port. |
| */ |
| public static void assertUrlReachable(String url) { |
| try { |
| getHttpStatusCode(url); |
| } catch (InterruptedException e) { |
| Thread.currentThread().interrupt(); |
| throw new RuntimeException("Interrupted for "+url+" (in assertion that is reachable)", e); |
| } catch (Exception e) { |
| throw new IllegalStateException("Server at "+url+" failed to respond (in assertion that is reachable): "+e, e); |
| } |
| } |
| |
| public static void assertUrlUnreachable(String url) { |
| try { |
| int statusCode = getHttpStatusCode(url); |
| fail("Expected url "+url+" unreachable, but got status code "+statusCode); |
| } catch (InterruptedException e) { |
| Thread.currentThread().interrupt(); |
| throw new RuntimeException("Interrupted for "+url+" (in assertion that unreachable)", e); |
| } catch (Exception e) { |
| IOException cause = Exceptions.getFirstThrowableOfType(e, IOException.class); |
| if (cause != null) { |
| // success; clean shutdown transitioning from 400 to error |
| } else { |
| Throwables.propagate(e); |
| } |
| } |
| } |
| |
| public static void assertUrlUnreachableEventually(final String url) { |
| assertUrlUnreachableEventually(MutableMap.<String, Object>of(), url); |
| } |
| |
| public static void assertUrlUnreachableEventually(Map<String,?> flags, final String url) { |
| Asserts.succeedsEventually(flags, new Runnable() { |
| public void run() { |
| assertUrlUnreachable(url); |
| } |
| }); |
| } |
| |
| public static void assertHttpStatusCodeEquals(String url, int... acceptableReturnCodes) { |
| List<Integer> acceptableCodes = Lists.newArrayList(); |
| for (int code : acceptableReturnCodes) { |
| acceptableCodes.add((Integer)code); |
| } |
| try { |
| int actualCode = getHttpStatusCode(url); |
| assertTrue(acceptableCodes.contains(actualCode), "code="+actualCode+"; expected="+acceptableCodes+"; url="+url); |
| |
| } catch (InterruptedException e) { |
| Thread.currentThread().interrupt(); |
| throw new RuntimeException("Interrupted for "+url+" (in assertion that result code is "+acceptableCodes+")", e); |
| } catch (Exception e) { |
| throw new IllegalStateException("Server at "+url+" failed to respond (in assertion that result code is "+acceptableCodes+"): "+e, e); |
| } |
| } |
| |
| public static void assertHttpStatusCodeEventuallyEquals(final String url, final int expectedCode) { |
| assertHttpStatusCodeEventuallyEquals(MutableMap.<String, Object>of(), url, expectedCode); |
| } |
| |
| public static void assertHttpStatusCodeEventuallyEquals(Map<String,?> flags, final String url, final int expectedCode) { |
| Asserts.succeedsEventually(flags, new Runnable() { |
| public void run() { |
| assertHttpStatusCodeEquals(url, expectedCode); |
| } |
| }); |
| } |
| |
| public static void assertContentContainsText(final String url, final String phrase, final String ...additionalPhrases) { |
| try { |
| String contents = getContent(url); |
| Assert.assertTrue(contents != null && contents.length() > 0); |
| for (String text: Lists.asList(phrase, additionalPhrases)) { |
| if (!contents.contains(text)) { |
| LOG.warn("CONTENTS OF URL "+url+" MISSING TEXT: "+text+"\n"+contents); |
| Assert.fail("URL "+url+" does not contain text: "+text); |
| } |
| } |
| } catch (Exception e) { |
| throw Throwables.propagate(e); |
| } |
| } |
| |
| public static void assertContentNotContainsText(final String url, final String phrase, final String ...additionalPhrases) { |
| try { |
| String contents = getContent(url); |
| Assert.assertTrue(contents != null); |
| for (String text: Lists.asList(phrase, additionalPhrases)) { |
| if (contents.contains(text)) { |
| LOG.warn("CONTENTS OF URL "+url+" HAS TEXT: "+text+"\n"+contents); |
| Assert.fail("URL "+url+" contain text: "+text); |
| } |
| } |
| } catch (Exception e) { |
| throw Throwables.propagate(e); |
| } |
| } |
| |
| public static void assertErrorContentContainsText(final String url, final String phrase, final String ...additionalPhrases) { |
| try { |
| String contents = getErrorContent(url); |
| Assert.assertTrue(contents != null && contents.length() > 0); |
| for (String text: Lists.asList(phrase, additionalPhrases)) { |
| if (!contents.contains(text)) { |
| LOG.warn("CONTENTS OF URL "+url+" MISSING TEXT: "+text+"\n"+contents); |
| Assert.fail("URL "+url+" does not contain text: "+text); |
| } |
| } |
| } catch (Exception e) { |
| throw Throwables.propagate(e); |
| } |
| } |
| |
| |
| public static void assertErrorContentNotContainsText(final String url, final String phrase, final String ...additionalPhrases) { |
| try { |
| String err = getErrorContent(url); |
| Assert.assertTrue(err != null); |
| for (String text: Lists.asList(phrase, additionalPhrases)) { |
| if (err.contains(text)) { |
| LOG.warn("CONTENTS OF URL "+url+" HAS TEXT: "+text+"\n"+err); |
| Assert.fail("URL "+url+" contain text: "+text); |
| } |
| } |
| } catch (Exception e) { |
| throw Throwables.propagate(e); |
| } |
| } |
| |
| public static void assertContentEventuallyContainsText(final String url, final String phrase, final String ...additionalPhrases) { |
| assertContentEventuallyContainsText(MutableMap.<String, Object>of(), url, phrase, additionalPhrases); |
| } |
| |
| public static void assertContentEventuallyContainsText(Map<String,?> flags, final String url, final String phrase, final String ...additionalPhrases) { |
| Asserts.succeedsEventually(flags, new Runnable() { |
| public void run() { |
| assertContentContainsText(url, phrase, additionalPhrases); |
| } |
| }); |
| } |
| |
| public static void assertContentMatches(String url, String regex) { |
| String contents = getContent(url); |
| Assert.assertNotNull(contents); |
| Assert.assertTrue(contents.matches(regex), "Contents does not match expected regex ("+regex+"): "+contents); |
| } |
| |
| public static void assertContentEventuallyMatches(final String url, final String regex) { |
| assertContentEventuallyMatches(MutableMap.<String, Object>of(), url, regex); |
| } |
| |
| public static void assertContentEventuallyMatches(Map<String,?> flags, final String url, final String regex) { |
| Asserts.succeedsEventually(flags, new Runnable() { |
| @Override |
| public void run() { |
| assertContentMatches(url, regex); |
| } |
| }); |
| } |
| |
| public static String getErrorContent(String url) { |
| try { |
| HttpURLConnection connection = (HttpURLConnection) connectToUrl(url); |
| long startTime = System.currentTimeMillis(); |
| |
| String err; |
| int status; |
| try { |
| InputStream errStream = connection.getErrorStream(); |
| err = Streams.readFullyString(errStream); |
| status = connection.getResponseCode(); |
| } finally { |
| closeQuietly(connection); |
| } |
| |
| if (LOG.isDebugEnabled()) |
| LOG.debug("read of err {} ({}ms) complete; http code {}", new Object[] { url, Time.makeTimeStringRounded(System.currentTimeMillis()-startTime), status}); |
| return err; |
| |
| } catch (Exception e) { |
| throw Exceptions.propagate(e); |
| } |
| } |
| |
| public static String getContent(String url) { |
| try { |
| return Streams.readFullyString(SslTrustUtils.trustAll(new URL(url).openConnection()).getInputStream()); |
| } catch (Exception e) { |
| throw Throwables.propagate(e); |
| } |
| } |
| |
| /** |
| * Schedules (with the given executor) a poller that repeatedly accesses the given url, to confirm it always gives |
| * back the expected status code. |
| * |
| * Expected usage is to query the future, such as: |
| * |
| * <pre> |
| * {@code |
| * Future<?> future = assertAsyncHttpStatusCodeContinuallyEquals(executor, url, 200); |
| * // do other stuff... |
| * if (future.isDone()) future.get(); // get exception if it's failed |
| * } |
| * </pre> |
| * |
| * For stopping it, you can either do future.cancel(true), or you can just do executor.shutdownNow(). |
| * |
| * TODO Look at difference between this and WebAppMonitor, to decide if this should be kept. |
| */ |
| public static ListenableFuture<?> assertAsyncHttpStatusCodeContinuallyEquals(ListeningExecutorService executor, final String url, final int expectedStatusCode) { |
| return executor.submit(new Runnable() { |
| @Override public void run() { |
| // TODO Need to drop logging; remove sleep when that's done. |
| while (!Thread.currentThread().isInterrupted()) { |
| assertHttpStatusCodeEquals(url, expectedStatusCode); |
| try { |
| Thread.sleep(1000); |
| } catch (InterruptedException e) { |
| return; // graceful return |
| } |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Consumes the input stream entirely and then cleanly closes the connection. |
| * Ignores all exceptions completely, not even logging them! |
| * |
| * Consuming the stream fully is useful for preventing idle TCP connections. |
| * See {@linkplain http://docs.oracle.com/javase/8/docs/technotes/guides/net/http-keepalive.html}. |
| */ |
| public static void consumeAndCloseQuietly(HttpURLConnection connection) { |
| try { Streams.readFully(connection.getInputStream()); } catch (Exception e) {} |
| closeQuietly(connection); |
| } |
| |
| /** |
| * Closes all streams of the connection, and disconnects it. Ignores all exceptions completely, |
| * not even logging them! |
| */ |
| public static void closeQuietly(HttpURLConnection connection) { |
| try { connection.disconnect(); } catch (Exception e) {} |
| try { connection.getInputStream().close(); } catch (Exception e) {} |
| try { connection.getOutputStream().close(); } catch (Exception e) {} |
| try { connection.getErrorStream().close(); } catch (Exception e) {} |
| } |
| } |