Java tutorial
/* * Copyright 2010 DTO Labs, Inc. (http://dtolabs.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.dtolabs.client.utils; import com.dtolabs.rundeck.core.CoreException; import com.dtolabs.utils.Streams; import org.apache.commons.httpclient.*; import org.apache.commons.httpclient.methods.*; import org.apache.log4j.Logger; import java.io.*; import java.net.*; import java.net.URI; import java.util.*; /** * HttpClientChannel abstract base for making an HTTP request and getting the reply data. Subclasses should implement the * abstract methods. Also, the {@link #preMakeRequest(org.apache.commons.httpclient.HttpMethod) preMakeRequest} method * can be overridden. */ abstract class HttpClientChannel implements BaseHttpClient { static Logger logger = Logger.getLogger(HttpClientChannel.class.getName()); private HttpClient httpc; private HttpMethod httpMethod; private InputStream resultStream; private String resultType; private int resultCode; private String reasonCode; boolean requestMade = false; private String requestUrl; private HttpMethod reqMadeMethod; private URL requestURL; private OutputStream destinationStream; private int contentLengthRetrieved; private String expectedContentType; private String methodType = "GET"; /** contains request headers to be reused if requests need to be recreated for auth purposes */ private HashMap reqHeaders = new HashMap(); /** * Create a connection to a URL with a username, password, and Map of query parameters. * * @param uriSpec URL for request * @param query Map of name and values for the request query string parameters */ public HttpClientChannel(String uriSpec, Map query) throws CoreException { init(uriSpec, query); } /** * Create a connection to a URL with a username, password, and Map of query parameters. * * @param uriSpec URL for request * @param query Map of name and values for the request query string parameters */ public HttpClientChannel(String uriSpec, Map query, OutputStream destinationStream) throws CoreException { this(uriSpec, query); this.destinationStream = destinationStream; } /** * Create a connection to a URL with a username, password, and Map of query parameters. * * @param uriSpec URL for request * @param query Map of name and values for the request query string parameters */ public HttpClientChannel(String uriSpec, Map query, OutputStream destinationStream, String expectedContentType) throws CoreException { this(uriSpec, query, destinationStream); this.expectedContentType = expectedContentType; } /** * Process the URL based on the query params, and create the HttpURLConnection with appropriate headers for username * and password. */ protected void init(String uriSpec, Map query) throws CoreException { requestUrl = uriSpec; if (query != null && query.size() > 0) { requestUrl = constructURLQuery(uriSpec, query); } URI uri; try { uri = new URI(requestUrl); } catch (URISyntaxException e) { throw new CoreException(e.getMessage()); } requestURL = null; try { requestURL = uri.toURL(); } catch (MalformedURLException e) { throw new CoreException(e.getMessage()); } logger.debug("creating connection object to URL: " + requestUrl); httpc = new HttpClient(); } private static final HashSet validMethodTypes = new HashSet(); static { validMethodTypes.add("GET"); validMethodTypes.add("POST"); validMethodTypes.add("PUT"); validMethodTypes.add("DELETE"); } public void setMethodType(String type) { if (!validMethodTypes.contains(type.toUpperCase())) { throw new IllegalArgumentException("Unknown method type: " + type); } this.methodType = type; } /** * Create new HttpMethod objects for the requestUrl, and set any request headers specified previously */ private HttpMethod initMethod() { if ("GET".equalsIgnoreCase(getMethodType())) { httpMethod = new GetMethod(requestUrl); } else if ("POST".equalsIgnoreCase(getMethodType())) { httpMethod = new PostMethod(requestUrl); } else if ("PUT".equalsIgnoreCase(getMethodType())) { httpMethod = new PutMethod(requestUrl); } else if ("DELETE".equalsIgnoreCase(getMethodType())) { httpMethod = new DeleteMethod(requestUrl); } else { throw new IllegalArgumentException("Unknown method type: " + getMethodType()); } if (reqHeaders.size() > 0) { for (Iterator i = reqHeaders.keySet().iterator(); i.hasNext();) { String s = (String) i.next(); String v = (String) reqHeaders.get(s); httpMethod.setRequestHeader(s, v); } } return httpMethod; } /** * Create new URL with query parameters */ static String constructURLQuery(String urlbase, Map query) { StringBuffer sb = new StringBuffer(urlbase); if (null != query) { sb.append("?"); boolean seen = false; for (final Object o : query.entrySet()) { if (seen) { sb.append("&"); } seen = true; Map.Entry entry = (Map.Entry) o; String key = (String) entry.getKey(); Object val = entry.getValue(); if (null == val) { val = ""; } try { sb.append(URLEncoder.encode(key, "UTF-8")).append("=") .append(URLEncoder.encode(val.toString(), "UTF-8")); } catch (java.io.UnsupportedEncodingException exc) { throw new RuntimeException(exc); } } } return sb.toString(); } /** * Set a Header field for the request. Must be made before makeConnection is called. */ public void setRequestHeader(String name, String value) { reqHeaders.put(name, value); } /** * applies request properties from map onto the url connection object */ private void setHeaders(Map map) { for (final Object o : map.entrySet()) { Map.Entry entry = (Map.Entry) o; String key = (String) entry.getKey(); String value = (String) entry.getValue(); setRequestHeader(key, value); } } /** * subclasses should return a byte[] of data, or null. This will only be called if the {@link #isPostMethod()} method * returns true. */ protected abstract RequestEntity getRequestEntity(PostMethod method); /** * subclasses should return a byte[] of data, or null. This will only be called if the {@link #isPostMethod()} method * returns true. */ protected abstract NameValuePair[] getRequestBody(PostMethod method); /** * Return true if the request method is POST. * @return */ protected abstract boolean isPostMethod(); /** * this method is called at the end of makeRequest, allowing subclasses to handle the result before makeRequest * returns. */ protected abstract void postMakeRequest(); /** * Called before making the request. Subclasses may override to perform other logic. Return true to continue making * the request, or false to finish without making the request. */ protected boolean preMakeRequest(HttpMethod method) throws HttpClientException { return true; } /** * Called before making the request. Subclasses may override to perform other logic. Return true to continue making * the request, or false to finish without making the request. May be called again if {@link #needsReAuthentication(int, org.apache.commons.httpclient.HttpMethod)} returns true. */ protected boolean doAuthentication(HttpMethod method) throws HttpClientException { return true; } /** * Return true if initial result of request indicates that authentication needs to be performed again. * @param resultCode * @param method * @return */ protected boolean needsReAuthentication(int resultCode, HttpMethod method) { return false; } /** * Gets the HttpClient used in the request making. * * @return */ HttpClient getHttpClient() { return httpc; } public HttpMethod getRequestMethod() { return reqMadeMethod; } /** * Gets headers from the response. * * @param name * * @return */ public Header getResponseHeader(String name) { return reqMadeMethod.getResponseHeader(name); } /** * Return the raw bytes from the response. * @return byte[] of the result data, or null if nothing returned or request not yet made * @throws IOException */ public byte[] getResponseBody() throws IOException { if (reqMadeMethod != null) return reqMadeMethod.getResponseBody(); else return null; } /** * Perform the HTTP request. Can only be performed once. */ public void makeRequest() throws IOException, HttpClientException { if (requestMade) { return; } requestMade = true; RequestEntity reqEntity = null; NameValuePair[] postBody = null; if (isPostMethod()) { setMethodType("POST"); } HttpMethod method = initMethod(); if (isPostMethod()) { reqEntity = getRequestEntity((PostMethod) method); if (null != reqEntity) { logger.debug("preparing to post request entity data: " + reqEntity.getContentType()); ((PostMethod) method).setRequestEntity(reqEntity); } else { logger.debug("preparing to post form data"); postBody = getRequestBody((PostMethod) method); ((PostMethod) method).setRequestBody(postBody); } } logger.debug("calling preMakeRequest"); if (!preMakeRequest(method)) { return; } logger.debug("calling doAuthentication..."); if (!doAuthentication(method)) { return; } int bytesread = 0; try { if (!isPostMethod()) { method.setFollowRedirects(true); } logger.debug("make request..."); resultCode = httpc.executeMethod(method); reasonCode = method.getStatusText(); if (isPostMethod()) { //check redirect after post method = checkFollowRedirect(method, resultCode); } logger.debug("check needs reauth..."); if (needsReAuthentication(resultCode, method)) { logger.debug("re-authentication needed, performing..."); method.releaseConnection(); method.abort(); //need to re-authenticate. method = initMethod(); if (isPostMethod() && null != reqEntity) { ((PostMethod) method).setRequestEntity(reqEntity); } else if (isPostMethod() && null != postBody) { ((PostMethod) method).setRequestBody(postBody); } if (!doAuthentication(method)) { //user login failed return; } //user login has succeeded logger.debug("remaking original request..."); resultCode = httpc.executeMethod(method); reasonCode = method.getStatusText(); if (needsReAuthentication(resultCode, method)) { //user request was unauthorized throw new HttpClientException("Unauthorized Action: " + (null != method.getResponseHeader(Constants.X_RUNDECK_ACTION_UNAUTHORIZED_HEADER) ? method.getResponseHeader(Constants.X_RUNDECK_ACTION_UNAUTHORIZED_HEADER) .getValue() : reasonCode)); } } logger.debug("finish..."); if (null != method.getResponseHeader("Content-Type")) { resultType = method.getResponseHeader("Content-Type").getValue(); } String type = resultType; if (type != null && type.indexOf(";") > 0) { type = type.substring(0, type.indexOf(";")).trim(); } if (null == expectedContentType || expectedContentType.equals(type)) { if (null != destinationStream && resultCode >= 200 && resultCode < 300) { //read the input stream and write it to the destination contentLengthRetrieved = Streams.copyStreamCount(method.getResponseBodyAsStream(), destinationStream); } else { final ByteArrayOutputStream outputBytes = new ByteArrayOutputStream(1024 * 50); Streams.copyStream(method.getResponseBodyAsStream(), outputBytes); resultStream = new ByteArrayInputStream(outputBytes.toByteArray()); } } reqMadeMethod = method; } catch (HttpException e) { logger.error("HTTP error: " + e.getMessage(), e); } finally { method.releaseConnection(); } logger.debug("Response received"); postMakeRequest(); } private HttpMethod checkFollowRedirect(final HttpMethod method, final int res) throws IOException, HttpClientException { if ((res == HttpStatus.SC_MOVED_TEMPORARILY) || (res == HttpStatus.SC_MOVED_PERMANENTLY) || (res == HttpStatus.SC_SEE_OTHER) || (res == HttpStatus.SC_TEMPORARY_REDIRECT)) { final Header locHeader = method.getResponseHeader("Location"); if (locHeader == null) { throw new HttpClientException("Redirect with no Location header, request URL: " + method.getURI()); } final String location = locHeader.getValue(); logger.debug("Follow redirect: " + res + ": " + location); method.releaseConnection(); final GetMethod followMethod = new GetMethod(location); followMethod.setFollowRedirects(true); resultCode = httpc.executeMethod(followMethod); reasonCode = followMethod.getStatusText(); logger.debug("Result: " + resultCode); return followMethod; } return method; } /** * return content type */ public String getResultContentType() { return this.resultType; } /** * Get the HTTP response code. * @return */ int getResultCode() { return resultCode; } /** * Get the URL used for the request. * @return */ URL getRequestURL() { return requestURL; } public int getContentLengthRetrieved() { return contentLengthRetrieved; } public String getExpectedContentType() { return expectedContentType; } public String getMethodType() { return methodType; } public String getReasonCode() { return reasonCode; } public InputStream getResultStream() { return resultStream; } }