/******************************************************************************* * Copyright (c) 2008, 2009 Brian Ballantine and Bug Labs, Inc. * * MIT License * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. *******************************************************************************/ package com.buglabs.util.simplerestclient; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.HttpURLConnection; import java.net.URLEncoder; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import com.buglabs.util.Base64; /** * class for dealing RESTfully with HTTP Requests * * Example Usage: * HttpRequest req = new HttpRequest(myConnectionProvider) * HttpResponse resp = req.get("http://some.url") * System.out.println(resp.getString()); * * @author Brian * * Revisions * 09-03-2008 AK added a Map header parameter to "put" and "post" to support http header * * */ public class HTTPRequest { /** * Implementors can configure the http connection before every call is made. * Useful for setting headers that always need to be present in every WS call to a given server. * * @author kgilmer * */ public interface HTTPConnectionInitializer { public void initialize(HttpURLConnection connection); } //////////////////////////////////////////////// HTTP REQUEST METHODS private static final String HEADER_TYPE = "Content-Type"; private static final String HEADER_PARA = "Content-Disposition: form-data"; private static final String CONTENT_TYPE = "multipart/form-data"; private static final String LINE_ENDING = "\r\n"; private static final String BOUNDARY = "boundary="; private static final String PARA_NAME = "name"; private static final String FILE_NAME = "filename"; private List<HTTPConnectionInitializer> configurators; private IConnectionProvider _connectionProvider; /** * constructor where client provides connectionProvider * */ public HTTPRequest(IConnectionProvider connectionProvider) { _connectionProvider = connectionProvider; } /** * constructor that uses default connection provider */ public HTTPRequest() { _connectionProvider = new DefaultConnectionProvider(); } /** * Do an authenticated HTTP GET from url * * @param url String URL to connect to * @return HttpURLConnection ready with response data */ public HTTPResponse get(String url) throws IOException { HttpURLConnection conn = getAndConfigureConnection(url); conn.setDoInput(true); conn.setDoOutput(false); return connect(conn); } /** * @param url * @return * @throws IOException */ private HttpURLConnection getAndConfigureConnection(String url) throws IOException { HttpURLConnection connection = _connectionProvider.getConnection(url); if (configurators == null) return connection; for (HTTPConnectionInitializer c: configurators) c.initialize(connection); return connection; } /** * Do an authenticated HTTP GET from url * * @param url String URL to connect to * @return HttpURLConnection ready with response data */ public HTTPResponse get(String url, Map<String, String> headers) throws IOException { HttpURLConnection conn = getAndConfigureConnection(url); conn.setDoInput(true); conn.setDoOutput(false); for (Entry<String, String> e: headers.entrySet()) { conn.addRequestProperty(e.getKey(), e.getValue()); } return connect(conn); } /** * Do an HTTP POST to url * * @param url String URL to connect to * @param data String data to post * @return HttpURLConnection ready with response data */ public HTTPResponse post(String url, String data) throws IOException { return post(url, data, null); } /** * Do an HTTP POST to url w/ extra http headers * * @param url * @param data * @param headers * @return * @throws IOException */ public HTTPResponse post(String url, String data, Map<String, String> headers) throws IOException { HttpURLConnection conn = getAndConfigureConnection(url); if (headers != null) for (Entry<String, String> e: headers.entrySet()) conn.setRequestProperty(e.getKey(), e.getValue()); conn.setDoOutput(true); OutputStreamWriter osr = new OutputStreamWriter(conn.getOutputStream()); osr.write(data); osr.flush(); osr.close(); return connect(conn); } /** * Do an HTTP POST to url * * @param url String URL to connect to * @param stream InputStream data to post * @return HttpURLConnection ready with response data */ public HTTPResponse post(String url, InputStream stream) throws IOException { byte[] buff = streamToByteArray(stream); String data = Base64.encodeBytes(buff); return post(url, data); } /** * Posts a Map of key, value pair properties, like a web form * * @param url * @param properties * @return * @throws IOException */ public HTTPResponse post(String url, Map<String, String> properties) throws IOException { String data = propertyString(properties); HashMap<String, String> headers = new HashMap<String, String>(); headers.put("Content-Type", "application/x-www-form-urlencoded"); return post(url, data, headers); } /** * Posts a Map of key, value pair properties, like a web form * * @param url * @param properties * @return * @throws IOException */ public HTTPResponse post(String url, Map<String, String> properties, Map<String, String> headers) throws IOException { String data = propertyString(properties); headers.put("Content-Type", "application/x-www-form-urlencoded"); return post(url, data, headers); } /** * Post byte data to a url * * @param url * @param data * @return * @throws IOException */ public HTTPResponse post(String url, byte[] data) throws IOException { HttpURLConnection conn = getAndConfigureConnection(url); conn.setRequestProperty("Content-Length", String.valueOf(data.length)); conn.setRequestMethod("POST"); conn.setDoOutput(true); OutputStream os = conn.getOutputStream(); os.write(data); return connect(conn); } /** * Does a multipart post which is different than a regular post * mostly use this one if you're posting files * * @param url * @param parameters * Key-Value pairs in map. Keys are always string. Values can be string or IFormFile * @param properties * @return */ public HTTPResponse postMultipart(String url, Map<String, String> parameters) throws IOException { HttpURLConnection conn = getAndConfigureConnection(url); conn.setRequestMethod("POST"); String boundary = createMultipartBoundary(); conn.setRequestProperty(HEADER_TYPE, CONTENT_TYPE +"; "+ BOUNDARY + boundary); conn.setDoOutput(true); // write things out to connection OutputStream os = conn.getOutputStream(); // add parameters Object [] elems = parameters.keySet().toArray(); StringBuffer buf; // lil helper IFormFile file; for (int i=0; i < elems.length; i++) { String key = (String)elems[i]; Object obj = parameters.get(key); //System.out.println("--" + key); buf = new StringBuffer(); if (obj instanceof IFormFile) { file = (IFormFile)obj; buf.append("--"+ boundary+LINE_ENDING); buf.append(HEADER_PARA); buf.append("; "+ PARA_NAME +"=\""+ key +"\""); buf.append("; "+ FILE_NAME +"=\""+ file.getFilename() +"\""+ LINE_ENDING); buf.append(HEADER_TYPE + ": " + file.getContentType() + ";"); buf.append(LINE_ENDING); buf.append(LINE_ENDING); os.write(buf.toString().getBytes()); os.write(file.getBytes()); } else if (obj != null) { buf.append("--"+ boundary+LINE_ENDING); buf.append(HEADER_PARA); buf.append("; "+ PARA_NAME +"=\""+ key +"\""); buf.append(LINE_ENDING); buf.append(LINE_ENDING); buf.append(obj.toString()); os.write(buf.toString().getBytes()); } os.write(LINE_ENDING.getBytes()); } os.write(("--"+ boundary+"--"+LINE_ENDING).getBytes()); return connect(conn); } /** * Do an HTTP PUT to url * * @param url String URL to connect to * @param data String data to post * @return HttpURLConnection ready with response data */ public HTTPResponse put(String url, String data) throws IOException { return put(url, data, null); } /** * Do an HTTP PUT to url with extra headers * * @param url * @param data * @param headers * @return * @throws IOException */ public HTTPResponse put(String url, String data, Map<String, String> headers) throws IOException{ HttpURLConnection connection = getAndConfigureConnection(url); if (headers != null) for (Entry<String, String> e: headers.entrySet()) connection.setRequestProperty(e.getKey(), e.getValue()); connection.setDoOutput(true); connection.setRequestMethod("PUT"); OutputStreamWriter osr = new OutputStreamWriter(connection.getOutputStream()); osr.write(data); osr.flush(); osr.close(); return connect(connection); } /** * Do an HTTP PUT to url * * @param url String URL to connect to * @param stream InputStream data to put * @return HttpURLConnection ready with response data */ public HTTPResponse put(String url, InputStream stream) throws IOException { byte[] buff = streamToByteArray(stream); String data = Base64.encodeBytes(buff); return put(url, data); } /** * Do an HTTP DELETE to url * * @param url * @return * @throws IOException */ public HTTPResponse delete(String url) throws IOException { HttpURLConnection connection = getAndConfigureConnection(url); connection.setDoInput(true); connection.setRequestMethod("DELETE"); return connect(connection); } /** * Do an HTTP DELETE to url * * @param url * @param headers * @return * @throws IOException */ public HTTPResponse delete(String url, Map<String, String> headers) throws IOException { HttpURLConnection connection = getAndConfigureConnection(url); if (headers != null) for (Entry<String, String> e: headers.entrySet()) connection.setRequestProperty(e.getKey(), e.getValue()); connection.setDoInput(true); connection.setRequestMethod("DELETE"); return connect(connection); } /** * Puts a Map of key, value pair properties, like a web form * * @param url * @param properties * @return * @throws IOException */ public HTTPResponse put(String url, Map<String, String> properties) throws IOException { String data = propertyString(properties); HashMap<String, String> headers = new HashMap<String, String>(); headers.put("Content-Type", "application/x-www-form-urlencoded"); return put(url, data, headers); } /** * Puts a Map of key, value pair properties, like a web form * * @param url * @param properties * @return * @throws IOException */ public HTTPResponse put(String url, Map<String, String> properties, Map<String, String> headers) throws IOException { String data = propertyString(properties); headers.put("Content-Type", "application/x-www-form-urlencoded"); return put(url, data, headers); } /** * Do an HTTP HEAD to url * * @param url String URL to connect to * @return HttpURLConnection ready with response data */ public HTTPResponse head(String url) throws IOException { HttpURLConnection connection = getAndConfigureConnection(url); connection.setDoOutput(true); connection.setRequestMethod("HEAD"); return connect(connection); } ////////////////////////////////////////////////////////////// THESE HELP /** * Connect to server, check the status, and return the new HTTPResponse */ private HTTPResponse connect(HttpURLConnection connection) throws HTTPException, IOException { HTTPResponse response = new HTTPResponse(connection); response.checkStatus(); return response; } /** * A simple helper function * * @param in InputStream to turn into a byte array * @return byte array (byte[]) w/ contents of input stream */ public static byte[] streamToByteArray(InputStream in) throws IOException { ByteArrayOutputStream os = new ByteArrayOutputStream(); int read = 0; byte[] buff = new byte[4096]; try { while ((read = in.read(buff)) > 0) { os.write(buff, 0, read); } } catch (IOException e1) { e1.printStackTrace(); } return os.toByteArray(); } /** * turns a map into a key=value property string for sending to bugnet */ public static String propertyString(Map<String, String> props) throws IOException { String propstr = new String(); String key; for (Iterator<String> i = props.keySet().iterator(); i.hasNext();) { key = i.next(); propstr = propstr + URLEncoder.encode(key, "UTF-8") + "=" + URLEncoder.encode((String) props.get(key), "UTF-8"); if (i.hasNext()) { propstr = propstr + "&"; } } return propstr; } /** * helper to create multipart form boundary * * @return */ private static String createMultipartBoundary() { StringBuffer buf = new StringBuffer(); buf.append("---------------------------"); for (int i=0; i < 15; i++) { double rand = Math.random() * 35; if (rand < 10) { buf.append((int)rand); } else { int ascii = 87 + (int)rand; char symbol = (char)ascii; buf.append(symbol); } } return buf.toString(); } /** * Add a initializer that will be called for each http operation before the call is made. * @param c */ public void addConfigurator(HTTPConnectionInitializer c) { if (configurators == null) configurators = new ArrayList<HTTPRequest.HTTPConnectionInitializer>(); if (!configurators.contains(c)) configurators.add(c); } /** * Remove a initializer. * @param c */ public void removeConfigurator(HTTPConnectionInitializer c) { if (configurators == null) return; configurators.remove(c); if (configurators.size() == 0) configurators = null; } }