mirror of https://github.com/apache/jclouds.git
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:
parent
7cb2f6ebc7
commit
e89aab8453
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue