jueves, 14 de abril de 2016
RESTful Services with CodeIgniter
Posted by
daycry
at
11:59
Labels:
Client Api
,
Codeigniter
,
Codeinigniter3
,
Framework
,
PHP
,
REST Api
In August 2011, I wrote a blog post called Building a RESTful Service using CodeIgniter. It was intended to be a learning tutorial on RESTful concepts and how to implement them using theCodeIgniter framework. It’s been almost two years since I published that post, and it still sees a lot of traffic. REST has grown in significance since then, and people are clearly still interested in using CodeIgniter to implement RESTful APIs. I was never entirely satisfied with a few aspects of my original implementation, so I am taking this opportunity to provide a better one. If you want an updated tutorial on RESTful concepts and also a recommendation for a simple, quick, and good implementation of a RESTful service using CodeIgniter, then you have come to the right place.
Before diving into the concepts, my recommendation for the implementation is Chris Kacerguis’s CodeIgniter REST Server available at his public repository at GitHub. Chris has produced an excellent implementation (based on Phil Sturgeon’s original), which is clean, easy to use, and full of great features. In fact, Chris’s REST server fixed the two things that I never liked about my original implementation, namely the use of query strings in the request and the complex authentication method. When I first wrote this article in April, 2013, Phil’s REST server used a single monolithic controller for all resources, but Chris’s updated version allows you to use a controller for every resource. Having the resources handled by separate controllers makes maintenance of the service much easier. As you will see, CodeIgniter remains a good framework for implementing a RESTful service.
All of this code shown in this post can be downloaded at the chriskacerguis/codeigniter-restserver repository on GitHub.
RESTful Concept
Before diving into the implementation, let’s first review the RESTful concept. Representational State Transfer (REST) is an architectural model for web services rather than a standard. As REST has grown to be the dominant model for web services over the past few years, however, most implementations follow common design patterns.
Use Nouns instead of Verbs
Your RESTful API should provide access to things and not actions.
So, instead of this:
createCustomer getCustomer?id=666
Do this:
POST /customers GET /customers/666
Everything Has an ID
The beauty of REST is its simplicity. In a sense, REST is really just a collection of Universal Resource Identifiers (URIs). To be an identifier, every resource should have a unique ID.
GET /customers/666 GET /products/4234
HTTP Verbs Provide the Actions
Use a distinct HTTP verb for each of the Create, Read, Update, Delete (CRUD) actions.
- POST to create a resource.
- GET to read a resource.
- PUT to update a resource.
- DELETE to delete a resource.
Using this mapping for a REST service provides a standard way for any client to execute actions against resources without needing a deeper understanding of the service. That standardization lends itself well to REST being able to fulfill the needs of Service Oriented Architectures (SOA) by providing for key SOA concepts like Service Abstraction, Loose Coupling, Reusability, Discoverability, and Composability1.
By mapping CRUD actions to HTTP verbs in a consistent way, clients also know which verbs provide idempotent actions that can be safely repeated if no response is received. Idempotent means that the request can be repeated with the same results on the server. According to the HTTP standard, GET, PUT, and DELETE are idempotent. POST is not, and that is why POST is used to create and PUT is used to update.
Link Things Together
A key concept of REST is called “Hypermedia As The Engine Of Application State”, shortened to the charming acronym HATEOAS. This means that resources within the application should always be represented as links (hypermedia) where possible. The client then navigates to the various resources (or application states) by following the links.
From the perspective of the service, this means that related resources should be returned to the client as links instead of including representations of resources within the response.
In other words, do this:
<officer id="1"> <name>James T. Kirk</name> <rank>Captain</rank> <subordinates> <link ref="http://ncc1701/api/officers/2"> <link ref="http://ncc1701/api/officers/3"> </subordinates> </officer>
Don’t do this:
<officer id="1"> <name>James T. Kirk</name> <rank>Captain</rank> <subordinates> <officer id="2"> <name>Spock</name> <rank>Commander</rank> </officer> <officer id="3"> <name>Doctor Leonard McCoy</name> <rank>Commander</rank> </officer> </subordinates> </officer>
The related resources should be referenced by hyperlinks containing full URIs. This requires the client to request the current state of those other resources, maintaining the HATEOAS principle. Using full URIs also prevents the need for the client to have extra knowledge about your API and how to retrieve other resources. Instead, the client simply follows the link.
Provide Multiple Representations for Resources
The “R” in REST stands for Representational. This means that REST services should provide different representations in order to serve a wide spectrum of clients.
Using HTTP content negotiation, a client can ask for a representation in a specific format. REST services should attempt to support the standard ones to encourage interoperability with many clients.
For example, a client might ask for a resource as XML:
GET /customers/666 Accept: application/xml
As a vCard for the customer’s address:
GET /customers/666 Accept: application/vcard
As a PDF for a printer-friendly version:
GET /customers/666 Accept: application/pdf
Or even as a custom format that’s specific to your API:
GET /customers/666 Accept: application/vnd.mycompany.customer-v1+json
Use HTTP Status Codes for Responses
HTTP status codes provide a standard way for the server to inform the client about the status of the request.
- 200 (OK) confirms the success of a GET, PUT, or DELETE request.
- 201 (Created) confirms the success of a POST request.
- 304 (Not Modified) is used by a conditional GET to inform the client that the resource has not been modified.
- 400 (Bad Request) indicates a malformed request, often for a POST or PUT in which the request’s content is invalid.
- 401 (Unauthorized) is used to indicate that authentication is required.
- 403 (Forbidden) indicates that the client is not authorized for the requested action.
- 404 (Not Found) is used to respond to any request to indicate that the resource could not be found.
- 405 (Method Not Allowed) informs the client the that requested HTTP method is not available for that resource.
- 409 (Conflict) should be used for situations where there is a conflict which prevents the service to perform the operation, but there is still a chance that the client might be able to resolve the conflict and resubmit the request.
Implementation
CodeIgniter is a popular Model-View-Controller (MVC) framework for PHP. The MVC pattern works well for implementing RESTful services. The Controller handles the client’s request and returns the appropriate response and the Model encapsulates all the logic for the CRUDoperations on the resources. The View can be used to create the representation of the resource to send back to the client in the requested format, but in this implementation a controller method is used for formatting and returning the response to make things clean and simple.
In this section, we’ll step through the implementation. The code for this example controller can be downloaded at the awhitney42/codeigniter-restserver-resources repository on GitHub.
The implementation starts with an abstract library class called
REST_Controller
that extends the native CI_Controller
. The REST_Controller
does most of the heavy lifting by handling the request, calling the model, and then formatting and sending back the response. Each of your resource controllers will extend REST_Controller
.
Let’s step through an example resource controller called
Widgets
that extends REST_Controller
.class Widgets extends REST_Controller
The first method of the
Widgets
class is get()
, which is called for an HTTP GET. As you can see, this method calls the protected method _get()
from the parent class to retrieve the ID parameter from the request. The method then uses the widgets_model
to call getWidgets()
orgetWidget($id)
depending on whether or not an ID parameter was passed. The resulting data from the model is returned by calling the response()
method of the parent class, which sends back the appropriate HTTP return code and the message in the requested format.function index_get($id='') { if(!$id) $id = $this->get('id'); if(!$id) { $widgets = $this->widgets_model->getWidgets(); if($widgets) $this->response($widgets, 200); // 200 being the HTTP response code else $this->response(array('error' => 'Couldn\'t find any widgets!'), 404); } $widget = $this->widgets_model->getWidget($id); if($widget) $this->response($widget, 200); // 200 being the HTTP response code else $this->response(array('error' => 'Widget could not be found'), 404); }
cURL is a great command-line tool for testing REST services. We’ll use it to request the widgets resource from our API in JSON format.
$ curl -i -H "Accept: application/json" -X GET http://foo.com/index.php/api/widgets
This returns the following response.
HTTP/1.1 200 OK Status: 200 Content-Type: application/json {"1":{"id":1,"name":"sprocket"},"2":{"id":2,"name":"gear"}}
Requesting a particular widget is just as easy. This time we’ll request widget ID 2 in XML format.
$ curl -i -H "Accept: application/xml" -X GET http://foo.com/index.php/api/widgets/2
This returns the following response.
HTTP/1.1 200 OK Status: 200 Content-Type: application/xml <?xml version="1.0" encoding="utf-8"?> <xml><id>2</id><name>gear</name></xml>
Requesting an invalid widget results in a 404 (Not Found) code from the server.
HTTP/1.1 404 Not Found Status: 404 Content-Type: application/xml <?xml version="1.0" encoding="utf-8"?> <xml><error>Widget could not be found</error></xml>
The next method of the
Widgets
class is post()
, which is called for an HTTP POST to create a new widget. As you can see, this method retrieves the request data from $this->_post_args
. The parent class does all the magic to parse the request data in the whatever format the client submitted, assuming it is one of the formats handled by libraries/Format.php
like XML or JSON, and then puts the resulting data in $this->_post_args
. Next, the post()
method uses thewidgets_model
to call createWidgets($data)
. The model can then throw exceptions if the data is invalid or the request conflicts with an existing resource. In that case the exceptions are caught and returned as the response. If the call to was successful, then we call getWidget($id)
to get the newly created widget from the model. We return the widget to the client in the requested format along with a 201 (Created) return code.function index_post() { $data = $this->_post_args; try { $id = $this->widgets_model->createWidget($data); } catch (Exception $e) { // Here the model can throw exceptions like the following: // * Invalid input data: // throw new Exception('Invalid request data', 400); // * Conflict when attempting to create, like a resubmit: // throw new Exception('Widget already exists', 409) $this->response(array('error' => $e->getMessage()), $e->getCode()); } if ($id) { $widget = $this->widgets_model->getWidget($id); $this->response($widget, 201); // 201 is the HTTP response code } else $this->response(array('error' => 'Widget could not be created'), 404); }
To test the creation of a new resource by sending a POST request to the API, we’ll need to wrap our cURL calls in a bit of PHP code. Place the following code in a file called
rest_client.php
.<?php print "\n-----TESTING REST POST-----\n"; test_post(); function test_post() { $data = array("name" => "bolt"); $data_string = json_encode($data); $ch = curl_init('http://foo.com/index.php/api/widgets'); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'Content-Type: application/json', 'Content-Length: ' . strlen($data_string)) ); $result = curl_exec($ch); $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $contenttype = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); print "Status: $httpcode" . "\n"; print "Content-Type: $contenttype" . "\n"; print "\n" . $result . "\n"; }
You can test by running
rest_client.php
from the command-line.$ php rest_client.php
This returns the following response.
-----TESTING REST POST----- Status: 201 Content-Type: application/xml <?xml version="1.0" encoding="utf-8"?> <xml><id>3</id><name>bolt</name></xml>
The next method of the
Widgets
class is put()
, which is called for an HTTP PUT to update an existing widget. The implementation is very similar to post()
. The primary differences are using$this->_put_args
instead of $this->_post_args
and returning a 200 code instead of 201 code for success.public function index_put() { $data = $this->_put_args; try { //$id = $this->widgets_model->updateWidget($data); $id = $data['id']; // test code //throw new Exception('Invalid request data', 400); // test code } catch (Exception $e) { // Here the model can throw exceptions like the following: // * For invalid input data: new Exception('Invalid request data', 400) // * For a conflict when attempting to create, like a resubmit: new Exception('Widget already exists', 409) $this->response(array('error' => $e->getMessage()), $e->getCode()); } if ($id) { $widget = array('id' => $data['id'], 'name' => $data['name']); // test code //$widget = $this->widgets_model->getWidget($id); $this->response($widget, 200); // 200 being the HTTP response code } else $this->response(array('error' => 'Widget could not be found'), 404); }
To test updating an existing resource by sending a PUT request to the API, we’ll again wrap our cURL calls in a bit of PHP code in
rest_client.php
. Most web servers disable PUT and DELETE methods by default, so we use the X-HTTP-Method-Override header to submit our PUT via a POST. In doing so, the web server sees this as a POST request, but the REST service see it as a PUT.<?php print "\n-----TESTING REST PUT-----\n"; test_put(); function test_put() { $data = array("id" => "3", "name" => "nut"); $data_string = json_encode($data); $ch = curl_init('http://foo.com/index.php/api/widgets'); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'X-HTTP-Method-Override: PUT', 'Content-Type: application/json', 'Content-Length: ' . strlen($data_string)) ); $result = curl_exec($ch); $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $contenttype = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); print "Status: $httpcode" . "\n"; print "Content-Type: $contenttype" . "\n"; print "\n" . $result . "\n"; }
You can test again by running
rest_client.php
from the command-line.$ php rest_client.php
This returns the following response.
-----TESTING REST PUT----- Status: 200 Content-Type: application/xml <?xml version="1.0" encoding="utf-8"?> <xml><id>3</id><name>nut</name></xml>
The final method of the
Widgets
class is delete()
, which is called for an HTTP DELETE to delete an existing widget. For this method, an ID number must be supplied or a 400 (Bad Request) will be returned because we do not want to allow clients to delete all widgets at once. If an ID is supplied but the widget cannot be found, then a 404 (Not Found) is returned. Finally, if a widget can be retrieved by calling getWidget($id)
, then an attempt is made to call deleteWidget($id)
. Any exceptions thrown by deleteWidget($id)
are caught and returned as the response. Otherwise, if the delete is successful then a 200 (Success) is returned.function index_delete() { if(!$id) $id = $this->get('id'); if(!$id) { $this->response(array('error' => 'An ID must be supplied to delete a widget'), 400); } //$widget = $this->widgets_model->getWidget($id); $widget = @$widgets[$id]; // test code if($widget) { try { $this->widgets_model->deleteWidget($id); } catch (Exception $e) { // Here the model can throw exceptions like the following: // * Client is not authorized: new Exception('Forbidden', 403) $this->response(array('error' => $e->getMessage()), $e->getCode()); } $this->response($widget, 200); // 200 being the HTTP response code } else $this->response(array('error' => 'Widget could not be found'), 404); }
cURL on the command-line can again be used to test a DELETE request, as the ID parameter is passed in the URI. Just like with PUT, we use the X-HTTP-Method-Override header to submit our DELETE via a POST. In doing so, the web server sees this as a POST request, but the REST service see it as a DELETE.
$ curl -i -H "Accept: application/xml" -H "X-HTTP-Method-Override: DELETE" -X POST http://foo.com/index.php/api/widgets/1
This results in the following 200 (Success) response from the server, meaning that widget ID 1 was successfully deleted.
HTTP/1.1 200 OK Status: 200 Content-Type: application/xml <?xml version="1.0" encoding="utf-8"?> <xml><id>1</id><name>sprocket</name></xml>
If an invalid ID number is passed the service will return a 404 (Not Found) error. If we do not have authorization to delete the resource, then the service will return a 403 (Forbidden) error.
That covers the implementation. As mentioned above, the code for the sample controller and the test client can be downloaded at the awhitney42/codeigniter-restserver-resourcesrepository on GitHub.
Authentication
As mentioned in the introduction, Chris’s REST server is a great improvement on my original complex authentication method. It greatly simplifies things by using standard HTTP authentication. Both basic access authentication and digest access authentication are available. It also provides great features like the ability to integrate the authentication with an LDAP directory. The authentication options can be enabled in the
config/rest.php
file.
If you so choose, the service also allows for the use of API keys in place of (or in addition to) authentication, as well as white-listing clients by IP address.
Data Transfer Objects
My original implementation relied heavily on the use a Data Transfer Object (DTO) for every resource to translate data between different representations (formats). Chris also did away with that complexity through the use of the
Format
class in libraries/Format.php
, and some seemingly magic calls to it from the REST_Controller
class. This simplification can be seen in the Widgets controller above, where request and response data structures are used effortlessly by single-line calls to the $this->_post_args
member (for example) or the $this->response()
method. This is not to say that DTOs are never useful, but it does suggest that they can often be a needless complexity in many cases. With my original implementation, I was never comfortable with the fact that the client really needed the same DTO library as the server, as it violated the KISSprinciple.Conclusion
REST has become the de facto architectural model for implementing web services. REST is the underpinning of not only the programmable web, the sharing of processing logic and data among heterogeneous applications and services, but also the service-oriented architecture(SOA) that enables mature user interfaces in both web and mobile development.
As you have seen, the CodeIgniter framework makes implementing a RESTful service easy. Download the at the chriskacerguis/codeigniter-restserver repository on GitHub, and get started today!
Epilogue
REST is an architectural style that follows common design patterns. It has evolved over the past few years and will continue to evolve, so please comment on this post if you have questions or suggestions for improvements in either the contents of this post or in the implementation of the service.
Suscribirse a:
Enviar comentarios
(
Atom
)
Sígueme en las Redes Sociales
Donaciones
Datos personales
Entradas populares
-
En este apartado vamos a explicar como ejercutar archivos PHP a través del terminal de Ubuntu. Lo primero que tendríamos que hacer es inst...
-
En este blog voy a comentar un tema que se utilizan en casi todas las páginas web que existen, y es el tema de la paginación. La paginaci...
-
Este post trata de la integración de la librería PHPExcel en Codeigniter, aunque se podría aplicar a cualquier librería, como por ejemplo mP...
-
Ejemplo para añadir o sumar un número determinado de hora/s, minuto/s, segundo/s a una fecha en php. Con la función strtotime se puede ...
-
Este tema es uno de los temas primordiales sobre el framework Codeigniter, ya que en alguna ocación nos hemos visto obligados a recoger dato...
© Espacio Daycry - Espacio de programación 2013 . Powered by Bootstrap , Blogger templates and RWD Testing Tool
No hay comentarios :
Publicar un comentario