asdasdasdasd
2007年12月9日星期日
2007年12月8日星期六
askeet 10
symfony advent calendar day seventeen: API
Previously on symfony
The askeet application was just put online yesterday, and we already have a lot of feedback about feature tweaking and additions. The user input is fundamental to the design of a web 2.0 application, and even if the concept of the application is new, it has to be experimented with as soon as possible.
But we will add unplanned functionalities on day 21. Before that, we have scheduled a handful of advanced web application development techniques to show you through askeet, and the first to be revealed today is the programming of an external API requiring an HTTP authentication.
As we made quite a lot of little changes yesterday, you are strongly advised to start today's tutorial with a fresh downloaded version of askeet from the day 16 tagged version in the askeet repository.
The API
An Application Programming Interface, or API, is a developer's interface to a particular service on your application, so that it can be included in external websites. Think about Google Maps or Flickr, which are used to extend lots of websites over the Internet thanks to their APIs.
Askeet makes no exception, and we believe that in order to increase the service's popularity, it has to be made available to other websites. The RSS feed developed during day 11 was a first approach to that requirement, but we can do much better.
Askeet will provide an API of answers to a question asked by the user. The access to this API will be restricted to registered askeet users, through HTTP authentication. The API response format chosen is Representational State Transfer, or REST - that means that the response is a simple XML block similar to most of the output of main APIs in the web:
[xml]
<?xml version="1.0" encoding="utf-8" ?>
<rsp stat="ok" version="1.0">
<question href="http://www.askeet.com/question/what-shall-i-do-tonight-with-my-girlfriend" time="2005-11-21T21:19:18Z" >
<title>What shall I do tonight with my girlfriend?</title>
<tags>
<tag>activities</tag>
<tag>relatives</tag>
<tag>girl</tag>
<tags>
<answers>
<answer relevancy="50" time="2005-11-22T12:21:53Z">You can try to read her poetry. Chicks love that kind of things.</answer>
<answer relevancy="0" time="2005-11-22T15:45:03Z">Don't bring her to a doughnuts shop. Ever. Girls don't like to be seen eating with their fingers - although it's nice.</answer>
</answers>
</question>
</rsp>
We will implement the API in a new module of the frontend application, so use the command line to build the module skeleton:
$ symfony init-module frontend api
HTTP Authentication
We choose to limit the use of the API to registered askeet users. For that, we will use the HTTP authentication process, which is a built-in authentication mechanism of the HTTP protocol. It is different from the web authentication that we have seen previously because it doesn't even require a web page - all the exchanges take place in the HTTP headers.
We will need the authentication method included in a custom validator during day six, so first of all we will do some refactoring and relocate the login code in the UserPeer model class:
[php]
public static function getAuthenticatedUser($login, $password)
{
$c = new Criteria();
$c->add(UserPeer::NICKNAME, $login);
$user = UserPeer::doSelectOne($c);
// nickname exists?
if ($user)
{
// password is OK?
if (sha1($user->getSalt().$password) == $user->getSha1Password())
{
return $user;
}
}
return null;
}
The new class method UserPeer::getAutenticatedUser() can now be used in the myLoginValidator.class.php (we'll leave that to you) and in the new api/index web service:
[php]
<?php
class apiActions extends sfActions
{
public function preExecute()
{
sfConfig::set('sf_web_debug', false);
}
public function executeIndex()
{
$user = $this->authenticateUser();
if (!$user)
{
$this->error_code = 1;
$this->error_message = 'login failed';
$this->forward('api', 'error');
}
// do some stuff
}
private function authenticateUser()
{
if (isset($_SERVER['PHP_AUTH_USER']))
{
if ($user = UserPeer::getAuthenticatedUser($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']))
{
$this->getContext()->getUser()->signIn($user);
return $user;
}
}
header('WWW-Authenticate: Basic realm="askeet API"');
header('HTTP/1.0 401 Unauthorized');
}
public function executeError()
{
}
}
?>
askeet 10
symfony advent calendar day seventeen: API
Previously on symfony
The askeet application was just put online yesterday, and we already have a lot of feedback about feature tweaking and additions. The user input is fundamental to the design of a web 2.0 application, and even if the concept of the application is new, it has to be experimented with as soon as possible.
But we will add unplanned functionalities on day 21. Before that, we have scheduled a handful of advanced web application development techniques to show you through askeet, and the first to be revealed today is the programming of an external API requiring an HTTP authentication.
As we made quite a lot of little changes yesterday, you are strongly advised to start today's tutorial with a fresh downloaded version of askeet from the day 16 tagged version in the askeet repository.
The API
An Application Programming Interface, or API, is a developer's interface to a particular service on your application, so that it can be included in external websites. Think about Google Maps or Flickr, which are used to extend lots of websites over the Internet thanks to their APIs.
Askeet makes no exception, and we believe that in order to increase the service's popularity, it has to be made available to other websites. The RSS feed developed during day 11 was a first approach to that requirement, but we can do much better.
Askeet will provide an API of answers to a question asked by the user. The access to this API will be restricted to registered askeet users, through HTTP authentication. The API response format chosen is Representational State Transfer, or REST - that means that the response is a simple XML block similar to most of the output of main APIs in the web:
[xml]
<?xml version="1.0" encoding="utf-8" ?>
<rsp stat="ok" version="1.0">
<question href="http://www.askeet.com/question/what-shall-i-do-tonight-with-my-girlfriend" time="2005-11-21T21:19:18Z" >
<title>What shall I do tonight with my girlfriend?</title>
<tags>
<tag>activities</tag>
<tag>relatives</tag>
<tag>girl</tag>
<tags>
<answers>
<answer relevancy="50" time="2005-11-22T12:21:53Z">You can try to read her poetry. Chicks love that kind of things.</answer>
<answer relevancy="0" time="2005-11-22T15:45:03Z">Don't bring her to a doughnuts shop. Ever. Girls don't like to be seen eating with their fingers - although it's nice.</answer>
</answers>
</question>
</rsp>
We will implement the API in a new module of the frontend application, so use the command line to build the module skeleton:
$ symfony init-module frontend api
HTTP Authentication
We choose to limit the use of the API to registered askeet users. For that, we will use the HTTP authentication process, which is a built-in authentication mechanism of the HTTP protocol. It is different from the web authentication that we have seen previously because it doesn't even require a web page - all the exchanges take place in the HTTP headers.
We will need the authentication method included in a custom validator during day six, so first of all we will do some refactoring and relocate the login code in the UserPeer model class:
[php]
public static function getAuthenticatedUser($login, $password)
{
$c = new Criteria();
$c->add(UserPeer::NICKNAME, $login);
$user = UserPeer::doSelectOne($c);
// nickname exists?
if ($user)
{
// password is OK?
if (sha1($user->getSalt().$password) == $user->getSha1Password())
{
return $user;
}
}
return null;
}
The new class method UserPeer::getAutenticatedUser() can now be used in the myLoginValidator.class.php (we'll leave that to you) and in the new api/index web service:
[php]
<?php
class apiActions extends sfActions
{
public function preExecute()
{
sfConfig::set('sf_web_debug', false);
}
public function executeIndex()
{
$user = $this->authenticateUser();
if (!$user)
{
$this->error_code = 1;
$this->error_message = 'login failed';
$this->forward('api', 'error');
}
// do some stuff
}
private function authenticateUser()
{
if (isset($_SERVER['PHP_AUTH_USER']))
{
if ($user = UserPeer::getAuthenticatedUser($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']))
{
$this->getContext()->getUser()->signIn($user);
return $user;
}
}
header('WWW-Authenticate: Basic realm="askeet API"');
header('HTTP/1.0 401 Unauthorized');
}
public function executeError()
{
}
}
?>