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