Date post: | 31-May-2015 |
Category: |
Technology |
Upload: | corley-srl |
View: | 8,658 times |
Download: | 7 times |
@walterdalmut - www.corley.it - www.upcloo.com
RESTful Modules in ZF2Walter Dal Mut – [email protected] - @walterdalmut
https://github.com/wdalmut
@walterdalmut - www.corley.it - www.upcloo.com
About Me
• Walter Dal Mut (@walterdalmut)• Electronic Engineer• Polytechnic University of Turin
• Startupper• Corley S.r.l. – www.corley.it
• Cloud Computing Services• UpCloo Ltd. – www.upcloo.com
• Semantic Most Related Links service
@walterdalmut - www.corley.it - www.upcloo.com
Summary
• REST introduction• REST constraints• Types of RESTful services• ZF2 RESTful modules• RESTful ZF2 URI tunneling module• RESTful ZF2 CRUD module
@walterdalmut - www.corley.it - www.upcloo.com
RESTful? What it means in few words…• Representational State Transfer (REST)
• Architecture is based on Client-Server• Clients initiate requests to servers; servers process requests and return
appropriate responses
• Flexible, the application must know messages format• XML• JSON• Etc.
@walterdalmut - www.corley.it - www.upcloo.com
RESTful services are resource centric• Resources (sources of specific information)• Each resource is referenced with a global identifier (URI, etc.)• Any number of connectors can mediate the request• Clients• Servers• Caches• Tunnels
@walterdalmut - www.corley.it - www.upcloo.com
REST Constraints
• Client-Server• A uniform interface separates clients from servers.
• Stateless• The client–server communication is further constrained by no client context being stored
on the server between requests.
• Cacheable• Clients can cache responses
• Layered System• Intermediary servers may improve system scalability by enabling load-balancing and by
providing shared caches. They may also enforce security policies.
• Uniform interface• Simplify and decouples the architecture
@walterdalmut - www.corley.it - www.upcloo.com
Several types of RESTful services
• URI Templates• http://rest.domain.tld/order/{orderID}• One of the major uses for URI templates is as human- and machine-readable
documentation.
• URI Tunneling• http://rest.domain.tld/PlaceOrder?pizza=margherita&type=classic
• Rest.domain.tld service• PlaceOrder method• Pizza=margherita&type=classic arguments
• POX – Plain Old XML over HTTP• Similar to URI Tunneling but information will be sent as XML document in the HTTP
request from the customer.
@walterdalmut - www.corley.it - www.upcloo.com
POX – Plain Old XML over HTTP example
POST /PlaceOrder HTTP/1.1Content-Type: application/xml
<Order> <Pizza> <Name>margherita</Name> <Type>classic</Type> </Pizza></Order>
Client application Application domain
HTTP/1.1 200 OK
<OrderConfirmation> <OrderID>1345</OrderID></OrderConfirmation>
@walterdalmut - www.corley.it - www.upcloo.com
CRUD Webservices
• CRUD what it means?• Create, Read, Update and Delete • Patterns for manipulating resources across the network
• Extended usage of HTTP verbs• GET• POST• PUT• DELETE
• Using HTTP as an application protocol instead of a transport protocol• Web is really a big framework for building distributed systems.
@walterdalmut - www.corley.it - www.upcloo.com
HTTP verbs in CRUD services
• Get Read Operation• Used to retrive resource details• http://rest.service.tld/order/10
• Post Create Operation• Used to create new resources
• Put Update Operation• Used to update existing resources
• Delete Delete Operation• Used to delete existing resources
POST/PUT can be exchanged and sometimes PUT/DELETE can be excluded to enable javascript integration (PUT/DELETE not supported browser side [HTTP_X_HTTP_METHOD_OVERRIDE parameter])
@walterdalmut - www.corley.it - www.upcloo.com
CRUD Summary
Verb URI or Template Use
POST /order Create a new order, and upon success, receive a Location header specifying the new order’s URI.
GET /order/{id} Request the current state of the order specified by theURI.
PUT /order/{id} Update an order at the given URI with new information,providing the full representation.
DELETE /order/{id} Logically remove the order identified by the given URI.
@walterdalmut - www.corley.it - www.upcloo.com
Status code definition (short list)
• 2xx (Positives)• 200 OK – The request has succeeded. • 201 Created – The server accept the request and it has created the resource.• 202 Accepted – The request has been accepted for processing, but the processing has not been completed.
• 4xx (Client Errors)• 400 Bad Request – The request could not be understood by the server due to malformed syntax.• 401 Unauthorized – The request requires user authentication.• 403 Forbidden – The server understood the request, but is refusing to fulfill it.• 404 Not found – The server has not found anything matching the Request-URI.• 405 Method not allowed – The method specified in the Request-Line is not allowed for the resource identified by the
Request-URI.
• 5xx (Server Errors)• 500 Internal Server Error – The server encountered an unexpected condition which prevented it from fulfilling the
request.• 501 Not implemented – The server does not support the functionality required to fulfill the request.• 503 Service unavailable – The server is currently unable to handle the request due to a temporary overloading or
maintenance of the server.
@walterdalmut - www.corley.it - www.upcloo.com
RESTful module idea
Router
RESTfulControllers
POSTProcessor
Models
requests
Responses
• Router• Wire requests to RESTful controllers
• RESTful Controllers• Uses HTTP verbs to call dedicated
actions• Query models in order to serve
responses• POST Processors
• Create valid messages using formats• JSON• XML• Etc.
@walterdalmut - www.corley.it - www.upcloo.com
Realize ZF2 tunneling RESTful module• ZF1 provides «Zend_Rest_Server» that realize URI tunneling• We can realize the same thing in 2 minutes thanks to ZF2 flexibility.
• We need to configure• Router• Events
• A simple example here: • https://github.com/wdalmut/ZF2-Tunneling-Restful-Module-Skeleton
@walterdalmut - www.corley.it - www.upcloo.com
ZF2 URI Tunneling - Configuration<?phpreturn array( 'controllers' => array( 'invokables' => array( 'index' => 'Tunneling\Rest\Controller', ) ), 'router' => array( 'routes' => array( 'tunneling' => array( 'type' => 'Zend\Mvc\Router\Http\Segment', 'options' => array( 'route' => '/tunnel', 'defaults' => array( 'controller' => 'index', 'action' => 'index' ), ), 'may_terminate' => true, 'child_routes' => array( 'default' => array( 'type' => 'Zend\Mvc\Router\Http\Segment', 'options' => array( 'route' => '[/:model/:action]', 'constraints' => array( 'controller' => 'index', 'model' => '[a-zA-Z][a-zA-Z0-9_]*', 'action' => '[a-zA-Z][a-zA-Z0-9_]*' ), ), ), ), ), ), ));
We have created a simple base route «/tunnel» and a dynamic rule that select a «model» and an attached «action»
In simple words• http://my-app.local/tunnel/menu/get?id=1• «menu»
• The model «Tunneling\Model\Menu»• «get»
• The model action «get()»• «id=1>
• The action parameters «get(1)»
@walterdalmut - www.corley.it - www.upcloo.com
ZF2 URI Tunneling - Controller<?phpnamespace Tunneling\Rest;
Use …
class Controller extends AbstractController{ public function onDispatch(MvcEvent $e) { $routeMatch = $e->getRouteMatch(); $params = $routeMatch->getParams(); $vars = get_object_vars($e->getRequest()->getQuery());
$filter = new \Zend\Filter\FilterChain(); $filter->attach(new \Zend\Filter\Word\DashToCamelCase()); $filter->attach(new \Zend\Filter\Callback("lcfirst")); $action = $filter->filter($params["action"]); $filter->attach(new \Zend\Filter\Callback("ucfirst")); $model = $filter->filter($params["model"]);
$classname = "\\Tunneling\\Model\\{$model}"; if (class_exists($classname)) { $clazz = new $classname; if (property_exists($clazz, $action)) { $ret = call_user_func_array(array($clazz, $action), $vars); $e->setResult($ret); return; } else { throw new InvalidArgumentException("Method \"{$action}\" doesn't exists'"); } } else { throw new InvalidArgumentException("Class \"{$classname}\" doesn't exists'"); } }}
The base «AbstractController» is very flexible and enable us to use the «dispatch» action to realize what we need in few lines.
In practice we allocate the model and call the requested action. The return variable is used as response.
In case of missing model or missing action an «InvalidArgumentException» is thrown.
@walterdalmut - www.corley.it - www.upcloo.com
ZF2 URI Tunneling – Model example<?phpnamespace Tunneling\Model;
class Menu{ public function get($id) { return array("id" => $id); }
public function add($name, $value) { return array("name" => $name, "value" => $value); }}
As you can see, the «model» is a simple class definition.
• /tunnel/menu/get?id=1• /tunnel/menu/add?x=pizza&y=4
Very simple implementation
@walterdalmut - www.corley.it - www.upcloo.com
ZF2 URI Tunneling – JSON responses<?phpnamespace Tunneling;
use Zend\Mvc\MvcEvent;
class Module{ public function onBootstrap($e) { /** @var \Zend\ModuleManager\ModuleManager $moduleManager */ $moduleManager = $e->getApplication()->getServiceManager()->get('modulemanager'); /** @var \Zend\EventManager\SharedEventManager $sharedEvents */ $sharedEvents = $moduleManager->getEventManager()->getSharedManager();
$sharedEvents->attach( 'Zend\Mvc\Controller\AbstractController', MvcEvent::EVENT_DISPATCH, array($this, 'postProcess'), -100 ); }//… public function postProcess(MvcEvent $e) { $routeMatch = $e->getRouteMatch(); if (\strpos($routeMatch->getMatchedRouteName(), "tunneling") !== false) { $e->getResponse()->setContent(json_encode($e->getResult()->getVariables())); return $e->getResponse(); } }}
Thanks to events we can wire controller output to a post processor that converts responses in json messages.
• Attach a «postProcess» action and create a json message.
@walterdalmut - www.corley.it - www.upcloo.com
ZF2 RESTful CRUD modules
• ZF2 provides a base controller class that can help us to realize RESTful modules in few steps• Zend\Mvc\Controller\AbstractRestfulController
• CRUD based implementation (Extended)• get($id)• delete($id)• update($id)• create($id)
@walterdalmut - www.corley.it - www.upcloo.com
ZF2 RESTful CRUD module example
• Clone ZF2 Skeleton Application• Git clone https://github.com/zendframework/ZendSkeletonApplication.git my-app• Git submodule init• Git submodule update
• Clone a ZF2 RESTful module• Git submodule add https://github.com/wdalmut/ZF2-Restful-Module-Skeleton.git module/Main
• Add «Main» module into «configs/application.config.php»• Create you application virtual host and try it
• http://my-app.local/rest/info/json [getList]• http://my-app.local/rest/info/json/1 [get]• curl –X POST –d ‘hello=world’ http://my-app.local/rest/info [create]• curl –X PUT –d ‘hello=ciao’ http://my-app.local/rest/info/1 [update]• curl –X DELETE http://my-app.local/rest/info/1 [delete]
@walterdalmut - www.corley.it - www.upcloo.com
RESTful Controllers<?php
namespace Main\Controller;
use Zend\Mvc\Controller\AbstractRestfulController;
class InfoController extends AbstractRestfulController{ public function getList() { return array('ciao' => 'mondo'); }
public function get($id) {
}
public function delete($id) {
}
public function update($id, $data) {
}
public function create($data = null) {
}}
• RESTful Controller should extends AbstractRestfulController• 5 abstract methods (CRUD + List)
• getList• GET operation without parameters• /rest/json/info
• Get• READ resource with parameters (ID)• /rest/json/info/1
• Delete• DELETE a resource
• Update• UPDATE a resource
• Create• CREATE a new resource
@walterdalmut - www.corley.it - www.upcloo.com
First of all play with routes
'router' => array( 'routes' => array( 'restful' => array( 'type' => 'Zend\Mvc\Router\Http\Segment', 'options' => array( 'route' => '/rest[/:formatter]', 'constraints' => array( 'formatter' => '[a-zA-Z0-9_-]*', ), ), 'may_terminate' => true, 'child_routes' => array( 'default' => array( 'type' => 'Zend\Mvc\Router\Http\Segment', 'options' => array( 'route' => '[/:controller[/:id]]', 'constraints' => array( 'controller' => '[a-zA-Z][a-zA-Z0-9_-]*', 'id' => '[a-zA-Z0-9_-]*' ), ), ), ), ), ),
• Create a base route /rest that enable also formatters• Formatter allow us to switch easly to JSON,
XML etc.• Child routes play with controller and identifiers
@walterdalmut - www.corley.it - www.upcloo.com
Formatters
• RESTful services can handle different type of messages• JSON• XML• Images• Etc
• The high modularity of ZF2 MVC implementation enable us to add different layers of abstractions and formatters is one of this.• Client must know what kind of messages type have to handle.• Post Processors are used to render messages
@walterdalmut - www.corley.it - www.upcloo.com
Formatters «configs/module.config.php»
'errors' => array( 'post_processor' => 'json-pp', 'show_exceptions' => array( 'message' => true, 'trace' => true ) ), 'di' => array( 'instance' => array( 'alias' => array( 'json-pp' => 'Main\PostProcessor\Json', 'jsonp-pp' => 'Main\PostProcessor\Jsonp', 'image-pp' => 'Main\PostProcessor\Image' ) ) ),
• The configuration allows us to define different «post processors»• Errors can be detailed more, for example traces, messages etc.
@walterdalmut - www.corley.it - www.upcloo.com
Wiring events (Module.php) /** * @param MvcEvent $e */ public function onBootstrap($e) { /** @var \Zend\ModuleManager\ModuleManager $moduleManager */ $moduleManager = $e->getApplication()->getServiceManager()->get('modulemanager'); /** @var \Zend\EventManager\SharedEventManager $sharedEvents */ $sharedEvents = $moduleManager->getEventManager()->getSharedManager();
$sharedEvents->attach( 'Zend\Mvc\Controller\AbstractRestfulController', MvcEvent::EVENT_DISPATCH, array($this, 'postProcess'), -100 );
$sharedEvents->attach( 'Main\Controller\InfoController', MvcEvent::EVENT_DISPATCH, array($e->getApplication()->getServiceManager()->get('Main\Http\Restful'), 'onDispatch'), 100 );
$sharedEvents->attach( 'Zend\Mvc\Application', MvcEvent::EVENT_DISPATCH_ERROR, array($this, 'errorProcess'), 999 ); }
• Event position -100• Handle post processors
• Event position 100• Handle HTTP Method Override
• Event position 999• Handle errors
@walterdalmut - www.corley.it - www.upcloo.com
Attach Post Processor to Actions public function postProcess(MvcEvent $e) { $routeMatch = $e->getRouteMatch(); $formatter = $routeMatch->getParam('formatter', false);
$di = $e->getTarget()->getServiceLocator()->get('di');
if ($formatter !== false) { if ($e->getResult() instanceof \Zend\View\Model\ViewModel) { if (($e->getResult()->getVariables())) { $vars = $e->getResult()->getVariables(); } else { $vars = null; } } else { $vars = $e->getResult(); }
$postProcessor = $di->get($formatter . '-pp', array( 'request' => $e->getRequest(), 'response' => $e->getResponse(), 'vars' => $vars, ));
$postProcessor->process();
return $postProcessor->getResponse(); }
return null; }
@walterdalmut - www.corley.it - www.upcloo.com
Example of JSON Post Processor<?phpnamespace Main\PostProcessor;
/** * */class Json extends AbstractPostProcessor{ public function process() { $result = json_encode($this->_vars);
$headers = $this->getResponse()->getHeaders(); $headers->addHeaderLine('Content-Type', 'application/json');
$this->getResponse()->setHeaders($headers); $this->getResponse()->setContent($result); }}
<?phpnamespace Main\PostProcessor;
abstract class AbstractPostProcessor{ protected $_vars = null; private $_request = null; private $_response = null;
public function __construct (\Zend\Http\Request $request, \Zend\Http\Response $response, $vars = null) { $this->_vars = $vars; $this->_response = $response; $this->_request = $request; }
public function getResponse() { return $this->_response; }
public function getRequest() { return $this->_request; }
abstract public function process();}
@walterdalmut - www.corley.it - www.upcloo.com
ZF2 RESTful Modules
Thanks for listening…
Any questions?