* @license PHP License * @package WB * @subpackage base */ WBClass::load('WBService' , 'WBLog' ); /** * Service Rest * * Generic REST server * * @version 0.3.0 * @package WB * @subpackage base */ class WBService_Rest extends WBService { /** * my part * @var string */ protected $part = ''; /** * decide whether to start locale tools * @var bool */ protected $useI18n = true; /** * decide whether to start WBConfig * @var bool */ protected $useConfig = false; /** * Concrete processing component * * @var WBContent */ private $comp; /** * Rest serializer to unserilize request * * @var WBRest_Serializer */ private $unser; /** * Rest serializer to serialize response * * @var WBRest_Serializer */ private $ser; /** * debug flag * @var bool */ private $debug = false; /** * Display * * @param int $status * @param mixed $data to be send * @param string $checksum * @return bool true */ public function run(&$status, &$data, &$checksum = null) { $this->conf = WBClass::create( 'WBConfig' ); $this->conf->load('rest'); // set debug flag if (0 < intval($this->req->getMeta('HTTP_X_DEBUG', 0))) { $this->debug = true; } if (!$this->startSerializer()) { $status = 400; $data = $this->ser->sprintfError(1, 'Header: "Accept" points to invalid content type.'); return true; } $status = 200; $response = array( 'status' => 'ok', 'data' => array(), 'errors' => null ); // require user agent to identify client $agent = $this->req->getMeta('HTTP_USER_AGENT'); if (empty($agent)) { $status = 400; $data = $this->ser->sprintfError(1, 'Header: "User-Agent" is required.'); return true; } // run application $services = $this->conf->get('services/anonymous'); if ($this->login()) { $services = array_merge($services, $this->conf->get('services/user')); } // imagine the possibilities :-) if (!empty($this->path)) { $this->part = array_shift($this->path); } if (empty($this->part)) { $response['data'] = array( 'services' => $services ); if ($this->debug) { $response['debug'] = array( 'data' => array(), 'elapsed' => WBClock::stop() ); } $data = $this->ser->serialize($response); return true; } // try to start concrete application $this->part = strtolower($this->part); if (!in_array($this->part, $services)) { $status = 400; $data = $this->ser->sprintfError(3, 'Requested service is not available'); return true; } try{ $this->comp = WBClass::create( 'WBRest_Component_' . ucfirst($this->part) ); } catch (Exception $e) { $data = $this->ser->sprintfError(4, 'Could not instantiate component.'); $status = 400; return true; } try{ $in = $this->unserializeBody(); $this->req->import($in); // run actual component $this->comp->setRequest($this->req->getMeta('REQUEST_METHOD'), $in); if (empty($this->path)) { $this->comp->emptyRequest(); } else { $function = 'handle' . ucFirst(strtolower($this->path[0])); if (method_exists($this->comp, $function)) { array_shift($this->path); call_user_func_array(array($this->comp, $function), $this->path); } else { $this->comp->defaultRequest($this->path); } } $this->comp->getResponse($response['status'], $response['data'], $response['errors']); // add debug data if ($this->debug) { $response['debug'] = array( 'data' => $in, 'elapsed' => WBClock::stop() ); } $data = $this->ser->serialize($response); } catch (WBException $e) { $data = $this->ser->sprintfError($e->getCode(), $e->getMessage()); $status = 400; } return true; } /** * Unserialize Request Body * * Check for request method (PUT, POST) if request body is required. * * In case there is a request body, try to find suitable unserializer. According * to HTTP-header Content-Type. * * The next step is to pass the request body to the unserializer to receive the * data array. * * @return array $data */ private function unserializeBody() { $method = strtolower($this->req->getMeta('REQUEST_METHOD')); if ($method != WBRest::METHOD_POST && $method != WBRest::METHOD_PUT) { return array(); } // load request body $body = $this->req->getRaw(); if (empty($body)) { return array(); } // load unserializer $type = trim(strtolower($this->req->getMeta('CONTENT_TYPE', $this->conf->get('header/default/contenttype', 'text/xml') . '; charset=utf-8;'))); $type = explode(';', $type); $type = array_shift($type); if ('text/html' == $type) { $type = $this->conf->get('header/default/contenttype', 'text/xml'); } $type = explode('/', $type); $type = ucfirst($type[1]); try{ $this->unser = WBClass::create('WBRest_Serializer_' . $type); } catch(WBException_Class $e) { // hide actual error messages from REST user throw new WBException('Invalid unserializer: "' . $type . '"!', 1, __CLASS__); } // parse request body return $this->unser->unserialize($body); } /** * Start serializer * * Extract proper serializer according HTTP header "Accept". * If there is no such a serializer, use XML-Serializer * * Also set debug flag and proper content header of response object */ private function startSerializer() { // load unserializer $type = trim(strtolower($this->req->getMeta('HTTP_ACCEPT', $this->conf->get('header/default/accept', 'text/xml') . ';'))); $type = explode(';', $type); $type = array_shift($type); $type = explode(',', $type); $type = array_shift($type); if ('text/html' == $type) { $type = $this->conf->get('header/default/accept'); } $type = explode('/', $type); $this->res->addHeader('Content-Language', patI18n::getLocale(patI18n::LOCALE_TYPE_SHORT)); try{ $this->ser = WBClass::create('WBRest_Serializer_' . ucfirst($type[1])); } catch(WBException_Class $e) { // hide actual error messages from REST user $this->ser = WBClass::create('WBRest_Serializer_Xml'); $this->res->addHeader('Content-Type', 'text/xml; charset=utf-8;'); return false; } $this->res->addHeader('Content-Type', implode('/', $type) . '; charset=utf-8;'); $this->ser->setDebug($this->debug); return true; } /** * continue session or login uzser * * Receive authentication state of current user. In case user is not * logged on, try to login using http-basic-auth * * @return bool */ protected function login() { // load user WBClass::load( 'WBUser_Auth' ); $user = WBUser_Auth::getCurrent(); /** @var $user WBUser_Auth */ $nickname = $this->req->getMeta('PHP_AUTH_USER'); $password = $this->req->getMeta('PHP_AUTH_PW'); // not logged in? if ($user->isAuthenticated()) { $data = $user->getData(); if (strlen($nickname) == 0 || $nickname == $data['nickname']) { return true; } $user->logout(); } // username and password are required, otherwise login is not possible if (empty($nickname) || empty($password)) { $this->res->addHeader('WWW-Authenticate', 'Basic realm="'. $this->conf->get('auth/realm') .'"'); return false; } // try to log in. $credential = array( 'nickname' => $nickname, 'password' => $password ); $user->login($credential); if ($user->isAuthenticated()) { return true; } $this->res->addHeader('WWW-Authenticate', 'Basic realm="'. $this->conf->get('auth/realm') .'"'); return false; } } ?>