-
Notifications
You must be signed in to change notification settings - Fork 7
REST API
Representational State Transfer (REST) is a software paradigm describing the mode of interaction between various entities in a distributed environment. Since it’s a paradigm, it is not bound to a specific programing language or implementation. Any application that fulfils the architectural constraints given by REST can be a RESTful application. Those are:
- Client-Server
- Stateless
- Cache
- Uniform Interface
- Layered System
- (Code-On-Demand)
We use the REST paradigm for the backend API. The API allows people to retrieve data from our webserver that is running Wordpress. More particularly, a user sends a specific string to the API, which then parses the string and returns the desired value. A user could be the Integreat smartphone-app requesting the most recent contents for a specific instance like Augsburg.
A good documentation of the API is available at https://integreat.github.io/cms/
Some more words about the theory. To make it more concrete, the text contains various examples that combine theory and our Integreat REST API.
- Client-Server
- This basically means, that we have clients demanding a service provided by a server.
- In our system Clients are represented by the Integreat smartphone-app, whereas the Server is represented by the webserver hosting Wordpress.
- Stateless
- The server does not store any state-information about clients. The client itself has to provide all necessary information in order to make her request viable. HTTP itself for example is stateless. HTTP Cookies introduce state to the HTTP protocol.
- We do not require any state in order to grant service. All we need is a timestamp provided by the client. Every couple of hours the Integreat App queries the server for content. Obviously, the server only transfers which has not been received by the client yet. For this purpose, the client sends date and time of her most current content download.
- Cache
- The server temporarily stores data of responses to previous requests in order to reuse that data for future. This is especially important if generating data is expensive and future requests are likely to desire the same piece of data and can thus be reused.
- Since we have no performance issues so far, caching is not involved in our API yet. It however makes sense to introduce caching. This is because gathering the data requires processing various database queries. Furthermore, it is very likely that the information of one response can be reused for future responses. New and fresh content has to be propagated to all clients and it is very likely, that most clients are missing the same piece of information.
- Uniform Interface
- RESTful applications utilize uniform interfaces to communicate with the clients. Or in other words, clients know how to deal with a REST interface.
- Resources are uniquely identified by their URI:
HTTP://example.com/cat/lucywill return the dataset to cat Lucy andHTTP://example.com/dog/paulwill return information about a dog named Paul. - Data is accessed using uniform methods. HTTP already comes up with GET to retrieve content, PUT/POST to push/modify content or DELETE to erase data. In the examples above, we only used GET methods.
- The representation of a resource (data format) is independent from the resource. Depending on the preceding request, it the data format of the response can be XML, HTML, JSON, …
- Resources are uniquely identified by their URI:
- The GET-request
https://cms.integreat-app.de/augsburg/de/wp-json/extensions/v0/modified_content/pages?since=2015-01-25T09%3A27%3A49%2B0000returns a JSON containing all modified content since the 25th of January 2016. - Layered System
- Layering is kind of misleading. It is more about the separation of concerns. The REST interface hides the complexity to the client. Everything she needs to know is where to find the resource how the response representation looks like. Request processing is completely hidden to the client.
The server on the other hand, the server has no clue about the data processing on the client side. - Receiving a client request, our REST API first parses the request URI. That way we get to know about which particular data the client desires. This is followed by a couple of database requests. The resulting dataset is then tied up to a JSON parcel and delivered back to the client. Parsing, SQL-actions, formatting, etc. is hidden to the clients view.
Similarly, the server is not concerned with the data processing on the client side.
We utilize the Wordpress REST API and extended it with the ig-wp-api-extension plugin such that it satisfies all our needs.
The Wordpress REST API utilizes the terms Endpoint and Route.
Endpoints are functions that are available through the API and they perform several actions like retrieving posts (e.G.: GET wp/v2/posts/100) or creating a new user. More particularly, an endpoint triggers a method that performs a specific task.
Routes are pointers to an endpoint (e.G.: /posts/100). Wordpress however has to know about those routes. Thus we have to register them. This is done using the function
register_rest_route($namespace, $route, $args = array(), $override= false).
It takes 4 (callback is part of args) arguments:
- namespace:
- Namespaces are the first part of the URL for the endpoint. They should be used as a vendor/package prefix to prevent clashes between custom routes.
- Namespaces in general should follow the pattern of vendor/v1, where vendor is typically your plugin or theme slug, and v1 represents the first version of the API.
- That way, if I write a plugin that adds a route called menus, it can be used side by side with a plugin you wrote that adds a route called menus—just as long as we use different namespaces that correspond to the plugin’s name.
- route: That's the URL-part after the namepace
- callback: function to treat the client request
- args: By default, routes receive all arguments passed in from the request. Arguments are defined as a map in the key args for each endpoint (next to your callback option). This map uses the name of the argument of the key, with the value being a map of options for that argument. This array can contain a key for default, required, sanitize_callback and validate_callback.
- overwrite: false by default, true means that already existing, declared routes will be overwritten.
Further information about the WP REST API can be found here.
In this chapter, you’ll get to know about the implementation of our API.
The ig-wp-api-extension-plugin is initiated with in the plugin.php-file.
There we add a hook to the rest_api_init-action, which fires when an API request arrives.
add_action('rest_api_init', function () { /*Set up REST API*/ });
In the corresponding callback function we initiate the REST API by initiating our endpoints. We have 5 different endpoints:
const ENDPOINT_MULTISITES = 'multisites';const ENDPOINT_LANGUAGES = 'languages';const ENDPOINT_PAGES = 'pages';const ENDPOINT_EVENTS = 'events';const ENDPOINT_DISCLAIMER = 'disclaimer';
Each endpoint is meant for a different route. The languages-endpoint serves requests to the route /languages/wpml/ whereas the pages-endpoint serves requests to the resource at /modified_content/. Since the pages-endpoint is the most vivid instance, I’ll use this endpoint to explain the working of the plugin.
First of all, the route to the endpoint has to be registered:
register_rest_route($namespace, $baseRoute . $subPath, $routeOptions);
More particularly
-
plugin.php:$endpoint->register_routes($versioned_namespace); -
RestApi_ModifiedContent.php:parent::register_route( /*…*/); -
RestApi_ExtensionBase.php:register_rest_route( /*..*/);
As explained above, the function register_rest_route requires namespace, route and args as parameters.
-
namespace: The globally defined namespace is/extensions/for Version V1 of the API and/extensions/V0/for Version V0 of the API. -
route:/modified_content/ -
args -
method:'methods' => WP_REST_Server::READABLE // which is GET -
callback:'callback' => 'get_modified_content'The callback function is used to retrieve the modified contents from the database. -
args:'since' => ['required' => true,'validate_callback' => [$this, 'validate_datetime']]
The endpoint requires the client to specify the point of time (since) when she has last received contents from the server. E.g.:
…/extensions/v0/modified_content/pages?since=2015-01-25T09%3A27%3A49%2B0000
Now if a request comes in, Wordpress:
- Searches for a registered route (namespace, URI and method have to match).
- Executes the corresponding callback function (here:
'get_modified_content').
- Creates a response with the return value of the callback function.
The ig-extras plugins hooks into the Multisites endpoint and delivers a set of variables that represent available extras for each city. The extras currently are:
- ige-evts for Events
- ige-pn for Push Notifications
- ige-zip contains the zip code for the blog
- ige-srl for Serlo
- ige-ilb for IHK Lehrstellen Börse
- ige-ilp for IHK Praktikums Börse
- ige-lr for Lehrstellenradar
- ige-sbt for Sprungbrett
Example
{
"enabled": 1,
"url": "https://www.sprungbrett-intowork.de/ajax/app-search-internships?location=augsburg"
}
To create a PDF, use the API call https://web.integreat-app.de/wp-json/ig-mpdf/v1/[BLOG_ID]/[POST_ID]. The returned JSON contains a link to the created PDF.
https://web.integreat-app.de/wp-json/extensions/v1/multisites/
Result
[
{
"id":"1",
"name":"Integreat",
"icon":"https:\/\/web.integreat-app.de\/wp-content\/uploads\/2015\/10\/cropped-INTEGREAT_APP_ICON_1.jpg",
"cover_image":"",
"color":"#FFA000",
"path":"\/",
"description":"Integreat",
"live":false,
"ige-srl":"0",
"ige-sbt":"0",
"ige-evts":"0",
"ige-pn":"0",
"ige-c4r":"0",
"ige-lr":"0",
"ige-zip":"0",
"ige-ilb":"0",
"ige-ipb":"0"
},
{
...
}
]
https://web.integreat-app.de/{location}/{language}/wp-json/extensions/v0/modified_content/disclaimer?since={since}
https://web.integreat-app.de/{location}/{language}/wp-json/extensions/v0/modified_content/pages?since={since}
https://web.integreat-app.de/{location}/de/wp-json/extensions/v0/languages/wpml
Request URL
https://web.integreat-app.de/{location}/{language}/wp-json/extensions/v0/extras/
Return
{
"name": [string],
"type": [string],
"enabled": [boolean],
"extension": {
"url": [string]
}
}