fixed content-disposition tests

This commit is contained in:
Adrian Cole 2010-09-18 18:43:33 -07:00
parent de5ec94344
commit a8852e54f9
8 changed files with 291 additions and 278 deletions

View File

@ -28,7 +28,6 @@ import static com.google.common.io.Closeables.closeQuietly;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.Authenticator;
import java.net.HttpURLConnection;
@ -198,8 +197,6 @@ public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorSe
connection.setRequestProperty(HttpHeaders.USER_AGENT, USER_AGENT);
if (request.getPayload() != null) {
OutputStream out = null;
try {
if (request.getPayload().getContentMD5() != null)
connection.setRequestProperty("Content-MD5", CryptoStreams.base64(request.getPayload().getContentMD5()));
if (request.getPayload().getContentType() != null)
@ -213,11 +210,12 @@ public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorSe
connection.setRequestProperty(HttpHeaders.CONTENT_LENGTH, length.toString());
connection.setFixedLengthStreamingMode(length.intValue());
}
out = connection.getOutputStream();
request.getPayload().writeTo(out);
out.flush();
} finally {
closeQuietly(out);
// writeTo will close the output stream
try {
request.getPayload().writeTo(connection.getOutputStream());
} catch (IOException e){
e.printStackTrace();
throw e;
}
} else {
connection.setRequestProperty(HttpHeaders.CONTENT_LENGTH, "0");
@ -227,7 +225,8 @@ public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorSe
}
/**
* Only disconnect if there is no content, as disconnecting will throw away unconsumed content.
* Only disconnect if there is no content, as disconnecting will throw away
* unconsumed content.
*/
@Override
protected void cleanup(HttpURLConnection connection) {

View File

@ -180,6 +180,7 @@ public abstract class BasePayload<V> implements Payload {
InputStream in = getInput();
try {
copy(in, outstream);
outstream.flush();
} finally {
closeQuietly(in);
}

View File

@ -31,7 +31,7 @@ import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
@ -39,17 +39,21 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.jclouds.crypto.CryptoStreams;
import org.jclouds.http.options.GetOptions;
import org.jclouds.io.InputSuppliers;
import org.jclouds.io.Payload;
import org.jclouds.io.Payloads;
import org.jclouds.util.Utils;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
import com.google.common.io.Closeables;
/**
* Tests for functionality all HttpCommandExecutorServices must express. These tests will operate
* against an in-memory http engine, so as to ensure end-to-end functionality works.
* Tests for functionality all HttpCommandExecutorServices must express. These
* tests will operate against an in-memory http engine, so as to ensure
* end-to-end functionality works.
*
* @author Adrian Cole
*/
@ -118,8 +122,10 @@ public abstract class BaseHttpCommandExecutorServiceIntegrationTest extends Base
assertEquals(CryptoStreams.md5Base64(InputSuppliers.of(input)), md5);
} catch (RuntimeException e) {
Closeables.closeQuietly(input);
// since we are parsing client side, and not through a response handler, the user
// must retry directly. In this case, we are assuming lightning doesn't strike
// since we are parsing client side, and not through a response
// handler, the user
// must retry directly. In this case, we are assuming lightning doesn't
// strike
// twice in the same spot.
if (Utils.getFirstThrowableOfType(e, IOException.class) != null) {
input = getConsitution();
@ -161,8 +167,8 @@ public abstract class BaseHttpCommandExecutorServiceIntegrationTest extends Base
}
/**
* Tests sending a big file to the server. Note: this is a heavy test, takes several minutes to
* finish.
* Tests sending a big file to the server. Note: this is a heavy test, takes
* several minutes to finish.
*
* @throws java.io.IOException
*/
@ -177,17 +183,12 @@ public abstract class BaseHttpCommandExecutorServiceIntegrationTest extends Base
f.deleteOnExit();
long length = (long) (Runtime.getRuntime().freeMemory() * 1.1);
os = new BufferedOutputStream(new FileOutputStream(f.getAbsolutePath()));
MessageDigest eTag;
try {
eTag = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Could not find the MD5 algorithm", e);
}
MessageDigest digester = context.utils().crypto().md5();
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
for (long i = 0; i < length; i++) {
eTag.update((byte) 'a');
digester.update((byte) 'a');
os.write((byte) 'a');
}
os.flush();
@ -197,9 +198,13 @@ public abstract class BaseHttpCommandExecutorServiceIntegrationTest extends Base
Closeables.closeQuietly(out);
}
// upload and verify the response
assertEquals(client.postWithMd5("fileso", CryptoStreams.base64Encode(InputSuppliers.of(eTag.digest())), f)
.trim(), "created");
Payload payload = Payloads.newFilePayload(f);
byte[] digest = digester.digest();
payload.setContentMD5(digest);
Multimap<String, String> headers = client.postPayloadAndReturnHeaders("", payload);
assertEquals(headers.get("x-Content-MD5"), Collections.singleton(CryptoStreams.base64Encode(InputSuppliers
.of(digest))));
payload.release();
} finally {
if (os != null)
os.close();
@ -228,9 +233,13 @@ public abstract class BaseHttpCommandExecutorServiceIntegrationTest extends Base
}
@Test(invocationCount = 5, timeOut = 5000)
public void testPostContentDisposition() throws MalformedURLException, ExecutionException, InterruptedException,
TimeoutException {
assertEquals(client.postWithContentDisposition("", "attachment; filename=photo.jpg", "foo").trim(), "content-disposition:photo.jpg");
public void testPostContentDisposition() throws ExecutionException, InterruptedException, TimeoutException,
IOException {
Payload payload = Payloads.newStringPayload("foo");
payload.setContentDisposition("attachment; filename=photo.jpg");
Multimap<String, String> headers = client.postPayloadAndReturnHeaders("", payload);
assertEquals(headers.get("x-Content-Disposition"), Collections.singleton("attachment; filename=photo.jpg"));
payload.release();
}
@Test(invocationCount = 5, timeOut = 5000)

View File

@ -25,6 +25,7 @@ import static com.google.common.io.ByteStreams.copy;
import static com.google.common.io.ByteStreams.join;
import static com.google.common.io.ByteStreams.newInputStreamSupplier;
import static com.google.common.io.ByteStreams.toByteArray;
import static com.google.common.io.Closeables.closeQuietly;
import static javax.ws.rs.core.HttpHeaders.CONTENT_LENGTH;
import static org.jclouds.rest.RestContextFactory.contextSpec;
import static org.jclouds.rest.RestContextFactory.createContextBuilder;
@ -43,6 +44,7 @@ import java.util.zip.GZIPInputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.HttpHeaders;
import org.jclouds.Constants;
import org.jclouds.crypto.CryptoStreams;
@ -118,33 +120,13 @@ public abstract class BaseJettyTest {
response.sendError(500, "no content");
}
} else if (request.getMethod().equals("POST")) {
if (redirectEveryTwentyRequests(request, response))
// don't redirect large objects
if (request.getContentLength() < 10240 && redirectEveryTwentyRequests(request, response))
return;
if (failEveryTenRequests(request, response))
return;
if (request.getContentLength() > 0) {
if (request.getHeader("Content-MD5") != null) {
String expectedMd5 = request.getHeader("Content-MD5");
String realMd5FromRequest = CryptoStreams.md5Base64(InputSuppliers.of(request.getInputStream()));
boolean matched = expectedMd5.equals(realMd5FromRequest);
if (matched) {
response.setContentType("text/xml");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println("created");
}
//TODO: insert here additional header detection
} else if (request.getHeader("Content-Disposition") != null) {
//get the filename
String content = request.getHeader("Content-Disposition");
int pos = content.lastIndexOf("=");
String fileName = pos > 0 ? content.substring(pos + 1) : "";
response.setContentType("text/xml");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println("content-disposition:" + fileName);
} else {
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println(toStringAndClose(request.getInputStream()) + "POST");
}
handlePost(request, response);
} else {
handleAction(request, response);
}
@ -156,7 +138,8 @@ public abstract class BaseJettyTest {
response.getWriter().println("test");
} else if (request.getMethod().equals("HEAD")) {
/*
* NOTE: by HTML specification, HEAD response MUST NOT include a body
* NOTE: by HTML specification, HEAD response MUST NOT include a
* body
*/
response.setContentType("text/xml");
response.setStatus(HttpServletResponse.SC_OK);
@ -187,6 +170,39 @@ public abstract class BaseJettyTest {
assert client.newStringBuffer() != null;
}
private static void handlePost(HttpServletRequest request, HttpServletResponse response) throws IOException {
try {
if (request.getHeader("Content-MD5") != null) {
String expectedMd5 = request.getHeader("Content-MD5");
String realMd5FromRequest;
realMd5FromRequest = CryptoStreams.md5Base64(InputSuppliers.of(request.getInputStream()));
boolean matched = expectedMd5.equals(realMd5FromRequest);
if (matched) {
response.setStatus(HttpServletResponse.SC_OK);
response.addHeader("x-Content-MD5", realMd5FromRequest);
} else {
response.sendError(500, "didn't match");
}
} else {
for (String header : new String[] { "Content-Disposition", HttpHeaders.CONTENT_LANGUAGE,
HttpHeaders.CONTENT_ENCODING })
if (request.getHeader(header) != null) {
response.addHeader("x-" + header, request.getHeader(header));
}
response.setStatus(HttpServletResponse.SC_OK);
String responseString = "POST";
if (request.getContentLength() < 10240) {
responseString = toStringAndClose(request.getInputStream()) + "POST";
} else {
closeQuietly(request.getInputStream());
}
response.getWriter().println(responseString);
}
} catch (IOException e) {
response.sendError(500, e.toString());
}
}
protected void setupAndStartSSLServer(final int testPort) throws Exception {
Handler server2Handler = new AbstractHandler() {
public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch)
@ -198,26 +214,14 @@ public abstract class BaseJettyTest {
}
} else if (request.getMethod().equals("POST")) {
if (request.getContentLength() > 0) {
if (request.getHeader("Content-MD5") != null) {
String expectedMd5 = request.getHeader("Content-MD5");
String realMd5FromRequest;
realMd5FromRequest = CryptoStreams.md5Base64(InputSuppliers.of(request.getInputStream()));
boolean matched = expectedMd5.equals(realMd5FromRequest);
if (matched) {
response.setContentType("text/xml");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println("created");
}
} else {
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println(toStringAndClose(request.getInputStream()) + "POST");
}
handlePost(request, response);
} else {
handleAction(request, response);
}
} else if (request.getMethod().equals("HEAD")) {
/*
* NOTE: by HTML specification, HEAD response MUST NOT include a body
* NOTE: by HTML specification, HEAD response MUST NOT include a
* body
*/
response.setContentType("text/xml");
response.setStatus(HttpServletResponse.SC_OK);

View File

@ -19,13 +19,13 @@
package org.jclouds.http;
import java.io.File;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Map;
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.HEAD;
import javax.ws.rs.HeaderParam;
@ -37,12 +37,13 @@ import javax.ws.rs.PathParam;
import org.jclouds.http.functions.ParseSax;
import org.jclouds.http.options.HttpRequestOptions;
import org.jclouds.rest.Binder;
import org.jclouds.io.Payload;
import org.jclouds.rest.annotations.BinderParam;
import org.jclouds.rest.annotations.ExceptionParser;
import org.jclouds.rest.annotations.MapBinder;
import org.jclouds.rest.annotations.MapPayloadParam;
import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.annotations.ResponseParser;
import org.jclouds.rest.annotations.XMLResponseParser;
import org.jclouds.rest.binders.BindMapToMatrixParams;
import org.jclouds.rest.binders.BindToJsonPayload;
@ -50,6 +51,7 @@ import org.jclouds.rest.binders.BindToStringPayload;
import org.jclouds.util.Utils;
import com.google.common.base.Function;
import com.google.common.collect.Multimap;
import com.google.common.util.concurrent.ListenableFuture;
/**
@ -120,23 +122,20 @@ public interface IntegrationTestAsyncClient {
}
}
@POST
@Path("/objects/{id}")
ListenableFuture<String> postWithMd5(@PathParam("id") String id, @HeaderParam("Content-MD5") String base64MD5,
@BinderParam(BindToFilePayload.class) File file);
@Singleton
static class ResponsePayload implements Function<HttpResponse, Multimap<String, String>> {
static class BindToFilePayload implements Binder {
@Override
public void bindToRequest(HttpRequest request, Object payload) {
File f = (File) payload;
request.setPayload(f);
public Multimap<String, String> apply(HttpResponse from) {
return from.getHeaders();
}
}
@POST
@Path("/objects/{id}")
String postWithContentDisposition(@PathParam("id") String id, @HeaderParam("Content-Disposition") String contentDisposition,
@BinderParam(BindToStringPayload.class) String toPut);
@ResponseParser(ResponsePayload.class)
ListenableFuture<Multimap<String, String>> postPayloadAndReturnHeaders(@PathParam("id") String id,
Payload payload);
@POST
@Path("/objects/{id}")

View File

@ -19,12 +19,14 @@
package org.jclouds.http;
import java.io.File;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.jclouds.concurrent.Timeout;
import org.jclouds.http.options.HttpRequestOptions;
import org.jclouds.io.Payload;
import com.google.common.collect.Multimap;
/**
* Sample test for the behaviour of our Integration Test jetty server.
@ -51,9 +53,7 @@ public interface IntegrationTestClient {
String postAsInputStream(String id, String toPut);
String postWithMd5(String id, String base64MD5, File file);
String postWithContentDisposition(String id, String contentDisposition, String toPut);
Multimap<String, String> postPayloadAndReturnHeaders(String id, Payload payload);
String postJson(String id, String toPut);

View File

@ -72,8 +72,10 @@ public abstract class BaseRestClientTest {
bind(TransformingHttpCommandExecutorService.class).toInstance(mock);
}
}
protected void assertPayloadEquals(HttpRequest request, String toMatch, String contentType, boolean contentMD5) {
assertPayloadEquals(request, toMatch, contentType, null, contentMD5);
}
protected void assertPayloadEquals(HttpRequest request, String toMatch, String contentType, String contentDispositon, boolean contentMD5) {
if (request.getPayload() == null) {
assertNull(toMatch);
} else {
@ -86,7 +88,7 @@ public abstract class BaseRestClientTest {
assertEquals(payload, toMatch);
Long length = new Long(payload.getBytes().length);
try {
assertContentHeadersEqual(request, contentType, length, contentMD5 ? CryptoStreams
assertContentHeadersEqual(request, contentType, contentDispositon, length, contentMD5 ? CryptoStreams
.md5(request.getPayload()) : null);
} catch (IOException e) {
propagate(e);
@ -94,7 +96,7 @@ public abstract class BaseRestClientTest {
}
}
protected void assertContentHeadersEqual(HttpRequest request, String contentType, Long length, byte[] contentMD5) {
protected void assertContentHeadersEqual(HttpRequest request, String contentType, String contentDispositon, Long length, byte[] contentMD5) {
if (request.getFirstHeaderOrNull(TRANSFER_ENCODING) == null) {
assertEquals(request.getPayload().getContentLength(), length);
} else {
@ -103,8 +105,8 @@ public abstract class BaseRestClientTest {
|| request.getPayload().getContentLength().equals(length);
}
assertEquals(request.getPayload().getContentType(), contentType);
assertEquals(request.getPayload().getContentDisposition(), contentDispositon);
assertEquals(request.getPayload().getContentMD5(), contentMD5);
}
//FIXME Shouldn't be assertPayloadHeadersEqual?

View File

@ -1500,8 +1500,8 @@ public class RestAnnotationProcessorTest extends BaseRestClientTest {
payload.setContentDisposition("attachment; filename=photo.jpg");
HttpRequest request = factory(TestQuery.class).createRequest(method, payload);
assertRequestLineEquals(request, "PUT http://localhost:9999?x-ms-version=2009-07-17 HTTP/1.1");
assertNonPayloadHeadersEqual(request, "Content-Disposition: attachment; filename=photo.jpg");
assertPayloadEquals(request, "whoops", "application/unknown", false);
assertNonPayloadHeadersEqual(request, "");
assertPayloadEquals(request, "whoops", "application/unknown", "attachment; filename=photo.jpg", false);
}
public void testPutPayloadWithGeneratedMD5AndNoContentType() throws SecurityException, NoSuchMethodException,
@ -2068,8 +2068,7 @@ public class RestAnnotationProcessorTest extends BaseRestClientTest {
@Override
protected void configure() {
bind(URI.class).annotatedWith(Localhost2.class).toInstance(
URI.create("http://localhost:1111"));
bind(URI.class).annotatedWith(Localhost2.class).toInstance(URI.create("http://localhost:1111"));
}
}));