Author: beaton
Date: Sun Jul 20 23:01:03 2008
New Revision: 678354

URL: http://svn.apache.org/viewvc?rev=678354&view=rev
Log:
Add caching of OAuth responses.

Modified:
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpResponse.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthFetcher.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthFetcherFactory.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/http/HttpResponseTest.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthFetcherTest.java

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpResponse.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpResponse.java?rev=678354&r1=678353&r2=678354&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpResponse.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpResponse.java
 Sun Jul 20 23:01:03 2008
@@ -331,17 +331,25 @@
       this.headers.put("Cache-Control", Lists.newArrayList("public,max-age=" + 
forcedCacheTtl));
     }
   }
+  
+  /**
+   * Sets cache-control headers indicating the response is not cacheable.
+   */
+  public void setNoCache() {
+    this.headers.put("Cache-Control", Lists.newArrayList("no-cache"));
+    this.headers.put("Pragma", Lists.newArrayList("no-cache"));
+  }
 
   /**
    * @return consolidated cache expiration time or -1
    */
   public long getCacheExpiration() {
-    if (httpStatusCode != SC_OK) {
-      return getDate() + negativeCacheTtl;
-    }
     if (isStrictNoCache()) {
       return -1;
     }
+    if (httpStatusCode != SC_OK) {
+      return getDate() + negativeCacheTtl;
+    }
     long maxAge = getCacheControlMaxAge();
     if (maxAge != -1) {
       return getDate() + maxAge;

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthFetcher.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthFetcher.java?rev=678354&r1=678353&r2=678354&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthFetcher.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthFetcher.java
 Sun Jul 20 23:01:03 2008
@@ -21,6 +21,8 @@
 import org.apache.shindig.common.crypto.BlobCrypterException;
 import org.apache.shindig.gadgets.ChainedContentFetcher;
 import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.http.HttpCache;
+import org.apache.shindig.gadgets.http.HttpCacheKey;
 import org.apache.shindig.gadgets.http.HttpFetcher;
 import org.apache.shindig.gadgets.http.HttpResponse;
 import org.apache.shindig.gadgets.http.HttpRequest;
@@ -142,6 +144,11 @@
    * to the gadget spec for information (e.g. OAuth URLs).
    */
   private final boolean bypassSpecCache;
+  
+  /**
+   * HTTP cache.
+   */
+  private HttpCache cache;
 
   /**
    *
@@ -151,13 +158,15 @@
    * @param authToken user's gadget security token
    * @param params OAuth fetch parameters sent from makeRequest
    * @param tokenStore storage for long lived tokens.
+   * @param cache cache to use for HTTP responses.
    */
   public OAuthFetcher(
       GadgetOAuthTokenStore tokenStore,
       BlobCrypter oauthCrypter,
       HttpFetcher nextFetcher,
       SecurityToken authToken,
-      OAuthRequestParams params) {
+      OAuthRequestParams params,
+      HttpCache cache) {
     super(nextFetcher);
     this.oauthCrypter = oauthCrypter;
     this.authToken = authToken;
@@ -180,6 +189,7 @@
       this.origClientState = new HashMap<String, String>();
     }
     this.tokenStore = tokenStore;
+    this.cache = cache;
   }
 
   /**
@@ -226,6 +236,12 @@
   }
 
   public HttpResponse fetch(HttpRequest request) throws GadgetException {
+    HttpCacheKey cacheKey = makeCacheKey(request);
+    HttpResponse response = cache.getResponse(cacheKey, request);
+    if (response != null) {
+      return response;
+    }
+    
     try {
       lookupOAuthMetadata();
     } catch (GadgetException e) {
@@ -234,11 +250,7 @@
     }
     
     this.realRequest = request;
-    // Work around for busted HttpCache interface that can't
-    // properly handle authenticated content.
-    this.realRequest.getOptions().ignoreCache = true;
 
-    HttpResponse response = null;
     int attempts = 0;
     boolean retry;
     do {
@@ -259,9 +271,25 @@
           GadgetException.Code.INTERNAL_SERVER_ERROR,
           "No response for OAuth fetch to " + realRequest.getUri());
     }
-    return response;
+    return cache.addResponse(cacheKey, request, response);
   }
   
+  // Builds up a cache key based on the same key that we use into the OAuth
+  // token storage, which should identify precisely which data to return for 
the
+  // response.  Using the OAuth access token as the cache key is another
+  // possibility.
+  private HttpCacheKey makeCacheKey(HttpRequest request) {
+    HttpCacheKey key = new HttpCacheKey(request);
+    key.set("authentication", "oauth");
+    OAuthStore.TokenKey tokenKey = buildTokenKey();
+    key.set("user", tokenKey.getUserId());
+    key.set("gadget", tokenKey.getGadgetUri());
+    key.set("instance", Long.toString(tokenKey.getModuleId()));
+    key.set("service", tokenKey.getServiceName());
+    key.set("token", tokenKey.getTokenName());
+    return key;
+  }
+
   private HttpResponse buildErrorResponse(GadgetException e) {
     if (error == null) {
       error = OAuthError.UNKNOWN_PROBLEM;
@@ -566,6 +594,7 @@
   private HttpResponse buildNonDataResponse() {
     HttpResponse response = new HttpResponse(0, null, null);
     addResponseMetadata(response);
+    response.setNoCache();
     return response;
   }
 
@@ -678,6 +707,9 @@
           realRequest.getContentType(),
           realRequest.getPostBodyAsString(),
           realRequest.getOptions());
+      
+      // Not externally cacheable.
+      oauthHttpRequest.getOptions().ignoreCache = true;
 
       HttpResponse response = nextFetcher.fetch(oauthHttpRequest);
       

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthFetcherFactory.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthFetcherFactory.java?rev=678354&r1=678353&r2=678354&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthFetcherFactory.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/oauth/OAuthFetcherFactory.java
 Sun Jul 20 23:01:03 2008
@@ -24,6 +24,7 @@
 import org.apache.shindig.common.crypto.Crypto;
 import org.apache.shindig.gadgets.GadgetException;
 import org.apache.shindig.gadgets.GadgetSpecFactory;
+import org.apache.shindig.gadgets.http.HttpCache;
 import org.apache.shindig.gadgets.http.HttpFetcher;
 
 import com.google.inject.Inject;
@@ -42,6 +43,9 @@
   /** persistent storage for OAuth tokens */
   protected GadgetOAuthTokenStore tokenStore;
 
+  /** shared HTTP cache */
+  private HttpCache cache;
+
   private static final Logger logger
       = Logger.getLogger(OAuthFetcherFactory.class.getName());
 
@@ -50,7 +54,7 @@
    * BlobCrypter and consumer keys/secrets read from oauth.js
    */
   @Inject
-  public OAuthFetcherFactory(GadgetSpecFactory specFactory) {
+  public OAuthFetcherFactory(GadgetSpecFactory specFactory, HttpCache cache) {
     try {
       this.oauthCrypter = new BasicBlobCrypter(
           Crypto.getRandomBytes(BasicBlobCrypter.MASTER_KEY_MIN_LEN));
@@ -59,6 +63,7 @@
           new BasicGadgetOAuthTokenStore(new BasicOAuthStore(), specFactory);
       store.initFromConfigFile();
       this.tokenStore = store;
+      this.cache = cache;
     } catch (Throwable t) {
       // Since this happens at startup, we don't want to kill the server just
       // because we can't initialize the OAuth config.
@@ -71,12 +76,15 @@
    *
    * @param oauthCrypter used to wrap client side state
    * @param tokenStore used as interface to persistent token store.
+   * @param cache shared HTTP cache
    */
   protected OAuthFetcherFactory(
       BlobCrypter oauthCrypter,
-      GadgetOAuthTokenStore tokenStore) {
+      GadgetOAuthTokenStore tokenStore,
+      HttpCache cache) {
     this.oauthCrypter = oauthCrypter;
     this.tokenStore = tokenStore;
+    this.cache = cache;
   }
 
   /**
@@ -95,7 +103,7 @@
       SecurityToken token,
       OAuthRequestParams params) throws GadgetException {
     OAuthFetcher fetcher = new OAuthFetcher(
-        tokenStore, oauthCrypter, nextFetcher, token, params);
+        tokenStore, oauthCrypter, nextFetcher, token, params, cache);
     return fetcher;
   }
 }

Modified: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/http/HttpResponseTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/http/HttpResponseTest.java?rev=678354&r1=678353&r2=678354&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/http/HttpResponseTest.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/http/HttpResponseTest.java
 Sun Jul 20 23:01:03 2008
@@ -249,6 +249,12 @@
     long ttl = HttpResponse.error().getCacheTtl();
     assertTrue(ttl <= HttpResponse.DEFAULT_TTL && ttl > 0);
   }
+  
+  public void testStrictNoCacheAndNegativeCaching() {
+    HttpResponse response = new HttpResponse(401, UTF8_DATA, null);
+    response.setNoCache();
+    assertEquals(-1, response.getCacheTtl());
+  }
 
   public void testNullHeaderNamesStripped() {
     addHeader(null, "dummy");

Modified: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthFetcherTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthFetcherTest.java?rev=678354&r1=678353&r2=678354&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthFetcherTest.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/oauth/OAuthFetcherTest.java
 Sun Jul 20 23:01:03 2008
@@ -30,6 +30,8 @@
 import org.apache.shindig.common.crypto.BlobCrypter;
 import org.apache.shindig.gadgets.FakeGadgetSpecFactory;
 import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.http.BasicHttpCache;
+import org.apache.shindig.gadgets.http.HttpCache;
 import org.apache.shindig.gadgets.http.HttpFetcher;
 import org.apache.shindig.gadgets.http.HttpRequest;
 import org.apache.shindig.gadgets.http.HttpResponse;
@@ -49,6 +51,7 @@
   private GadgetOAuthTokenStore tokenStore;
   private BlobCrypter blobCrypter;
   private FakeOAuthServiceProvider serviceProvider;
+  private HttpCache cache;
 
   public static final String GADGET_URL = "http://www.example.com/gadget.xml";;
   public static final String GADGET_URL_NO_KEY =
@@ -59,6 +62,7 @@
     serviceProvider = new FakeOAuthServiceProvider();
     tokenStore = getOAuthStore();
     blobCrypter = new BasicBlobCrypter("abcdefghijklmnop".getBytes());
+    cache = new BasicHttpCache(10);
   }
 
   /**
@@ -133,7 +137,7 @@
   public HttpFetcher getFetcher(SecurityToken authToken,
       OAuthRequestParams params) throws GadgetException {
     OAuthFetcher fetcher = new OAuthFetcher(
-        tokenStore, blobCrypter, serviceProvider, authToken, params);
+        tokenStore, blobCrypter, serviceProvider, authToken, params, cache);
     return fetcher;
   }
 
@@ -236,6 +240,7 @@
             clientState, false));
     request = new HttpRequest(
         new URI(FakeOAuthServiceProvider.RESOURCE_URL));
+    request.getOptions().ignoreCache = true;
     response = fetcher.fetch(request);
 
     clientState = response.getMetadata().get("oauthState");
@@ -250,6 +255,7 @@
             clientState, false));
     request = new HttpRequest(
         new URI(FakeOAuthServiceProvider.RESOURCE_URL));
+    request.getOptions().ignoreCache = true;
     response = fetcher.fetch(request);
     assertEquals("User data is reapproved", response.getResponseAsString());
   }
@@ -294,6 +300,7 @@
             clientState, false));
     request = new HttpRequest(
         new URI(FakeOAuthServiceProvider.RESOURCE_URL));
+    request.getOptions().ignoreCache = true;
     response = fetcher.fetch(request);
 
     clientState = response.getMetadata().get("oauthState");
@@ -308,6 +315,7 @@
             clientState, false));
     request = new HttpRequest(
         new URI(FakeOAuthServiceProvider.RESOURCE_URL));
+    request.getOptions().ignoreCache = true;
     response = fetcher.fetch(request);
     assertEquals("User data is reapproved", response.getResponseAsString());
   }
@@ -371,6 +379,7 @@
             false));
     request = new HttpRequest(
         new URI(FakeOAuthServiceProvider.RESOURCE_URL));
+    request.getOptions().ignoreCache = true;
     response = fetcher.fetch(request);
     clientState = response.getMetadata().get("oauthState");
     assertNotNull(clientState);
@@ -387,8 +396,8 @@
         getSecurityToken("owner", "owner"),
         new OAuthRequestParams(FakeGadgetSpecFactory.SERVICE_NAME, null,
             clientState, false));
-    request = new HttpRequest(
-        new URI(FakeOAuthServiceProvider.RESOURCE_URL));
+    request = new HttpRequest(new URI(FakeOAuthServiceProvider.RESOURCE_URL));
+    request.getOptions().ignoreCache = true;
     response = fetcher.fetch(request);
     assertEquals("User data is hello-oauth", response.getResponseAsString());
     clientState = response.getMetadata().get("oauthState");
@@ -401,8 +410,8 @@
         getSecurityToken("owner", "owner"),
         new OAuthRequestParams(FakeGadgetSpecFactory.SERVICE_NAME, null,
             clientState, false));
-    request = new HttpRequest(
-        new URI(FakeOAuthServiceProvider.RESOURCE_URL));
+    request = new HttpRequest(new URI(FakeOAuthServiceProvider.RESOURCE_URL));
+    request.getOptions().ignoreCache = true;
     response = fetcher.fetch(request);
     assertEquals("User data is hello-oauth", response.getResponseAsString());
     clientState = response.getMetadata().get("oauthState");
@@ -417,8 +426,8 @@
         getSecurityToken("owner", "owner"),
         new OAuthRequestParams(FakeGadgetSpecFactory.SERVICE_NAME, null, null,
             false));
-    request = new HttpRequest(
-        new URI(FakeOAuthServiceProvider.RESOURCE_URL));
+    request = new HttpRequest(new URI(FakeOAuthServiceProvider.RESOURCE_URL));
+    request.getOptions().ignoreCache = true;
     response = fetcher.fetch(request);
     Map<String, String> metadata = response.getMetadata();
     assertNotNull(metadata);
@@ -438,8 +447,8 @@
         getSecurityToken("owner", "owner"),
         new OAuthRequestParams(FakeGadgetSpecFactory.SERVICE_NAME, null,
             clientState, false));
-    request = new HttpRequest(
-        new URI(FakeOAuthServiceProvider.RESOURCE_URL));
+    request = new HttpRequest(new URI(FakeOAuthServiceProvider.RESOURCE_URL));
+    request.getOptions().ignoreCache = true;
     response = fetcher.fetch(request);
     clientState = response.getMetadata().get("oauthState");
 
@@ -490,8 +499,7 @@
             reqToken.secret);
 
     fetcher = getFetcher(getSecurityToken("owner", "owner"), params);
-    request = new HttpRequest(
-        new URI(FakeOAuthServiceProvider.RESOURCE_URL));
+    request = new HttpRequest(new URI(FakeOAuthServiceProvider.RESOURCE_URL));
     response = fetcher.fetch(request);
     
     String clientState = response.getMetadata().get("oauthState");
@@ -504,8 +512,8 @@
     assertEquals(1, serviceProvider.getResourceAccessCount());
     
     fetcher = getFetcher(getSecurityToken("owner", "owner"), params);
-    request = new HttpRequest(
-        new URI(FakeOAuthServiceProvider.RESOURCE_URL));
+    request = new HttpRequest(new URI(FakeOAuthServiceProvider.RESOURCE_URL));
+    request.getOptions().ignoreCache = true;
     response = fetcher.fetch(request);
     
     assertEquals("User data is preapproved", response.getResponseAsString());
@@ -514,8 +522,8 @@
     assertEquals(2, serviceProvider.getResourceAccessCount());
     
     fetcher = getFetcher(getSecurityToken("owner", "owner"), params);
-    request = new HttpRequest(
-        new URI(FakeOAuthServiceProvider.RESOURCE_URL));
+    request = new HttpRequest(new URI(FakeOAuthServiceProvider.RESOURCE_URL));
+    request.getOptions().ignoreCache = true;
     response = fetcher.fetch(request);
     
     assertEquals("User data is preapproved", response.getResponseAsString());
@@ -610,5 +618,52 @@
     assertEquals("User data is hello-oauth", response.getResponseAsString());
   }
   
+  @Test
+  public void testCachedResponse() throws Exception {
+    HttpFetcher fetcher;
+    HttpRequest request;
+    HttpResponse response;
+    
+    assertEquals(0, serviceProvider.getRequestTokenCount());
+    assertEquals(0, serviceProvider.getAccessTokenCount());
+    assertEquals(0, serviceProvider.getResourceAccessCount());
+
+    fetcher = getFetcher(
+        getSecurityToken("owner", "owner"),
+        new OAuthRequestParams(FakeGadgetSpecFactory.SERVICE_NAME, null, null,
+            false));
+    request = new HttpRequest(new URI(FakeOAuthServiceProvider.RESOURCE_URL));
+    response = fetcher.fetch(request);
+    String clientState = response.getMetadata().get("oauthState");
+    assertNotNull(clientState);
+    String approvalUrl = response.getMetadata().get("oauthApprovalUrl");
+    assertNotNull(approvalUrl);
+
+    serviceProvider.browserVisit(approvalUrl + "&user_data=hello-oauth");
 
+    fetcher = getFetcher(
+        getSecurityToken("owner", "owner"),
+        new OAuthRequestParams(FakeGadgetSpecFactory.SERVICE_NAME, null,
+            clientState, false));
+    request = new HttpRequest(new URI(FakeOAuthServiceProvider.RESOURCE_URL));
+    response = fetcher.fetch(request);
+    assertEquals("User data is hello-oauth", response.getResponseAsString());
+
+    assertEquals(1, serviceProvider.getRequestTokenCount());
+    assertEquals(1, serviceProvider.getAccessTokenCount());
+    assertEquals(1, serviceProvider.getResourceAccessCount());
+    
+    fetcher = getFetcher(
+        getSecurityToken("owner", "somebody else"),
+        new OAuthRequestParams(FakeGadgetSpecFactory.SERVICE_NAME, null, null,
+            false));
+    request = new HttpRequest(
+        new URI(FakeOAuthServiceProvider.RESOURCE_URL));
+    response = fetcher.fetch(request);
+    assertEquals("User data is hello-oauth", response.getResponseAsString());
+    
+    assertEquals(1, serviceProvider.getRequestTokenCount());
+    assertEquals(1, serviceProvider.getAccessTokenCount());
+    assertEquals(1, serviceProvider.getResourceAccessCount());
+  }
 }


Reply via email to