eZ Publish 5 REST API internals
This week saw a release of final version of eZ Publish 5 Kilimanjaro. One of the most prominent and advertised features available is certainly the brand new eZ Publish REST API. I know for a fact that support for full CRUD in eZ Publish REST API is one of the most wished for features in last couple of years. I’ve been honored to work on implementing the new eZ Publish REST API as part of Netgens R&D partnership with eZ Systems and the intention of this blog post is to give you insight on inner workings of the new API and give you some examples.
First things first: for those who love a good RFC, eZ Publish has you covered. REST API v2 RFC is available in Public API GitHub repository. The RFC lists every possible resource supported by REST API with example requests and responses. At the bottom of the document, you can find the list of all XSD schemas used by REST API. If you do read the RFC, you will see that the new REST API has a level 3 of Richardson Maturity Model. This means three things:
- (Level 1) REST API is composed of resources for various parts of the API. That means that different parts of REST API are accessed through different URIs. For example: working with locations is done through "/content/locations" resource, working with user groups is done through "/user/groups" and so on.
- (Level 2) REST API uses full range of HTTP verbs for different operations and full range of HTTP response codes to indicate what is happening. Fetching a locations is done through GET verb, creating a location is done with POST, deleting is done with DELETE and updating a location is done with PATCH verb. Creating a location will return status code "201 Created". If you try to create a location with remote ID that already exists, you will get "403 Forbidden" response and so on.
- (Level 3) HATEOAS support, which means that you don’t need to know what resources exist to use them. All REST server responses will return the links to related resources which you can use to "browse" to them easily, so basicly, you only need to know the URI to the root resource (which is "/") and you’re set.
The new REST API is composed of two parts, PHP client and server. Currently, only server part is available, and PHP client will hopefully be available in 5.1 release.
Server part of the REST API is thin, which means couple of things: It only has a handful of resources available, but those resources implement most of the features desired. Secondly, it acts as a middleman between you and Public API located on remote server. It doesn’t contain any complicated logic and basicly forwards your requests to Public API and outputs the result returned by Public API.
New eZ Publish REST API supports two types of request/response formats: XML and JSON, and of course, you are free to implement additional ones to fit your needs. Selecting which format you wish to use is pretty straightforward and employs the Accept header. If you wish to use XML, your request headers to load a location with ID 42 will look something like this (the relevant part being "+xml" at the end of Accept header):
GET /api/ezp/v2/content/locations/1/2/42 HTTP/1.1 Host: ezpublish5.local Accept: application/vnd.ez.api.Location+xml
If you wish to use JSON, your Accept header will look like this:
The first part of the Accept header is also interesting. With "application/vnd.ez.api.*", you specify which type of resource you want returned for the same resource URI. For example, specifying the following request headers will return the list of content types from content type group with ID 1, without embedded field definitions:
GET /api/ezp/v2/content/typegroups/1/types HTTP/1.1 Host: ezpublish5.local Accept: application/vnd.ez.api.ContentTypeInfoList+xml
while using the following Accept header will return the list of content types with field definitions embedded in response:
Since there’s no easy way (yet) to test the new eZ Publish REST API, short of generating the requests by hand, I’ve prepared a handful of shell scripts to test the API through Linux shell. The code is located on my GitHub account and repository contains scripts for most of the features available, both in XML and JSON flavor. To properly use these scripts, you will need an eZ Publish 5 installation with demo content. Detailed installation instructions are located in the README file in the repository. After completing installation steps, using the scripts is pretty straightforward: just call the script you wish from the shell.
Of course, you don’t need to use those scripts to work with the REST API, curl is more than enough. For example, loading content with ID 58 via curl could look something like this:
eddie@abyss: ~ $ curl -v --user admin:publish --header "Accept: application/vnd.ez.api.Content+xml" http://ezpublish5.local/api/ezp/v2/content/objects/58 > GET /api/ezp/v2/content/objects/58 HTTP/1.1 > Authorization: Basic YWRtaW46cHVibGlzaA== > User-Agent: curl/7.27.0 > Host: ezpublish5.local > Accept: application/vnd.ez.api.Content+xml > < HTTP/1.0 200 OK < Date: Wed, 21 Nov 2012 16:21:14 GMT < Server: Apache/2.2.22 (Ubuntu) < X-Powered-By: PHP/5.4.8-1.1~ppa1~quantal < accept-patch: application/vnd.ez.api.ContentUpdate+xml < cache-control: no-cache < x-debug-token: 50acff7a6712b < Connection: close < Content-Type: application/vnd.ez.api.Content+xml < <?xml version="1.0" encoding="UTF-8"?> <Content media-type="application/vnd.ez.api.Content+xml" href="/api/ezp/v2/content/objects/58" remoteId="7c013ad2b5221a74704c3d8313c4936a" id="58"> <ContentType media-type="application/vnd.ez.api.ContentType+xml" href="/api/ezp/v2/content/types/21"/> <Name>Getting Started</Name> <Versions media-type="application/vnd.ez.api.VersionList+xml" href="/api/ezp/v2/content/objects/58/versions"/> <CurrentVersion media-type="application/vnd.ez.api.Version+xml" href="/api/ezp/v2/content/objects/58/currentversion"> <Version media-type="application/vnd.ez.api.Version+xml" href="/api/ezp/v2/content/objects/58/versions/1"> <VersionInfo> <id>505</id> <versionNo>1</versionNo> <status>PUBLISHED</status> <modificationDate>2012-03-28T15:03:22+02:00</modificationDate> <Creator media-type="application/vnd.ez.api.User+xml" href="/api/ezp/v2/user/users/14"/> <creationDate>2012-03-28T15:03:20+02:00</creationDate> <initialLanguageCode>eng-GB</initialLanguageCode> <languageCodes>eng-GB</languageCodes> <names> <value languageCode="eng-GB">Getting Started</value> </names> <Content media-type="application/vnd.ez.api.ContentInfo+xml" href="/api/ezp/v2/content/objects/58"/> </VersionInfo> <Fields> <field> <id>188</id> <fieldDefinitionIdentifier>name</fieldDefinitionIdentifier> <languageCode>eng-GB</languageCode> <fieldValue>Getting Started</fieldValue> </field> ... </Fields> <Relations media-type="application/vnd.ez.api.RelationList+xml" href="/api/ezp/v2/content/objects/58/versions/1/relations"/> </Version> </CurrentVersion> <Section media-type="application/vnd.ez.api.Section+xml" href="/api/ezp/v2/content/sections/1"/> <MainLocation media-type="application/vnd.ez.api.Location+xml" href="/api/ezp/v2/content/locations/1/2/60"/> <Locations media-type="application/vnd.ez.api.LocationList+xml" href="/api/ezp/v2/content/objects/58/locations"/> <Owner media-type="application/vnd.ez.api.User+xml" href="/api/ezp/v2/user/users/14"/> <lastModificationDate>2012-03-28T15:03:22+02:00</lastModificationDate> <publishedDate>2012-03-28T11:33:10+02:00</publishedDate> <mainLanguageCode>eng-GB</mainLanguageCode> <alwaysAvailable>false</alwaysAvailable> </Content>
Finally, I’m sure that the new REST API will bring new ways of building cool services around eZ Publish 5, which I’m looking forward to seeing & using!