Java tutorial
/* * This file is part of Minus-Java. * * Minus-Java is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Minus-Java is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Minus-Java. If not, see <http://www.gnu.org/licenses/>. */ package net.paissad.minus.utils; import static net.paissad.minus.AppConstants.APP_USER_AGENT; import static net.paissad.minus.MinusConstants.MINUS_COOKIE_NAME; import static net.paissad.minus.MinusConstants.MINUS_DOMAIN_NAME; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.net.ssl.SSLHandshakeException; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; import org.apache.http.NoHttpResponseException; import org.apache.http.annotation.NotThreadSafe; import org.apache.http.client.CookieStore; import org.apache.http.client.HttpRequestRetryHandler; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.params.ClientPNames; import org.apache.http.client.params.CookiePolicy; import org.apache.http.client.protocol.ClientContext; import org.apache.http.cookie.Cookie; import org.apache.http.entity.FileEntity; import org.apache.http.impl.client.BasicCookieStore; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.cookie.BasicClientCookie2; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.ExecutionContext; import org.apache.http.protocol.HTTP; import org.apache.http.protocol.HttpContext; import org.apache.http.util.EntityUtils; import net.paissad.minus.api.MinusHttpResponse; import net.paissad.minus.exception.MinusException; /** * @author Papa Issa DIAKHATE (paissad) * */ public class HttpClientUtils { public static enum RequestType { GET, POST, } public static enum ExpectedResponseType { STRING, HTTP_ENTITY; } /** This class should not be instantiated. */ private HttpClientUtils() { } /** * Sends a HTTP GET request. * * @param baseURL - The base URL of the request. * @param parametersBody - The parameters of the request. * @param sessionId _ The session id to re-use. If <code>null</code> or * empty, then let the server give a new session id. * @param additionalRequestHeaders * @param expectedResponseType * @return An instance of {@link MinusHttpResponse} * @throws MinusException */ public static MinusHttpResponse doGet(final String baseURL, final Map<String, String> parametersBody, final String sessionId, final Header[] additionalRequestHeaders, final ExpectedResponseType expectedResponseType) throws MinusException { return sendRequest(baseURL, parametersBody, sessionId, RequestType.GET, additionalRequestHeaders, expectedResponseType); } /** * Sends a HTTP POST request. * * @param baseURL - The base URL of the request. * @param parametersBody - The parameters of the request. * @param sessionId _ The session id to re-use. If <code>null</code> or * empty, then let the server give a new session id. * @param additionalRequestHeaders * @param expectedResponseType * @return An instance of {@link MinusHttpResponse} * @throws MinusException */ public static MinusHttpResponse doPost(final String baseURL, final Map<String, String> parametersBody, final String sessionId, final Header[] additionalRequestHeaders, final ExpectedResponseType expectedResponseType) throws MinusException { return sendRequest(baseURL, parametersBody, sessionId, RequestType.POST, additionalRequestHeaders, expectedResponseType); } // _________________________________________________________________________ /** * Convenience method for sending HTTP requests. * <b><span style="color:red">Note</span></b>: This method is intended to be * used internally, not by end-users. * * @param baseURL - <b>Example</b>: http://minus.com/api * @param parametersBody - The parameters (name => value pairs) to pass to * the request. * @param sessionId - If <tt>null</tt> or empty, then create and use a new * session, otherwise, use the specified session_id (which is * stored in a cookie). * @param requestType * @param additionalRequestHeaders - * @param expectedResponseType * @return The response retrieved from Minus API. * @throws MinusException */ private static MinusHttpResponse sendRequest(final String baseURL, final Map<String, String> parametersBody, final String sessionId, final RequestType requestType, final Header[] additionalRequestHeaders, final ExpectedResponseType expectedResponseType) throws MinusException { DefaultHttpClient client = null; HttpRequestBase request = null; InputStream responseContent = null; boolean errorOccured = false; try { if (requestType == RequestType.GET) { request = new HttpGet(baseURL); if (parametersBody != null && !parametersBody.isEmpty()) { request = appendParametersToRequest(request, parametersBody); } } else if (requestType == RequestType.POST) { UrlEncodedFormEntity encodedEntity = new UrlEncodedFormEntity(getHttpParamsFromMap(parametersBody), HTTP.UTF_8); request = new HttpPost(baseURL); ((HttpPost) request).setEntity(encodedEntity); } else { throw new MinusException("The method (" + requestType + ") is unknown, weird ..."); } request.addHeader(new BasicHeader("User-Agent", APP_USER_AGENT)); if (additionalRequestHeaders != null && additionalRequestHeaders.length > 0) { for (Header aHeader : additionalRequestHeaders) { request.addHeader(aHeader); } } client = new DefaultHttpClient(); client.setHttpRequestRetryHandler(new MinusHttpRequestRetryHandler()); client.getParams().setParameter(ClientPNames.HANDLE_REDIRECTS, true); client.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.BEST_MATCH); CookieStore cookieStore = new BasicCookieStore(); Cookie sessionCookie = null; if (sessionId != null && !sessionId.trim().isEmpty()) { sessionCookie = new BasicClientCookie2(MINUS_COOKIE_NAME, sessionId); ((BasicClientCookie2) sessionCookie).setPath("/"); ((BasicClientCookie2) sessionCookie).setDomain(MINUS_DOMAIN_NAME); ((BasicClientCookie2) sessionCookie).setVersion(0); cookieStore.addCookie(sessionCookie); } client.setCookieStore(cookieStore); HttpContext localContext = new BasicHttpContext(); // Bind custom cookie store to the local context localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore); // Execute the request ... pass local context as a parameter HttpResponse resp = client.execute(request, localContext); // Let's update the cookie have the name 'sessionid' for (Cookie aCookie : client.getCookieStore().getCookies()) { if (aCookie.getName().equals(MINUS_COOKIE_NAME)) { sessionCookie = aCookie; break; } } Object result = null; int statusCode = resp.getStatusLine().getStatusCode(); if (statusCode == HttpStatus.SC_OK) { HttpEntity entity = resp.getEntity(); if (entity != null) { if (expectedResponseType == ExpectedResponseType.STRING) { result = EntityUtils.toString(entity); EntityUtils.consume(entity); } else if (expectedResponseType == ExpectedResponseType.HTTP_ENTITY) { result = entity; } } } else { // The response code is not OK. StringBuilder errMsg = new StringBuilder(); errMsg.append("HTTP ").append(requestType).append(" failed => ").append(resp.getStatusLine()); if (request != null) { errMsg.append(" : ").append(request.getURI()); } throw new MinusException(errMsg.toString()); } return new MinusHttpResponse(result, sessionCookie); } catch (Exception e) { errorOccured = true; if (request != null) { request.abort(); } String errMsg = "Error while executing the HTTP " + requestType + " request : " + e.getMessage(); throw new MinusException(errMsg, e); } finally { if (client != null) { // We must not close the client is the expected response is an // InputStream. Indeed, if ever we close the client, we won't be // able to read the response because of SocketException. if (errorOccured) { client.getConnectionManager().shutdown(); } else if (expectedResponseType != ExpectedResponseType.HTTP_ENTITY) { client.getConnectionManager().shutdown(); } } CommonUtils.closeAllStreamsQuietly(responseContent); } } // _________________________________________________________________________ /** * * @param baseURL * @param parametersBody * @param sessionId * @param fileToUpload - The file to upload. * @param filename - The name of the file to use during the upload process. * @return The response received from the Minus API. * @throws MinusException */ public static MinusHttpResponse doUpload(final String baseURL, final Map<String, String> parametersBody, final String sessionId, final File fileToUpload, final String filename) throws MinusException { DefaultHttpClient client = null; HttpPost uploadRequest = null; InputStream responseContent = null; try { String url = baseURL; if (parametersBody != null && !parametersBody.isEmpty()) { url += CommonUtils.encodeParams(parametersBody); } uploadRequest = new HttpPost(url); client = new DefaultHttpClient(); client.setHttpRequestRetryHandler(new MinusHttpRequestRetryHandler()); FileEntity fileEntity = new FileEntity(fileToUpload, "application/octet-stream"); uploadRequest.setEntity(fileEntity); // We add this headers as specified by the Minus.com API during file // upload. uploadRequest.addHeader("Content-Disposition", "attachment; filename=a.bin"); uploadRequest.addHeader("Content-Type", "application/octet-stream"); client.getParams().setParameter(ClientPNames.HANDLE_REDIRECTS, true); client.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.BEST_MATCH); CookieStore cookieStore = new BasicCookieStore(); Cookie sessionCookie = null; if (sessionId != null && !sessionId.trim().isEmpty()) { sessionCookie = new BasicClientCookie2(MINUS_COOKIE_NAME, sessionId); ((BasicClientCookie2) sessionCookie).setPath("/"); ((BasicClientCookie2) sessionCookie).setDomain(MINUS_DOMAIN_NAME); ((BasicClientCookie2) sessionCookie).setVersion(0); cookieStore.addCookie(sessionCookie); } client.setCookieStore(cookieStore); HttpContext localContext = new BasicHttpContext(); localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore); HttpResponse httpResponse = client.execute(uploadRequest); // Let's update the cookie have the name 'sessionid' for (Cookie aCookie : client.getCookieStore().getCookies()) { if (aCookie.getName().equals(MINUS_COOKIE_NAME)) { sessionCookie = aCookie; break; } } StringBuilder result = new StringBuilder(); int statusCode = httpResponse.getStatusLine().getStatusCode(); if (statusCode == HttpStatus.SC_OK) { HttpEntity respEntity = httpResponse.getEntity(); if (respEntity != null) { result.append(EntityUtils.toString(respEntity)); EntityUtils.consume(respEntity); } } else { // The response code is not OK. StringBuilder errMsg = new StringBuilder(); errMsg.append(" Upload failed => ").append(httpResponse.getStatusLine()); if (uploadRequest != null) { errMsg.append(" : ").append(uploadRequest.getURI()); } throw new MinusException(errMsg.toString()); } return new MinusHttpResponse(result.toString(), sessionCookie); } catch (Exception e) { if (uploadRequest != null) { uploadRequest.abort(); } String errMsg = "Error while uploading file (" + fileToUpload + ") : " + e.getMessage(); throw new MinusException(errMsg, e); } finally { if (client != null) { client.getConnectionManager().shutdown(); } CommonUtils.closeAllStreamsQuietly(responseContent); } } // _________________________________________________________________________ private static List<NameValuePair> getHttpParamsFromMap(final Map<String, String> parametersBody) { List<NameValuePair> params = new ArrayList<NameValuePair>(); if (parametersBody != null && !parametersBody.isEmpty()) { Iterator<Entry<String, String>> iter = parametersBody.entrySet().iterator(); while (iter.hasNext()) { Entry<String, String> o = iter.next(); params.add(new BasicNameValuePair(o.getKey(), o.getValue())); } } return params; } // _________________________________________________________________________ /** * Adds/appends the content of the map to the current parameters of the * specified {@link HttpRequestBase}. * * @param request - The request for which we want to add new parameters. * @param parametersBody - The parameters to add. */ private static HttpRequestBase appendParametersToRequest(HttpRequestBase request, final Map<String, String> parametersBody) { Iterator<Entry<String, String>> iter = parametersBody.entrySet().iterator(); while (iter.hasNext()) { Entry<String, String> o = iter.next(); request.getParams().setParameter(o.getKey(), o.getValue()); } return request; } // _________________________________________________________________________ // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/fundamentals.html#d4e292 @NotThreadSafe private static class MinusHttpRequestRetryHandler implements HttpRequestRetryHandler { private static final int MAX_RETRIES = 5; @Override public boolean retryRequest(IOException exception, int executionCount, HttpContext context) { if (executionCount >= MAX_RETRIES) { return false; // Do not retry if over max retry count } if (exception instanceof NoHttpResponseException) { return true; // Retry if the server dropped connection on us } if (exception instanceof SSLHandshakeException) { return false; // Do not retry on SSL handshake exception } HttpRequest request = (HttpRequest) context.getAttribute(ExecutionContext.HTTP_REQUEST); boolean idempotent = !(request instanceof HttpEntityEnclosingRequest); if (idempotent) { return true; // Retry if the request is considered idempotent } return false; } } // _________________________________________________________________________ }