thekid/rfc — Issue #216: REST server API implementation
Author: thekid
Created: 2011-10-10 16:06:18+00:00
State: open

Scope of Change
==
Beside of other types of webserives (e.g. SOAP, JSON) support for REST, which is a resource based type of webservice, will be added to the framework.

Rationale
==
Currently few types of webservices are supported but the standard for webservices is now REST: it's flexible, it's simple and easy to use.

Functionality
==
Since all webservice implementation are in the webservice package, the REST implmenetation will also be there.

Packages
--
The following packages currently exists:
* webservices.json
* webservices.soap
* webservices.uddi
* webservices.wddx
* webservices.xmlrpc

A new package will be created there containing the REST implementation:
* webservices.rest

Architecture
--
The entry point for a webservice is a scriptlet since it is accessible via HTTP. The basic implementation scriptlet.http.HttpScriptlet will be extended to new scriptlet providing functions for handling the
requests.

The request is received by the scriptlet which will then ask a router to return a valid route for the request. When a route can not be found an exception will be thrown. In case a route can be found the route will be processed.

The router itself contains a routing table and a routing processor. The routing table contains configured routing items which each of them has a verb, a path and a target configured. The routing processor is responsible to process a route, e.g. by invoking them and passing the required arguments to it.

To have support for multiple content types, e.g. JSON or XML, the scriptlets request and response will not be passed directly to the handler functions. Instead adapter instances will be used and passed to the handler which provide common functionality for accessing request and response properties.

Entry point
--
As mentioned above, the entry point will be a scriptlet which extends the HttpScriptlet. The webservices.rest.RestHttpScriptlet contains the main functionality for receiving a request and providing a response.

Additonally to this is also does the routing processing by using a router. The router is asked for a matching route for the current request and response. After this the route is processed by a routing processor.

Routing
--
The routing is managed by a webservices.rest.routing.RestRouter, which is only an interface for the routing logic, which stores a routing table containing all valid routes, named webservices.rest.routing.RestRouting.

A router implementation will be provided which routes requests based on annotation to given handler and function: webservices.rest.routing.RestAnnotationRouter. It will scan for handler classes in a given package and will build a RestRouting table based on the annotations in the classes.

Here are some examples how to configure this routing by annotations:

```php
#@webservice
class TheService extends Object {

#[@webmethod(verb= 'GET', path= '/data')]
public function getSomething() { }

#[@webmethod(verb= 'GET', path= '/item/{id}')]
public function getSomething($id) { }

#[@webmethod(verb= 'PUT', path= '/item/{id}', inject= array('id', 'payload'))]
public function saveSomething($id, $data) { }
}
```

First of all the class needs to be declared as REST handler by using the @webmethod annotation. All functions of this class which should be accessible by the webservice needs to declare the @webmethod annotation.

There are the following settings which can be used in the @webmethod annotation to configure the routing:
* verb - only route request with given HTTP method (GET, POST, PUT, ...)
* path - only route request matching this path (you can use placeholders if you need some values from the path by using "{var}")
* inject - declare which values should be injected to the function when invoking it (if omitted parameters will be matched against the function argument names, otherwise only the named parameters will be passed - exception: use "payload" to get the body's data)

This is the only implementation which will be available, but can be extended by implementing the RestRouter interface in a different way.

Routing items
--
All router implementations will build a routing table as mentioned already above. The resulting RestRouting table contains for each route a webservice.rest.routing.RestRoutingItem which represents
a route. A route itself have the following properties:
* verb - a simple string containing the verb (e.g. GET, POST, ...)
* path - a webservices.rest.routing.RestPath representing the matching path pattern for the route
* target - a webservices.rest.routing.RestRoute representing the target to process (e.g. invoke a method)
* args - a webservices.rest.routing.RestRoutingArgs which contains the argument for route processing

Routing targets
--
The routing target is the target which receives the request and is invoked to do the processing of the request. To implement different ways of possible targets the RestRoute interface should be implemented. The most common way of a target is to invoke a method of a class. For this the webservices.rest.routing.RestMethodRoute is implemented which will get the method to invoke.

Routing arguments
--
When building the routing table by the RestRouter and filling up with RestRoutingItems the arguments which should be passed to the RestRoute will also be gathered into a RestRoutingArgs object.

This object contains the meta information for each argument. It contains the name of the argument, the data type of the argument and the kind of argument (passed or injected argument).

Route processing
--
Finally, when a route was found for a given request, the route should be processed. This is handled by the webservices.rest.routing.RestRoutingProcessor. This processor takes care about handling the request and providing the correct information and parameters for the routing target which should be processed (e.g. invoke a RestMethodRoute with the correct arguments or parameters).

When invoking the target the arguments were prepared in front of the invocation to be able to pass the requested data to the target. The RestRoutingArgs object will be used to retrieve the information about the arguments required for the target and a webservices.rest.RestDataCaster is used to convert the parameters into the correct format - at least it tries it best to convert it.

In case everything went well, it will invoke the method.

Data caster
--
The webservices.rest.RestDataCaster is used to convert the request's parameters into the type of the parameter required by the target. It implements a kind of serialize and unserialize function to convert the data into different types.

The following types will be supported:
* scalar values (integer, string, ... ) - the standard types of PHP
* stdclass - The PHP's standard classes
* primitives - the XP Framework's lang.Primitives wrappers
* XP types - the XP Framework's lang.types.* wrapper types
* hash maps - the XP Framework's util.Hashmap

When converting a value having child data (e.g. array) the RestDataCaster will try to convert the values recursively.

Requests and responses
--
When transferring data from client to server or vice versa there should be a standard how to encode the data. Usually a REST implementation should support JSON and XML. In the first step only JSON will be supported.

In what way the data is encoded is determied by the Content-Type header of the HTTP request (e.g. application/json), and the way how the response data is encoded should be specified by the Accept header.

The request and response object will be created in the first step of the request processing the in the RestHttpScriptlet. This will create adapter classes for the different encodings supported for the request and response which implements the webservices.rest.transport.HttpRequestAdapter and webservices.rest.transport.HttpResponseAdapter interfaces which provides basic access to request and response properties - for JSON there will be a JsonHttpRequestAdapter and JsonHttpResponseAdapter.

Security considerations
==
n/a

Speed impact
==
None.

Dependencies
==
n/a

Related documents
==
n/a