Recently I’ve been putting together a Web application for a research project. I decided it was about time I really looked properly into REST so my Web interfaces are better structured. I won’t go into all the benefits here, you can read for yourself. Suffice to say it seems like a good approach to take.
This is quite a long article and you might only be interested in some of it so here are the sections:
- Getting access to request data
- Returning appropriate responses
- Handling different data formats
- Passing all requests to a single PHP script
- Determining the full requested URL
- A generic REST Service class
If you have an suggestions for improvement, please let me know – this was a first attempt!
Some credit for this article should go to lornajane for her PHP REST Server articles (Part 1, Part 2, Part 3) which I used as a good starting point.
Getting access to request data
Getting access to HTTP GET arguments or POST data is well known and easy. With a REST interface you are likely to support other HTTP verbs such as PUT and DELETE. Getting data from these requests is not obvious but luckily lornajane had worked this out for me:
parse_str(file_get_contents('php://input'), $arguments);
Returning appropriate responses
With REST you should really make use of the HTTP Status Codes for all requests (rather that using 200 OK for everything). In PHP, this can be done with the header function in a couple of ways:
// When setting just the status code
header('HTTP/1.1 405 Method Not Allowed');
// When you're setting another header at the same time
header('Allow: GET, HEAD, POST, DELETE', true, 405);
I haven’t found a way of sending just the status code number (e.g. 405) without specifying the exact text (‘Method Not Allowed’) but these are part of a fixed specification so it’s not a huge problem.
The following is a list of status codes that I’ve used and why:
- 200 OK: successful request when data is returned
- 201 Created: Successful request when something is created at another URL (specified by the value returned in the Location header)
- 204 No Content: Successful request when no data is returned
- 400 Bad Request: Incorrect parameters specified on request
- 404 Not Found: No resource at the specified URL
- 405 Method Not Allowed: when a client makes a request using an HTTP verb not supported at the requested URL (supported verbs are returned in the Allow header)
- 406 Not Acceptable: Requested data format not supported
- 500 Internal Server Error: An unexpected error occurred
- 501 Not Implemented: when a client makes a request using an unknown HTTP verb
Handling different data formats
As URLs are supposed to represent resources in REST, it is a feasible requirement that client applications would like to receive responses from the same URL in different formats. Clients can specify the data formats they are capable of understanding using the HTTP Accept header. In Javacript this can be set using the setRequestHeader method on the XMLHttpRequest object. Using jQuery as my current Javascript library of choice, the code for doing this is as follows:
$.ajax({
type: method, // GET, POST, PUT, DELETE etc.
url: url, // The actual URL to make the request
data: data, // Any data/parameters to send to the server
beforeSend: function(xmlHttpRequest) {
xmlHttpRequest.setRequestHeader('Accept', format); // MIME Type
}
});
This then needs to be handled by the server. In PHP this data can be retrieved using $_SERVER['HTTP_ACCEPT']. I also found a handy library for parsing this header according to the specification (not trivial), cleverly named ‘HTTP_ACCEPT‘. I then wanted a way of knowing the most appropriate format from a list of formats I would support so I threw the following code together:
$accept = new HTTP_Accept($_SERVER['HTTP_ACCEPT']);
foreach ($supportedFormats as $supportedFormat) {
$supportedFormatQuality = $accept->getQuality($supportedFormat);
if ((!isset($bestFormat) && $supportedFormatQuality > 0) ||
$bestQuality < $supportedFormatQuality) {
$bestFormat = $supportedFormat;
$bestQuality = $supportedFormatQuality;
}
}
The server also needs to specify the type of content it is returning; this can be done using the Content-Type header as follows:
header("Content-Type: $format");
Passing all requests to a single PHP script
I needed all requests with a certain URL prefix to be handled by a single script. This was easily solved using the following URL re-writing rule in a .htaccess file:
Options +FollowSymLinks
RewriteEngine on
RewriteRule ^.*$ index.php
Note that I had to enable the mod_rewrite module in Apache and ensure that directives could be overridden with the following:
<Directory /var/www/>
AllowOverride All
</Directory>
Determining the full requested URL
Another requirement of my application complicated by URL rewriting was that I needed access to the full URL. I achieved this with the following bit of code:
$protocol = $_SERVER['HTTPS'] == 'on' ? 'https' : 'http';
$location = $_SERVER['REQUEST_URI'];
if ($_SERVER['QUERY_STRING']) {
$location = substr($location, 0, strrpos($location, $_SERVER['QUERY_STRING']) - 1);
}
$url = $protocol.'://'.$_SERVER['HTTP_HOST'].$location;
To allow me to reuse some of the REST code I’d created, I put it into a generic REST Service class which I subclassed and replaced different methods for different means:
class RestService {
private $supportedMethods;
public function __construct($supportedMethods) {
$this->supportedMethods = $supportedMethods;
}
public function handleRawRequest($_SERVER, $_GET, $_POST) {
$url = $this->getFullUrl($_SERVER);
$method = $_SERVER['REQUEST_METHOD'];
switch ($method) {
case 'GET':
case 'HEAD':
$arguments = $_GET;
break;
case 'POST':
$arguments = $_POST;
break;
case 'PUT':
case 'DELETE':
parse_str(file_get_contents('php://input'), $arguments);
break;
}
$accept = $_SERVER['HTTP_ACCEPT'];
$this->handleRequest($url, $method, $arguments, $accept);
}
protected function getFullUrl($_SERVER) {
$protocol = $_SERVER['HTTPS'] == 'on' ? 'https' : 'http';
$location = $_SERVER['REQUEST_URI'];
if ($_SERVER['QUERY_STRING']) {
$location = substr($location, 0, strrpos($location, $_SERVER['QUERY_STRING']) - 1);
}
return $protocol.'://'.$_SERVER['HTTP_HOST'].$location;
}
public function handleRequest($url, $method, $arguments, $accept) {
switch($method) {
case 'GET':
$this->performGet($url, $arguments, $accept);
break;
case 'HEAD':
$this->performHead($url, $arguments, $accept);
break;
case 'POST':
$this->performPost($url, $arguments, $accept);
break;
case 'PUT':
$this->performPut($url, $arguments, $accept);
break;
case 'DELETE':
$this->performDelete($url, $arguments, $accept);
break;
default:
/* 501 (Not Implemented) for any unknown methods */
header('Allow: ' . $this->supportedMethods, true, 501);
}
}
protected function methodNotAllowedResponse() {
/* 405 (Method Not Allowed) */
header('Allow: ' . $this->supportedMethods, true, 405);
}
public function performGet($url, $arguments, $accept) {
$this->methodNotAllowedResponse();
}
public function performHead($url, $arguments, $accept) {
$this->methodNotAllowedResponse();
}
public function performPost($url, $arguments, $accept) {
$this->methodNotAllowedResponse();
}
public function performPut($url, $arguments, $accept) {
$this->methodNotAllowedResponse();
}
public function performDelete($url, $arguments, $accept) {
$this->methodNotAllowedResponse();
}
}
Hhh, Hello I just know you a little(Real a little“`)Just want to make friend with youI'm Forest from China Hope you can see this messageMy e-mail:897514939@qq.comBy the way ,you are so handsome!
hi gentelmen i has problem i can not open some web sit because of the block from the system in the internet i deal with so if there is any ideia to solve this problem if pasible to get som proxy to prevent the block .regards
nice article! really the first well web resource on PHP-REST web service i found! It's simple and clear!REST is simple in its concepts, but i think there is still much confusion around what's the differences between the diffrent kind of web-services. I find this article a good starting point for those who doesnt know (or believe to know) about web-services.
Hey, glad you liked the article! REST is definitely confused by many people, really pleased that this helps a little.
Nice introductory article. What do you think about Konstrukt? (http://www.konstrukt.dk)
nice, REST finally started to make sense to me a few days ago when I was messing with headers. This article filled in a lot of the gaps. Thanks.
I've not seen Konstrukt before but looks interesting – will check it out. Thanks for the pointer. Glad you liked the article too
Great, nice to know it's of use to someone. Sometimes you write these things and wonder whether it'll actually be of use to anyone!
This is excellent. Does anyone have any sample code showing the contents of index.php as well as an example of a class that implements RestService?
Your index.php could be as simple as the following:$service = new RestService();$service->handleRequest($_SERVER, $_GET, $_POST);
As pointed out by Spac32, this should actually be:
$service = new RestService();
$service->handleRawRequest($_SERVER, $_GET, $_POST);
Thanks for the great write-up. I'm planning on giving your directions a spin in a few days. Wasn't easy to find an article as good as yours to get me started, so thanks a million.-Ace
No worries, good luck with it.
Pingback: RESTful in PHP: first piece of code « Lietoservetruth's Blog
“Here air jordan 21 products xx, has fashion model, superior quality and service, cheap price and updates quickly.I support strongly always! I want to buy XX, I hesitate to select which style more better.Hope your unique recommends.
“
This post is useful.
REST is not!!
I’m planning on giving your directions a spin in a few days. Wasn’t easy to find an article as good as yours to get me started, so thanks a million.
Great article especially the part on url rewriting and extracting the full url. Thanks
Thanks ! for sharing your valuable knowledge with us. I really like your post and quite excited to see your upcoming post.
I am hoping the exact same finest function from you inside the long term also.
Absolutely excellent article! Thank you!
I have read at least a dozen others tonight, but this was the first one that made complete sense.
For those who feel that REST isn’t useful, try building an application with regular get/post and an action parameter. You’ll see how useful REST is within the first few weeks of development.
Excellent article!
one note, this line of code didn’t work for me
$protocol = $_SERVER['HTTPS'] == ‘on’ ? ‘https’ : ‘http’;I rewrote it and now it works:
if (!empty($_SERVER['HTTPS'])) {
$protocol = ‘https’;
} else {
$protocol = ‘http’;
}
Also, your reply to PepperHill:
$service->handleRequest($_SERVER, $_GET, $_POST);
Shouldn’t it be
$service->handleRawRequest($_SERVER, $_GET, $_POST);
Thanks again
Hey, thanks for your comments. No idea if the HTTPS thing is a change in PHP or a platform-specific thing or what but glad you found a solution. Yes, you’re right about my reply to PepperHill – good spot!
Hi Garethj,
Nice Article!
Check out Luracast Restler (https://github.com/Luracast/Restler) It is an open source micro-framework and it is very similar to your approach. Looking forward for your contributions
Regards,
Arul
Great article!
One question: How would you handle CSS/JS/IMG files? The rewrite cond converts requests for css/js/etc files to index.php. How do you add exceptions?
Well I probably wouldn’t mix a REST service in the same folder structure with CSS/JS/IMG files but if you needed to, you should be able to do this with more complex mod_rewrite statements. I’d recommend reading the Apache documentation (http://httpd.apache.org/docs/current/mod/mod_rewrite.html) for details, or search for a tutorial.