Fork me on GitHub

1.4 API Request

4.1 Specify service: ?service=XXX.XXX

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.

4.2 When to use GET params?

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.

4.3 When to use POST params?

On the otherhand, we suggest to use POST params while we in the situations as beblow.

    1. sensitive data, such as password.
    1. some special characters or big data package.

4.4 How to get header information?

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

4.5 Switch to other param source

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);
    }

4.6 Encrypt requst 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.

4.7 API signature with filter

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.

    1. exclude sign param(default to be sign)
    1. sort all the rest of params
    1. concatenate all the value of params as string after sort
    1. md5

For example, we will request such a URI: ?service=Default.Index&username=dogstar.

  1. exclude sign param(default to be sign) ?service=Default.Index&username=dogstar

  2. sort all the rest of params
    service=Default.Index
    username=dogstar

  3. concatenate all the value of params as string after sort
    "Default.Indexdogstar" = "Default.Index" + "dogstar"

  4. md5
    sign = 35321cc43cfc1e4008bf6f1bf9b7e3b8 = md5("Default.Indexdogstar")

At last, client need to add sign into GET params. Finally, the URI should be: ?service=Default.Index&username=dogstar&sign=35321cc43cfc1e4008bf6f1bf9b7e3b8.

Check it out!

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');

How to cancel signature verification in some APIs?

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()
    {
    }
}

4.8 COOKIE

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');

Smart COOKIE in PhalApi

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!

Some advise as other framwork will provide too

    1. DO NOT save sensitive data into COOKIE, protect your data
    1. DO NOT save too much data into COOKIE, just keep client fit