You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by ch...@apache.org on 2009/04/01 01:12:00 UTC

svn commit: r760722 - in /incubator/shindig/trunk/php/src: common/RemoteContentRequest.php common/sample/BasicRemoteContent.php gadgets/GadgetSpecParser.php gadgets/render/GadgetHrefRenderer.php gadgets/sample/BasicGadgetSpecFactory.php

Author: chabotc
Date: Tue Mar 31 23:12:00 2009
New Revision: 760722

URL: http://svn.apache.org/viewvc?rev=760722&view=rev
Log:
This adds a mostly working data-pipelining implementation, the remaining issues that need to be solved before it's complete are:

1) The signing fetcher currently uses the $_POST superglobal and not the $request->getPostBody() to build the signature, resulting in invalid oauth sig's
2) the signing fetch also uses the same, parsed, $_POST vars, to set a new post body on the request, removing the actual post body with the data-pipelining information
3) JSP-EL type parsing of dynamic tags still needs to be implemented.

Almost there though! :)


Modified:
    incubator/shindig/trunk/php/src/common/RemoteContentRequest.php
    incubator/shindig/trunk/php/src/common/sample/BasicRemoteContent.php
    incubator/shindig/trunk/php/src/gadgets/GadgetSpecParser.php
    incubator/shindig/trunk/php/src/gadgets/render/GadgetHrefRenderer.php
    incubator/shindig/trunk/php/src/gadgets/sample/BasicGadgetSpecFactory.php

Modified: incubator/shindig/trunk/php/src/common/RemoteContentRequest.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/common/RemoteContentRequest.php?rev=760722&r1=760721&r2=760722&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/common/RemoteContentRequest.php (original)
+++ incubator/shindig/trunk/php/src/common/RemoteContentRequest.php Tue Mar 31 23:12:00 2009
@@ -42,7 +42,7 @@
    * @var Options
    */
   private $options;
-  
+
   /**
    * @var SecurityToken
    */

Modified: incubator/shindig/trunk/php/src/common/sample/BasicRemoteContent.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/common/sample/BasicRemoteContent.php?rev=760722&r1=760721&r2=760722&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/common/sample/BasicRemoteContent.php (original)
+++ incubator/shindig/trunk/php/src/common/sample/BasicRemoteContent.php Tue Mar 31 23:12:00 2009
@@ -158,7 +158,7 @@
             $ttl = $expires - $date;
           }
         }
-        // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html : The Cache-Control: max-age=<seconds> overrides the expires header, sp if both are present this one will overwrite the $ttl
+        // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html : The Cache-Control: max-age=<seconds> overrides the expires header, so if both are present this one will overwrite the $ttl
         if (($cacheControl = $request->getResponseHeader('Cache-Control')) != null) {
           $bits = explode('=', $cacheControl);
           foreach ($bits as $key => $val) {

Modified: incubator/shindig/trunk/php/src/gadgets/GadgetSpecParser.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/GadgetSpecParser.php?rev=760722&r1=760721&r2=760722&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/GadgetSpecParser.php (original)
+++ incubator/shindig/trunk/php/src/gadgets/GadgetSpecParser.php Tue Mar 31 23:12:00 2009
@@ -129,7 +129,7 @@
             break;
           case 'os:ActivitiesRequest':
             $tag['type'] = 'os:DataRequest';
-            $tag['method'] = 'activity.get';
+            $tag['method'] = 'activities.get';
             break;
         }
         $dataPipeliningTags[] = $tag;

Modified: incubator/shindig/trunk/php/src/gadgets/render/GadgetHrefRenderer.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/render/GadgetHrefRenderer.php?rev=760722&r1=760721&r2=760722&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/render/GadgetHrefRenderer.php (original)
+++ incubator/shindig/trunk/php/src/gadgets/render/GadgetHrefRenderer.php Tue Mar 31 23:12:00 2009
@@ -1,4 +1,5 @@
 <?php
+
 /**
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -18,6 +19,27 @@
  * under the License.
  */
 
+/*
+ * TODO Dynamically evaluate the limited EL subset expressions on the following tags:
+ * Any attribute on os:DataRequest other than @key and @method
+ * @userId
+ * @groupId
+ * @fields
+ * @startIndex
+ * @count
+ * @sortBy
+ * @sortOrder
+ * @filterBy
+ * @filterOp
+ * @filterValue
+ * @activityIds
+ * @href
+ * @params
+ * Example:
+ * <os:PeopleRequest key="PagedFriends" userId="@owner" groupId="@friends" startIndex="${ViewParams.first}" count="20"/>
+ * <os:HttpRequest href="http://developersite.com/api?ids=${PagedFriends.ids}"/>
+ */
+
 class GadgetHrefRenderer extends GadgetRenderer {
 
   /**
@@ -28,6 +50,12 @@
    * @param array $view
    */
   public function renderGadget(Gadget $gadget, $view) {
+
+    $dataPipelining = false;
+    if (count($view['dataPipelining'])) {
+      $dataPipelining = $this->fetchDataPipelining($view['dataPipelining']);
+    }
+
     /* TODO
      * We should really re-add OAuth fetching support some day, uses these view atributes:
      * $view['oauthServiceName'], $view['oauthTokenName'], $view['oauthRequestToken'], $view['oauthRequestTokenSecret'];
@@ -42,28 +70,162 @@
 
     // rewrite our $_GET to match the outgoing request, this is currently needed for the oauth library
     // to generate it's correct signature
-    $_GET = $_POST = array();
     $uri = parse_url($href);
     parse_str($uri['query'], $_GET);
 
-    $request = new RemoteContentRequest($href);
-    $request->setMethod('GET');
-    $request->setToken($token);
-    $request->setRefreshInterval($refreshInterval);
-    $request->setAuthType($authz);
+    if ($dataPipelining) {
+      // if data-pipeling results are set in $dataPipelining, post the json encoded version to the remote url
+      $request = new RemoteContentRequest($href, "Content-type: application/json\n", json_encode($dataPipelining));
+      $request->setMethod('POST');
+    } else {
+      // no data-pipelining set, use GET and set cache/refresh interval options
+      $request = new RemoteContentRequest($href);
+      $request->setMethod('GET');
+      $request->setRefreshInterval($refreshInterval);
+      $request->getOptions()->ignoreCache = $gadget->gadgetContext->getIgnoreCache();
+    }
 
-    $signingFetcherFactory = false;
+    $signingFetcherFactory = $gadgetSigner = false;
     if ($authz != 'none') {
+      $request->setToken($token);
+      $request->setAuthType($authz);
       $signingFetcherFactory = new SigningFetcherFactory(Config::get("private_key_file"));
+      $_GET = $_POST = array();
     }
 
     $basicFetcher = new BasicRemoteContentFetcher();
     $basicRemoteContent = new BasicRemoteContent($basicFetcher, $signingFetcherFactory, $gadgetSigner);
-    $response = $basicRemoteContent->fetch($request, $gadget->gadgetContext, $authz);
+    $response = $basicRemoteContent->fetch($request);
     echo $response->getResponseContent();
   }
 
   /**
+   * Fetches the requested data-pipeling info
+   *
+   * @param array $dataPipelining contains the parsed data-pipelining tags
+   * @return array result
+   */
+  private function fetchDataPipelining($dataPipelining) {
+    $result = array();
+    do {
+      // See which requests we can batch together, that either don't use dynamic tags or who's tags are resolvable
+      $requestQueue = array();
+      foreach ($dataPipelining as $key => $request) {
+        if (($resolved = $this->resolveRequest($request, $result)) !== false) {
+          $requestQueue[] = $resolved;
+          unset($dataPipelining[$key]);
+        }
+      }
+      if (count($requestQueue)) {
+        $result = array_merge($this->performRequests($requestQueue), $result);
+      }
+    } while (count($requestQueue));
+    return $result;
+  }
+
+  /**
+   * Peforms the actual http fetching of the data-pipelining requests, all social requests
+   * are made to $_SERVER['SERVER_NAME'] (the virtual host name of this server) / (optional) web_prefix / social / rpc, and
+   * the httpRequest's are made to $_SERVER['SERVER_NAME'] (the virtual host name of this server) / (optional) web_prefix / gadgets / makeRequest
+   * both request types use the current security token ($_GET['st']) when performing the requests so they happen in the correct context
+   *
+   * @param array $requests
+   * @return array response
+   */
+  private function performRequests($requests) {
+    $jsonRequests = array();
+    $httpRequests = array();
+    $decodedResponse = array();
+    // Using the same gadget security token for all social & http requests so everything happens in the right context
+    $securityToken = $_GET['st'];
+    foreach ($requests as $request) {
+      switch ($request['type']) {
+        case 'os:DataRequest':
+          // Add to the social request batch
+          $id = $request['key'];
+          $method = $request['method'];
+          // remove our internal fields so we can use the remainder as params
+          unset($request['key']);
+          unset($request['method']);
+          unset($request['type']);
+          $jsonRequests[] = array('method' => $method, 'id' => $id, 'params' => $request);
+          break;
+        case 'os:HttpRequest':
+          $id = $request['key'];
+          $url = $request['href'];
+          unset($request['key']);
+          unset($request['type']);
+          unset($request['href']);
+          $httpRequests[] = array('id' => $id, 'url' => $url, 'queryStr' => implode('&', $request));
+          break;
+      }
+    }
+    if (count($jsonRequests)) {
+      // perform social api requests
+      $request = new RemoteContentRequest($_SERVER['SERVER_NAME'] . Config::get('web_prefix') . '/social/rpc?st=' . urlencode($securityToken) . '&format=json', "Content-type: application/json\n", json_encode($jsonRequests));
+      $request->setMethod('POST');
+      $basicFetcher = new BasicRemoteContentFetcher();
+      $basicRemoteContent = new BasicRemoteContent($basicFetcher);
+      $response = $basicRemoteContent->fetch($request);
+      $decodedResponse = json_decode($response->getResponseContent(), true);
+    }
+    if (count($httpRequests)) {
+      $requestQueue = array();
+      foreach ($httpRequests as $request) {
+        $req = new RemoteContentRequest($_SERVER['SERVER_NAME'] . Config::get('web_prefix') . '/gadgets/makeRequest?url=' . urlencode($request['url']) . '&st=' . urlencode($securityToken) . (! empty($request['queryStr']) ? '&' . $request['queryStr'] : ''));
+        $req->getOptions()->ignoreCache = $this->context->getIgnoreCache();
+        $req->setNotSignedUri($request['url']);
+        $requestQueue[] = $req;
+      }
+      $basicRemoteContent = new BasicRemoteContent();
+      $resps = $basicRemoteContent->multiFetch($requestQueue);
+      foreach ($resps as $response) {
+        // strip out the UNPARSEABLE_CRUFT (see makeRequestHandler.php) on assigning the body
+        $resp = json_decode(str_replace("throw 1; < don't be evil' >", '', $response->getResponseContent()), true);
+        if (is_array($resp)) {
+          //FIXME: make sure that this is the format that java-shindig produces as well, the spec doesn't really state
+          $decodedResponse = array_merge($resp, $decodedResponse);
+        }
+      }
+    }
+    return $decodedResponse;
+  }
+
+  /**
+   * If a request (data-pipelining tag) doesn't include any dynamic tags, it's returned as is. If
+   * however it does contain said tag, this function will attempt to resolve it using the $result
+   * array, returning the parsed request on success, or FALSE on failure to resolve.
+   *
+   * @param array $request
+   */
+  private function resolveRequest($request, $result) {
+    foreach ($request as $key => $val) {
+      if (($pos = strpos($val, '${')) !== false) {
+        $key = substr($val, $pos + 2);
+        $key = substr($key, 0, strpos($key, '}'));
+        if (($resolved = $this->resolveExpression($key, $result)) !== null) {
+          $request[$key] = str_replace('${' . $key . '}', $resolved, $val);
+        } else {
+          return false;
+        }
+      }
+    }
+    return $request;
+  }
+
+  /**
+   * Resolves simplified JSP-EL expressions if the matching entry exists in $data
+   *
+   * @param string $expression
+   * @param array $data
+   */
+  private function resolveExpression($expression, $data) {
+    //TODO implement this, see http://opensocial-resources.googlecode.com/svn/spec/draft/OpenSocial-Data-Pipelining.xml#rfc.section.14
+    // always return null (aka can't resolve) until it's implemented
+    return null;
+  }
+
+  /**
    * Builds the outgoing URL by taking the href attribute of the view and appending
    * the country, lang, and opensocial query params to it
    *
@@ -75,7 +237,7 @@
     $href = $view['href'];
     if (empty($href)) {
       throw new Exception("Invalid empty href in the gadget view");
-    }    // add the required country and lang param to the URL
+    } // add the required country and lang param to the URL
     $lang = isset($_GET['lang']) ? $_GET['lang'] : 'en';
     $country = isset($_GET['country']) ? $_GET['country'] : 'US';
     $firstSeperator = strpos($href, '?') === false ? '?' : '&';

Modified: incubator/shindig/trunk/php/src/gadgets/sample/BasicGadgetSpecFactory.php
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/gadgets/sample/BasicGadgetSpecFactory.php?rev=760722&r1=760721&r2=760722&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/gadgets/sample/BasicGadgetSpecFactory.php (original)
+++ incubator/shindig/trunk/php/src/gadgets/sample/BasicGadgetSpecFactory.php Tue Mar 31 23:12:00 2009
@@ -47,7 +47,10 @@
    */
   private function fetchFromWeb($url, $ignoreCache) {
     $remoteContentRequest = new RemoteContentRequest($url);
-    $remoteContentRequest->getRequest($url, $ignoreCache);
+    $remoteContentRequest->getOptions()->ignoreCache = $ignoreCache;
+    $remoteContent = new BasicRemoteContent($this->fetcher);
+    $spec = $remoteContent->fetch($remoteContentRequest);
+    
     $spec = $this->fetcher->fetchRequest($remoteContentRequest);
     $specParser = new GadgetSpecParser();
     $context = new ProxyGadgetContext($url);