allow filtering to be chained by returning the request on filter()

git-svn-id: http://jclouds.googlecode.com/svn/trunk@1854 3d8758e0-26b5-11de-8745-db77d3ebf521
This commit is contained in:
adrian.f.cole 2009-08-09 22:59:33 +00:00
parent c1c6ab959b
commit fbb7740f17
6 changed files with 95 additions and 34 deletions

View File

@ -23,14 +23,16 @@
*/ */
package org.jclouds.aws.s3.filters; package org.jclouds.aws.s3.filters;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import static com.google.common.base.Preconditions.checkNotNull;
import org.jclouds.aws.s3.reference.S3Constants; import org.jclouds.aws.s3.reference.S3Constants;
import org.jclouds.http.HttpException; import org.jclouds.http.HttpException;
@ -53,8 +55,8 @@ import com.google.inject.name.Named;
*/ */
@Singleton @Singleton
public class RequestAuthorizeSignature implements HttpRequestFilter { public class RequestAuthorizeSignature implements HttpRequestFilter {
private final String[] firstHeadersToSign = new String[] { private final String[] firstHeadersToSign = new String[] { "Content-MD5",
"Content-MD5", HttpHeaders.CONTENT_TYPE, HttpHeaders.DATE }; HttpHeaders.CONTENT_TYPE, HttpHeaders.DATE };
private final String accessKey; private final String accessKey;
private final String secretKey; private final String secretKey;
@ -100,18 +102,16 @@ public class RequestAuthorizeSignature implements HttpRequestFilter {
timeStamp = new AtomicReference<String>(createNewStamp()); timeStamp = new AtomicReference<String>(createNewStamp());
} }
public void filter(HttpRequest request) throws HttpException { public HttpRequest filter(HttpRequest request) throws HttpException {
replaceDateHeader(request);
String toSign = createStringToSign(request); String toSign = createStringToSign(request);
calculateAndReplaceAuthHeader(request, toSign);
addAuthHeader(request, toSign); return request;
} }
public String createStringToSign(HttpRequest request) { public String createStringToSign(HttpRequest request) {
StringBuilder buffer = new StringBuilder(); StringBuilder buffer = new StringBuilder();
// re-sign the request // re-sign the request
removeOldHeaders(request);
addDateHeader(request);
appendMethod(request, buffer); appendMethod(request, buffer);
appendHttpHeaders(request, buffer); appendHttpHeaders(request, buffer);
appendAmzHeaders(request, buffer); appendAmzHeaders(request, buffer);
@ -120,27 +120,30 @@ public class RequestAuthorizeSignature implements HttpRequestFilter {
return buffer.toString(); return buffer.toString();
} }
private void removeOldHeaders(HttpRequest request) { private void calculateAndReplaceAuthHeader(HttpRequest request, String toSign)
request.getHeaders().removeAll(HttpHeaders.AUTHORIZATION); throws HttpException {
request.getHeaders().removeAll(HttpHeaders.DATE); String signature = signString(toSign);
request.getHeaders().replaceValues(HttpHeaders.AUTHORIZATION,
Collections.singletonList("AWS " + accessKey + ":" + signature));
} }
private void addAuthHeader(HttpRequest request, String toSign) throws HttpException { public String signString(String toSign) {
String signature; String signature;
try { try {
signature = HttpUtils.hmacSha1Base64(toSign, secretKey.getBytes()); signature = HttpUtils.hmacSha1Base64(toSign, secretKey.getBytes());
} catch (Exception e) { } catch (Exception e) {
throw new HttpException("error signing request", e); throw new HttpException("error signing request", e);
} }
request.getHeaders().put(HttpHeaders.AUTHORIZATION, "AWS " + accessKey + ":" + signature); return signature;
} }
private void appendMethod(HttpRequest request, StringBuilder toSign) { private void appendMethod(HttpRequest request, StringBuilder toSign) {
toSign.append(request.getMethod()).append("\n"); toSign.append(request.getMethod()).append("\n");
} }
private void addDateHeader(HttpRequest request) { private void replaceDateHeader(HttpRequest request) {
request.getHeaders().put(HttpHeaders.DATE, timestampAsHeaderString()); request.getHeaders().replaceValues(HttpHeaders.DATE,
Collections.singletonList(timestampAsHeaderString()));
} }
private void appendAmzHeaders(HttpRequest request, StringBuilder toSign) { private void appendAmzHeaders(HttpRequest request, StringBuilder toSign) {
@ -174,7 +177,7 @@ public class RequestAuthorizeSignature implements HttpRequestFilter {
@VisibleForTesting @VisibleForTesting
void appendUriPath(HttpRequest request, StringBuilder toSign) { void appendUriPath(HttpRequest request, StringBuilder toSign) {
toSign.append(request.getEndpoint().getPath()); toSign.append(request.getEndpoint().getRawPath());
// ...however, there are a few exceptions that must be included in the signed URI. // ...however, there are a few exceptions that must be included in the signed URI.
if (request.getEndpoint().getQuery() != null) { if (request.getEndpoint().getQuery() != null) {

View File

@ -33,22 +33,60 @@ import org.jclouds.aws.s3.reference.S3Constants;
import org.jclouds.http.HttpMethod; import org.jclouds.http.HttpMethod;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.util.DateService; import org.jclouds.util.DateService;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.Guice; import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.name.Names; import com.google.inject.name.Names;
@Test(groups = "unit", testName = "s3.RequestAuthorizeSignatureTest") @Test(groups = "unit", testName = "s3.RequestAuthorizeSignatureTest")
public class RequestAuthorizeSignatureTest { public class RequestAuthorizeSignatureTest {
private Injector injector;
private RequestAuthorizeSignature filter;
@DataProvider(parallel = true)
public Object[][] dataProvider() {
return new Object[][] {
{ new HttpRequest(HttpMethod.GET, URI.create("http://s3.amazonaws.com:80")) },
{ new HttpRequest(
HttpMethod.GET,
URI
.create("http://adriancole.s3int5.s3-external-3.amazonaws.com:80/testObject")) },
{ new HttpRequest(HttpMethod.GET, URI.create("http://s3.amazonaws.com:80/?acl"))
} };
}
/**
* NOTE this test is dependent on how frequently the timestamp updates. At the time of writing,
* this was once per second. If this timestamp update interval is increased, it could make this
* test appear to hang for a long time.
*/
@Test(threadPoolSize = 3, dataProvider = "dataProvider", timeOut = 3000)
void testIdempotent(HttpRequest request) {
filter.filter(request);
String signature = request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION);
String date = request.getFirstHeaderOrNull(HttpHeaders.DATE);
int iterations = 1;
while (filter.filter(request).getFirstHeaderOrNull(HttpHeaders.DATE).equals(date)) {
iterations++;
assertEquals(signature, request.getFirstHeaderOrNull(HttpHeaders.AUTHORIZATION));
}
System.out.printf("%s: %d iterations before the timestamp updated %n", Thread.currentThread()
.getName(), iterations);
}
@Test @Test
void testAppendBucketNameHostHeader() { void testAppendBucketNameHostHeader() {
URI host = URI.create("http://s3.amazonaws.com:80"); URI host = URI.create("http://s3.amazonaws.com:80");
HttpRequest request = new HttpRequest(HttpMethod.GET, host); HttpRequest request = new HttpRequest(HttpMethod.GET, host);
request.getHeaders().put(HttpHeaders.HOST, "adriancole.s3int5.s3.amazonaws.com"); request.getHeaders().put(HttpHeaders.HOST, "adriancole.s3int5.s3.amazonaws.com");
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
createFilter().appendBucketName(request, builder); filter.appendBucketName(request, builder);
assertEquals(builder.toString(), "/adriancole.s3int5"); assertEquals(builder.toString(), "/adriancole.s3int5");
} }
@ -57,7 +95,7 @@ public class RequestAuthorizeSignatureTest {
URI host = URI.create("http://s3.amazonaws.com:80/?acl"); URI host = URI.create("http://s3.amazonaws.com:80/?acl");
HttpRequest request = new HttpRequest(HttpMethod.GET, host); HttpRequest request = new HttpRequest(HttpMethod.GET, host);
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
createFilter().appendUriPath(request, builder); filter.appendUriPath(request, builder);
assertEquals(builder.toString(), "/?acl"); assertEquals(builder.toString(), "/?acl");
} }
@ -69,7 +107,7 @@ public class RequestAuthorizeSignatureTest {
HttpRequest request = new HttpRequest(HttpMethod.GET, host); HttpRequest request = new HttpRequest(HttpMethod.GET, host);
request.getHeaders().put(HttpHeaders.HOST, "s3.amazonaws.com"); request.getHeaders().put(HttpHeaders.HOST, "s3.amazonaws.com");
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
createFilter().appendBucketName(request, builder); filter.appendBucketName(request, builder);
assertEquals(builder.toString(), ""); assertEquals(builder.toString(), "");
} }
@ -78,13 +116,12 @@ public class RequestAuthorizeSignatureTest {
URI host = URI.create("http://adriancole.s3int5.s3-external-3.amazonaws.com:80"); URI host = URI.create("http://adriancole.s3int5.s3-external-3.amazonaws.com:80");
HttpRequest request = new HttpRequest(HttpMethod.GET, host); HttpRequest request = new HttpRequest(HttpMethod.GET, host);
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
createFilter().appendBucketName(request, builder); filter.appendBucketName(request, builder);
assertEquals(builder.toString(), "/adriancole.s3int5"); assertEquals(builder.toString(), "/adriancole.s3int5");
} }
@Test @Test
void testUpdatesOnlyOncePerSecond() throws NoSuchMethodException, InterruptedException { void testUpdatesOnlyOncePerSecond() throws NoSuchMethodException, InterruptedException {
RequestAuthorizeSignature filter = createFilter();
// filter.createNewStamp(); // filter.createNewStamp();
String timeStamp = filter.timestampAsHeaderString(); String timeStamp = filter.timestampAsHeaderString();
// replay(filter); // replay(filter);
@ -96,8 +133,13 @@ public class RequestAuthorizeSignatureTest {
// verify(filter); // verify(filter);
} }
private RequestAuthorizeSignature createFilter() { /**
return Guice.createInjector(new AbstractModule() { * before class, as we need to ensure that the filter is threadsafe.
*
*/
@BeforeClass
protected void createFilter() {
injector = Guice.createInjector(new AbstractModule() {
protected void configure() { protected void configure() {
bindConstant().annotatedWith(Names.named(S3Constants.PROPERTY_AWS_ACCESSKEYID)).to( bindConstant().annotatedWith(Names.named(S3Constants.PROPERTY_AWS_ACCESSKEYID)).to(
@ -107,7 +149,8 @@ public class RequestAuthorizeSignatureTest {
bind(DateService.class); bind(DateService.class);
} }
}).getInstance(RequestAuthorizeSignature.class); });
filter = injector.getInstance(RequestAuthorizeSignature.class);
} }
} }

View File

@ -29,5 +29,5 @@ package org.jclouds.http;
* @author Adrian Cole * @author Adrian Cole
*/ */
public interface HttpRequestFilter { public interface HttpRequestFilter {
void filter(HttpRequest request) throws HttpException; HttpRequest filter(HttpRequest request) throws HttpException;
} }

View File

@ -101,10 +101,11 @@ public interface IntegrationTestClient {
Future<String> downloadFilter(@PathParam("id") String id, @HeaderParam("filterme") String header); Future<String> downloadFilter(@PathParam("id") String id, @HeaderParam("filterme") String header);
static class Filter implements HttpRequestFilter { static class Filter implements HttpRequestFilter {
public void filter(HttpRequest request) throws HttpException { public HttpRequest filter(HttpRequest request) throws HttpException {
if (request.getHeaders().containsKey("filterme")) { if (request.getHeaders().containsKey("filterme")) {
request.getHeaders().put("test", "test"); request.getHeaders().put("test", "test");
} }
return request;
} }
} }

View File

@ -29,6 +29,7 @@ import static org.testng.Assert.assertEquals;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.URI; import java.net.URI;
import java.net.URLEncoder;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
@ -60,6 +61,7 @@ import org.jclouds.util.DateService;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.mortbay.jetty.HttpHeaders; import org.mortbay.jetty.HttpHeaders;
import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.common.base.Function; import com.google.common.base.Function;
@ -201,14 +203,16 @@ public class JaxrsAnnotationProcessorTest {
static class TestRequestFilter1 implements HttpRequestFilter { static class TestRequestFilter1 implements HttpRequestFilter {
public void filter(HttpRequest request) throws HttpException { public HttpRequest filter(HttpRequest request) throws HttpException {
return null;
} }
} }
static class TestRequestFilter2 implements HttpRequestFilter { static class TestRequestFilter2 implements HttpRequestFilter {
public void filter(HttpRequest request) throws HttpException { public HttpRequest filter(HttpRequest request) throws HttpException {
return null;
} }
} }
@ -586,13 +590,22 @@ public class JaxrsAnnotationProcessorTest {
} }
public void testCreateGetRequest() throws SecurityException, NoSuchMethodException { @DataProvider(name = "strings")
public Object[][] createData() {
return new Object[][] { { "apples" }, { "sp ace" }, { "unic¿de" }, { "qu?stion" } };
}
@Test(dataProvider = "strings")
public void testCreateGetRequest(String key) throws SecurityException, NoSuchMethodException,
UnsupportedEncodingException {
Method method = TestRequest.class.getMethod("get", String.class, String.class); Method method = TestRequest.class.getMethod("get", String.class, String.class);
URI endpoint = URI.create("http://localhost"); URI endpoint = URI.create("http://localhost");
HttpRequest httpMethod = factory.create(TestRequest.class).createRequest(endpoint, method, HttpRequest httpMethod = factory.create(TestRequest.class).createRequest(endpoint, method,
new Object[] { "1", "localhost" }); new Object[] { key, "localhost" });
assertEquals(httpMethod.getEndpoint().getHost(), "localhost"); assertEquals(httpMethod.getEndpoint().getHost(), "localhost");
assertEquals(httpMethod.getEndpoint().getPath(), "/1"); String expectedPath = "/" + URLEncoder.encode(key, "UTF-8").replaceAll("\\+", "%20");
assertEquals(httpMethod.getEndpoint().getRawPath(), expectedPath);
assertEquals(httpMethod.getEndpoint().getPath(), "/" + key);
assertEquals(httpMethod.getMethod(), HttpMethod.GET); assertEquals(httpMethod.getMethod(), HttpMethod.GET);
assertEquals(httpMethod.getHeaders().size(), 1); assertEquals(httpMethod.getHeaders().size(), 1);
assertEquals(httpMethod.getHeaders().get(HttpHeaders.HOST), Collections assertEquals(httpMethod.getHeaders().get(HttpHeaders.HOST), Collections

View File

@ -88,9 +88,10 @@ public class AuthenticateRequest implements HttpRequestFilter {
authToken = new AtomicReference<String>(); authToken = new AtomicReference<String>();
} }
public void filter(HttpRequest request) throws HttpException { public HttpRequest filter(HttpRequest request) throws HttpException {
request.getHeaders().replaceValues(RackspaceHeaders.AUTH_TOKEN, request.getHeaders().replaceValues(RackspaceHeaders.AUTH_TOKEN,
Collections.singletonList(getAuthToken())); Collections.singletonList(getAuthToken()));
return request;
} }
} }