All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openrdf.util.http.HttpClientUtil Maven / Gradle / Ivy

The newest version!
/*  Sesame - Storage and Querying architecture for RDF and RDF Schema
 *  Copyright (C) 2001-2007 Aduna
 *
 *  Contact:
 *  	Aduna
 *  	Prinses Julianaplein 14 b
 *  	3817 CS Amersfoort
 *  	The Netherlands
 *  	tel. +33 (0)33 465 99 87
 *  	fax. +33 (0)33 465 99 87
 *
 *  	http://aduna-software.com/
 *  	http://www.openrdf.org/
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library 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 this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.openrdf.util.http;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.GZIPInputStream;

import org.openrdf.util.io.IOUtil;

/**
 * Utility methods for HTTP clients.
 **/
public class HttpClientUtil {

	private static final String CRLF = "\r\n";

/*---------------------+
| Methods              |
+---------------------*/

	/**
	 * Prepares an HttpURLConnection for a POST request that sends the
	 * supplied parameters.
	 *
	 * @param connection The connection to prepare for the request.
	 * @param parameters The key-value pairs to send in the POST
	 * request.
	 *
	 * @exception IOException If an I/O error occurs.
	 **/
	public static void preparePostRequest(HttpURLConnection connection, Map parameters)
		throws IOException
	{
		// Create x-www-url-encoded parameter string
		String postData = buildQueryString(parameters);

		// Set up request
		connection.setRequestMethod("POST");
		connection.setDoInput(true);
		connection.setDoOutput(true);
		connection.setUseCaches(false);
		
		// Write the form data to the connection
		OutputStream postStream = connection.getOutputStream();
		Writer postWriter = new OutputStreamWriter(postStream);
		postWriter.write(postData);
		postWriter.flush();
		postWriter.close();
	}

	public static void prepareMultipartPostRequest(HttpURLConnection connection, Map parameters, String encoding)
		throws UnsupportedEncodingException, IOException
	{
		String boundary = "---8qP3mZ1yyysss---";
		byte[] postData = buildMultipartFormData(parameters, boundary, encoding);
		
		// Build the form data as a multipart/form-data byte array.
		connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
		connection.setRequestProperty("Content-Length", String.valueOf(postData.length));

		// Set up request
		connection.setRequestMethod("POST");
		connection.setDoInput(true);
		connection.setDoOutput(true);
		connection.setUseCaches(false);
		
		// Write the form data to the connection
		OutputStream postStream = connection.getOutputStream();
		postStream.write(postData);
		postStream.flush();
		postStream.close();
	}
	
	public static void prepareMultipartPostRequestInputStreamAware(HttpURLConnection connection, Map parameters, String encoding)
		throws UnsupportedEncodingException, IOException
	{
		// Partition the parameters into InputStream or other, so that
		// we can write the non-InputStream parameters first as they
		// are likely to be much smaller than the InputStreams
		Map inputStreamParameters = new HashMap();
		Map nonInputStreamParameters = new HashMap();
		
		Iterator iter = parameters.entrySet().iterator();
		while (iter.hasNext()) {
			Map.Entry entry = (Map.Entry)iter.next();
			String key = (String)entry.getKey();
			Object value = entry.getValue();
			
			if (value instanceof InputStream) {
				inputStreamParameters.put(key, value);
			}
			else {
				nonInputStreamParameters.put(key, value);
			}
		}
		
		String boundary = "---8qP3mZ1yyysss---";
		byte[] postParams = buildMultipartFormData(nonInputStreamParameters, boundary, encoding, false);
		
		// Build the form data as a multipart/form-data byte array.
		connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

		// Set up request
		connection.setRequestMethod("POST");
		connection.setDoInput(true);
		connection.setDoOutput(true);
		connection.setUseCaches(false);
		tryChunkedStreamingMode(connection);
		
		// Write the form data to the connection
		OutputStream postStream = connection.getOutputStream();
		postStream.write(postParams);
		postStream.flush();
		
		byte[] startBoundary = (CRLF + "--" + boundary + CRLF).getBytes();
		byte[] endBoundary = (CRLF + "--" + boundary + "--" + CRLF).getBytes();
		
		Iterator isIter = inputStreamParameters.entrySet().iterator();
		while (isIter.hasNext()) {
			Map.Entry entry = (Map.Entry)isIter.next();
			String key = (String)entry.getKey();
			Object value = entry.getValue();
			
			InputStream in = (InputStream) value;
			
			byte[] partHeader = ("Content-Disposition: form-data; name=\"" + key + "\"" + CRLF + CRLF).getBytes();
			
			postStream.write(startBoundary);
			postStream.write(partHeader);
			IOUtil.transfer(in, postStream);
			postStream.flush();
		}
		postStream.write(endBoundary);
		
		postStream.flush();
		postStream.close();
	}

	private static boolean tryChunkedStreamingMode(HttpURLConnection connection) {
		// If possible, set "transfer-encoding: chunked" on the connection to
		// allow the request to be streamed in chunks to the remote repository.
		// This method is only availible in JDK >1.5 (see Sun bug #5026745:
		// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5026745 )
		try {
			Method m = connection.getClass().getMethod("setChunkedStreamingMode", new Class[] {Integer.TYPE});
			m.invoke(connection, new Object[] {new Integer(8192)});
			return true;
		}
		catch (NoSuchMethodException e) {
			// ignore, we're probably running in jdk 1.4
		}
		catch (SecurityException e) {
			// ignore, not allow to do method lookup
		}
		catch (IllegalAccessException e) {
			// ignore, not allowed to use method
		}
		catch (InvocationTargetException e) {
			// ignore, IllegalStateException thrown by setChunkedStreamingMode(int)?
		}

		return false;
	}

	/**
	 * Builds a multipart/form-data encoded byte array of the specified parameters
	 * (This method includes the end boundary by default).
	 * 
	 * @see #buildMultipartFormData(Map, String, String, boolean)
	 */
	public static byte[] buildMultipartFormData(Map parameters, String boundary, String encoding)
		throws UnsupportedEncodingException
	{
		return buildMultipartFormData(parameters, boundary, encoding, true);
	}

	/**
	 * Builds a multipart/form-data encoded byte array of the specified parameters,
	 * that complies to RFC 1867.
	 * Note that the request sent to the server should have a header of the following
	 * form set: Content-type: multipart/form-data, boundary=boundary-parameter.
	 * E.g.:
	 * 
	 * HttpURLConnection connection = (HttpURLConnection)url.openConnection();
	 * connection.setRequestProperty("Content-type", "multipart/form-data, boundary=AaB03x");
	 * 
* * @param parameters A map of String keys to values that are either FileParts, in * which case its contents will be uploaded; byte arrays, which will be uploaded as-is; or * any other Object, in which case the value of the object's toString() method * will be used. * @param boundary A boundary to use as separator between the encoded parts of the data. * @param encoding The character encoding for the data, e.g. "UTF-8". * @param includeEndBoundary Whether or not to include the end boundary * @return A byte array in multipart/form-data format. **/ public static byte[] buildMultipartFormData(Map parameters, String boundary, String encoding, boolean includeEndBoundary) throws UnsupportedEncodingException { List parts = new ArrayList(parameters.size()); int partLengthSum = 0; Iterator iter = parameters.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry)iter.next(); String key = (String)entry.getKey(); Object value = entry.getValue(); byte[] partHeader, partContents; if (value instanceof FilePart) { FilePart fp = (FilePart)value; partHeader = ("Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + fp.getName() + "\"" + CRLF + "Content-Type: " + fp.getContentType() + CRLF + CRLF).getBytes(); partContents = fp.getBytes(); } else { partHeader = ("Content-Disposition: form-data; name=\"" + key + "\"" + CRLF + CRLF).getBytes(); if (value instanceof byte[]) { partContents = (byte[])value; } else { partContents = value.toString().getBytes(encoding); } } byte[] part = new byte[partHeader.length + partContents.length]; System.arraycopy(partHeader, 0, part, 0, partHeader.length); System.arraycopy(partContents, 0, part, partHeader.length, partContents.length); parts.add(part); partLengthSum += part.length; } byte[] startBoundary = (CRLF + "--" + boundary + CRLF).getBytes(); byte[] endBoundary = (CRLF + "--" + boundary + "--" + CRLF).getBytes(); int totalLength = parts.size() * startBoundary.length + (includeEndBoundary ? endBoundary.length : 0) + partLengthSum; byte[] result = new byte[totalLength]; int idx = 0; for (int i = 0; i < parts.size(); i++) { byte[] part = (byte[])parts.get(i); System.arraycopy(startBoundary, 0, result, idx, startBoundary.length); idx += startBoundary.length; System.arraycopy(part, 0, result, idx, part.length); idx += part.length; } if (includeEndBoundary) { System.arraycopy(endBoundary, 0, result, idx, endBoundary.length); } return result; } /** * Builds a query string from the provided key-value-pairs. All * spaces are substituted by '+' characters, and all non US-ASCII * characters are escaped to hexadecimal notation (%xx). **/ public static String buildQueryString(Map keyValuePairs) { StringBuffer result = new StringBuffer(20*keyValuePairs.size()); Set entrySet = keyValuePairs.entrySet(); Iterator iter = entrySet.iterator(); while (iter.hasNext()) { // Iterate over all key-value pairs Map.Entry keyValuePair = (Map.Entry)iter.next(); String key = (String)keyValuePair.getKey(); String value = (String)keyValuePair.getValue(); // Escape both key and value and combine them with an '=' _formUrlEncode(key, result); result.append('='); _formUrlEncode(value, result); // If there are more key-value pairs, append an '&' if (iter.hasNext()) { result.append('&'); } } return result.toString(); } /** * Encodes a string according to RFC 1738 : Uniform Resource locators (URL). * According to this spec, any characters outside the range 0x20 - 0x7E must * be escaped because they are not printable characters. Within the range * a number of characters are deemed unsafe or are marked as reserved. In * short: According to the spec only the alphanumerics and the special * characters from $-_.+!*'(), can be left unencoded. To be save * this method will encode all characters that are not alphanumerics. **/ private static void _formUrlEncode(String s, StringBuffer buf) { for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); int cInt = (int)c; // Only characters in the range 48 - 57 (numbers), 65 - 90 (upper // case letters), 97 - 122 (lower case letters) can be left // unencoded. The rest needs to be escaped. if (cInt >= 48 && cInt <= 57 || cInt >= 65 && cInt <= 90 || cInt >= 97 && cInt <= 122) { // alphanumeric character buf.append(c); } else { // Escape all non-alphanumerics buf.append('%'); String hexVal = Integer.toHexString(cInt); // Ensure use of two characters if (hexVal.length() == 1) { buf.append('0'); } buf.append(hexVal); } } } /** * Sets a request property on the supplied connection indicating that a * server can respond with gzip-encoded data if it wants to. * * @see #getInputStream **/ public static void setAcceptGZIPEncoding(URLConnection conn) { conn.setRequestProperty("Accept-Encoding", "gzip"); } /** * Gets the InputStream for reading the response from a server. This method * handles any encoding-related decoding of the data, e.g. gzip. * * @see #setAcceptGZIPEncoding **/ public static InputStream getInputStream(URLConnection conn) throws IOException { InputStream responseStream = conn.getInputStream(); String contentEncoding = conn.getContentEncoding(); if ("gzip".equalsIgnoreCase(contentEncoding)) { responseStream = new GZIPInputStream(responseStream); } return responseStream; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy