Author: lryan Date: Tue May 6 13:01:12 2008 New Revision: 653895 URL: http://svn.apache.org/viewvc?rev=653895&view=rev Log: Allow for cache header control using 'refresh' param on non-json proxy requests Fix locked domain check to do allow for subdomains of embed-host. Support for signed-preloads. Various fixes to cache management
Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/AbstractContentCache.java Modified: incubator/shindig/trunk/features/core.io/io.js incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicContentCache.java incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetServer.java incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/HashLockedDomainService.java incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/LockedDomainService.java incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/RemoteContentRequest.java incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/SigningFetcher.java incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpUtil.java incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/ProxyHandler.java incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/Preload.java incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetServerTest.java incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/SigningFetcherTest.java Modified: incubator/shindig/trunk/features/core.io/io.js URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/features/core.io/io.js?rev=653895&r1=653894&r2=653895&view=diff ============================================================================== --- incubator/shindig/trunk/features/core.io/io.js (original) +++ incubator/shindig/trunk/features/core.io/io.js Tue May 6 13:01:12 2008 @@ -217,7 +217,7 @@ function respondWithPreload(postData, params, callback) { if (gadgets.io.preloaded_ && gadgets.io.preloaded_[postData.url]) { var preload = gadgets.io.preloaded_[postData.url]; - if (postData.httpMethod == "GET" && postData.authz == "none") { + if (postData.httpMethod == "GET") { delete gadgets.io.preloaded_[postData.url]; if (preload.rc !== 200) { callback({errors : ["Error " + preload.rc]}); @@ -288,6 +288,8 @@ params.REFRESH_INTERVAL = 3600; } } + var signOwner = params.OWNER_SIGNED; + var signViewer = params.VIEWER_SIGNED; var headers = params.HEADERS || {}; if (params.METHOD === "POST" && !headers["Content-Type"]) { @@ -303,7 +305,9 @@ st : st || "", oauthState : reqState || "", oauthService : oauthService || "", - oauthToken : oauthToken || "" + oauthToken : oauthToken || "", + signOwner : signOwner || "true", + signViewer : signViewer || "true" }; if (!respondWithPreload(paramData, params, callback, processResponse)) { Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/AbstractContentCache.java URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/AbstractContentCache.java?rev=653895&view=auto ============================================================================== --- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/AbstractContentCache.java (added) +++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/AbstractContentCache.java Tue May 6 13:01:12 2008 @@ -0,0 +1,182 @@ +/* + * 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.apache.shindig.gadgets; + +import java.net.URI; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +/** + * Base class for content caches. Defines cache expiration rules and + * and restrictions on allowed content. + * + * TODO: Move cache checking code into HttpUtil + */ +public abstract class AbstractContentCache implements ContentCache { + + /** + * Used to parse Expires: header. + */ + private final static DateFormat dateFormat + = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z"); + + public final RemoteContent getContent(RemoteContentRequest request) { + if (canCacheRequest(request)) { + return getContent(request.getUri()); + } + return null; + } + + public final RemoteContent getContent(URI uri) { + if (uri == null) return null; + return checkContent(getContentImpl(uri)); + } + + protected abstract RemoteContent getContentImpl(URI uri); + + public void addContent(RemoteContentRequest request, RemoteContent content) { + if (canCacheRequest(request)) { + addContent(request.getUri(), content); + } + } + + public void addContent(URI uri, RemoteContent content) { + content = checkContent(content); + if (uri == null || content == null) return; + // Clone the URI to prevent outside references from preventing collection + addContentImpl(URI.create(uri.toString()), content); + } + + protected abstract void addContentImpl(URI uri, RemoteContent content); + + public RemoteContent removeContent(RemoteContentRequest request) { + return removeContent(request.getUri()); + } + + public RemoteContent removeContent(URI uri) { + if (uri == null) return null; + RemoteContent content = getContentImpl(uri); + removeContentImpl(uri); + return checkContent(content); + } + + protected abstract RemoteContent removeContentImpl(URI uri); + + /** + * Utility function to verify that an entry is cacheable and not expired + * Returns null if the content is no longer cacheable. + * + * @param request + * @return content or null + */ + protected boolean canCacheRequest(RemoteContentRequest request) { + return ("GET".equals(request.getMethod()) && + !request.getOptions().ignoreCache); + } + + /** + * Utility function to verify that an entry is cacheable and not expired + * Returns null if the content is no longer cacheable. + * + * @param content + * @return content or null + */ + protected RemoteContent checkContent(RemoteContent content) { + if (content == null) return null; + + if (content.getHttpStatusCode() != 200) return null; + + long now = System.currentTimeMillis(); + + String expires = content.getHeader("Expires"); + if (expires != null) { + try { + Date expiresDate = dateFormat.parse(expires); + long expiresMs = expiresDate.getTime(); + if (expiresMs > now) { + return content; + } else { + return null; + } + } catch (ParseException e) { + return null; + } + } + + // Cache-Control headers may be an explicit max-age, or no-cache, which + // means we use a default expiration time. + String cacheControl = content.getHeader("Cache-Control"); + if (cacheControl != null) { + String[] directives = cacheControl.split(","); + for (String directive : directives) { + directive = directive.trim(); + // boolean params + if (directive.equals("no-cache")) { + return null; + } + if (directive.startsWith("max-age")) { + String[] parts = directive.split("="); + if (parts.length == 2) { + try { + // Record the max-age and store it in the content as an + // absolute expiration + long maxAgeMs = Long.parseLong(parts[1]) * 1000; + Date newExpiry = new Date(now + maxAgeMs); + content.getAllHeaders() + .put("Expires", Arrays.asList(dateFormat.format(newExpiry))); + return content; + } catch (NumberFormatException e) { + return null; + } + } + } + } + } + + // Look for Pragma: no-cache. If present, return null. + List<String> pragmas = content.getHeaders("Pragma"); + if (pragmas != null) { + for (String pragma : pragmas) { + if ("no-cache".equals(pragma)) { + return null; + } + } + } + + // Assume the content is cacheable for the default TTL + // if no other directives exist + Date newExpiry = new Date(now + getDefaultTTL()); + content.getAllHeaders() + .put("Expires", Arrays.asList(dateFormat.format(newExpiry))); + return content; + } + + /** + * Default TTL for an entry in the cache that does not have any + * cache controlling headers + * @return default TTL for cache entries + */ + protected long getDefaultTTL() { + // 5 mins + return 5L * 60L * 1000L; + } +} Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicContentCache.java URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicContentCache.java?rev=653895&r1=653894&r2=653895&view=diff ============================================================================== --- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicContentCache.java (original) +++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/BasicContentCache.java Tue May 6 13:01:12 2008 @@ -18,161 +18,26 @@ package org.apache.shindig.gadgets; import java.net.URI; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Date; -import java.util.List; import java.util.Map; import java.util.WeakHashMap; /** - * Simple cache of RemoteContent + * Simple cache of RemoteContent. Uses WeakHashMap for memory management */ -public class BasicContentCache implements ContentCache { - - /** - * Used to parse Expires: header. - */ - private final static DateFormat dateFormat - = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z"); +public class BasicContentCache extends AbstractContentCache { private final Map<URI, RemoteContent> cache = new WeakHashMap<URI, RemoteContent>(); - public RemoteContent getContent(URI uri) { - if (uri == null) return null; - return checkContent(cache.get(uri)); - } - - public RemoteContent getContent(RemoteContentRequest request) { - if (canCacheRequest(request)) { - return getContent(request.getUri()); - } - return null; - } - - public void addContent(URI uri, RemoteContent content) { - content = checkContent(content); - if (uri == null || content == null) return; - // Clone the URI to prevent outside references from preventing collection - cache.put(URI.create(uri.toString()), content); - } - - public void addContent(RemoteContentRequest request, RemoteContent content) { - if (canCacheRequest(request)) { - addContent(request.getUri(), content); - } - } - - public RemoteContent removeContent(URI uri) { - if (uri == null) return null; - RemoteContent content = cache.get(uri); - cache.remove(uri); - return checkContent(content); - } - - public RemoteContent removeContent(RemoteContentRequest request) { - return removeContent(request.getUri()); - } - - /** - * Utility function to verify that an entry is cacheable and not expired - * Returns null if the content is no longer cacheable. - * - * @param request - * @return content or null - */ - protected boolean canCacheRequest(RemoteContentRequest request) { - return ("GET".equals(request.getMethod()) && - !request.getOptions().ignoreCache); + protected RemoteContent getContentImpl(URI uri) { + return cache.get(uri); } - /** - * Utility function to verify that an entry is cacheable and not expired - * Returns null if the content is no longer cacheable. - * - * @param content - * @return content or null - */ - protected RemoteContent checkContent(RemoteContent content) { - if (content == null) return null; - - if (content.getHttpStatusCode() != 200) return null; - - long now = System.currentTimeMillis(); - - String expires = content.getHeader("Expires"); - if (expires != null) { - try { - Date expiresDate = dateFormat.parse(expires); - long expiresMs = expiresDate.getTime(); - if (expiresMs > now) { - return content; - } else { - return null; - } - } catch (ParseException e) { - return null; - } - } - - // Cache-Control headers may be an explicit max-age, or no-cache, which - // means we use a default expiration time. - String cacheControl = content.getHeader("Cache-Control"); - if (cacheControl != null) { - String[] directives = cacheControl.split(","); - for (String directive : directives) { - directive = directive.trim(); - // boolean params - if (directive.equals("no-cache")) { - return null; - } - if (directive.startsWith("max-age")) { - String[] parts = directive.split("="); - if (parts.length == 2) { - try { - // Record the max-age and store it in the content as an - // absolute expiration - long maxAgeMs = Long.parseLong(parts[1]) * 1000; - Date newExpiry = new Date(now + maxAgeMs); - content.getAllHeaders() - .put("Expires", Arrays.asList(dateFormat.format(newExpiry))); - return content; - } catch (NumberFormatException e) { - return null; - } - } - } - } - } - - // Look for Pragma: no-cache. If present, return null. - List<String> pragmas = content.getHeaders("Pragma"); - if (pragmas != null) { - for (String pragma : pragmas) { - if ("no-cache".equals(pragma)) { - return null; - } - } - } - - // Assume the content is cacheable for the default TTL - // if no other directives exist - Date newExpiry = new Date(now + getDefaultTTL()); - content.getAllHeaders() - .put("Expires", Arrays.asList(dateFormat.format(newExpiry))); - return content; + protected void addContentImpl(URI uri, RemoteContent content) { + cache.put(uri, content); } - /** - * Default TTL for an entry in the cache that does not have any - * cache controlling headers - * @return default TTL for cache entries - */ - protected long getDefaultTTL() { - // 5 mins - return 5L * 60L * 1000L; + protected RemoteContent removeContentImpl(URI uri) { + return cache.remove(uri); } } Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetServer.java URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetServer.java?rev=653895&r1=653894&r2=653895&view=diff ============================================================================== --- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetServer.java (original) +++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/GadgetServer.java Tue May 6 13:01:12 2008 @@ -17,6 +17,7 @@ */ package org.apache.shindig.gadgets; +import org.apache.shindig.gadgets.spec.Auth; import org.apache.shindig.gadgets.spec.Feature; import org.apache.shindig.gadgets.spec.GadgetSpec; import org.apache.shindig.gadgets.spec.LocaleSpec; @@ -201,10 +202,16 @@ CompletionService<RemoteContent> preloadProcessor = new ExecutorCompletionService<RemoteContent>(executor); for (Preload preload : gadget.getSpec().getModulePrefs().getPreloads()) { - PreloadTask task = new PreloadTask(gadget.getContext(), preload, - preloadFetcherFactory); - Future<RemoteContent> future = preloadProcessor.submit(task); - gadget.getPreloadMap().put(preload, future); + // Cant execute signed/oauth preloads without the token + if ((preload.getAuth() == Auth.NONE || + gadget.getContext().getToken() != null) && + (preload.getViews().size() == 0 || + preload.getViews().contains(gadget.getContext().getView()))) { + PreloadTask task = new PreloadTask(gadget.getContext(), preload, + preloadFetcherFactory); + Future<RemoteContent> future = preloadProcessor.submit(task); + gadget.getPreloadMap().put(preload, future); + } } } @@ -341,6 +348,8 @@ public RemoteContent call() { RemoteContentRequest request = new RemoteContentRequest(preload.getHref()); + request.getOptions().ownerSigned = preload.isSignOwner(); + request.getOptions().viewerSigned = preload.isSignViewer(); try { switch (preload.getAuth()) { case NONE: Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/HashLockedDomainService.java URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/HashLockedDomainService.java?rev=653895&r1=653894&r2=653895&view=diff ============================================================================== --- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/HashLockedDomainService.java (original) +++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/HashLockedDomainService.java Tue May 6 13:01:12 2008 @@ -17,10 +17,6 @@ */ package org.apache.shindig.gadgets; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - import org.apache.commons.codec.digest.DigestUtils; import org.apache.shindig.gadgets.spec.Feature; import org.apache.shindig.util.Base32; @@ -28,6 +24,10 @@ import com.google.inject.Inject; import com.google.inject.name.Named; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + /** * Locked domain implementation based on sha1. * @@ -79,12 +79,16 @@ } } + public boolean isEnabled() { + return enabled; + } + public String getEmbedHost() { return embedHost; } public boolean embedCanRender(String host) { - return (!enabled || host.equals(embedHost)); + return (!enabled || host.endsWith(embedHost)); } public boolean gadgetCanRender(String host, Gadget gadget, String container) { Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/LockedDomainService.java URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/LockedDomainService.java?rev=653895&r1=653894&r2=653895&view=diff ============================================================================== --- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/LockedDomainService.java (original) +++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/LockedDomainService.java Tue May 6 13:01:12 2008 @@ -28,6 +28,12 @@ public interface LockedDomainService { /** + * Is locked domain enabled + * @return true is locked domain is enabled + */ + public boolean isEnabled(); + + /** * Check whether embedded content (img src, for example) can render on * a particular host. * Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/RemoteContentRequest.java URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/RemoteContentRequest.java?rev=653895&r1=653894&r2=653895&view=diff ============================================================================== --- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/RemoteContentRequest.java (original) +++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/RemoteContentRequest.java Tue May 6 13:01:12 2008 @@ -205,13 +205,15 @@ * Creates a new request to a different URL using all request data from * an existing request. * + * TODO - Need to copy by value + * * @param uri * @param base The base request to copy data from. */ public RemoteContentRequest(URI uri, RemoteContentRequest base) { this.uri = uri; this.method = base.method; - this.options = base.options; + this.options = new Options(base.options); this.headers = base.headers; this.contentType = base.contentType; this.postBody = base.postBody; @@ -351,5 +353,18 @@ */ public static class Options { public boolean ignoreCache = false; + public boolean ownerSigned = true; + public boolean viewerSigned = true; + + public Options() {}; + + /** + * Copy constructor + */ + public Options(Options copyFrom) { + this.ignoreCache = copyFrom.ignoreCache; + this.ownerSigned = copyFrom.ownerSigned; + this.viewerSigned = copyFrom.viewerSigned; + } } } Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/SigningFetcher.java URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/SigningFetcher.java?rev=653895&r1=653894&r2=653895&view=diff ============================================================================== --- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/SigningFetcher.java (original) +++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/SigningFetcher.java Tue May 6 13:01:12 2008 @@ -174,7 +174,7 @@ URI resource = request.getUri(); String query = resource.getRawQuery(); List<Parameter> cacheableParams = sanitize(OAuth.decodeForm(query)); - addOpenSocialParams(cacheableParams); + addOpenSocialParams(request.getOptions(), cacheableParams); addOAuthNonTemporalParams(cacheableParams); String cacheableQuery = OAuth.formEncode(cacheableParams); URL url = new URL( @@ -202,7 +202,7 @@ msgParams.addAll(queryParams); msgParams.addAll(postParams); - addOpenSocialParams(msgParams); + addOpenSocialParams(req.getOptions(), msgParams); addOAuthParams(msgParams); @@ -259,14 +259,15 @@ } - private void addOpenSocialParams(List<Parameter> msgParams) { + private void addOpenSocialParams(RemoteContentRequest.Options options, + List<Parameter> msgParams) { String owner = authToken.getOwnerId(); - if (owner != null) { + if (owner != null && options.ownerSigned) { msgParams.add(new OAuth.Parameter(OPENSOCIAL_OWNERID, owner)); } String viewer = authToken.getViewerId(); - if (viewer != null) { + if (viewer != null && options.viewerSigned) { msgParams.add(new OAuth.Parameter(OPENSOCIAL_VIEWERID, viewer)); } Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpUtil.java URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpUtil.java?rev=653895&r1=653894&r2=653895&view=diff ============================================================================== --- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpUtil.java (original) +++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/HttpUtil.java Tue May 6 13:01:12 2008 @@ -19,16 +19,23 @@ package org.apache.shindig.gadgets.http; +import org.apache.shindig.gadgets.ContainerConfig; import org.apache.shindig.gadgets.Gadget; import org.apache.shindig.gadgets.GadgetContext; -import org.apache.shindig.gadgets.ContainerConfig; import org.apache.shindig.gadgets.spec.GadgetSpec; import org.apache.shindig.gadgets.spec.View; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletResponse; @@ -41,6 +48,9 @@ // 1 year. private static final int DEFAULT_TTL = 60 * 60 * 24 * 365; + public static final DateFormat DATE_HEADER_FORMAT = new SimpleDateFormat( + "EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); + /** * Sets default caching Headers (Expires, Cache-Control, Last-Modified) * @@ -73,7 +83,74 @@ } // Firefox requires this for certain cases. response.setDateHeader("Last-Modified", START_TIME); + } + + /** + * Takes a set of recevied HTTP headers and adjusts them to enforce + * a desired cache lifetime + */ + public static Map<String, List<String>> enforceCachePolicy( + Map<String, List<String>> originalHeaders, + long age, + boolean ignoreOriginalCacheControl) { + + Map<String, List<String>> newHeaders = new HashMap<String, List<String>>( + originalHeaders); + + long originalAge = 0L; + if (newHeaders.containsKey("Expires")) { + try { + Date date = DATE_HEADER_FORMAT + .parse(originalHeaders.get("Expires").get(0)); + originalAge = date.getTime() - System.currentTimeMillis(); + } catch (Exception e) { + // Dont care + } + } + + if (newHeaders.containsKey("Cache-Control")) { + try { + String cacheControl = originalHeaders.get("Cache-Control").get(0); + if (cacheControl != null) { + String[] directives = cacheControl.split(","); + for (String directive : directives) { + directive = directive.trim(); + // boolean params + if (directive.equals("no-cache") || directive.equals("no-store")) { + // Always respect no-cache or no-store no matter what + return newHeaders; + } + if (directive.startsWith("max-age")) { + String[] parts = directive.split("="); + if (parts.length == 2) { + // Record the max-age and store it in the content as an + // absolute expiration + originalAge = Long.parseLong(parts[1]) * 1000; + } + } + } + } + } catch (Exception e) { + // Dont care + } + } + + if (!ignoreOriginalCacheControl && age < originalAge) { + age = originalAge; + } + // Strip the original + newHeaders.remove("Expires"); + newHeaders.remove("Cache-Control"); + + // Replace with new consistent headers + newHeaders.put("Expires", Arrays.asList( + DATE_HEADER_FORMAT.format(new Date(age + System.currentTimeMillis())))); + + newHeaders.put("Cache-Control", + Arrays.asList("public, max-age=" + (age / 1000L))); + + return newHeaders; } /** Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/ProxyHandler.java URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/ProxyHandler.java?rev=653895&r1=653894&r2=653895&view=diff ============================================================================== --- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/ProxyHandler.java (original) +++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/ProxyHandler.java Tue May 6 13:01:12 2008 @@ -18,6 +18,23 @@ */ package org.apache.shindig.gadgets.http; +import org.apache.shindig.gadgets.ContentFetcher; +import org.apache.shindig.gadgets.ContentFetcherFactory; +import org.apache.shindig.gadgets.GadgetException; +import org.apache.shindig.gadgets.GadgetToken; +import org.apache.shindig.gadgets.GadgetTokenDecoder; +import org.apache.shindig.gadgets.LockedDomainService; +import org.apache.shindig.gadgets.RemoteContent; +import org.apache.shindig.gadgets.RemoteContentRequest; +import org.apache.shindig.gadgets.oauth.OAuthRequestParams; +import org.apache.shindig.gadgets.spec.Auth; +import org.apache.shindig.gadgets.spec.Preload; +import org.apache.shindig.util.InputStreamConsumer; +import org.json.JSONException; +import org.json.JSONObject; + +import com.google.inject.Inject; + import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; @@ -36,23 +53,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.shindig.gadgets.ContentFetcher; -import org.apache.shindig.gadgets.ContentFetcherFactory; -import org.apache.shindig.gadgets.GadgetException; -import org.apache.shindig.gadgets.GadgetToken; -import org.apache.shindig.gadgets.GadgetTokenDecoder; -import org.apache.shindig.gadgets.LockedDomainService; -import org.apache.shindig.gadgets.RemoteContent; -import org.apache.shindig.gadgets.RemoteContentRequest; -import org.apache.shindig.gadgets.oauth.OAuthRequestParams; -import org.apache.shindig.gadgets.spec.Auth; -import org.apache.shindig.gadgets.spec.Preload; -import org.apache.shindig.util.InputStreamConsumer; -import org.json.JSONException; -import org.json.JSONObject; - -import com.google.inject.Inject; - public class ProxyHandler { public static final String UNPARSEABLE_CRUFT = "throw 1; < don't be evil' >"; public static final String POST_DATA_PARAM = "postData"; @@ -60,9 +60,11 @@ public static final String SECURITY_TOKEN_PARAM = "st"; public static final String HEADERS_PARAM = "headers"; public static final String NOCACHE_PARAM = "nocache"; + public static final String SIGN_VIEWER = "signViewer"; + public static final String SIGN_OWNER = "signOwner"; public static final String URL_PARAM = "url"; private static final String REFRESH_PARAM = "refresh"; - + private static final Logger logger = Logger.getLogger(ProxyHandler.class.getPackage().getName()); @@ -196,6 +198,15 @@ RemoteContentRequest.Options options = new RemoteContentRequest.Options(); options.ignoreCache = "1".equals(request.getParameter(NOCACHE_PARAM)); + if (request.getParameter(SIGN_VIEWER) != null) { + options.viewerSigned = Boolean + .parseBoolean(request.getParameter(SIGN_VIEWER)); + } + if (request.getParameter(SIGN_OWNER) != null) { + options.ownerSigned = Boolean + .parseBoolean(request.getParameter(SIGN_OWNER)); + } + return new RemoteContentRequest( method, url, headers, postBody, options); } catch (UnsupportedEncodingException e) { @@ -286,14 +297,17 @@ RemoteContentRequest rcr = buildRemoteContentRequest(request); RemoteContent results = contentFetcherFactory.get().fetch(rcr); + // Default interval of 1 hour + int refreshInterval = 60 * 60; + if (request.getParameter(REFRESH_PARAM) != null) { + refreshInterval = Integer.valueOf(request.getParameter(REFRESH_PARAM)); + } + int status = results.getHttpStatusCode(); response.setStatus(status); if (status == HttpServletResponse.SC_OK) { - Map<String, List<String>> headers = results.getAllHeaders(); - if (headers.get("Cache-Control") == null) { - // Cache for 1 hour by default for static files. - HttpUtil.setCachingHeaders(response, 60 * 60); - } + Map<String, List<String>> headers = HttpUtil.enforceCachePolicy( + results.getAllHeaders(), refreshInterval * 1000L, false); for (Map.Entry<String, List<String>> entry : headers.entrySet()) { String name = entry.getKey(); List<String> values = entry.getValue(); Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/Preload.java URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/Preload.java?rev=653895&r1=653894&r2=653895&view=diff ============================================================================== --- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/Preload.java (original) +++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/spec/Preload.java Tue May 6 13:01:12 2008 @@ -22,6 +22,8 @@ import org.w3c.dom.Element; import java.net.URI; +import java.util.HashSet; +import java.util.Set; /** * Represents an addressable piece of content that can be preloaded by the server @@ -48,6 +50,30 @@ } /** + * [EMAIL PROTECTED] + */ + private final boolean signViewer; + public boolean isSignViewer() { + return signViewer; + } + + /** + * [EMAIL PROTECTED] + */ + private final boolean signOwner; + public boolean isSignOwner() { + return signOwner; + } + + /** + * [EMAIL PROTECTED] + */ + private final Set<String> views = new HashSet<String>(); + public Set<String> getViews() { + return views; + } + + /** * Produces an xml representation of the Preload. */ @Override @@ -64,11 +90,22 @@ * @throws SpecParserException When the href is not specified */ public Preload(Element preload) throws SpecParserException { + signOwner = XmlUtil.getBoolAttribute(preload, "sign_owner", true); + signViewer = XmlUtil.getBoolAttribute(preload, "sign_viewer", true); href = XmlUtil.getUriAttribute(preload, "href"); if (href == null) { throw new SpecParserException("[EMAIL PROTECTED] is required."); } + // Record all the associated views + String viewNames = XmlUtil.getAttribute(preload, "views", ""); + for (String s: viewNames.split(",")) { + s = s.trim(); + if (s.length() > 0) { + views.add(s.trim()); + } + } + String authAttr = XmlUtil.getAttribute(preload, AUTHZ_ATTR); try { auth = Auth.parse(authAttr); @@ -76,5 +113,4 @@ throw new SpecParserException("Preload@" + ge.getMessage()); } } - } \ No newline at end of file Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetServerTest.java URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetServerTest.java?rev=653895&r1=653894&r2=653895&view=diff ============================================================================== --- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetServerTest.java (original) +++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/GadgetServerTest.java Tue May 6 13:01:12 2008 @@ -18,6 +18,7 @@ package org.apache.shindig.gadgets; import org.apache.shindig.gadgets.spec.GadgetSpec; +import org.apache.shindig.util.BlobCrypterException; import static org.easymock.EasyMock.eq; import static org.easymock.EasyMock.expect; @@ -38,6 +39,21 @@ public URI getUrl() { return SPEC_URL; } + + @Override + @SuppressWarnings("unused") + public GadgetToken getToken() throws GadgetException { + try { + return new BasicGadgetToken("o", "v", "a", "d", "u", "m"); + } catch (BlobCrypterException bce) { + throw new RuntimeException(bce); + } + } + + @Override + public String getView() { + return "v2"; + } }; private final static String BASIC_SPEC_XML = "<Module>" + @@ -130,6 +146,133 @@ .get().getResponseAsString()); } + public void testPreloadViewMatch() throws Exception { + String preloadUrl = "http://example.org/preload.txt"; + String preloadData = "Preload Data"; + RemoteContentRequest preloadRequest + = new RemoteContentRequest(URI.create(preloadUrl)); + + String gadgetXml + = "<Module>" + + " <ModulePrefs title=\"foo\">" + + " <Preload href=\"" + preloadUrl + "\" views=\"v1\"/>" + + " </ModulePrefs>" + + " <Content type=\"html\" view=\"v1,v2\">dummy</Content>" + + "</Module>"; + expect(fetcherFactory.get()).andReturn(fetcher); + expect(fetcher.fetch(SPEC_REQUEST)) + .andReturn(new RemoteContent(gadgetXml)); + expect(fetcher.fetch(preloadRequest)) + .andReturn(new RemoteContent(preloadData)); + replay(); + + GadgetContext context = new GadgetContext() { + @Override + public URI getUrl() { + return SPEC_URL; + } + + @Override + public String getView() { + return "v1"; + } + }; + + Gadget gadget = gadgetServer.processGadget(context); + + assertTrue(gadget.getPreloadMap().size() == 1); + } + + public void testPreloadAntiMatch() throws Exception { + String preloadUrl = "http://example.org/preload.txt"; + String preloadData = "Preload Data"; + RemoteContentRequest preloadRequest + = new RemoteContentRequest(URI.create(preloadUrl)); + + String gadgetXml + = "<Module>" + + " <ModulePrefs title=\"foo\">" + + " <Preload href=\"" + preloadUrl + "\" views=\"v1,v3\"/>" + + " </ModulePrefs>" + + " <Content type=\"html\" view=\"v1,v2\">dummy</Content>" + + "</Module>"; + expect(fetcherFactory.get()).andReturn(fetcher); + expect(fetcher.fetch(SPEC_REQUEST)) + .andReturn(new RemoteContent(gadgetXml)); + expect(fetcher.fetch(preloadRequest)) + .andReturn(new RemoteContent(preloadData)); + replay(); + + GadgetContext context = new GadgetContext() { + @Override + public URI getUrl() { + return SPEC_URL; + } + + @Override + public String getView() { + return "v2"; + } + }; + + Gadget gadget = gadgetServer.processGadget(context); + assertTrue(gadget.getPreloadMap().size() == 0); + } + + public void testNoSignedPreloadWithoutToken() throws Exception { + String preloadUrl = "http://example.org/preload.txt"; + String preloadData = "Preload Data"; + RemoteContentRequest preloadRequest + = new RemoteContentRequest(URI.create(preloadUrl)); + + String gadgetXml + = "<Module>" + + " <ModulePrefs title=\"foo\">" + + " <Preload href=\"" + preloadUrl + "\" authz=\"signed\"/>" + + " </ModulePrefs>" + + " <Content type=\"html\" view=\"v1,v2\">dummy</Content>" + + "</Module>"; + expect(fetcher.fetch(SPEC_REQUEST)) + .andReturn(new RemoteContent(gadgetXml)); + replay(); + + GadgetContext context = new GadgetContext() { + @Override + public URI getUrl() { + return SPEC_URL; + } + }; + + Gadget gadget = gadgetServer.processGadget(context); + assertTrue(gadget.getPreloadMap().size() == 0); + } + + public void testSignedPreloadWithToken() throws Exception { + String preloadUrl = "http://example.org/preload.txt"; + String preloadData = "Preload Data"; + RemoteContentRequest preloadRequest + = new RemoteContentRequest(URI.create(preloadUrl)); + + String gadgetXml + = "<Module>" + + " <ModulePrefs title=\"foo\">" + + " <Preload href=\"" + preloadUrl + "\" authz=\"signed\"/>" + + " </ModulePrefs>" + + " <Content type=\"html\" view=\"v1,v2\">dummy</Content>" + + "</Module>"; + expect(fetcher.fetch(SPEC_REQUEST)) + .andReturn(new RemoteContent(gadgetXml)); + expect(fetcherFactory.getSigningFetcher(BASIC_CONTEXT.getToken())) + .andReturn(fetcher); + expect(fetcher.fetch(preloadRequest)) + .andReturn(new RemoteContent(preloadData)); + replay(); + + Gadget gadget = gadgetServer.processGadget(BASIC_CONTEXT); + assertTrue(gadget.getPreloadMap().size() == 1); + } + + public void testBlacklistedGadget() throws Exception { expect(blacklist.isBlacklisted(eq(SPEC_URL))).andReturn(true); replay(); Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/SigningFetcherTest.java URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/SigningFetcherTest.java?rev=653895&r1=653894&r2=653895&view=diff ============================================================================== --- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/SigningFetcherTest.java (original) +++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/SigningFetcherTest.java Tue May 6 13:01:12 2008 @@ -114,6 +114,28 @@ assertTrue(contains(queryParams, "xoauth_signature_publickey", "foo")); } + public void testNoSignViewer() throws Exception { + RemoteContentRequest unsigned + = makeContentRequest("GET", "http://test", null); + unsigned.getOptions().viewerSigned = false; + RemoteContentRequest out = signAndInspect(unsigned); + List<OAuth.Parameter> queryParams + = OAuth.decodeForm(out.getUri().getRawQuery()); + assertTrue(contains(queryParams, "opensocial_owner_id", "o")); + assertFalse(contains(queryParams, "opensocial_viewer_id", "v")); + } + + public void testNoSignOwner() throws Exception { + RemoteContentRequest unsigned + = makeContentRequest("GET", "http://test", null); + unsigned.getOptions().ownerSigned = false; + RemoteContentRequest out = signAndInspect(unsigned); + List<OAuth.Parameter> queryParams + = OAuth.decodeForm(out.getUri().getRawQuery()); + assertFalse(contains(queryParams, "opensocial_owner_id", "o")); + assertTrue(contains(queryParams, "opensocial_viewer_id", "v")); + } + public void testTrickyParametersInQuery() throws Exception { String tricky = "%6fpensocial_owner_id=gotcha"; RemoteContentRequest unsigned