Java tutorial
/** * Copyright (C) 2013 Cloudant * * Copyright (C) 2011 Ahmed Yehia (ahmed.yehia.m@gmail.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.cloudant.mazha; import com.cloudant.mazha.json.JSONHelper; import com.cloudant.sync.util.Misc; import com.google.common.base.Strings; import com.google.common.io.Resources; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpRequestInterceptor; import org.apache.http.HttpResponse; import org.apache.http.HttpResponseInterceptor; import org.apache.http.HttpStatus; import org.apache.http.client.HttpClient; 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.HttpRequestBase; import org.apache.http.client.params.HttpClientParams; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.params.ConnManagerParams; import org.apache.http.conn.params.ConnPerRoute; import org.apache.http.conn.routing.HttpRoute; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.InputStreamEntity; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.apache.http.params.HttpProtocolParams; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URL; import java.util.Properties; public class HttpRequests { public static final int CONN_PER_ROUT = 4; private HttpClient httpClient; private JSONHelper jsonHelper; private HttpHost host; private BasicHttpContext context; private boolean debugging; private String authHeaderValue; public HttpRequests(CouchConfig config) { this.httpClient = createHttpClient(config); this.jsonHelper = new JSONHelper(); String username = config.getUsername(); String password = config.getPassword(); if (!Strings.isNullOrEmpty(username) && !Strings.isNullOrEmpty(password)) { String authString = username + ":" + password; Base64 base64 = new Base64(); authHeaderValue = "Basic " + new String(base64.encode(authString.getBytes())); } } // HTTP GET Requests InputStream get(URI uri) { HttpGet get = new HttpGet(uri); get.addHeader("Accept", "application/json"); return get(get); } InputStream getCompressed(URI uri) { HttpGet get = new HttpGet(uri); get.addHeader("Accept-Encoding", "gzip"); return get(get); } InputStream get(HttpGet httpGet) { HttpResponse response = executeRequest(httpGet); return getStream(response); } HttpResponse getResponse(URI uri) { HttpGet get = new HttpGet(uri); get.addHeader("Accept", "application/json"); return getResponse(get); } HttpResponse getResponse(HttpGet httpGet) { return executeRequest(httpGet); } // HTTP DELETE Requests InputStream delete(URI uri) { HttpDelete delete = new HttpDelete(uri); return delete(delete); } InputStream delete(HttpDelete delete) { HttpResponse response = deleteResponse(delete); return getStream(response); } HttpResponse deleteResponse(URI uri) { HttpDelete delete = new HttpDelete(uri); return deleteResponse(delete); } HttpResponse deleteResponse(HttpDelete delete) { return executeRequest(delete); } // HTTP PUT Requests InputStream put(URI uri) { HttpPut put = new HttpPut(uri); return getStream(this.putResponse(put)); } InputStream put(URI uri, String payload) { HttpPut put = new HttpPut(uri); put.addHeader("Accept", "application/json"); setEntity(put, payload); return getStream(this.putResponse(put)); } InputStream put(URI uri, String contentType, byte[] payload) { HttpPut put = new HttpPut(uri); put.addHeader("Accept", "application/json"); setEntity(put, contentType, payload); return getStream(this.putResponse(put)); } InputStream putStream(URI uri, String contentType, InputStream is, long contentLength) { HttpPut put = new HttpPut(uri); put.addHeader("Accept", "application/json"); setEntity(put, contentType, is, contentLength); return getStream(this.putResponse(put)); } HttpResponse putResponse(HttpPut put) { return executeRequest(put); } // HTTP POST Requests InputStream post(URI uri, String payload) { HttpResponse response = postResponse(uri, payload); return getStream(response); } HttpResponse postResponse(URI uri, String payload) { HttpPost post = new HttpPost(uri); setEntity(post, payload); return executeRequest(post); } // HTTP HEAD Requests HttpResponse head(URI uri) { HttpHead head = new HttpHead(uri); return executeRequest(head); } /** * Executes a HTTP request. * * @param request The HTTP request to execute. * @return {@link org.apache.http.HttpResponse} */ private HttpResponse executeRequest(HttpRequestBase request) { try { if (authHeaderValue != null) { request.addHeader("Authorization", authHeaderValue); } HttpResponse response = httpClient.execute(host, request, context); validate(request, response); return response; } catch (IOException e) { request.abort(); throw new ServerException(e); } } /** * @return {@link org.apache.http.impl.client.DefaultHttpClient} instance. */ private HttpClient createHttpClient(CouchConfig config) { try { this.host = new HttpHost(config.getHost(), config.getPort(), config.getProtocol()); this.context = new BasicHttpContext(); HttpParams params = getHttpConnectionParams(config); ClientConnectionManager manager = getClientConnectionManager(params); DefaultHttpClient httpClient = new DefaultHttpClient(manager, params); addDebuggingInterceptor(httpClient); return httpClient; } catch (Exception e) { throw new IllegalStateException(e); } } private ClientConnectionManager getClientConnectionManager(HttpParams params) { SchemeRegistry schemeRegistry = new SchemeRegistry(); schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443)); return new ThreadSafeClientConnManager(params, schemeRegistry); } private HttpParams getHttpConnectionParams(CouchConfig config) { BasicHttpParams params = new BasicHttpParams(); // Turn off stale checking. Our connections break all the time anyway, // and it's not worth it to pay the penalty of checking every time. HttpConnectionParams.setStaleCheckingEnabled(params, config.isStaleConnectionCheckingEnabled()); HttpConnectionParams.setConnectionTimeout(params, config.getConnectionTimeout()); HttpConnectionParams.setSoTimeout(params, config.getSocketTimeout()); HttpConnectionParams.setSocketBufferSize(params, config.getBufferSize()); // Don't handle redirects -- return them to the caller. Our code // often wants to re-POST after a redirect, which we must do ourselves. HttpClientParams.setRedirecting(params, config.isHandleRedirectEnabled()); // Set the specified user agent and register standard protocols. HttpProtocolParams.setUserAgent(params, this.getUserAgent()); ConnManagerParams.setMaxConnectionsPerRoute(params, new ConnPerRoute() { @Override public int getMaxForRoute(HttpRoute route) { return CONN_PER_ROUT; } }); return params; } private static final String default_user_agent = "CloudantSync"; private static String userAgent = null; public String getUserAgent() { if (userAgent == null) { String ua = this.getUserAgentFromResource(); if (Misc.isRunningOnAndroid()) { try { Class c = Class.forName("android.os.Build$VERSION"); String codename = (String) c.getField("CODENAME").get(null); int sdkInt = c.getField("SDK_INT").getInt(null); userAgent = String.format("%s Android %s %d", ua, codename, sdkInt); } catch (Exception e) { userAgent = String.format("%s Android unknown version", ua); } } else { userAgent = String.format("%s (%s; %s; %s)", ua, System.getProperty("os.arch"), System.getProperty("os.name"), System.getProperty("os.version")); } } return userAgent; } private String getUserAgentFromResource() { final URL url = getClass().getClassLoader().getResource("mazha.properties"); final Properties properties = new Properties(); try { properties.load(Resources.newInputStreamSupplier(url).getInput()); return properties.getProperty("user.agent", default_user_agent); } catch (Exception ioException) { return default_user_agent; } } private void addDebuggingInterceptor(DefaultHttpClient httpclient) { if (!isDebugging()) { return; } // request interceptor httpclient.addRequestInterceptor(new HttpRequestInterceptor() { public void process(final HttpRequest request, final HttpContext context) throws IOException { System.out.println(">> " + request.getRequestLine()); } }); // response interceptor httpclient.addResponseInterceptor(new HttpResponseInterceptor() { public void process(final HttpResponse response, final HttpContext context) throws IOException { System.out.println("<< Status: " + response.getStatusLine().getStatusCode()); } }); } /** * General validation of response, and only checks 404 error, and it leaves caller to check specific error code. */ private void validate(HttpRequest request, HttpResponse response) throws CouchException { int code = response.getStatusLine().getStatusCode(); if (code == 200 || code == 201 || code == 202) { // success (ok | created | accepted) return; } String msg = String.format("Request: %s << Status: %s (%s) ", request.getRequestLine(), code, response.getStatusLine().getReasonPhrase()); if (code == HttpStatus.SC_NOT_FOUND) { throw new NoResourceException(msg); } else { CouchException exception = null; try { exception = getCouchErrorFromResponse(response); } catch (Exception e) { throw new ServerException(msg); } throw exception; } } private CouchException getCouchErrorFromResponse(HttpResponse response) throws IOException { int code = response.getStatusLine().getStatusCode(); InputStream is = null; try { is = response.getEntity().getContent(); return this.jsonHelper.fromJson(new InputStreamReader(is), CouchException.class); } catch (IOException e) { return new CouchException("Unknown error", code); } finally { IOUtils.closeQuietly(is); } } protected void setEntity(HttpEntityEnclosingRequestBase httpRequest, String json) { try { StringEntity entity = new StringEntity(json, "UTF-8"); entity.setContentType("application/json"); httpRequest.setEntity(entity); } catch (UnsupportedEncodingException e) { throw new IllegalArgumentException(e); } } protected void setEntity(HttpEntityEnclosingRequestBase httpRequest, String contentType, byte[] data) { ByteArrayEntity entity = new ByteArrayEntity(data); entity.setContentType(contentType); httpRequest.setEntity(entity); } protected void setEntity(HttpEntityEnclosingRequestBase httpRequest, String contentType, InputStream is, long contentLength) { InputStreamEntity entity = new InputStreamEntity(is, contentLength); entity.setContentType(contentType); httpRequest.setEntity(entity); } InputStream getStream(HttpResponse response) { try { return response.getEntity().getContent(); } catch (Exception e) { throw new ServerException(e); } } /** * Shuts down the connection manager used by this client instance. */ public void shutdown() { this.httpClient.getConnectionManager().shutdown(); } public boolean isDebugging() { return debugging; } public void setDebugging(boolean debugging) { this.debugging = debugging; } }