All Downloads are FREE. Search and download functionalities are using the official Maven repository.

main.java.com.cloudant.http.HttpConnection Maven / Gradle / Ivy

There is a newer version: 2.9.0
Show newest version
//  Copyright (c) 2015 IBM Cloudant. All rights reserved.
//
//  Licensed 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.cloudant.http;

import com.cloudant.http.interceptors.BasicAuthInterceptor;
import com.cloudant.http.internal.AgentHelper;
import com.cloudant.http.internal.DefaultHttpUrlConnectionFactory;

import org.apache.commons.io.IOUtils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;


/**
 * Created by tomblench on 23/03/15.
 */

/**
 * 

* A wrapper for HttpURLConnections. *

* *

* Provides some convenience methods for making requests and sending/receiving data as streams, * strings, or byte arrays. *

* *

* Typical usage: *

* *
 * HttpConnection hc = new HttpConnection("POST", "application/json", new URL("http://somewhere"));
 * hc.requestProperties.put("x-some-header", "some-value");
 * hc.setRequestBody("{\"hello\": \"world\"});
 * String result = hc.execute().responseAsString();
 * // get the underlying HttpURLConnection if you need to do something a bit more advanced:
 * int response = hc.getConnection().getResponseCode();
 * hc.disconnect();
 * 
* *

* Important: this class is not thread-safe and HttpConnections should not be * shared across threads. *

* * @see java.net.HttpURLConnection */ public class HttpConnection { private static final Logger logger = Logger.getLogger(HttpConnection.class.getCanonicalName()); private final String requestMethod; public final URL url; private final String contentType; // created in executeInternal private HttpURLConnection connection; // set by the various setRequestBody() methods private InputStream input; private long inputLength; public final HashMap requestProperties; public final List requestInterceptors; public final List responseInterceptors; /** * A connectionFactory for opening the URLs, can be set, but configured with a default */ public HttpUrlConnectionFactory connectionFactory = new DefaultHttpUrlConnectionFactory(); private int numberOfRetries = 10; public HttpConnection(String requestMethod, URL url, String contentType) { this.requestMethod = requestMethod; this.url = url; this.contentType = contentType; this.requestProperties = new HashMap(); this.requestInterceptors = new LinkedList(); this.responseInterceptors = new LinkedList(); } /** * Sets the number of times this request can be retried. * This method must be called before {@link #execute()} * @param numberOfRetries the number of times this request can be retried. * @return an {@link HttpConnection} for method chaining */ public HttpConnection setNumberOfRetries(int numberOfRetries){ this.numberOfRetries = numberOfRetries; return this; } /** * Set the String of request body data to be sent to the server. * * @param input String of request body data to be sent to the server * @return an {@link HttpConnection} for method chaining */ public HttpConnection setRequestBody(final String input) { try { byte[] inputBytes = input.getBytes("UTF-8"); this.input = new ByteArrayInputStream(inputBytes); // input is in bytes, not characters this.inputLength = inputBytes.length; } catch (UnsupportedEncodingException e) { // This should never happen as every implementation of the java platform is required // to support UTF-8. } return this; } /** * Set the byte array of request body data to be sent to the server. * @param input byte array of request body data to be sent to the server * @return an {@link HttpConnection} for method chaining */ public HttpConnection setRequestBody(final byte[] input) { this.input = new ByteArrayInputStream(input); this.inputLength = input.length; return this; } /** * Set the InputStream of request body data to be sent to the server. * @param input InputStream of request body data to be sent to the server * @return an {@link HttpConnection} for method chaining */ public HttpConnection setRequestBody(InputStream input) { this.input = input; // -1 signals inputLength unknown this.inputLength = -1; return this; } /** * Set the InputStream of request body data, of known length, to be sent to the server. * @param input InputStream of request body data to be sent to the server * @param inputLength Length of request body data to be sent to the server, in bytes * @return an {@link HttpConnection} for method chaining */ public HttpConnection setRequestBody(InputStream input, long inputLength) { this.input = input; this.inputLength = inputLength; return this; } /** *

* Execute request without returning data from server. *

*

* Call {@code responseAsString}, {@code responseAsBytes}, or {@code responseAsInputStream} * after {@code execute} if the response body is required. *

* @return An {@link HttpConnection} which can be used to obtain the response body * @throws IOException if there was a problem writing data to the server */ public HttpConnection execute() throws IOException { boolean retry = true; int n = numberOfRetries; while (retry && n-- > 0) { connection = connectionFactory.openConnection(url); connection.setRequestProperty("User-Agent", AgentHelper.USER_AGENT); if (url.getUserInfo() != null) { requestInterceptors.add(new BasicAuthInterceptor(url.getUserInfo())); } // always read the result, so we can retrieve the HTTP response code connection.setDoInput(true); connection.setRequestMethod(requestMethod); if (contentType != null) { connection.setRequestProperty("Content-type", contentType); } HttpConnectionInterceptorContext currentContext = new HttpConnectionInterceptorContext(this); for (HttpConnectionRequestInterceptor requestInterceptor : requestInterceptors) { currentContext = requestInterceptor.interceptRequest(currentContext); } //set request properties after interceptors, in case the interceptors have added // to the properties map for (String key : requestProperties.keySet()) { connection.setRequestProperty(key, requestProperties.get(key)); } if (input != null) { connection.setDoOutput(true); if (inputLength != -1) { // TODO on 1.7 upwards this method takes a long, otherwise int connection.setFixedLengthStreamingMode((int) this.inputLength); } else { // TODO some situations where we can't do chunking, like multipart/related /// https://issues.apache.org/jira/browse/COUCHDB-1403 connection.setChunkedStreamingMode(1024); } // See "8.2.3 Use of the 100 (Continue) Status" in http://tools.ietf.org/html // /rfc2616 // Attempting to write to the connection's OutputStream may cause an exception to be // thrown. This is useful because it avoids sending large request bodies (such as // attachments) if the server is going to reject our request. Reasons for rejecting // requests could be 401 Unauthorized (eg cookie needs to be refreshed), etc. connection.setRequestProperty("Expect", "100-continue"); int bufSize = 1024; int nRead = 0; byte[] buf = new byte[bufSize]; InputStream is = input; OutputStream os = connection.getOutputStream(); while ((nRead = is.read(buf)) >= 0) { os.write(buf, 0, nRead); } os.flush(); // we do not call os.close() - on some JVMs this incurs a delay of several seconds // see http://stackoverflow.com/questions/19860436 } for (HttpConnectionResponseInterceptor responseInterceptor : responseInterceptors) { currentContext = responseInterceptor.interceptResponse(currentContext); } // retry flag is set from the final step in the response interceptRequest pipeline retry = currentContext.replayRequest; if (n == 0) { logger.info("Maximum number of retries reached"); } } // return ourselves to allow method chaining return this; } /** *

* Return response body data from server as a String. *

*

* Important: you must call execute() before calling this method. *

* @return String of response body data from server, if any * @throws IOException if there was a problem reading data from the server */ public String responseAsString() throws IOException { if (connection == null) { throw new IOException("Attempted to read response from server before calling execute()"); } InputStream is = connection.getInputStream(); String string = IOUtils.toString(is); is.close(); connection.disconnect(); return string; } /** *

* Return response body data from server as a byte array. *

*

* Important: you must call execute() before calling this method. *

* @return Byte array of response body data from server, if any * @throws IOException if there was a problem reading data from the server */ public byte[] responseAsBytes() throws IOException { if (connection == null) { throw new IOException("Attempted to read response from server before calling execute()"); } InputStream is = connection.getInputStream(); byte[] bytes = IOUtils.toByteArray(is); is.close(); connection.disconnect(); return bytes; } /** *

* Return response body data from server as an InputStream. *

*

* Important: you must call execute() before calling this method. *

* @return InputStream of response body data from server, if any * @throws IOException if there was a problem reading data from the server */ public InputStream responseAsInputStream() throws IOException { if (connection == null) { throw new IOException("Attempted to read response from server before calling execute()"); } InputStream is = connection.getInputStream(); return is; } /** * Get the underlying HttpURLConnection object, allowing clients to set/get properties not * exposed here. * @return HttpURLConnection the underlying {@link HttpURLConnection} object */ public HttpURLConnection getConnection() { return connection; } /** * Disconnect the underlying HttpURLConnection. Equivalent to calling: * * getConnection.disconnect() * */ public void disconnect() { connection.disconnect(); } /** * Factory used by HttpConnection to produce HttpUrlConnections. */ public interface HttpUrlConnectionFactory { /** * Called by HttpConnection to open URLs, can be implemented to provide customization. * * @param url the address of the URL to open * @return HttpURLConnection for the specified URL * @throws IOException if there is an issue communicating with the server */ HttpURLConnection openConnection(URL url) throws IOException; /** * Set a proxy server address. Note that this method must be called before {@link * HttpConnection#execute()} to have any effect. * * @param proxyUrl the URL of the HTTP proxy to use for this connection */ void setProxy(URL proxyUrl); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy