From 04cd9a93567ddb6bd55363083b008b87d7af97f1 Mon Sep 17 00:00:00 2001 From: jonson Date: Thu, 23 Feb 2012 11:45:50 -0500 Subject: [PATCH] Adding a few goodies from the android-async-http project: * request params * multipart upload support --- .../ignition/support/http/RequestParams.java | 252 ++++++++++++++++++ .../support/http/SimpleMultipartEntity.java | 185 +++++++++++++ 2 files changed, 437 insertions(+) create mode 100644 ignition-support/ignition-support-lib/src/main/java/com/github/ignition/support/http/RequestParams.java create mode 100644 ignition-support/ignition-support-lib/src/main/java/com/github/ignition/support/http/SimpleMultipartEntity.java diff --git a/ignition-support/ignition-support-lib/src/main/java/com/github/ignition/support/http/RequestParams.java b/ignition-support/ignition-support-lib/src/main/java/com/github/ignition/support/http/RequestParams.java new file mode 100644 index 0000000..93a83ec --- /dev/null +++ b/ignition-support/ignition-support-lib/src/main/java/com/github/ignition/support/http/RequestParams.java @@ -0,0 +1,252 @@ +/* + Android Asynchronous Http Client + Copyright (c) 2011 James Smith + http://loopj.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.github.ignition.support.http; + +import java.io.InputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.UnsupportedEncodingException; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.http.HttpEntity; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.utils.URLEncodedUtils; +import org.apache.http.message.BasicNameValuePair; + +/** + * A collection of string request parameters or files to send along with + * requests made from an {@link AsyncHttpClient} instance. + *

+ * For example: + *

+ *

+ * RequestParams params = new RequestParams();
+ * params.put("username", "james");
+ * params.put("password", "123456");
+ * params.put("email", "my@email.com");
+ * params.put("profile_picture", new File("pic.jpg")); // Upload a File
+ * params.put("profile_picture2", someInputStream); // Upload an InputStream
+ * params.put("profile_picture3", new ByteArrayInputStream(someBytes)); // Upload some bytes
+ * 
+ */ +public class RequestParams { + private static String ENCODING = "UTF-8"; + + protected ConcurrentHashMap urlParams; + protected ConcurrentHashMap fileParams; + + /** + * Constructs a new empty RequestParams instance. + */ + public RequestParams() { + init(); + } + + /** + * Constructs a new RequestParams instance containing the key/value + * string params from the specified map. + * @param source the source key/value string map to add. + */ + public RequestParams(Map source) { + init(); + + for(Map.Entry entry : source.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + /** + * Constructs a new RequestParams instance and populate it with a single + * initial key/value string param. + * @param key the key name for the intial param. + * @param value the value string for the initial param. + */ + public RequestParams(String key, String value) { + init(); + + put(key, value); + } + + /** + * Adds a key/value string pair to the request. + * @param key the key name for the new param. + * @param value the value string for the new param. + */ + public void put(String key, String value){ + if(key != null && value != null) { + urlParams.put(key, value); + } + } + + /** + * Adds a file to the request. + * @param key the key name for the new param. + * @param file the file to add. + */ + public void put(String key, File file) throws FileNotFoundException { + put(key, new FileInputStream(file), file.getName()); + } + + /** + * Adds an input stream to the request. + * @param key the key name for the new param. + * @param stream the input stream to add. + */ + public void put(String key, InputStream stream) { + put(key, stream, null); + } + + /** + * Adds an input stream to the request. + * @param key the key name for the new param. + * @param stream the input stream to add. + * @param fileName the name of the file. + */ + public void put(String key, InputStream stream, String fileName) { + put(key, stream, fileName, null); + } + + /** + * Adds an input stream to the request. + * @param key the key name for the new param. + * @param stream the input stream to add. + * @param fileName the name of the file. + * @param contentType the content type of the file, eg. application/json + */ + public void put(String key, InputStream stream, String fileName, String contentType) { + if(key != null && stream != null) { + fileParams.put(key, new FileWrapper(stream, fileName, contentType)); + } + } + + /** + * Removes a parameter from the request. + * @param key the key name for the parameter to remove. + */ + public void remove(String key){ + urlParams.remove(key); + fileParams.remove(key); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + for(ConcurrentHashMap.Entry entry : urlParams.entrySet()) { + if(result.length() > 0) + result.append("&"); + + result.append(entry.getKey()); + result.append("="); + result.append(entry.getValue()); + } + + for(ConcurrentHashMap.Entry entry : fileParams.entrySet()) { + if(result.length() > 0) + result.append("&"); + + result.append(entry.getKey()); + result.append("="); + result.append("FILE"); + } + + return result.toString(); + } + + HttpEntity getEntity() { + HttpEntity entity = null; + + if(!fileParams.isEmpty()) { + SimpleMultipartEntity multipartEntity = new SimpleMultipartEntity(); + + // Add string params + for(ConcurrentHashMap.Entry entry : urlParams.entrySet()) { + multipartEntity.addPart(entry.getKey(), entry.getValue()); + } + + // Add file params + int currentIndex = 0; + int lastIndex = fileParams.entrySet().size() - 1; + for(ConcurrentHashMap.Entry entry : fileParams.entrySet()) { + FileWrapper file = entry.getValue(); + if(file.inputStream != null) { + boolean isLast = currentIndex == lastIndex; + if(file.contentType != null) { + multipartEntity.addPart(entry.getKey(), file.getFileName(), file.inputStream, file.contentType, isLast); + } else { + multipartEntity.addPart(entry.getKey(), file.getFileName(), file.inputStream, isLast); + } + } + currentIndex++; + } + + entity = multipartEntity; + } else { + try { + entity = new UrlEncodedFormEntity(getParamsList(), ENCODING); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + + return entity; + } + + private void init(){ + urlParams = new ConcurrentHashMap(); + fileParams = new ConcurrentHashMap(); + } + + protected List getParamsList() { + List lparams = new LinkedList(); + + for(ConcurrentHashMap.Entry entry : urlParams.entrySet()) { + lparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); + } + + return lparams; + } + + protected String getParamString() { + return URLEncodedUtils.format(getParamsList(), ENCODING); + } + + private static class FileWrapper { + public InputStream inputStream; + public String fileName; + public String contentType; + + public FileWrapper(InputStream inputStream, String fileName, String contentType) { + this.inputStream = inputStream; + this.fileName = fileName; + this.contentType = contentType; + } + + public String getFileName() { + if(fileName != null) { + return fileName; + } else { + return "nofilename"; + } + } + } +} \ No newline at end of file diff --git a/ignition-support/ignition-support-lib/src/main/java/com/github/ignition/support/http/SimpleMultipartEntity.java b/ignition-support/ignition-support-lib/src/main/java/com/github/ignition/support/http/SimpleMultipartEntity.java new file mode 100644 index 0000000..7d9a432 --- /dev/null +++ b/ignition-support/ignition-support-lib/src/main/java/com/github/ignition/support/http/SimpleMultipartEntity.java @@ -0,0 +1,185 @@ +/* + Android Asynchronous Http Client + Copyright (c) 2011 James Smith + http://loopj.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. +*/ + +/* + This code is taken from Rafael Sanches' blog. + http://blog.rafaelsanches.com/2011/01/29/upload-using-multipart-post-using-httpclient-in-android/ +*/ + +package com.github.ignition.support.http; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Random; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.message.BasicHeader; + +class SimpleMultipartEntity implements HttpEntity { + private final static char[] MULTIPART_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); + + private String boundary = null; + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + boolean isSetLast = false; + boolean isSetFirst = false; + + public SimpleMultipartEntity() { + final StringBuffer buf = new StringBuffer(); + final Random rand = new Random(); + for (int i = 0; i < 30; i++) { + buf.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]); + } + this.boundary = buf.toString(); + + } + + public void writeFirstBoundaryIfNeeds(){ + if(!isSetFirst){ + try { + out.write(("--" + boundary + "\r\n").getBytes()); + } catch (final IOException e) { + e.printStackTrace(); + } + } + + isSetFirst = true; + } + + public void writeLastBoundaryIfNeeds() { + if(isSetLast){ + return; + } + + try { + out.write(("\r\n--" + boundary + "--\r\n").getBytes()); + } catch (final IOException e) { + e.printStackTrace(); + } + + isSetLast = true; + } + + public void addPart(final String key, final String value) { + writeFirstBoundaryIfNeeds(); + try { + out.write(("Content-Disposition: form-data; name=\"" +key+"\"\r\n\r\n").getBytes()); + out.write(value.getBytes()); + out.write(("\r\n--" + boundary + "\r\n").getBytes()); + } catch (final IOException e) { + e.printStackTrace(); + } + } + + public void addPart(final String key, final String fileName, final InputStream fin, final boolean isLast){ + addPart(key, fileName, fin, "application/octet-stream", isLast); + } + + public void addPart(final String key, final String fileName, final InputStream fin, String type, final boolean isLast){ + writeFirstBoundaryIfNeeds(); + try { + type = "Content-Type: "+type+"\r\n"; + out.write(("Content-Disposition: form-data; name=\""+ key+"\"; filename=\"" + fileName + "\"\r\n").getBytes()); + out.write(type.getBytes()); + out.write("Content-Transfer-Encoding: binary\r\n\r\n".getBytes()); + + final byte[] tmp = new byte[4096]; + int l = 0; + while ((l = fin.read(tmp)) != -1) { + out.write(tmp, 0, l); + } + if(!isLast) + out.write(("\r\n--" + boundary + "\r\n").getBytes()); + out.flush(); + } catch (final IOException e) { + e.printStackTrace(); + } finally { + try { + fin.close(); + } catch (final IOException e) { + e.printStackTrace(); + } + } + } + + public void addPart(final String key, final File value, final boolean isLast) { + try { + addPart(key, value.getName(), new FileInputStream(value), isLast); + } catch (final FileNotFoundException e) { + e.printStackTrace(); + } + } + + @Override + public long getContentLength() { + writeLastBoundaryIfNeeds(); + return out.toByteArray().length; + } + + @Override + public Header getContentType() { + return new BasicHeader("Content-Type", "multipart/form-data; boundary=" + boundary); + } + + @Override + public boolean isChunked() { + return false; + } + + @Override + public boolean isRepeatable() { + return false; + } + + @Override + public boolean isStreaming() { + return false; + } + + @Override + public void writeTo(final OutputStream outstream) throws IOException { + outstream.write(out.toByteArray()); + } + + @Override + public Header getContentEncoding() { + return null; + } + + @Override + public void consumeContent() throws IOException, + UnsupportedOperationException { + if (isStreaming()) { + throw new UnsupportedOperationException( + "Streaming entity does not implement #consumeContent()"); + } + } + + @Override + public InputStream getContent() throws IOException, + UnsupportedOperationException { + return new ByteArrayInputStream(out.toByteArray()); + } +} \ No newline at end of file