Client can request an API by specifing service, usually with service parameter by GET method. The form of URI to request is something like this.
API HOST + ENTRANCE + ?service=XXX.XXX
e.g:
http://dev.phalapi.com + /demo/ + ?service=User.GetBaseInfo
After we request with GET method in a browser, we can see some nginx logs which looks like:
127.0.0.1 - - [07/Feb/2015:22:46:46 -0800] "GET /demo/?service=User.GetBaseInfo&sign=&user_id=1 HTTP/1.1" 200 107 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:11.0) Gecko/20100101 Firefox/11.0"
If we request by POST instead, we will see:
127.0.0.1 - - [07/Feb/2015:19:32:05 -0800] "POST /demo/?service=User.GetBaseInfo&sign= HTTP/1.1" 200 135 "-" "-"
The first letter of service parameter is case-insensitive, but we encourage it should be a capital letter. The API we request in above is related to Api_User::getBaseInfo
.
class Api_User extends PhalApi_Api {
public function getBaseInfo() {
}
}
If there is no service
parameter, it will be Default.Index
in default.
When developing a project, we will run into some common API params, such as client agent, the version ofclient app, app key, etc. These params should be post by GET method, because we can statistics, analyse and locate where the bugs are by access.log in nginx in the future. That is one of best practices.
On the otherhand, we suggest to use POST params while we in the situations as beblow.
There are some headers in each request, and we can get header information as below.
Assume the request header is:
Host demo.phalapi.net
User-Agent Mozilla/5.0
If we want to just get one header, e.g. host in this case, we can:
$host = DI()->request->getHeader("Host"); // we will get "demo.phalapi.net"
If we want to get all the headers, we can:
$headers = DI()->request->getAllHeaders(); // which will return an array
As we can see before, we could request an API by GET method, or POST method, or both of them, which will retrieve client params from $_GET
, $_POST
, and $_REQUEST
. But in some situations, we need to use other param source. In this case, we can register DI()->request
with specified data source in the file index.php
.
For example, when force to use POST data, these code should be added into index.php
.
//vim ./index.php
$HTTP_RAW_POST_DATA = isset($GLOBALS['HTTP_RAW_POST_DATA']) ? $GLOBALS['HTTP_RAW_POST_DATA'] : "{}";
DI()->request = new PhalApi_Request(array_merge($_GET,json_decode($HTTP_RAW_POST_DATA, true)));
While running in unit tests, we need to mock data for API to be tested.
// mock data
$data = array(...);
DI()->request = new PhalApi_Request($data);
We can also mock data with PhalApi_Helper_TestRunner class.
public function testIndexByRunner()
{
//Step 1. build a URL
$url = 'service=Default.Index&username=dogstar';
$params = array();
//Step 2. excute request
$rs = PhalApi_Helper_TestRunner::go($url, $params);
}
It's a good idea to encrypt some sensitive client params for security in our project. In that way, we need to override PhalApi_Request::genData()
.
Assume our project want to post all the client params in $_POST['data']
after base64 encoding.
Firstly, create an new request class and implement how to decode client data.
<?php
class Common_Request extends PhalApi_Request {
public function genData($data) {
if (!isset($data) || !is_array($data)) {
$data = $_POST; // only recieve POST data
}
return isset($data['data']) ? base64_decode($data['data']) : array();
}
}
Secondly, register DI()->request
in file index.php
.
DI()->request = 'Common_Request';
So it is! Try it out, and how to encrypt client data is up to you.
In PhalApi, we treat many resource as back-end service. The filter servcie, which is DI()->filter
, can do some extral operations before excute a request. Usually, we can check API signature in the filter.
There is a default filter service, i.e. PhalApi_Filter_SimpleMD5 class. You can open it by uncommenting the code below in file init.php
.
// filter service
DI()->filter = 'PhalApi_Filter_SimpleMD5';
Then client have to generate a sign as below.
For example, we will request such a URI: ?service=Default.Index&username=dogstar
.
exclude sign param(default to be sign) ?service=Default.Index&username=dogstar
sort all the rest of params
service=Default.Index
username=dogstar
concatenate all the value of params as string after sort
"Default.Indexdogstar" = "Default.Index" + "dogstar"
At last, client need to add sign into GET params. Finally, the URI should be: ?service=Default.Index&username=dogstar&sign=35321cc43cfc1e4008bf6f1bf9b7e3b8
.
Firstly, let's request without sign. Of course, it'll fail.
http://localhost/phalapi/public/demo/?service=Default.Index&username=dogstar
Reponse:
{
"ret": 406,
"data": [],
"msg": "illegal request: wrong sign"
}
When fail to check the sign, we can get the right sign in logs.
2015-10-23 23:16:16|DEBUG|Wrong Sign|{"needSign":"35321cc43cfc1e4008bf6f1bf9b7e3b8"}
Secondly, let's request with a right sign, we generate in above.
http://localhost/phalapi/public/demo/?service=Default.Index&username=dogstar&sign=35321cc43cfc1e4008bf6f1bf9b7e3b8
And it works!
If we want to rename the name of sign parameter, we can set the new name when registering DI()->filter
. For example, we use s
for short instead.
DI()->filter = new PhalApi_Filter_SimpleMD5('s');
We can cancel signature verification by overriding PhalApi_Api::filterCheck()
function in our implementation API sub-class. And do nothing inside filterCheck()
.
//vim ./Demo/Api/Default.php
class Api_Default extends PhalApi_Api
{
//....
protected function filterCheck()
{
}
}
DI()->cookie
is not a default backend service, which means we need to register it in file init.php
when we want to deal with COOKIEs.
It's very simple to register DI()->cookie
as other backend services.
// COOKIE
DI()->cookie = 'PhalApi_Cookie';
There are three main operations in PhalApi_Cookie
, and they are set
, get
, and delete
.
// set a COOKIE
DI()->cookie->set('name', 'phalapi', $_SERVER['REQUEST_TIME'] + 600);
// get a COOKIE
echo DI()->cookie->get('name'); //输出 phalapi
// delete a COOKIE
DI()->cookie->delete('name');
In fact, it's more complicated to use COOKIE in the real world. According defferent situations, we might need to encrypt COOKIE, or get the cookie after we set in the same request. All these situations can be under control with out smart COOKIE service, i.e. PhalApi_Cookie_Multi class.
Again, if we want a smart COOKIE service, we need to register it with some config.
$config = array('crypt' => $crypt, 'key' => 'a secrect');
DI()->cookie = new PhalApi_Cookie_Multi($config);
We config how to encrypt COOKIE here. crypt
will encrypt the COOKIE, and default is DI()->crypt
, which need to implement interface PhalApi_Crypt. key
is the encryption key.
In order to show how to encrypt our COOKIE with PhalApi_Cookie_Multi
, let's take an example step by step.
First of all, create an encryption class, which implement interface PhalApi_Crypt. It is really simple, just doing base64_encode
and base64_decode
.
class Cookie_Crypt_Mock implements PhalApi_Crypt {
public function encrypt($data, $key) {
return base64_encode($data);
}
public function decrypt($data, $key) {
return base64_decode($data);
}
}
Then register DI()->cookie
with Cookie_Crypt_Mock in init.php
.
$config = array('crypt' => new Cookie_Crypt_Mock(), 'key' => 'a secrect');
DI()->cookie = new PhalApi_Cookie_Multi($config);
Now we can test whether it works correctly. Let's create an new API service as below.
public function cookieTest() {
$rs = array();
$rs['aEKey'] = DI()->cookie->get('name');
DI()->cookie->set('name', 'phalapi', $_SERVER['REQUEST_TIME'] + 600);
return $rs;
}
After request the API service, we can check the cookies in response.
AHA~! It works!