The key to victory is discipline,
and that means a well made bed.
Not sure if trolling or just “technically correct”
This chronological wang-dang-doodle
could destroy the very matrix of reality.
/api
) and one METHOD (POST
)
$_POST
) you get back some data
GET
POST
request to /api
with $_POST['action']
set to getLecturers
and get back a list of lecturers as the actual content
{
"lecturers":
[
{
"id": 1,
"name": "Davy De Winne"
},
{
"id": 2,
"name": "Bramus Van Damme"
}
]
}
/product
/products
/product/1234
/products/1234
/photos/product/1234
/products/1234/photos
/photos/product/1234/5678
/products/1234/photos/5678
/things
/animals
/dogs
/beagles
/search?course=WMD
/convert?from=EUR&to=USD&amount=100
/
/movies
/movies/{id}
/movies/{id}/photos
/actors
/actors/{id}
/actors/{id}/movies
/about
/contact
POST
request to /api/lecturers
and get back a list of lecturers as the actual content
{
"lecturers":
[
{
"id": 1,
"name": "Davy De Winne"
},
{
"id": 2,
"name": "Bramus Van Damme"
}
]
}
api.twitter.com/v1/…
is wrong)
?filter=foo&sort=asc
?fields=firstname,lastname,age
limit
and offset
URL params
?limit=10&offset=<max_id>
offset
, opt for id-constraint based on sorting method; this avoids doubles when paginating over a frequently updated result set.POST
= Create = INSERT
GET
= Read = SELECT
PUT
= Update = UPDATE
DELETE
= Delete = DELETE
POST /products
(data via $_POST
)GET /products
or GET /products/1234
PUT /products/1234
(data via “$_PUT
”)DELETE /products/1234
401 — Not Authorized
(e.g. No API Key given)405 — Method Not Allowed
(e.g. wrong verb for resource)500 — Internal Server Error
(Developer screwed up ;))GET
(SQL SELECT)
200 — OK
(requested data sent via response body)404 — Not Found
(resource not found, not “API not found”)POST
(SQL INSERT)
201 — Created
(=OK)400 — Bad Request
(Missing Parameters)PUT
(SQL UPDATE)
200 — OK
(requested data sent via response body)400 — Bad Request
(Missing Parameters)404 — Not Found
DELETE
(SQL DELETE)
204 — No Content
(=OK — Note: no response body sent!)404 — Not Found
$.ajax().error()
in jQuery!GET
request to /api/lecturers
and get back an HTTP 200
status code and a list of lecturers as the content
{
"lecturers":
[
{
"id": 1,
"name": "Davy De Winne"
},
{
"id": 2,
"name": "Bramus Van Damme"
}
]
}
POST
request to /api/lecturers
with some data in $_POST
to insert a new lecturer
201 — Created
if all OK400 — Bad Request
if parameters are missingPUT
request to /api/lecturers/2
with some data in $_PUT
to update a lecturer
200 — OK
if all OK400 — Bad Request
if parameters are missing404 — Not Found
if the lecturer does not existThe simultaneous presentation of information and controls such that the information becomes the affordance through which the user obtains choices and selects actions⚑
application/atom+xml
application/vnd.ikdoeict.whatever
accept
header which
Accept: application/collection+json
Accept: application/xml; q=0.8, application/json
Accept: application/vnd.ikdoeict.whatever-v2+json
Accept: application/collection+json;v=2
application/json
(Sorry Roy, gotta pave the cowpaths)
status
and content
objectlinks
array holding the links
rel
and href
property
rel
values are based on RFC5988
self
, up
, index
, next
, prev
, ...photos
)GET
request to /lecturers
and get back a custom JSON response with HTTP status code 200
containing a list of lecturers (with resource links) as the response content
{
"status":
{
"code": 200,
"text": "OK"
},
"content":
{
"size": 2,
"lecturers":
[
{
"id": 1,
"name": "Davy De Winne",
"links": [
{
"rel": "self",
"href": "/users/1"
}
]
},
{
"id": 2,
"name": "Bramus Van Damme",
"links": [
{
"rel": "self",
"href": "/users/2"
}
]
}
]
}
}
application/json
— It cuts the mustard
Hey, look! I found a robot fossil!
GET api.twitter.com/1/statuses/show/id.format
show
included in URLid
not a child of statuses
GET api.twitter.com/statuses/{id}
POST api.twitter.com/1/statuses/update/id.format
update
included in URLPOST api.twitter.com/users/{id}/statuses
POST api.twitter.com/1/statuses/destroy/id.format
destroy
included in URLDELETE api.twitter.com/users/{id}/statuses/{id}
GET api.twitter.com/1/statuses/retweets/id.format
GET api.twitter.com/statuses/{id}/retweets
Would it cheer you up if I punch Fry in the groin?
Cause I'll do it, regardless.
index.php
index.php
in such a way that it decides what to do based on the URL (found in $_SERVER['REQUEST_URI']
)
mod_rewrite
Apache Module
.htaccess
in your root to redirect requests to non-existing files to index.php
<IfModule mod_rewrite.c>
Options -MultiViews
RewriteEngine On
# RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ˆ /index.php [L]
</IfModule>
web.config
in your root to redirect requests to non-existing files to index.php
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Main Rule" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="index.php" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
index.php
by default
index.php
to force non-404 requests route to the correct file
<?php
$filename = __DIR__ . preg_replace('#(\?.*)$#', '', $_SERVER['REQUEST_URI']);
if (php_sapi_name() === 'cli-server' && is_file($filename)) {
return false;
}
// rest of your code here
GET
request to /movies
, show a list of moviesGET
request to /movies/{id}
, show the one specific moviePOST
request to /movies
, insert it$router = new Router();
$router->get('/', function() { … });
$router->get('/movies', function() { … });
$router->get('/movies/\d+', function() { … });
$router->post('/movies', function() { … });
$router->run();
Let's take a look at assets/07/examples/router/
→
match()
$router->match('GET|POST', 'pattern', function() { … });
$router->get('pattern', function() { … });
\d+
, \w+
, .*
, or .+
) will be converted to route variables which you pass into the handling function$router->get('/movies/\d+', function($movieId) {
echo 'Movie #' . $movieId . ' detail';
});
$router->get('/movies/\d+/photos/\d+', function($movieId, $photoId) {
echo 'Movie #' . $movieId . ' photo #' . $photoId);
});
$router->set404(function);
$router->before('GET|POST', 'pattern', function() {
// ...
});
$router->before('GET|POST', '/admin/.*', function() {
if (!isset($_SESSION['userId'])) { … }
});
run
function$router->run(function() { … });
Response
class
$status
and $content
datamembers + mutators200
<?php
namespace Ikdoeict\Rest;
class Response {
private $status, $content;
public function __construct() {
$this->setStatus(200);
$this->setContent('');
}
final public function setStatus($statusCode) {
$codes = array(
200 => 'OK',
// ... List of statuscode with status texts
);
$this->status = array(
'code' => $statusCode,
'text' => $codes[$statusCode]
);
header('HTTP/1.1 ' . $this->status['code'] . ' ' . $this->status['text']);
}
final public function setContent($content) {
$this->content = $content;
}
public function finish() {
$json = json_encode(
array(
'status' => $this->status,
'content' => $this->content
)
);
header('Cache-Control: no-cache, must-revalidate');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Content-type: application/json');
echo $json;
exit();
}
}
// EOF
$_GET['callback']
to the finish
functionpublic function finish($jsonp = null) {
$json = …
header('Cache-Control: no-cache, must-revalidate');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
if ($jsonp) {
header('Content-type: application/javascript');
echo $jsonp . '(' . $json . ');';
} else {
header('Content-type: application/json');
echo $json;
}
exit();
}
Let's take a look at assets/07/examples/response/
→
<?php
$router = new Ikdoeict\Routing\Router();
$response = new Ikdoeict\Rest\Response();
// Override the standard router 404
$router->set404(function() use ($response) {
$response->setStatus('404');
$response->setContent('Invalid resource');
$response->finish();
});
// Define our routes
$router->get('', function() use ($response) {
$response->setContent('Welcome!');
});
// … (more routes here)
// Run the router
$router->run(function() use ($response) {
$response->finish(isset($_GET['callback']) ? $_GET['callback'] : null);
});
// EOF
$_PUT
, you'll need to fake it<?php
//…
$router->put('/movies/\d+', function() {
// Fake $_PUT
$_PUT = array();
parse_str(file_get_contents('php://input'), $_PUT);
// …
});
//…
To the flying machine!
success
and error
functions
$.ajax({
url : 'http://api.myapp.tld/',
type: 'get|post|put|delete',
dataType : 'json',
data: 'foo=bar'
})
.success(function(data, textStatus, jqXHR) {
if (data) {
// All OK and data returned (200, 201)
} else {
// All OK but no data returned (e.g. 204 after a DELETE)
}
})
.error(function(jqXHR, textStatus, errorThrown) {
var data = jqXHR.responseText ? JSON.parse(jqXHR.responseText) : {};
// Got a 400 (params), 401, 404, 405 (method), 500, or 501 (not impl)
});
Who are those horrible orange creatures over there? — Grunka-Lunkas. — Tell them I hate them!
$router->before('GET|POST|PUT|DELETE', '.*', function() use ($response) {
$headers = apache_request_headers();
if(!isset($headers['X-Api-Key']) || !ApiDB::isValidApiKey($headers['X-Api-Key'])) {
$response->setStatus(401);
$response->setContent('Missing or invalid API Key.');
$response->finish();
}
});
X
and all words must be ucfirst'd and separated with -
apache_request_headers()
not always available (IIS)!
if (!function_exists('apache_request_headers')) {
eval('
function apache_request_headers() {
foreach($_SERVER as $key=>$value) {
if (substr($key,0,5)=="HTTP_") {
$key=str_replace(" ","-",ucwords(strtolower(str_replace("_"," ",substr($key,5)))));
$out[$key]=$value;
}
}
return $out;
}
');
}
client_id
) for identification), asking if consumer app may access user data at a certain scope
(read/write)redirect_uri
with an authorization_code
authorization_code
, along with its API-Key (client_id
) and secret key (client_secret
)access_token
and refresh_token
access_token
must passed with all calls made and is valid for a limited timeaccess_token
has expired, the consumer app needs to do a call to the OAuth Prodider with the refresh_token
to get a new pair access_token
& refresh_token