
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