Cool! (Also +spec list) o Perhaps we could iterate on the Shindig intepretation of adding friend relationships? o requestShareApp, last time I asked, required UI approval from the owner of the gadget (the user); is that still the case? Not sure how to do that in a protocol... though it's sort of parallel to sending a message of course.
John Panzer (http://abstractioneer.org) On Sat, Jul 19, 2008 at 4:43 PM, Chris Chabot <[EMAIL PROTECTED]> wrote: > Atom format input is now supported for creating activities and setting app > data, send a Content-Type: application/atom+xml header to tell shindig to > expect atom xml :) In other words it now supports all the write actions that > the gadgets have access too. > > 'Extra' actions (like adding friend relationships by doing a put to > /people/...) isn't supported yet, and to be honest i would first like to see > an overview of the expected actions that we should add before i start on > that, because i don't like guessing :) > > Also I'm not clear yet on how requestShareApp should work through the > RESTful API, so i'm waiting for input on that before i can put that in > place. (cc to John Panzer, is that information available?) > > Also PUT /messages/{guid}/outbox style message creation has been added, > though the sample container implementation just returns a NOT_SUPPORTED > error, but the structure is in place (and has been tested) so containers > that choose to support this now can. > > With that the RESTful API support is pretty much 'done' on the PHP side.. > (It could use some extra testing, but it works with my test scripts that > tries all possible actions that i could come up with). > > www.partuza.nl / modules.partuza.nl is running the latest code, so if you > feel like kicking the tires, feel free :) > > -- Chris > > On Jul 19, 2008, at 1:04 AM, Chris Chabot wrote: > > This has been a bit of a headache to develop (see spec list) but it's >> starting to take some shape. It's also quite difficult to test for me, since >> nothing in PHP really supports multipart http posts... So i had to hand >> craft both a test client, the input parsing in shindig, and the multi part >> output construct too. >> >> In other words, it works with the input that my test client creates, but i >> have no way to absolutely verify it is all completely valid http protocol >> compliant stuff ... ah the joys of developing in the dark with blindfolds on >> :) >> >> The batch proxy is advertised through XRDS simple: >> http://www.chabotc.com/xrds-test.php?url=http://www.partuza.nl >> >> When i shoot a hand crafted multipart payload into it it works & you can >> specify how you want your (presumably http multipart compliant) output on >> the main url (to batchProxy) with ?format=atom or ?format=json (json is >> assumed by default). You could even poke at it at >> http://modules.partuza.nl/social/rest/batchProxy .. just make sure to >> grab a valid token from a gadget iframe first to use in your requests and >> your golden :) The only missing bits are OAuth support, and Atom format >> input support, they will hopefully follow shortly. >> >> However my main question ... does anyone have any useful tools for >> actually testing and verifying multipart http posts & the multipart response >> ? :) >> >> -- Chris >> >> Begin forwarded message: >> >> From: [EMAIL PROTECTED] >>> Date: July 19, 2008 12:47:41 AM GMT+02:00 >>> To: [EMAIL PROTECTED] >>> Subject: svn commit: r678068 - in >>> /incubator/shindig/trunk/php/src/social-api: converters/ dataservice/ http/ >>> samplecontainer/ >>> Reply-To: [email protected] >>> >>> Author: chabotc >>> Date: Fri Jul 18 15:47:41 2008 >>> New Revision: 678068 >>> >>> URL: http://svn.apache.org/viewvc?rev=678068&view=rev >>> Log: >>> Initial support for the batch proxy request type. >>> >>> The url is /social/rest/batchProxy and it uses the http multipart >>> format as described in the RESTful API specification. >>> >>> It only supports ONE output format for a set of requests (which >>> is determined by the main url ?format=foo param, and defaults to >>> json). Input however will support mixing json and atom. >>> >>> Atom input is still missing (has a debug dump right now) but support >>> for that will follow quickly. >>> >>> OAuth and Atom input is still missing, but once their done PHP >>> Shindig will have full RESTful spec support, we're getting there! >>> >>> >>> Modified: >>> >>> >>> incubator/shindig/trunk/php/src/social-api/converters/OutputAtomConverter.php >>> >>> incubator/shindig/trunk/php/src/social-api/converters/OutputConverter.php >>> >>> >>> incubator/shindig/trunk/php/src/social-api/converters/OutputJsonConverter.php >>> incubator/shindig/trunk/php/src/social-api/dataservice/PeopleHandler.php >>> >>> incubator/shindig/trunk/php/src/social-api/dataservice/RestRequestItem.php >>> incubator/shindig/trunk/php/src/social-api/http/RestServlet.php >>> >>> >>> incubator/shindig/trunk/php/src/social-api/samplecontainer/BasicAppDataService.php >>> >>> >>> incubator/shindig/trunk/php/src/social-api/samplecontainer/BasicPeopleService.php >>> >>> Modified: >>> incubator/shindig/trunk/php/src/social-api/converters/OutputAtomConverter.php >>> URL: >>> http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/social-api/converters/OutputAtomConverter.php?rev=678068&r1=678067&r2=678068&view=diff >>> >>> ============================================================================== >>> --- >>> incubator/shindig/trunk/php/src/social-api/converters/OutputAtomConverter.php >>> (original) >>> +++ >>> incubator/shindig/trunk/php/src/social-api/converters/OutputAtomConverter.php >>> Fri Jul 18 15:47:41 2008 >>> @@ -1,4 +1,5 @@ >>> <?php >>> + >>> /* >>> * Licensed to the Apache Software Foundation (ASF) under one >>> * or more contributor license agreements. See the NOTICE file >>> @@ -19,7 +20,7 @@ >>> >>> /** >>> * Format = atom output converter, for format definition see: >>> - * >>> http://www.opensocial.org/Technical-Resources/opensocial-specification----implementation-version-08/restful-api-specification >>> + * >>> http://sites.google.com/a/opensocial.org/opensocial/Technical-Resources/opensocial-spec-v08/restful-api-specification >>> */ >>> class OutputAtomConverter extends OutputConverter { >>> private static $nameSpace = 'http://www.w3.org/2005/Atom'; >>> @@ -40,7 +41,7 @@ >>> $data = $responseItem->getResponse(); >>> $userId = >>> $requestItem->getUser()->getUserId($requestItem->getToken()); >>> $guid = 'urn:guid:' . $userId; >>> - $authorName = $_SERVER['HTTP_HOST'].':'.$userId; >>> + $authorName = $_SERVER['HTTP_HOST'] . ':' . $userId; >>> $updatedAtom = date(DATE_ATOM); >>> >>> // Check to see if this is a single entry, or a >>> collection, and construct either an atom >>> @@ -52,20 +53,18 @@ >>> >>> // The root Feed element >>> $entry = $this->addNode($doc, 'feed', '', false, >>> self::$nameSpace); >>> - >>> + >>> // Required Atom fields >>> $endPos = ($startIndex + $itemsPerPage) > >>> $totalResults ? $totalResults : ($startIndex + $itemsPerPage); >>> - $this->addNode($entry, 'title', $requestType.' >>> feed for id '.$authorName.' ('.$startIndex. ' - '. ($endPos - 1).' of >>> '.$totalResults.')'); >>> + $this->addNode($entry, 'title', $requestType . ' >>> feed for id ' . $authorName . ' (' . $startIndex . ' - ' . ($endPos - 1) . ' >>> of ' . $totalResults . ')'); >>> $author = $this->addNode($entry, 'author'); >>> $this->addNode($author, 'uri', $guid); >>> - $this->addNode($author, 'name', $authorName); >>> >>> + $this->addNode($author, 'name', $authorName); >>> $this->addNode($entry, 'updated', $updatedAtom); >>> $this->addNode($entry, 'id', $guid); >>> - $this->addNode($entry, 'link', '', array('rel' => >>> 'self', 'href' => 'http:// >>> '.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'])); >>> - >>> + $this->addNode($entry, 'link', '', array('rel' => >>> 'self', 'href' => 'http://' . $_SERVER['HTTP_HOST'] . >>> $_SERVER['REQUEST_URI'])); >>> // Add osearch & next link to the entry >>> $this->addPagingFields($entry, $startIndex, >>> $itemsPerPage, $totalResults); >>> - >>> // Add response entries to feed >>> $responses = >>> $responseItem->getResponse()->getEntry(); >>> foreach ($responses as $response) { >>> @@ -79,11 +78,10 @@ >>> $this->addNode($author, 'uri', $guid); >>> $this->addNode($author, 'name', >>> $authorName); >>> // Special hoisting rules for activities >>> - >>> if ($response instanceof Activity) { >>> $this->addNode($feedEntry, >>> 'category', '', array('term' => 'status')); >>> $this->addNode($feedEntry, >>> 'updated', date(DATE_ATOM, $response->postedTime)); >>> - $this->addNode($feedEntry, 'id', >>> 'urn:guid:'.$response->id); >>> + $this->addNode($feedEntry, 'id', >>> 'urn:guid:' . $response->id); >>> //FIXME should add a link field >>> but don't have URL's available yet: >>> // <link rel="self" >>> type="application/atom+xml" href=" >>> http://api.example.org/activity/feeds/.../af3778"/> >>> $this->addNode($feedEntry, >>> 'title', strip_tags($response->title)); >>> @@ -94,43 +92,49 @@ >>> unset($response->title); >>> unset($response->body); >>> } else { >>> - $this->addNode($feedEntry, 'id', >>> 'urn:guid:'.$idField); >>> - $this->addNode($feedEntry, >>> 'title', $requestType.' feed entry for id '.$idField); >>> + $this->addNode($feedEntry, 'id', >>> 'urn:guid:' . $idField); >>> + $this->addNode($feedEntry, >>> 'title', $requestType . ' feed entry for id ' . $idField); >>> $this->addNode($feedEntry, >>> 'updated', $updatedAtom); >>> } >>> >>> // recursively add responseItem data to >>> the xml structure >>> $this->addData($content, $requestType, >>> $response, self::$osNameSpace); >>> } >>> - >>> } else { >>> - >>> // Single entry = Atom:Entry >>> $entry = >>> $doc->appendChild($doc->createElementNS(self::$nameSpace, "entry")); >>> - >>> // Atom fields >>> - $this->addNode($entry, 'title', $requestType.' >>> entry for '.$authorName); >>> + $this->addNode($entry, 'title', $requestType . ' >>> entry for ' . $authorName); >>> $author = $this->addNode($entry, 'author'); >>> $this->addNode($author, 'uri', $guid); >>> $this->addNode($author, 'name', $authorName); >>> $this->addNode($entry, 'id', $guid); >>> $this->addNode($entry, 'updated', $updatedAtom); >>> $content = $this->addNode($entry, 'content', '', >>> array('type' => 'application/xml')); >>> - >>> // addData loops through the responseItem data >>> recursively creating a matching XML structure >>> $this->addData($content, $requestType, $data, >>> self::$osNameSpace); >>> } >>> $xml = $doc->saveXML(); >>> - if (self::$includeOsearch && $responseItem->getResponse() >>> instanceof RestFulCollection) { >>> + if ($responseItem->getResponse() instanceof >>> RestFulCollection) { >>> //FIXME dirty hack until i find a way to add >>> multiple name spaces using DomXML functions >>> - $xml = str_replace('<feed xmlns=" >>> http://www.w3.org/2005/Atom">', '<feed xmlns=" >>> http://www.w3.org/2005/Atom" xmlos:osearch=" >>> http://a9.com/-/spec/opensearch/1.1">' ,$xml); >>> + $xml = str_replace('<feed xmlns=" >>> http://www.w3.org/2005/Atom">', '<feed xmlns=" >>> http://www.w3.org/2005/Atom" xmlns:osearch=" >>> http://a9.com/-/spec/opensearch/1.1">', $xml); >>> } >>> echo $xml; >>> } >>> >>> function outputBatch(Array $responses, SecurityToken $token) >>> { >>> - //TODO once we support spec compliance batching, this >>> needs to be added too >>> + $this->boundryHeaders(); >>> + foreach ($responses as $response) { >>> + $request = $response['request']; >>> + $response = $response['response']; >>> + // output buffering supports multiple levels of >>> it.. it's a nice feature to abuse :) >>> + ob_start(); >>> + $this->outputResponse($response, $request); >>> + $part = ob_get_contents(); >>> + ob_end_clean(); >>> + $this->outputPart($part, $response->getError()); >>> + } >>> } >>> >>> /** >>> @@ -174,11 +178,9 @@ >>> */ >>> private function addPagingFields($entry, $startIndex, >>> $itemsPerPage, $totalResults) >>> { >>> - if (self::$includeOsearch) { >>> - $this->addNode($entry, 'osearch:totalResults', >>> $totalResults); >>> - $this->addNode($entry, 'osearch:startIndex', >>> $startIndex ? $startIndex : '0'); >>> - $this->addNode($entry, 'osearch:itemsPerPage', >>> $itemsPerPage); >>> - } >>> + $this->addNode($entry, 'osearch:totalResults', >>> $totalResults); >>> + $this->addNode($entry, 'osearch:startIndex', $startIndex >>> ? $startIndex : '0'); >>> + $this->addNode($entry, 'osearch:itemsPerPage', >>> $itemsPerPage); >>> // Create a 'next' link based on our current url if this >>> is a pageable collection & there is more to display >>> if (($startIndex + $itemsPerPage) < $totalResults) { >>> $nextStartIndex = ($startIndex + $itemsPerPage) - >>> 1; >>> @@ -255,6 +257,9 @@ >>> } >>> $this->addData($newElement, $key, >>> $val); >>> } else { >>> + if (is_numeric($key)) { >>> + $key = is_object($val) ? >>> get_class($val) : $key = $name; >>> + } >>> >>> $elm = >>> $newElement->appendChild($this->doc->createElement($key)); >>> >>> $elm->appendChild($this->doc->createTextNode($val)); >>> } >>> >>> Modified: >>> incubator/shindig/trunk/php/src/social-api/converters/OutputConverter.php >>> URL: >>> http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/social-api/converters/OutputConverter.php?rev=678068&r1=678067&r2=678068&view=diff >>> >>> ============================================================================== >>> --- >>> incubator/shindig/trunk/php/src/social-api/converters/OutputConverter.php >>> (original) >>> +++ >>> incubator/shindig/trunk/php/src/social-api/converters/OutputConverter.php >>> Fri Jul 18 15:47:41 2008 >>> @@ -22,6 +22,50 @@ >>> * >>> */ >>> abstract class OutputConverter { >>> + private $boundry; >>> + >>> abstract function outputResponse(ResponseItem $responseItem, >>> RestRequestItem $requestItem); >>> abstract function outputBatch(Array $responses, SecurityToken >>> $token); >>> + >>> + /** >>> + * Output the multipart/mixed headers and returns the boundry >>> token used >>> + * >>> + */ >>> + public function boundryHeaders() >>> + { >>> + $this->boundry = '--batch-'.md5(rand(0,32000)); >>> + header("HTTP/1.1 200 OK", true); >>> + header("Content-Type: multipart/mixed; >>> boundary=$this->boundry", true); >>> + } >>> + >>> + public function outputPart($part, $code) >>> + { >>> + $boundryHeader = "{$this->boundry}\n". >>> + "Content-Type: >>> application/http;version=1.1\n". >>> + "Content-Transfer-Encoding: binary\n\n"; >>> + echo $boundryHeader; >>> + switch ($code) { >>> + case BAD_REQUEST: >>> + $code = '400 Bad Request'; >>> + break; >>> + case UNAUTHORIZED: >>> + $code = '401 Unauthorized'; >>> + break; >>> + case FORBIDDEN: >>> + $code = '403 Forbidden'; >>> + break; >>> + case FORBIDDEN: >>> + $code = '404 Not Found'; >>> + break; >>> + case NOT_IMPLEMENTED: >>> + $code = '501 Not Implemented'; >>> + break; >>> + case INTERNAL_ERROR: >>> + default: >>> + $code = '200 OK'; >>> + break; >>> + } >>> + echo "$code\n\n"; >>> + echo $part."\n"; >>> + } >>> } >>> >>> Modified: >>> incubator/shindig/trunk/php/src/social-api/converters/OutputJsonConverter.php >>> URL: >>> http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/social-api/converters/OutputJsonConverter.php?rev=678068&r1=678067&r2=678068&view=diff >>> >>> ============================================================================== >>> --- >>> incubator/shindig/trunk/php/src/social-api/converters/OutputJsonConverter.php >>> (original) >>> +++ >>> incubator/shindig/trunk/php/src/social-api/converters/OutputJsonConverter.php >>> Fri Jul 18 15:47:41 2008 >>> @@ -30,6 +30,17 @@ >>> >>> function outputBatch(Array $responses, SecurityToken $token) >>> { >>> + $this->boundryHeaders(); >>> + foreach ($responses as $response) { >>> + $request = $response['request']; >>> + $response = $response['response']; >>> + $part = json_encode($response); >>> + $this->outputPart($part, $response->getError()); >>> + } >>> + } >>> + >>> + function outputJsonBatch(Array $responses, SecurityToken $token) >>> + { >>> echo json_encode(array("responses" => $responses, "error" >>> => false)); >>> } >>> } >>> >>> Modified: >>> incubator/shindig/trunk/php/src/social-api/dataservice/PeopleHandler.php >>> URL: >>> http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/social-api/dataservice/PeopleHandler.php?rev=678068&r1=678067&r2=678068&view=diff >>> >>> ============================================================================== >>> --- >>> incubator/shindig/trunk/php/src/social-api/dataservice/PeopleHandler.php >>> (original) >>> +++ >>> incubator/shindig/trunk/php/src/social-api/dataservice/PeopleHandler.php Fri >>> Jul 18 15:47:41 2008 >>> @@ -20,7 +20,9 @@ >>> class PeopleHandler extends DataRequestHandler { >>> private $service; >>> private static $PEOPLE_PATH = >>> "/people/{userId}/{groupId}/{personId}"; >>> - protected static $DEFAULT_PERSON_FIELDS = array("id", "name", >>> "thumbnailUrl"); >>> + //FIXME change this back to array("id", "name", "thumbnailUrl") >>> once the dust settles >>> + // on the spec discussion related to this >>> + protected static $DEFAULT_PERSON_FIELDS = array('all' => 'all'); >>> >>> public function __construct() >>> { >>> >>> Modified: >>> incubator/shindig/trunk/php/src/social-api/dataservice/RestRequestItem.php >>> URL: >>> http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/social-api/dataservice/RestRequestItem.php?rev=678068&r1=678067&r2=678068&view=diff >>> >>> ============================================================================== >>> --- >>> incubator/shindig/trunk/php/src/social-api/dataservice/RestRequestItem.php >>> (original) >>> +++ >>> incubator/shindig/trunk/php/src/social-api/dataservice/RestRequestItem.php >>> Fri Jul 18 15:47:41 2008 >>> @@ -45,12 +45,12 @@ >>> >>> public function createRequestItemWithRequest($request, $token) >>> { >>> - $this->url = $request->url; >>> - $this->parameters = >>> $this->createParameterMap($request->url); >>> + $this->url = $request['url']; >>> + $this->parameters = >>> $this->createParameterMap($request['url']); >>> $this->token = $token; >>> - $this->method = $request->method; >>> - if (isset($request->postData)) { >>> - $this->postData = $request->postData; >>> + $this->method = $request['method']; >>> + if (isset($request['postData'])) { >>> + $this->postData = $request['postData']; >>> } >>> } >>> >>> >>> Modified: incubator/shindig/trunk/php/src/social-api/http/RestServlet.php >>> URL: >>> http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/social-api/http/RestServlet.php?rev=678068&r1=678067&r2=678068&view=diff >>> >>> ============================================================================== >>> --- incubator/shindig/trunk/php/src/social-api/http/RestServlet.php >>> (original) >>> +++ incubator/shindig/trunk/php/src/social-api/http/RestServlet.php Fri >>> Jul 18 15:47:41 2008 >>> @@ -51,8 +51,6 @@ >>> require 'src/social-api/converters/OutputAtomConverter.php'; >>> require 'src/social-api/converters/OutputJsonConverter.php'; >>> >>> -//FIXME Delete should respond with a 204 No Content to indicate success >>> - >>> class RestException extends Exception {} >>> >>> /* >>> @@ -64,42 +62,80 @@ >>> define('FORBIDDEN', "forbidden"); >>> define('BAD_REQUEST', "badRequest"); >>> define('INTERNAL_ERROR', "internalError"); >>> +//FIXME Delete should respond with a 204 No Content to indicate success >>> >>> class RestServlet extends HttpServlet { >>> >>> + // The json Batch Route is used by the gadgets >>> private static $JSON_BATCH_ROUTE = "jsonBatch"; >>> + // The Batch Proxy route is used the one defined in the RESTful >>> API specification >>> + private static $BATCH_PROXY_ROUTE = "batchProxy"; >>> + >>> + public function doGet() >>> + { >>> + $this->doPost('GET'); >>> + } >>> >>> + public function doPut() >>> + { >>> + $this->doPost('PUT'); >>> + } >>> + >>> + public function doDelete() >>> + { >>> + $this->doPost('DELETE'); >>> + } >>> + >>> public function doPost($method = 'POST') >>> { >>> - $this->setNoCache(true); >>> - // if oauth, create a token from it's values instead of >>> one based on $_get['st']/$_post['st'] >>> - // NOTE : if no token is provided an anonymous one is >>> created (owner = viewer = appId = modId = 0) >>> - // keep this in mind when creating your data services.. >>> - $token = $this->getSecurityToken(); >>> - $outputFormat = $this->getOutputFormat(); >>> - switch ($outputFormat) { >>> - case 'json': >>> - >>> $this->setContentType('application/json'); >>> - $outputConverter = new >>> OutputJsonConverter(); >>> - break; >>> - case 'atom': >>> - >>> $this->setContentType('application/atom+xml'); >>> - $outputConverter = new >>> OutputAtomConverter(); >>> - break; >>> - default: >>> - $this->outputError(new >>> ResponseItem(NOT_IMPLEMENTED, "Invalid output format")); >>> - break; >>> - } >>> - if ($this->isBatchUrl()) { >>> - $responses = $this->handleBatchRequest($token); >>> - $outputConverter->outputBatch($responses, >>> $token); >>> - } else { >>> - $response = $this->handleSingleRequest($token, >>> $method); >>> - >>> $outputConverter->outputResponse($response['response'], >>> $response['request']); >>> + try { >>> + $this->setNoCache(true); >>> + // if oauth, create a token from it's values >>> instead of one based on $_get['st']/$_post['st'] >>> + // NOTE : if no token is provided an anonymous >>> one is created (owner = viewer = appId = modId = 0) >>> + // keep this in mind when creating your data >>> services.. >>> + $token = $this->getSecurityToken(); >>> + $outputFormat = $this->getOutputFormat(); >>> + switch ($outputFormat) { >>> + case 'json': >>> + >>> $this->setContentType('application/json'); >>> + $outputConverter = new >>> OutputJsonConverter(); >>> + break; >>> + case 'atom': >>> + >>> $this->setContentType('application/atom+xml'); >>> + $outputConverter = new >>> OutputAtomConverter(); >>> + break; >>> + default: >>> + $this->outputError(new >>> ResponseItem(NOT_IMPLEMENTED, "Invalid output format")); >>> + break; >>> + } >>> + if ($this->isJsonBatchUrl()) { >>> + // custom json batch format used by the >>> gadgets >>> + $responses = >>> $this->handleJsonBatchRequest($token); >>> + >>> $outputConverter->outputJsonBatch($responses, $token); >>> + } elseif ($this->isBatchProxyUrl()) { >>> + // spec compliant batch proxy >>> + $this->noHeaders = true; >>> + $responses = >>> $this->handleBatchProxyRequest($token); >>> + $outputConverter->outputBatch($responses, >>> $token); >>> + } else { >>> + // single rest request >>> + $response = $this->handleRequest($token, >>> $method); >>> + >>> $outputConverter->outputResponse($response['response'], >>> $response['request']); >>> + } >>> + } catch (Exception $e) { >>> + header("HTTP/1.0 500 Internal Server Error"); >>> + echo "<html><body><h1>500 Internal Server >>> Error</h1>"; >>> + if (Config::get('debug')) { >>> + echo "Message: ".$e->getMessage()."<br >>> />\n"; >>> + echo "<pre>\n"; >>> + print_r(debug_backtrace()); >>> + echo "\n</pre>"; >>> + } >>> + echo "</body></html>"; >>> } >>> } >>> >>> - private function handleSingleRequest($token, $method) >>> + private function handleRequest($token, $method) >>> { >>> $params = $this->getListParams(); >>> $requestItem = new RestRequestItem(); >>> @@ -109,14 +145,115 @@ >>> $responseItem = $this->getResponseItem($requestItem); >>> return array('request' => $requestItem, 'response' => >>> $responseItem); >>> } >>> - >>> - private function getRouteFromParameter($pathInfo) >>> + >>> + private function handleJsonBatchRequest($token) >>> { >>> - $pathInfo = substr($pathInfo, 1); >>> - $indexOfNextPathSeparator = strpos($pathInfo, "/"); >>> - return $indexOfNextPathSeparator != - 1 ? >>> substr($pathInfo, 0, $indexOfNextPathSeparator) : $pathInfo; >>> + // we support both a raw http post (without >>> application/x-www-form-urlencoded headers) like java does >>> + // and a more php / curl safe version of a form post with >>> 'request' as the post field that holds the request json data >>> + if (isset($GLOBALS['HTTP_RAW_POST_DATA']) || >>> isset($_POST['request'])) { >>> + $requests = $this->getRequestParams(); >>> + $responses = array(); >>> + foreach ($requests as $key => $value) { >>> + $requestItem = new RestRequestItem(); >>> + >>> $requestItem->createRequestItemWithRequest($value, $token); >>> + $responses[$key] = >>> $this->getResponseItem($requestItem); >>> + } >>> + return $responses; >>> + } else { >>> + throw new Exception("No post data set"); >>> + } >>> + } >>> + >>> + private function handleBatchProxyRequest($token) >>> + { >>> + // Is this is a multipath/mixed post? Check content type: >>> + if (isset($GLOBALS['HTTP_RAW_POST_DATA']) && >>> strpos($_SERVER['CONTENT_TYPE'], 'multipart/mixed') !== false && >>> strpos($_SERVER['CONTENT_TYPE'],'boundary=') !== false) { >>> + // Ok looks swell, see what our boundry is.. >>> + $boundry = substr($_SERVER['CONTENT_TYPE'], >>> strpos($_SERVER['CONTENT_TYPE'],'boundary=') + strlen('boundary=')); >>> + // Split up requests per boundry >>> + $requests = explode($boundry, >>> $GLOBALS['HTTP_RAW_POST_DATA']); >>> + $responses = array(); >>> + foreach ($requests as $request) { >>> + $request = trim($request); >>> + if (!empty($request)) { >>> + // extractBatchRequest() does the >>> magic parsing of the raw post data to a meaninful request array >>> + $request = >>> $this->extractBatchRequest($request); >>> + $requestItem = new >>> RestRequestItem(); >>> + >>> $requestItem->createRequestItemWithRequest($request, $token); >>> + $responses[] = array('request' => >>> $requestItem, 'response' => $this->getResponseItem($requestItem)); >>> + } >>> + } >>> + } else { >>> + $this->outputError(new ResponseItem(BAD_REQUEST, >>> "Invalid multipart/mixed request")); >>> + } >>> + return $responses; >>> } >>> >>> + private function extractBatchRequest($request) >>> + { >>> + /* Multipart request is formatted like: >>> + * -batch-a73hdj3dy3mm347ddjjdf >>> + * Content-Type: application/http;version=1.1 >>> + * Content-Transfer-Encoding: binary >>> + * >>> + * GET >>> /people/@me/@friends?startPage=5&count=10&format=json >>> + * Host: api.example.org >>> + * If-None-Match: "837dyfkdi39df" >>> + * >>> + * but we only want to have the last bit (the actual >>> request), this filters that down first >>> + */ >>> + $emptyFound = false; >>> + $requestLines = explode("\n", $request); >>> + $request = ''; >>> + foreach ($requestLines as $line) { >>> + if ($emptyFound) { >>> + $request .= $line."\n"; >>> + } elseif (empty($line)) { >>> + $emptyFound = true; >>> + } >>> + } >>> + // Now that we have the basic request in $request, split >>> that up again & parse it into a meaningful representation >>> + $firstFound = $emptyFound = false; >>> + $requestLines = explode("\n", $request); >>> + $request = array(); >>> + $request['headers'] = array(); >>> + $request['postData'] = ''; >>> + foreach ($requestLines as $line) { >>> + if (!$firstFound) { >>> + $firstFound = true; >>> + $parts = explode(' ', trim($line)); >>> + if (count($parts) != 2) { >>> + throw new Exception("Mallshaped >>> request uri in multipart block"); >>> + } >>> + $request['method'] = >>> strtoupper(trim($parts[0])); >>> + // cut it down to an actual meaningful >>> url without the prefix/social/rest part right away >>> + $request['url'] = substr(trim($parts[1]), >>> strlen(Config::get('web_prefix') . '/social/rest')); >>> + } elseif (!$emptyFound && !empty($line)) { >>> + // convert the key to the PHP >>> 'CONTENT_TYPE' style naming convention.. it's ugly but consitent >>> + $key = str_replace('-', '_', >>> strtoupper(trim(substr($line, 0, strpos($line, ':'))))); >>> + $val = trim(substr($line, strpos($line, >>> ':') + 1)); >>> + $request['headers'][$key] = $val; >>> + } elseif (!$emptyFound && empty($line)) { >>> + $emptyFound = true; >>> + } else { >>> + if (get_magic_quotes_gpc()) { >>> + $line = stripslashes($line); >>> + } >>> + $request['postData'] .= $line."\n"; >>> + } >>> + } >>> + if (empty($request['postData'])) { >>> + // don't trip the requestItem into thinking there >>> is postData when there's not >>> + unset($request['postData']); >>> + } else { >>> + // if there was a post data blob present, decode >>> it into an array, the format is based on the >>> + // content type header, which is either >>> application/json or >>> + $format = >>> isset($request['headers']['CONTENT_TYPE']) && >>> strtolower($request['headers']['CONTENT_TYPE']) == 'application/atom+xml' ? >>> 'atom' : 'json'; >>> + $request['postData'] = >>> $this->decodeRequests($request['postData'], $format); >>> + } >>> + return $request; >>> + } >>> + >>> private function getResponseItem(RestRequestItem $requestItem) >>> { >>> $path = >>> $this->getRouteFromParameter($requestItem->getUrl()); >>> @@ -140,12 +277,26 @@ >>> $class = new $class(null); >>> $response = $class->handleMethod($requestItem); >>> } >>> - if ($response->getError() != null && >>> !$this->isBatchUrl()) { >>> + if ($response->getError() != null && >>> !$this->isJsonBatchUrl() && !$this->isBatchProxyUrl()) { >>> // Can't use http error codes in batch mode, >>> instead we return the error code in the response item >>> $this->outputError($response); >>> } >>> return $response; >>> } >>> + >>> + private function decodeRequests($requestParam, $format = 'json') >>> + { >>> + // temp hack until i know what the intended way to detect >>> format is >>> + if ($format == 'json') { >>> + return json_decode($requestParam, true); >>> + } elseif ($format == 'atom') { >>> + $xml = simplexml_load_string($requestParam); >>> + print_r($xml); >>> + return $xml; >>> + } else { >>> + throw Exception("Invalid or unsupported input >>> format"); >>> + } >>> + } >>> >>> private function getRequestParams() >>> { >>> @@ -154,44 +305,14 @@ >>> if (get_magic_quotes_gpc()) { >>> $requestParam = stripslashes($requestParam); >>> } >>> - $requests = json_decode($requestParam); >>> - if ($requests == (isset($GLOBALS['HTTP_RAW_POST_DATA']) ? >>> $GLOBALS['HTTP_RAW_POST_DATA'] : $post)) { >>> - return new ResponseItem(BAD_REQUEST, "Malformed >>> json string"); >>> - } >>> - return $requests; >>> - } >>> - >>> - private function handleBatchRequest($token) >>> - { >>> - // we support both a raw http post (without >>> application/x-www-form-urlencoded headers) like java does >>> - // and a more php / curl safe version of a form post with >>> 'request' as the post field that holds the request json data >>> - if (isset($GLOBALS['HTTP_RAW_POST_DATA']) || >>> isset($_POST['request'])) { >>> - $requests = $this->getRequestParams(); >>> - $responses = array(); >>> - foreach ($requests as $key => $value) { >>> - $requestItem = new RestRequestItem(); >>> - >>> $requestItem->createRequestItemWithRequest($value, $token); >>> - $responses[$key] = >>> $this->getResponseItem($requestItem); >>> - } >>> - return $responses; >>> - } else { >>> - throw new Exception("No post data set"); >>> - } >>> - } >>> - >>> - public function doGet() >>> - { >>> - $this->doPost('GET'); >>> - } >>> - >>> - public function doPut() >>> - { >>> - $this->doPost('PUT'); >>> + return $this->decodeRequests($requestParam); >>> } >>> >>> - public function doDelete() >>> + private function getRouteFromParameter($pathInfo) >>> { >>> - $this->doPost('DELETE'); >>> + $pathInfo = substr($pathInfo, 1); >>> + $indexOfNextPathSeparator = strpos($pathInfo, "/"); >>> + return $indexOfNextPathSeparator != - 1 ? >>> substr($pathInfo, 0, $indexOfNextPathSeparator) : $pathInfo; >>> } >>> >>> private function outputError(ResponseItem $response) >>> @@ -217,7 +338,6 @@ >>> default: >>> $code = '500 Internal Server Error'; >>> break; >>> - >>> } >>> header("HTTP/1.0 $code", true); >>> echo "$code - $errorMessage"; >>> @@ -265,8 +385,13 @@ >>> return substr($_SERVER["REQUEST_URI"], >>> strlen(Config::get('web_prefix') . '/social/rest')); >>> } >>> >>> - public function isBatchUrl() >>> + public function isJsonBatchUrl() >>> { >>> return strrpos($_SERVER["REQUEST_URI"], >>> RestServlet::$JSON_BATCH_ROUTE) > 0; >>> } >>> + >>> + public function isBatchProxyUrl() >>> + { >>> + return strrpos($_SERVER["REQUEST_URI"], >>> RestServlet::$BATCH_PROXY_ROUTE) > 0; >>> + } >>> } >>> >>> Modified: >>> incubator/shindig/trunk/php/src/social-api/samplecontainer/BasicAppDataService.php >>> URL: >>> http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/social-api/samplecontainer/BasicAppDataService.php?rev=678068&r1=678067&r2=678068&view=diff >>> >>> ============================================================================== >>> --- >>> incubator/shindig/trunk/php/src/social-api/samplecontainer/BasicAppDataService.php >>> (original) >>> +++ >>> incubator/shindig/trunk/php/src/social-api/samplecontainer/BasicAppDataService.php >>> Fri Jul 18 15:47:41 2008 >>> @@ -85,7 +85,7 @@ >>> switch($groupId->getType()) { >>> case 'self': >>> foreach ($fields as $key) { >>> - $value = isset($values->$key) ? >>> $values->$key : (@isset($values[$key]) ? @$values[$key] : null); >>> + $value = isset($values[$key]) ? >>> @$values[$key] : null; >>> >>> XmlStateFileFetcher::get()->setAppData($userId->getUserId($token), $key, >>> $value); >>> } >>> break; >>> >>> Modified: >>> incubator/shindig/trunk/php/src/social-api/samplecontainer/BasicPeopleService.php >>> URL: >>> http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/social-api/samplecontainer/BasicPeopleService.php?rev=678068&r1=678067&r2=678068&view=diff >>> >>> ============================================================================== >>> --- >>> incubator/shindig/trunk/php/src/social-api/samplecontainer/BasicPeopleService.php >>> (original) >>> +++ >>> incubator/shindig/trunk/php/src/social-api/samplecontainer/BasicPeopleService.php >>> Fri Jul 18 15:47:41 2008 >>> @@ -76,7 +76,7 @@ >>> if ($id == $token->getOwnerId()) { >>> $person->setIsOwner(true); >>> } >>> - if (is_array($profileDetails) && >>> count($profileDetails)) { >>> + if (is_array($profileDetails) && >>> count($profileDetails) && !in_array('all', $profileDetails)) { >>> $newPerson = array(); >>> $newPerson['isOwner'] = >>> $person->isOwner; >>> $newPerson['isViewer'] = >>> $person->isViewer; >>> >>> >> >

