Java tutorial
/* Copyright (c) 2011-2013 Pushing Inertia * All rights reserved. http://pushinginertia.com * * 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.pushinginertia.commons.net.client; import org.apache.http.client.methods.HttpPost; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.URL; import java.net.URLConnection; import java.net.URLEncoder; import java.net.UnknownHostException; import java.util.Map; import java.util.NoSuchElementException; import java.util.Scanner; /** * A base implementation of an http client that allows configuration of the connection properties and handles * HTTP POST request/response processing. * @param <C> the type of the connection object that will be instantiated by {@link #configureConnection(int)} */ public abstract class AbstractHttpPostClient<C extends HttpURLConnection> { private static final Logger LOG = LoggerFactory.getLogger(AbstractHttpPostClient.class); private String hostName; private int port; private String path; private int connectionTimeoutMillis = 15000; private String userAgent = "JavaHttpPostClient"; protected abstract String getUrl(); public void setHostName(final String hostName) { LOG.debug("Setting host to: " + hostName); this.hostName = hostName; } public String getHostName() { return hostName; } public void setPort(final int port) { LOG.debug("Setting port to: " + port); this.port = port; } public int getPort() { return port; } public String getPath() { return path; } public void setPath(final String path) { LOG.debug("Setting path to: " + path); this.path = path; } public int getConnectionTimeout() { return connectionTimeoutMillis; } /** * Sets the connection timeout to a custom value. See {@link #connectionTimeoutMillis} for the default setting. * @param timeoutMillis new value in milliseconds to set the connection timeout to */ public void setConnectionTimeout(final int timeoutMillis) { LOG.debug("Setting connection timeout to: " + timeoutMillis + "ms"); this.connectionTimeoutMillis = timeoutMillis; } public String getUserAgent() { return userAgent; } public void setUserAgent(final String userAgent) { LOG.debug("Setting user agent to: " + userAgent); this.userAgent = userAgent; } /** * Sends a message of name-value pairs to the remote host and returns its response. * @param postParameters name-value pairs to post * @param encoding character encoding * @return response received from the host * @throws HttpConnectException if the connection to the remote host cannot be completed or there is a problem encoding the request */ public String sendMessage(final Map<String, String> postParameters, final String encoding) throws HttpConnectException { final StringBuilder sb = new StringBuilder(); try { for (final Map.Entry<String, String> param : postParameters.entrySet()) { if (sb.length() > 0) sb.append('&'); sb.append(URLEncoder.encode(param.getKey(), encoding)); sb.append('='); sb.append(URLEncoder.encode(param.getValue(), encoding)); } return sendMessage(sb.toString(), encoding); } catch (UnsupportedEncodingException e) { LOG.error("Unable to encode parameter.", e); throw new HttpConnectException("Unable to encode parameter.", e); } } /** * Sends a message of name-value pairs to the remote host and returns its response. * @param requestPayload encoded name-value pairs using {@link URLEncoder#encode(String, String)} * @param encoding character encoding * @return response received from the host * @throws HttpConnectException if a connection to the remote host cannot be completed * @see #sendMessage(java.util.Map, String) */ public String sendMessage(final String requestPayload, final String encoding) throws HttpConnectException { LOG.info("Sending payload to host: " + requestPayload); // 1. create a connection instance final C con = configureConnection(requestPayload.length()); // 2. send request connectAndSend(con, requestPayload); // 3. check for a 200 OK response code verifyResponseCode(con); // 4. retrieve response message return getResponseMessage(con, encoding); } /** * Creates an http connection to the remote host so that a POST request can be made. This only creates the * connection instance and configures it, but does not actually open the remote connection. * * @param contentLength number of bytes in the payload to send * @return never null * @throws HttpConnectException if a problem occurs trying to instantiate the connection object */ protected C configureConnection(final int contentLength) throws HttpConnectException { try { final URL u = new URL(getUrl()); @SuppressWarnings("unchecked") final C con = (C) u.openConnection(); con.setDoOutput(true); // indicates a POST request con.setRequestMethod(HttpPost.METHOD_NAME); con.setFixedLengthStreamingMode(contentLength); // content length is known so set it for efficiency con.setConnectTimeout(getConnectionTimeout()); // default value is zero (never time out) con.setRequestProperty("Accept", "application/xml"); con.setRequestProperty("Content-Type", "application/xml"); con.setRequestProperty("User-Agent", userAgent); return con; } catch (Exception e) { final String msg = "Cannot open connection to [" + getUrl() + "]: " + e.getMessage(); LOG.error(getClass().getSimpleName(), msg, e); throw new HttpConnectException(msg, e); } } /** * Opens the TCP connection to the remote host and sends the given payload. * * @param con instantiated connection * @param payload data to send * @throws HttpConnectException if there is a communication problem with the remote host */ protected void connectAndSend(final URLConnection con, final String payload) throws HttpConnectException { try { final OutputStream os = new BufferedOutputStream(con.getOutputStream()); os.write(payload.getBytes()); os.flush(); } catch (UnknownHostException e) { // thrown when the host name cannot be resolved final String msg = "Cannot resolve host [" + getHostName() + "]: " + e.getMessage(); LOG.error(getClass().getSimpleName(), msg, e); throw new HttpConnectException(msg, e); } catch (SocketTimeoutException e) { final String msg = "Timed out waiting for connection to [" + getUrl() + "]: " + e.getMessage(); LOG.error(getClass().getSimpleName(), msg, e); throw new HttpConnectException(msg, e); } catch (SocketException e) { final String msg = "Failed to connect to [" + getUrl() + "]: " + e.getMessage(); LOG.error(getClass().getSimpleName(), msg, e); throw new HttpConnectException(msg, e); } catch (Exception e) { final String msg = "A communication error occurred while trying to send payload to [" + getUrl() + "]: " + e.getMessage(); LOG.error(getClass().getSimpleName(), msg, e); throw new HttpConnectException(msg, e); } } /** * Verifies that the response code is {@link java.net.HttpURLConnection#HTTP_OK}. * * @param con instantiated connection * @throws HttpConnectException if the response code is bad or some communication problem with the remote host occurs */ protected void verifyResponseCode(final HttpURLConnection con) throws HttpConnectException { try { if (con.getResponseCode() != HttpURLConnection.HTTP_OK) { final String msg = "Bad HTTP response code [" + con.getResponseCode() + ": " + con.getResponseMessage() + "] reported by host: " + getHostName(); LOG.error(getClass().getSimpleName(), msg); throw new HttpConnectException(msg); } } catch (HttpConnectException e) { // don't double-wrap the exception! throw e; } catch (Exception e) { final String msg = "An unexpected error occurred while reading the response code from [" + getUrl() + "]: " + e.getMessage(); LOG.error(getClass().getSimpleName(), msg, e); throw new HttpConnectException(msg, e); } } /** * Retrieves the response message from the remote host. * * @param con instantiated connection * @param encoding encoding to use in the response * @return response message from the remote host or null if none exists * @throws HttpConnectException if there is a problem retrieving the message */ protected String getResponseMessage(final HttpURLConnection con, final String encoding) throws HttpConnectException { try { final InputStream is = con.getInputStream(); final Scanner s = new Scanner(is, encoding); s.useDelimiter("\\A"); // \A is the beginning of input if (s.hasNext()) return s.next(); return null; } catch (NoSuchElementException e) { // no input return null; } catch (Exception e) { final String msg = "An unexpected error occurred while trying to retrieve the response message from [" + getUrl() + "]: " + e.getMessage(); LOG.error(getClass().getSimpleName(), msg, e); throw new HttpConnectException(msg, e); } } }