From e8c1d6148f6bfe5a76e4e65a27c265c249c49d47 Mon Sep 17 00:00:00 2001 From: James Smith Date: Tue, 31 May 2011 21:27:05 -0700 Subject: [PATCH] File uploads now work --- src/com/loopj/android/http/RequestParams.java | 107 +++++++++- .../android/http/SimpleMultipartEntity.java | 183 ++++++++++++++++++ 2 files changed, 281 insertions(+), 9 deletions(-) create mode 100644 src/com/loopj/android/http/SimpleMultipartEntity.java diff --git a/src/com/loopj/android/http/RequestParams.java b/src/com/loopj/android/http/RequestParams.java index f2d42d871..14706c43f 100644 --- a/src/com/loopj/android/http/RequestParams.java +++ b/src/com/loopj/android/http/RequestParams.java @@ -18,6 +18,8 @@ package com.loopj.android.http; +import java.io.ByteArrayInputStream; +import java.io.File; import java.io.UnsupportedEncodingException; import java.util.LinkedList; import java.util.List; @@ -32,7 +34,8 @@ public class RequestParams { private static String ENCODING = "UTF-8"; - protected ConcurrentHashMap urlParams; + protected ConcurrentHashMap stringParams; + protected ConcurrentHashMap fileParams; public RequestParams() { init(); @@ -54,39 +57,125 @@ public RequestParams(String key, String value) { public void put(String key, String value){ if(key != null && value != null) { - urlParams.put(key,value); + stringParams.put(key, value); + } + } + + public void put(String key, ByteArrayInputStream filedata) { + put(key, filedata, null, null); + } + + public void put(String key, ByteArrayInputStream filedata, String filename) { + put(key, filedata, filename, null); + } + + public void put(String key, ByteArrayInputStream filedata, String filename, String contentType){ + if(key != null && filedata != null) { + fileParams.put(key, new FileWrapper(filedata, filename, contentType)); } } public void remove(String key){ - urlParams.remove(key); + stringParams.remove(key); + fileParams.remove(key); } public String getParamString() { + if(!fileParams.isEmpty()) { + throw new RuntimeException("Uploading files is not supported with Http GET requests."); + } + return URLEncodedUtils.format(getParamsList(), ENCODING); } public HttpEntity getEntity() { HttpEntity entity = null; - try { - entity = new UrlEncodedFormEntity(getParamsList(), ENCODING); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); + + if(!fileParams.isEmpty()) { + SimpleMultipartEntity multipartEntity = new SimpleMultipartEntity(); + + // Add string params + for(ConcurrentHashMap.Entry entry : stringParams.entrySet()) { + multipartEntity.addPart(entry.getKey(), entry.getValue()); + } + + // Add file params + for(ConcurrentHashMap.Entry entry : fileParams.entrySet()) { + FileWrapper file = entry.getValue(); + if(file.bytes != null) { + String filename = file.filename; + if(filename == null) { + filename = "nofilename"; + } + + if(file.contentType != null) { + multipartEntity.addPart(entry.getKey(), filename, file.bytes, file.contentType); + } else { + multipartEntity.addPart(entry.getKey(), filename, file.bytes); + } + } + } + + entity = multipartEntity; + } else { + try { + entity = new UrlEncodedFormEntity(getParamsList(), ENCODING); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } } + return entity; } private void init(){ - urlParams = new ConcurrentHashMap(); + stringParams = new ConcurrentHashMap(); + fileParams = new ConcurrentHashMap(); } private List getParamsList() { List lparams = new LinkedList(); - for(ConcurrentHashMap.Entry entry : urlParams.entrySet()) { + for(ConcurrentHashMap.Entry entry : stringParams.entrySet()) { lparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); } return lparams; } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + for(ConcurrentHashMap.Entry entry : stringParams.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(); + } + + private static class FileWrapper { + public ByteArrayInputStream bytes; + public String filename; + public String contentType; + + public FileWrapper(ByteArrayInputStream bytes, String filename, String contentType) { + this.bytes = bytes; + this.filename = filename; + this.contentType = contentType; + } + } } \ No newline at end of file diff --git a/src/com/loopj/android/http/SimpleMultipartEntity.java b/src/com/loopj/android/http/SimpleMultipartEntity.java new file mode 100644 index 000000000..67c40149d --- /dev/null +++ b/src/com/loopj/android/http/SimpleMultipartEntity.java @@ -0,0 +1,183 @@ +/* + 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.loopj.android.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; + +public 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){ + addPart(key, fileName, fin, "application/octet-stream"); + } + + public void addPart(final String key, final String fileName, final InputStream fin, String type){ + 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); + } + 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) { + try { + addPart(key, value.getName(), new FileInputStream(value)); + } 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