Issue 99: added multipart form support

git-svn-id: http://jclouds.googlecode.com/svn/trunk@1928 3d8758e0-26b5-11de-8745-db77d3ebf521
This commit is contained in:
adrian.f.cole 2009-09-27 18:20:53 +00:00
parent 7cb2f6ebc7
commit e89aab8453
3 changed files with 363 additions and 0 deletions

View File

@ -0,0 +1,129 @@
/**
*
* Copyright (C) 2009 Global Cloud Specialists, Inc. <info@globalcloudspecialists.com>
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.jclouds.http;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.Map.Entry;
import org.apache.commons.io.IOUtils;
import org.jclouds.util.InputStreamChain;
import com.google.common.collect.Multimap;
/**
*
* @author Adrian Cole
*/
public class MultipartForm {
private static final String rn = "\r\n";
private static final String dd = "--";
private final InputStreamChain chain;
private long size;
public MultipartForm(String boundary, Part... parts) {
String boundaryrn = boundary + rn;
chain = new InputStreamChain();
for (Part part : parts) {
addHeaders(boundaryrn, part);
addData(part);
}
addFooter(boundary);
}
private void addData(Part part) {
chain.addInputStream(part.getData());
chain.addAsInputStream(rn);
size += part.getSize() + rn.length();
}
private void addHeaders(String boundaryrn, Part part) {
StringBuilder builder = new StringBuilder(dd).append(boundaryrn);
for (Entry<String, String> entry : part.getHeaders().entries()) {
String header = String.format("%s: %s%s", entry.getKey(), entry.getValue(), rn);
builder.append(header);
}
builder.append(rn);
chain.addAsInputStream(builder.toString());
size += builder.length();
}
private void addFooter(String boundary) {
String end = dd + boundary + dd + rn;
chain.addAsInputStream(end);
size += end.length();
}
public MultipartForm(Part... parts) {
this("__redrose__", parts);
}
public static class Part {
private final Multimap<String, String> headers;
private final InputStream data;
private final long size;
public Part(Multimap<String, String> headers, InputStream data, long size) {
this.headers = headers;
this.data = data;
this.size = size;
}
public Part(Multimap<String, String> headers, String data) {
this(headers, IOUtils.toInputStream(data), data.length());
}
public Part(Multimap<String, String> headers, File data) throws FileNotFoundException {
this(headers, new FileInputStream(data), data.length());
}
public Part(Multimap<String, String> headers, byte[] data) {
this(headers, new ByteArrayInputStream(data), data.length);
}
public Multimap<String, String> getHeaders() {
return headers;
}
public InputStream getData() {
return data;
}
public long getSize() {
return size;
}
}
public long getSize() {
return size;
}
public InputStream getData() {
return chain;
}
}

View File

@ -0,0 +1,139 @@
/**
*
* Copyright (C) 2009 Global Cloud Specialists, Inc. <info@globalcloudspecialists.com>
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.jclouds.util;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import org.apache.commons.io.IOUtils;
/**
* {@link InputStream} implementation that allows chaining of various streams for seamless
* sequential reading
*
* @author Adrian Cole
* @author Tomas Varaneckas <tomas.varaneckas@gmail.com>
*/
public class InputStreamChain extends InputStream {
/**
* Input stream chain
*/
private final LinkedList<InputStream> streams = new LinkedList<InputStream>();
/**
* Currently active stream
*/
private InputStream current;
/**
* Constructor with an initial stream
*
* @param first
* Initial InputStream
*/
public InputStreamChain(InputStream... inputStreams) {
for (InputStream stream : inputStreams) {
addInputStream(stream);
}
}
/**
* Adds input stream to the end of chain
*
* @param stream
* InputStream to add to chain
* @return instance of self (for fluent calls)
*/
public InputStreamChain addInputStream(final InputStream stream) {
streams.addLast(stream);
if (current == null) {
current = streams.removeFirst();
}
return this;
}
/**
* Adds a String to the end of chain
*
* @param value
* String to add to the chain
* @return instance of self (for fluent calls)
*/
public InputStreamChain addAsInputStream(final String value) {
return addInputStream(IOUtils.toInputStream(value));
}
@Override
public int read() throws IOException {
int bit = current.read();
if (bit == -1 && streams.size() > 0) {
try {
current.close();
} catch (final IOException e) {
// replace this with a call to logging facility
e.printStackTrace();
}
current = streams.removeFirst();
bit = read();
}
return bit;
}
@Override
public int available() throws IOException {
int available = current.available();
for (InputStream stream : streams) {
available += stream.available();
}
return available;
}
@Override
public void close() throws IOException {
current.close();
}
@Override
public boolean markSupported() {
return current.markSupported();
}
@Override
public synchronized void mark(int i) {
current.mark(i);
}
@Override
public synchronized void reset() throws IOException {
current.reset();
}
@Override
public long skip(long l) throws IOException {
return current.skip(l);
}
}

View File

@ -0,0 +1,95 @@
/**
*
* Copyright (C) 2009 Global Cloud Specialists, Inc. <info@globalcloudspecialists.com>
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.jclouds.http;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import org.jclouds.http.MultipartForm.Part;
import org.jclouds.util.Utils;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMultimap;
/**
* Tests parsing of a request
*
* @author Adrian Cole
*/
@Test(testName = "http.MultipartFormTest")
public class MultipartFormTest {
String boundary = "------------------------------c88555ffd14e";
public void testSinglePart() throws IOException {
StringBuilder builder = new StringBuilder();
addData(boundary, "hello", builder);
builder.append("--").append(boundary).append("--").append("\r\n");
String expects = builder.toString();
assertEquals(expects.length(), 197);
MultipartForm multipartForm = new MultipartForm(boundary, newPart("hello"));
assertEquals(Utils.toStringAndClose(multipartForm.getData()), expects);
assertEquals(multipartForm.getSize(), 197);
}
private Part newPart(String data) {
return new MultipartForm.Part(ImmutableMultimap.of("Content-Disposition",
"form-data; name=\"file\"; filename=\"testfile.txt\"", HttpHeaders.CONTENT_TYPE,
MediaType.TEXT_PLAIN), data);
}
private void addData(String boundary, String data, StringBuilder builder) {
builder.append("--").append(boundary).append("\r\n");
builder.append("Content-Disposition").append(": ").append(
"form-data; name=\"file\"; filename=\"testfile.txt\"").append("\r\n");
builder.append("Content-Type").append(": ").append("text/plain").append("\r\n");
builder.append("\r\n");
builder.append(data).append("\r\n");
}
public void testMultipleParts() throws IOException {
StringBuilder builder = new StringBuilder("--");
addData(boundary, "hello", builder);
addData(boundary, "goodbye", builder);
builder.append(boundary).append("--").append("\r\n");
String expects = builder.toString();
assertEquals(expects.length(), 348);
MultipartForm multipartForm = new MultipartForm(boundary, newPart("hello"),
newPart("goodbye"));
assertEquals(Utils.toStringAndClose(multipartForm.getData()), expects);
assertEquals(multipartForm.getSize(), 348);
}
}