http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/03dbd281/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpAsserts.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpAsserts.java b/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpAsserts.java new file mode 100644 index 0000000..bbfdbfb --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpAsserts.java @@ -0,0 +1,282 @@ +/* + * 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.util.http; + +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.test.Asserts; +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.stream.Streams; +import org.apache.brooklyn.util.time.Time; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Throwables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; + +/** + * Utility methods to aid testing HTTP. + * + * @author aled + */ +public class HttpAsserts { + + // TODO Delete methods from TestUtils, to just have them here (or switch so TestUtils delegates here, + // and deprecate methods in TestUtils until deleted). + + private static final Logger LOG = LoggerFactory.getLogger(HttpAsserts.class); + + public static void assertHealthyStatusCode(int code) { + if (code>=200 && code<=299) return; + Asserts.fail("Wrong status code: " + code); + } + + public static int getHttpStatusCode(String url) throws Exception { + URLConnection connection = HttpTool.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 + HttpTool.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+" Asserts.failed to respond (in assertion that is reachable): "+e, e); + } + } + + public static void assertUrlUnreachable(String url) { + try { + int statusCode = getHttpStatusCode(url); + Asserts.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(Maps.newLinkedHashMap(), url); + } + + public static void assertUrlUnreachableEventually(Map 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); + Asserts.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+" Asserts.failed to respond (in assertion that result code is "+acceptableCodes+"): "+e, e); + } + } + + public static void assertHttpStatusCodeEventuallyEquals(final String url, final int expectedCode) { + assertHttpStatusCodeEventuallyEquals(Maps.newLinkedHashMap(), url, expectedCode); + } + + public static void assertHttpStatusCodeEventuallyEquals(Map 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 = HttpTool.getContent(url); + Asserts.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); + Asserts.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 = HttpTool.getContent(url); + Asserts.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); + Asserts.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 = HttpTool.getErrorContent(url); + Asserts.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); + Asserts.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 = HttpTool.getErrorContent(url); + Asserts.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); + Asserts.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.of(), url, phrase, additionalPhrases); + } + + public static void assertContentEventuallyContainsText(Map 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 = HttpTool.getContent(url); + Asserts.assertNotNull(contents); + Asserts.assertTrue(contents.matches(regex), "Contents does not match expected regex ("+regex+"): "+contents); + } + + public static void assertContentEventuallyMatches(final String url, final String regex) { + Asserts.succeedsEventually(new Runnable() { + @Override + public void run() { + assertContentMatches(url, regex); + } + }); + } + + + + /** + * 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 Asserts.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 + } + } + } + }); + } + + +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/03dbd281/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpTool.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpTool.java b/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpTool.java new file mode 100644 index 0000000..8811a32 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpTool.java @@ -0,0 +1,525 @@ +/* + * 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.util.http; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.net.URLConnection; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; + +import com.google.common.base.Throwables; +import org.apache.brooklyn.util.crypto.SslTrustUtils; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.net.URLParamEncoder; +import org.apache.brooklyn.util.stream.Streams; +import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.time.Duration; +import org.apache.brooklyn.util.time.Time; +import org.apache.commons.codec.binary.Base64; +import org.apache.http.ConnectionReuseStrategy; +import org.apache.http.HttpEntity; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.HttpClient; +import org.apache.http.client.entity.UrlEncodedFormEntity; +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.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeSocketFactory; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.conn.ssl.TrustSelfSignedStrategy; +import org.apache.http.conn.ssl.TrustStrategy; +import org.apache.http.conn.ssl.X509HostnameVerifier; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.client.LaxRedirectStrategy; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.base.Optional; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimap; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSession; + +public class HttpTool { + + private static final Logger LOG = LoggerFactory.getLogger(HttpTool.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 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; + } + + + public static String getContent(String url) { + try { + return Streams.readFullyString(SslTrustUtils.trustAll(new URL(url).openConnection()).getInputStream()); + } catch (Exception e) { + throw Throwables.propagate(e); + } + } + + 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); + } + } + + /** + * 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 <a href="http://docs.oracle.com/javase/8/docs/technotes/guides/net/http-keepalive.html">Persistent Connections</a> + */ + 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) {} + } + + /** Apache HTTP commons utility for trusting all. + * <p> + * For generic java HTTP usage, see {@link SslTrustUtils#trustAll(java.net.URLConnection)} + * and static constants in the same class. */ + public static class TrustAllStrategy implements TrustStrategy { + @Override + public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException { + return true; + } + } + + public static HttpClientBuilder httpClientBuilder() { + return new HttpClientBuilder(); + } + + public static class HttpClientBuilder { + private ClientConnectionManager clientConnectionManager; + private HttpParams httpParams; + private URI uri; + private Integer port; + private Credentials credentials; + private boolean laxRedirect; + private Boolean https; + private SchemeSocketFactory socketFactory; + private ConnectionReuseStrategy reuseStrategy; + private boolean trustAll; + private boolean trustSelfSigned; + + public HttpClientBuilder clientConnectionManager(ClientConnectionManager val) { + this.clientConnectionManager = checkNotNull(val, "clientConnectionManager"); + return this; + } + public HttpClientBuilder httpParams(HttpParams val) { + checkState(httpParams == null, "Must not call httpParams multiple times, or after other methods like connectionTimeout"); + this.httpParams = checkNotNull(val, "httpParams"); + return this; + } + public HttpClientBuilder connectionTimeout(Duration val) { + if (httpParams == null) httpParams = new BasicHttpParams(); + long millis = checkNotNull(val, "connectionTimeout").toMilliseconds(); + if (millis > Integer.MAX_VALUE) throw new IllegalStateException("HttpClient only accepts upto max-int millis for connectionTimeout, but given "+val); + HttpConnectionParams.setConnectionTimeout(httpParams, (int) millis); + return this; + } + public HttpClientBuilder socketTimeout(Duration val) { + if (httpParams == null) httpParams = new BasicHttpParams(); + long millis = checkNotNull(val, "socketTimeout").toMilliseconds(); + if (millis > Integer.MAX_VALUE) throw new IllegalStateException("HttpClient only accepts upto max-int millis for socketTimeout, but given "+val); + HttpConnectionParams.setSoTimeout(httpParams, (int) millis); + return this; + } + public HttpClientBuilder reuseStrategy(ConnectionReuseStrategy val) { + this.reuseStrategy = checkNotNull(val, "reuseStrategy"); + return this; + } + public HttpClientBuilder uri(String val) { + return uri(URI.create(checkNotNull(val, "uri"))); + } + public HttpClientBuilder uri(URI val) { + this.uri = checkNotNull(val, "uri"); + if (https == null) https = ("https".equalsIgnoreCase(uri.getScheme())); + return this; + } + public HttpClientBuilder port(int val) { + this.port = val; + return this; + } + public HttpClientBuilder credentials(Credentials val) { + this.credentials = checkNotNull(val, "credentials"); + return this; + } + public void credential(Optional<Credentials> val) { + if (val.isPresent()) credentials = val.get(); + } + /** similar to curl --post301 -L` */ + public HttpClientBuilder laxRedirect(boolean val) { + this.laxRedirect = val; + return this; + } + public HttpClientBuilder https(boolean val) { + this.https = val; + return this; + } + public HttpClientBuilder socketFactory(SchemeSocketFactory val) { + this.socketFactory = checkNotNull(val, "socketFactory"); + return this; + } + public HttpClientBuilder trustAll() { + this.trustAll = true; + return this; + } + public HttpClientBuilder trustSelfSigned() { + this.trustSelfSigned = true; + return this; + } + public HttpClient build() { + final DefaultHttpClient httpClient = new DefaultHttpClient(clientConnectionManager); + httpClient.setParams(httpParams); + + // support redirects for POST (similar to `curl --post301 -L`) + // http://stackoverflow.com/questions/3658721/httpclient-4-error-302-how-to-redirect + if (laxRedirect) { + httpClient.setRedirectStrategy(new LaxRedirectStrategy()); + } + if (reuseStrategy != null) { + httpClient.setReuseStrategy(reuseStrategy); + } + if (https == Boolean.TRUE || (uri!=null && uri.toString().startsWith("https:"))) { + try { + if (port == null) { + port = (uri != null && uri.getPort() >= 0) ? uri.getPort() : 443; + } + if (socketFactory == null) { + if (trustAll) { + TrustStrategy trustStrategy = new TrustAllStrategy(); + X509HostnameVerifier hostnameVerifier = SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER; + socketFactory = new SSLSocketFactory(trustStrategy, hostnameVerifier); + } else if (trustSelfSigned) { + TrustStrategy trustStrategy = new TrustSelfSignedStrategy(); + X509HostnameVerifier hostnameVerifier = SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER; + socketFactory = new SSLSocketFactory(trustStrategy, hostnameVerifier); + } else { + // Using default https scheme: based on default java truststore, which is pretty strict! + } + } + if (socketFactory != null) { + Scheme sch = new Scheme("https", port, socketFactory); + httpClient.getConnectionManager().getSchemeRegistry().register(sch); + } + } catch (Exception e) { + LOG.warn("Error setting trust for uri {}", uri); + throw Exceptions.propagate(e); + } + } + + // Set credentials + if (uri != null && credentials != null) { + String hostname = uri.getHost(); + int port = uri.getPort(); + httpClient.getCredentialsProvider().setCredentials(new AuthScope(hostname, port), credentials); + } + if (uri==null && credentials!=null) { + LOG.warn("credentials have no effect in builder unless URI for host is specified"); + } + + return httpClient; + } + } + + protected static abstract class HttpRequestBuilder<B extends HttpRequestBuilder<B, R>, R extends HttpRequest> { + protected R req; + + protected HttpRequestBuilder(R req) { + this.req = req; + } + @SuppressWarnings("unchecked") + protected B self() { + return (B) this; + } + public B headers(Map<String,String> headers) { + if (headers!=null) { + for (Map.Entry<String,String> entry : headers.entrySet()) { + req.addHeader(entry.getKey(), entry.getValue()); + } + } + return self(); + } + public B headers(Multimap<String,String> headers) { + if (headers!=null) { + for (Map.Entry<String,String> entry : headers.entries()) { + req.addHeader(entry.getKey(), entry.getValue()); + } + } + return self(); + } + public R build() { + return req; + } + } + + protected static abstract class HttpEntityEnclosingRequestBaseBuilder<B extends HttpEntityEnclosingRequestBaseBuilder<B,R>, R extends HttpEntityEnclosingRequestBase> extends HttpRequestBuilder<B, R> { + protected HttpEntityEnclosingRequestBaseBuilder(R req) { + super(req); + } + public B body(byte[] body) { + if (body != null) { + HttpEntity httpEntity = new ByteArrayEntity(body); + req.setEntity(httpEntity); + } + return self(); + } + } + + public static class HttpGetBuilder extends HttpRequestBuilder<HttpGetBuilder, HttpGet> { + public HttpGetBuilder(URI uri) { + super(new HttpGet(uri)); + } + } + + public static class HttpHeadBuilder extends HttpRequestBuilder<HttpHeadBuilder, HttpHead> { + public HttpHeadBuilder(URI uri) { + super(new HttpHead(uri)); + } + } + + public static class HttpDeleteBuilder extends HttpRequestBuilder<HttpDeleteBuilder, HttpDelete> { + public HttpDeleteBuilder(URI uri) { + super(new HttpDelete(uri)); + } + } + + public static class HttpPostBuilder extends HttpEntityEnclosingRequestBaseBuilder<HttpPostBuilder, HttpPost> { + HttpPostBuilder(URI uri) { + super(new HttpPost(uri)); + } + } + + public static class HttpFormPostBuilder extends HttpRequestBuilder<HttpFormPostBuilder, HttpPost> { + HttpFormPostBuilder(URI uri) { + super(new HttpPost(uri)); + } + + public HttpFormPostBuilder params(Map<String, String> params) { + if (params != null) { + Collection<NameValuePair> httpParams = new ArrayList<NameValuePair>(params.size()); + for (Entry<String, String> param : params.entrySet()) { + httpParams.add(new BasicNameValuePair(param.getKey(), param.getValue())); + } + req.setEntity(new UrlEncodedFormEntity(httpParams)); + } + return self(); + } + } + + public static class HttpPutBuilder extends HttpEntityEnclosingRequestBaseBuilder<HttpPutBuilder, HttpPut> { + public HttpPutBuilder(URI uri) { + super(new HttpPut(uri)); + } + } + + public static HttpToolResponse httpGet(HttpClient httpClient, URI uri, Map<String,String> headers) { + HttpGet req = new HttpGetBuilder(uri).headers(headers).build(); + return execAndConsume(httpClient, req); + } + + public static HttpToolResponse httpPost(HttpClient httpClient, URI uri, Map<String,String> headers, byte[] body) { + HttpPost req = new HttpPostBuilder(uri).headers(headers).body(body).build(); + return execAndConsume(httpClient, req); + } + + public static HttpToolResponse httpPut(HttpClient httpClient, URI uri, Map<String, String> headers, byte[] body) { + HttpPut req = new HttpPutBuilder(uri).headers(headers).body(body).build(); + return execAndConsume(httpClient, req); + } + + public static HttpToolResponse httpPost(HttpClient httpClient, URI uri, Map<String,String> headers, Map<String, String> params) { + HttpPost req = new HttpFormPostBuilder(uri).headers(headers).params(params).build(); + return execAndConsume(httpClient, req); + } + + public static HttpToolResponse httpDelete(HttpClient httpClient, URI uri, Map<String,String> headers) { + HttpDelete req = new HttpDeleteBuilder(uri).headers(headers).build(); + return execAndConsume(httpClient, req); + } + + public static HttpToolResponse httpHead(HttpClient httpClient, URI uri, Map<String,String> headers) { + HttpHead req = new HttpHeadBuilder(uri).headers(headers).build(); + return execAndConsume(httpClient, req); + } + + public static HttpToolResponse execAndConsume(HttpClient httpClient, HttpUriRequest req) { + long startTime = System.currentTimeMillis(); + try { + HttpResponse httpResponse = httpClient.execute(req); + + try { + return new HttpToolResponse(httpResponse, startTime); + } finally { + EntityUtils.consume(httpResponse.getEntity()); + } + } catch (Exception e) { + throw Exceptions.propagate(e); + } + } + + public static boolean isStatusCodeHealthy(int code) { return (code>=200 && code<=299); } + + public static String toBasicAuthorizationValue(UsernamePasswordCredentials credentials) { + return "Basic "+Base64.encodeBase64String( (credentials.getUserName()+":"+credentials.getPassword()).getBytes() ); + } + + public static String encodeUrlParams(Map<?,?> data) { + if (data==null) return ""; + Iterable<String> args = Iterables.transform(data.entrySet(), + new Function<Map.Entry<?,?>,String>() { + @Override public String apply(Map.Entry<?,?> entry) { + Object k = entry.getKey(); + Object v = entry.getValue(); + return URLParamEncoder.encode(Strings.toString(k)) + (v != null ? "=" + URLParamEncoder.encode(Strings.toString(v)) : ""); + } + }); + return Joiner.on("&").join(args); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/03dbd281/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpToolResponse.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpToolResponse.java b/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpToolResponse.java new file mode 100644 index 0000000..70bcb17 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpToolResponse.java @@ -0,0 +1,183 @@ +/* + * 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.util.http; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.stream.Streams; +import org.apache.brooklyn.util.time.Duration; +import org.apache.brooklyn.util.time.Time; +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Objects; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.google.common.io.ByteStreams; + +public class HttpToolResponse { + + private static final Logger log = LoggerFactory.getLogger(HttpToolResponse.class); + + private final Object mutex = new Object(); + private final HttpResponse response; + private final long startTime; + private final long durationMillisOfFirstResponse; + private final long durationMillisOfFullContent; + private int responseCode; + private String reasonPhrase; + private Map<String,List<String>> headerLists; + private byte[] content; + + + public HttpToolResponse(HttpResponse response, long startTime) { + this.response = response; + this.startTime = startTime; + + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + HttpEntity entity = response.getEntity(); + if (entity != null) { + entity.getContentLength(); + durationMillisOfFirstResponse = Duration.sinceUtc(startTime).toMilliseconds(); + + ByteStreams.copy(entity.getContent(), out); + content = out.toByteArray(); + + entity.getContentLength(); + } else { + durationMillisOfFirstResponse = Duration.sinceUtc(startTime).toMilliseconds(); + content = new byte[0]; + } + durationMillisOfFullContent = Duration.sinceUtc(startTime).toMilliseconds(); + if (log.isTraceEnabled()) + log.trace("HttpPollValue latency "+Time.makeTimeStringRounded(durationMillisOfFirstResponse)+" / "+Time.makeTimeStringRounded(durationMillisOfFullContent)+", content size "+content.length); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } + + public HttpToolResponse(int responseCode, Map<String,? extends List<String>> headers, byte[] content, + long startTime, long durationMillisOfFirstResponse, long durationMillisOfFullContent) { + this.response = null; + this.responseCode = responseCode; + this.headerLists = ImmutableMap.copyOf(headers); + this.content = content; + this.startTime = startTime; + this.durationMillisOfFirstResponse = durationMillisOfFirstResponse; + this.durationMillisOfFullContent = durationMillisOfFullContent; + } + + public int getResponseCode() { + synchronized (mutex) { + if (responseCode == 0) { + responseCode = response.getStatusLine().getStatusCode(); + } + } + return responseCode; + } + + public String getReasonPhrase() { + synchronized (mutex) { + if (reasonPhrase == null) { + reasonPhrase = response.getStatusLine().getReasonPhrase(); + } + } + return reasonPhrase; + } + + /** returns the timestamp (millis since 1970) when this request was started */ + public long getStartTime() { + return startTime; + } + + /** returns latency, in milliseconds, if value was initialized with a start time */ + public long getLatencyFullContent() { + return durationMillisOfFullContent; + } + + /** returns latency, in milliseconds, before response started coming in */ + public long getLatencyFirstResponse() { + return durationMillisOfFirstResponse; + } + + public Map<String, List<String>> getHeaderLists() { + synchronized (mutex) { + if (headerLists == null) { + Map<String, List<String>> headerListsMutable = Maps.newLinkedHashMap(); + for (Header header : response.getAllHeaders()) { + List<String> vals = headerListsMutable.get(header.getName()); + if (vals == null) { + vals = new ArrayList<String>(); + headerListsMutable.put(header.getName(), vals); + } + vals.add(header.getValue()); + } + headerLists = Collections.unmodifiableMap(headerListsMutable); + } + } + return headerLists; + } + + public byte[] getContent() { + synchronized (mutex) { + if (content == null) { + InputStream in = null; + try { + in = response.getEntity().getContent(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteStreams.copy(in, out); + content = out.toByteArray(); + } catch (IOException e) { + throw Throwables.propagate(e); + } finally { + Streams.closeQuietly(in); + } + } + } + return content; + } + + public String getContentAsString() { + return new String(getContent()); + } + + public Maybe<HttpResponse> getResponse() { + return Maybe.fromNullable(response); + } + + @Override + public String toString() { + return Objects.toStringHelper(getClass()) + .add("responseCode", responseCode) + .toString(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/03dbd281/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpUtils.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpUtils.java b/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpUtils.java deleted file mode 100644 index 8eeef19..0000000 --- a/utils/common/src/main/java/org/apache/brooklyn/util/http/HttpUtils.java +++ /dev/null @@ -1,387 +0,0 @@ -/* - * 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.util.http; - -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.test.Asserts; -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.stream.Streams; -import org.apache.brooklyn.util.time.Time; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.base.Throwables; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.ListeningExecutorService; - -/** - * Utility methods to aid testing HTTP. - * - * @author aled - */ -public class HttpUtils { - - // TODO Delete methods from TestUtils, to just have them here (or switch so TestUtils delegates here, - // and deprecate methods in TestUtils until deleted). - - private static final Logger LOG = LoggerFactory.getLogger(HttpUtils.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; - Asserts.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+" Asserts.failed to respond (in assertion that is reachable): "+e, e); - } - } - - public static void assertUrlUnreachable(String url) { - try { - int statusCode = getHttpStatusCode(url); - Asserts.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(Maps.newLinkedHashMap(), url); - } - - public static void assertUrlUnreachableEventually(Map 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); - Asserts.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+" Asserts.failed to respond (in assertion that result code is "+acceptableCodes+"): "+e, e); - } - } - - public static void assertHttpStatusCodeEventuallyEquals(final String url, final int expectedCode) { - assertHttpStatusCodeEventuallyEquals(Maps.newLinkedHashMap(), url, expectedCode); - } - - public static void assertHttpStatusCodeEventuallyEquals(Map 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); - Asserts.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); - Asserts.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); - Asserts.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); - Asserts.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); - Asserts.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); - Asserts.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); - Asserts.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); - Asserts.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.of(), url, phrase, additionalPhrases); - } - - public static void assertContentEventuallyContainsText(Map 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); - Asserts.assertNotNull(contents); - Asserts.assertTrue(contents.matches(regex), "Contents does not match expected regex ("+regex+"): "+contents); - } - - public static void assertContentEventuallyMatches(final String url, final String regex) { - Asserts.succeedsEventually(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 Asserts.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 <a href="http://docs.oracle.com/javase/8/docs/technotes/guides/net/http-keepalive.html">Persistent Connections</a> - */ - 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) {} - } -}
