Skip to content

Commit

Permalink
File uploads now work
Browse files Browse the repository at this point in the history
  • Loading branch information
loopj committed Jun 1, 2011
1 parent 9072890 commit e8c1d61
Show file tree
Hide file tree
Showing 2 changed files with 281 additions and 9 deletions.
107 changes: 98 additions & 9 deletions src/com/loopj/android/http/RequestParams.java
Expand Up @@ -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;
Expand All @@ -32,7 +34,8 @@
public class RequestParams {
private static String ENCODING = "UTF-8";

protected ConcurrentHashMap<String,String> urlParams;
protected ConcurrentHashMap<String, String> stringParams;
protected ConcurrentHashMap<String, FileWrapper> fileParams;

public RequestParams() {
init();
Expand All @@ -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<String, String> entry : stringParams.entrySet()) {
multipartEntity.addPart(entry.getKey(), entry.getValue());
}

// Add file params
for(ConcurrentHashMap.Entry<String, FileWrapper> 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<String,String>();
stringParams = new ConcurrentHashMap<String, String>();
fileParams = new ConcurrentHashMap<String, FileWrapper>();
}

private List<BasicNameValuePair> getParamsList() {
List<BasicNameValuePair> lparams = new LinkedList<BasicNameValuePair>();

for(ConcurrentHashMap.Entry<String, String> entry : urlParams.entrySet()) {
for(ConcurrentHashMap.Entry<String, String> entry : stringParams.entrySet()) {
lparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}

return lparams;
}

@Override
public String toString() {
StringBuilder result = new StringBuilder();
for(ConcurrentHashMap.Entry<String, String> entry : stringParams.entrySet()) {
if(result.length() > 0)
result.append("&");

result.append(entry.getKey());
result.append("=");
result.append(entry.getValue());
}

for(ConcurrentHashMap.Entry<String, FileWrapper> 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;
}
}
}
183 changes: 183 additions & 0 deletions src/com/loopj/android/http/SimpleMultipartEntity.java
@@ -0,0 +1,183 @@
/*
Android Asynchronous Http Client
Copyright (c) 2011 James Smith <james@loopj.com>
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());
}
}

0 comments on commit e8c1d61

Please sign in to comment.