##Chapter 11 - REST Revisited
###Introduction
We discussed the basics of the REST architectural style in chapter 7. We will add a little more detail about the principles and then do several case studies in this chapter using REST and REST APIs in order to make these principles concrete. The principles are:
REST web services must have a uniform interface. For systems deployed with HTTP, this means that the HTTP methods are used. The POST method is often overloaded for clients like web browsers that do not support PUT or DELETE. Another case is to get around long URLs. Many clients and servers have limits on the length of a URL and for GET, the querystring is in the URL. Apache, for example, will not respond to requests for URLs longer than 8 kb. So if a client can't make a GET request to because of URL length restrictions, a POST request can simulate it because there is no length restriction for the HTTP entity body.
REST should be stateless. There are two kinds of state: application state for the client, and resource state for the server. For REST architectures, the benefits of statelessness come from keeping the server stateless and pushing it to client applications. The client can keep state in a simpler fashion since it is only interested in itself and the web service receives the client application state only in a request so resource state is the same for all clients.
REST must handle some state. Of course, statelessness is a goal and cannot be achieved all the time. An interesting example of non-stateless behavior violating statelessness the API key for REST APIs such as the one from Flickr. Flickr, like all such organizations, cannot really trust clients to keep all state information themselves. I know this will come as a shock, but there are some bad people out there that will lie and cheat. So Flickr has to keep track of API key sessions and this effects scalability. Flickr has many machines serving customers (server farm) and so this causes a complication. Every server in the farm needs to track the sessions and the load balancer needs to make sure that same session requests goes to the same machine. We saw an example of this complication with transactions and HTTP cookies in chapter 8.
REST should support caching. HTTP GET or HEAD requests should be cached.
Servers should use the Expires and cache- control headers in HTTP enforce this. PUT, POST, or DELETE requests should not be cached.
Furthermore, any cached responses for that URI should expire.
The REST architecture is composed of hierarchical layers. Each layer only sees the next layer that it is connected to. We have seen this behavior with the proxy clients that we have used in previous exercises where an HTML client connects with a PHP proxy client that requests the web service. This reduces complexity and improves scalability with proxy clients, caching, and load balancing.
Besides, we will learn a new XML vocabulary for syndication feeds called really simple syndication (RSS) and its relationship with REST services.
###RSS
We now turn to a different family of XML vocabularies under the heading of really simple syndication as RSS is most commonly known. It has also been known as rich site summary and RDF site summary. You are probably familiar with RSS as a news feed that is associated with blogs. Each blog entry is an item in the XML of RSS and makes it easy to download all the blog entries in one file. RSS is generally useful, however, in information systems. For example, in the BPEL runtime, we could use an RSS feed to monitor the activity of status messages in the workflow. In fact, RSS can be considered an early web service since a web request results in the return of an XML file rather than a web page. It will really be what we will call a REST web service in chapter 7. There are multiple versions, however, and RSS has a rather complicated history.
All RSS is XML with various schemas depending on version. The major versions are in two branches outlined below.
+ RDF Branch
- RSS 0.9 was the original Netscape version in 1999.
- RSS 1.0 is based on the resource description framework (RDF) - a more complicated XML vocabulary for graph data structures (2000).
- RSS 1.1 is the current RDF version.
+ RSS Branch
- RSS 0.91 was a simplified version of the original 0.9 version that removed the dependence on RDF (2000).
- RSS 0.92-94 extensions of version .091 (2001-2).
- RSS 2.0 is the current and major version for this branch (2003). For example CNN, the New York Times, and many other sites have adopted RSS 2.0.
- Atom 1.0 is a competing feed standard that separated from RSS 2.0 in 2005. Its developers were dissatisfied with the backward compatibility requirement for RSS and the lack of innovation that results. Google has adopted Atom.
The major difference between the branches is RDF. The RSS branch removed that for simplicity of adoption for web sites. Both RSS 2.0 and Atom are widely used today. We will look at this branch in this chapter and look at RDF in more detail in chapter 12. RDF is the foundation of the semantic web.
A sample of RSS 2.0 is given in listing 6.3. You can see that it is designed for structured information about web sites. RSS has a structure of modules for extensibility. Modules use namespaces to add names and concepts to RSS for specific application areas. Atom has a similar structure.
< ?xml version="1.0" encoding="UTF-8" ?>
RSS TitleThis is an example of an RSS feed
http://www.someexamplerssdomain.com/main.html
Mon, 06 Sep 2010 00:01:00 +0000 Mon, 06 Sep 2009 16:45:00 +0000 Example entry
Here is some text containing an interesting description.
http://en.wikipedia.org/wiki/RSS/
unique string per itemMon, 06 Sep 2009 16:45:00 +0000
Listing 6.3. RSS 2.0.
###RSS Feeds and REST Service
RSS feeds are really the first REST web service (before it was called that!). A RESTful URL HTTP request gets an XML return. We will use another PHP library to help us manipulate RSS feeds called magpie (http://magpierss.sourceforge.net/). It is an XML parser that works with all versions of RSS. listing 11.3 shows how to use the magpie library in a PHP program. Any library for any language that works with XML web services must use a parser.
< ?php
require_once 'magpie/rss_fetch.inc'; //1
$url = 'http://feeds.washingtonpost.com/rss/politics'; //2
$rss = fetch_rss($url);
$i=0;
echo "Site: ", $rss->channel['title'], " "; //3
foreach ($rss->items as $item) { //4
$title = $item[title];
$url = $item[link];
echo "$title ";
if(++$i==3) break;
}
?>
Listing 11.3. A PHP program using the magpie library.
The comment numbers are referenced below:
1. As usual, we must include the library code for use in the program.
2. I have used an RSS feed from the Washington Post about movie stars and their ilk. One can, of course, substitute the URL for any feed here. You should go to the feed URL and see that it is typically transformed by default in your web browser, but the program receives the XML. You can see this by using curl from the command-line as:
`curl http://feeds.washingtonpost.com/rss/politics`
You will see the XML feed rather than the HTML that your browser displays.
3. Magpie parses the XML of the feed into PHP arrays. That is how the code can address tags using array notation such as `channel['title']`. Note that magpie returns a PHP object and then uses the arrow notation to access each tag as in: `$rss->channel['title']`.
4. The foreach loop gets the desired subset of these tag variables for display.
###XML parsers
There are two basic kinds of XML parsers:
- Object-based (DOM)
- Event-based
- Push Parsers (SAX)
- Pull Parsers (StAX)
Object-based parsers parse the XML and create a single hierarchical object in memory that can be manipulated programmatically. Your web browser works this way to create the DOM and it is a very powerful technique as we saw in chapter 2. The drawbacks are that it can be a slow parser and consume a lot of memory. This is not a problem in your web browser since the documents are usually a manageable size and the DOM is required for javascript to operate globally. Other programs do not have these requirements and so an event-based parser works well. DOM parsers are usually built on top of event-based ones.
Event-based parsers are also called stream parsers. They read through the XML document and programmatically do something whenever an event happens such as reading a certain tag. They do not store the whole document in memory and are therefore very fast and not memory intensive.
There are two kinds of event-based parsers, push and pull parsers.
A push parsing model is a model where the parser pushes parsing events to the application. SAX (simple API for XML) is the most common example of a push parsing API for XML. An application using SAX would define a number of callbacks that would be called by the parser when certain events occur. For example, an application can define a startElement method that the parser will call every time it sees the beginning of a new element. SAX is a one-pass parser that goes through the document once from beginning to end and sends one event to the calling application for each defined XML construct it encounters. Magpie is this kind of parser and is created with an open-source XML parser library called expat.
A pull parsing mode is a model in which the application drives the parser (in contrast to the push model, where the parser drives the application), by pulling parsed XML constructs from the parser one by one, according to its needs. A Pull parser returns XML events to a client on request. An example of a pull parser is StAX (streaming API for XML) which is commonly used in java frameworks for web services.
###Mashups
A mashup is an application that uses and combines data from two or more sources to create new services. They are typically deployed on the web by creating a web browser-based application that requests data from the sources using web services and then combines them in such a way as to add value. You can see that REST APIs make this an efficient way to create powerful applications. There are many consumer-oriented mashups such as ones that use data from a source and then overlay it on google maps using location information. Business mashups are also common for combined information from various sources for business intelligence purposes. Mashups can be (most typically) client-based as are the ones that aggregate data in the web browser. They can also be server-based,
however, where the server aggregates the data and sends it to the client in the combined format. This is the way that an older technology called portals works. They are the same thing as a mashup, but pre-date the use of the web. The on-line course syllabus has links to some representative mashups. Mashups are an excellent example of what a SOA offers.
We can create a simple mashup using RSS from Movie-Blogger and the Omdbapi API for movies. We will go over this example in lecture in detail using the following files. Note that for any php file here, you can replace the .php with .txt to see the code (except for the first final hw one!).
1. Here is the final mashup that we are doing for hw [here](https://userpages.umbc.edu/~jianwu/is651/programs/ch11/omdbapi_movie_blogger_mashup.php).
2. Here is a starter file for you to modify [here](https://userpages.umbc.edu/~jianwu/is651/programs/ch11/omdbapi_mashup_starter.php). Be sure to make the url change each time to reflect a different title and [urlencode](http://www.w3schools.com/tags/ref_urlencode.asp) the title using the built-in [php function](http://php.net/manual/en/function.urlencode.php).
3. Note that the starter calls the [helper function](https://userpages.umbc.edu/~jianwu/is651/programs/ch11/omdbapi.txt) that I have written for you.
###Creating a REST API
We have been using REST APIs from around the Internet in clients that we created. In this section we want to create our own REST API to the catalog that we developed in chapter 9. I developed a cdStore service and you made some other kind of catalog available on line. A REST API to the cdStore service will be geared towards program to program processing rather than human users as in chapter 9. There are two complete examples below, one for CodeIgniter and one from scratch using php. The CodeIgniter example is to show you the principles of rest apis and how one can easily create a *working* api that does not follow rest principles correctly. The next 'scratch' example is the one you will use in your hw for ch11.
####REST API using CodeIgniter
We will change the service to use a simple database back-end for this section. For my cdStore, I would create a table called cds and import the SQL in listing 11.6. I can use phpmyadmin to import this file into my mysql database on gl to create the cds table in my default database (you will change it to your own table name).
CREATE TABLE `cds` (
`id` mediumint(11) NOT NULL auto_increment,
`artist` varchar(255) NOT NULL,
`title` varchar(255) NOT NULL,
`genre` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
Listing 11.6. SQL dump file.
We need to create a Rest class to expose our API. Listing 11.7 shows the first part of the controller file within CI. It is a bit long, but has a simple structure that is explained below. The files and working application are available from the on-line syllabus. The comment numbers are referenced below for each of the 4 HTTP methods. I have abbreviated the actual long hostname path on gl (https://userpages.umbc.edu/~jianwu/is651/programs/ch9/ci) with the shorter host.
1. GET has the function index in the controller. One can get all the records from the database with the URL: https://host/index.php?/Rest/ and a specific record using the id as: https://host/index.php?/Rest/index/1. The if statement checks for the id using the CI URI Class. The segments are numbered with #1 being rest. So #3 would the id=1 above. You can test all of these from links on the syllabus.
2. POST has the function create. One can create a record with a URL such as: https://host/index.php?/Rest/create/Motorhead/Hammered/Rock Note that the id can be ignored as it will be auto-incremented by the database.
3. PUT has the function edit since it updates an existing record. URLS are like: https://host/index.php?/Rest/edit/Motorhead/Hammered/Metal/2 if it has id=2. In this case the string Rock would be changed to Metal.
4. DELETE has the function delete and deletes records by id with URLS like: https://host/index.php?/Rest/delete/2. In each case, the data is sent to the Model and the XML is returned with the response function.
The ellipsis (...) in listing 11.7 is for the response function which calls the array2xml function. You can see both on-line in the syllabus and you will use them without modification in the exercise. The response function just creates XML for return to the REST web service user.
Note that CI is an MVC framework but there is no View here. This makes sense since this service is not for humans and so needs no view. The controller is in the file controllers/Rest.php and the model is in the file models/postmodel.php. These are the only two files that need to be inserted into the empty CI directory framework. One can check the API by putting the URLs into the web browser or by using curl. For example,
listing 11.8 shows the curl response for a PUT command I made to the service.
Note that the curl command uses the -v option to show all HTTP headers.
This allows us to see that the POST command was actually used due to the -X option which lets us specify the HTTP method for the command. The result Added! Shows us that the request succeeded. You should try several requests with different HTTP methods and verify that everything works as advertised.
So what is the problem? The problem is that we have not really implemented a uniform HTTP method interface. We can see this by issuing a GET command using curl to update (create). It works! This is bad because updates are supposed to use PUT. It works for PUT also, but that is because all the information is in the URL, it does not matter what method is used:
`=> curl -v -X GET
'https://userpages.umbc.edu/~jianwu/is651/programs/ch9/ci/index.php?/Rest/edit/Motorhead/Hammered/Metal/2'
`
See listing 11.9 to see that the GET method can update the service contrary to REST principles.
< ?php class Rest extends Controller {
function Rest()
{
parent::Controller();
$this->load->model('postmodel');
}
function index() //1. get
{
if($this->uri->segment(3)==FALSE)
{
$data = $this->postmodel->get_post();
$this->response($data);
}
else
{
$id=$this->uri->segment("3");
$data = $this->postmodel->get_post($id);
$this->response($data);
}
}
function create() //2. post
{
$data = array('artist' => $this->uri->segment(3),
'title' => $this->uri->segment(4),
'genre' => $this->uri->segment(5));
$this->postmodel->create_post($data);
$message = array('message' => 'Added!');
$this->response($message);
}
function edit() //3. put
{
$id = $this->uri->segment(6);
$put_data = array('artist' => $this->uri->segment(3), 'title' => $this->uri->segment(4), 'genre' => $this->uri->segment(5));
$this->postmodel->update_post($put_data, $id);
$message = array('id' => $id, 'message' => 'Edited!');
$this->response($message);
}
function delete() //4. delete
{
$id = $this->uri->segment(3);
$this->postmodel->delete_post($id);
$message = array('message' => 'Deleted!');
$this->response($message);
}
...
?>
Listing 11.7. The Rest controller.
=>curl -v -X POST https://userpages.umbc.edu/~jianwu/is651/programs/ch9/ci/index.php?/Rest/create/Motorhead/Hammered/Rock
* Trying 130.85.30.120:443...
* Connected to userpages.umbc.edu (130.85.30.120) port 443 (#0)
...
> POST /~jianwu/is651/programs/ch9/ci/index.php?/Rest/create/Motorhead/Hammered/Rock HTTP/1.1
> Host: userpages.umbc.edu
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Mon, 03 May 2021 19:54:52 GMT
< Server: Apache/2.4.6 (CentOS)
< X-Powered-By: PHP/5.4.16
< Vary: Accept-Encoding
< Content-Length: 85
< Connection: close
< Content-Type: text/html
<
Added!
Listing 11.8. Using curl for the REST API.
I should emphasize that this works fine and might even be fine for some particular purpose, but it offers more of an rpc-style service rather than a RESTful service with a uniform HTTP interface. We need to modify the service to enforce the HTTP method interface to make it RESTful.
listing 11.10 at the end of the chapter shows how this can be done by using only an index function and detecting which HTTP method was used for the call.
The comment numbers for listing 11.10 are referenced below:
1. We use only one function index and the switch conditional flow operator to find how the HTTP request was made.
2. The variable $request_method holds the HTTP method gotten directly from the HTTP header. PHP offers the built-in $_SERVER variable to do that. The CI URI Class is used to parse the URL for the id if it exists.
3. The switch statement does something different for each HTTP method so we do not need multiple functions like create(), edit(), etc. as we had in listing 11.7. There are 2 sets of switch cases, one for a URL with an id and one for those without an id that are separated with the if/else statement.
4. We can get the posted querystring from the HTTP entity body using the built-in PHP variable $_POST.
5. There is no built-in PHP variable $_PUT, so we have to use the statement here to do it. The parse_str(file_get_contents("php://input") part of the statement gets the entity body for us (you can just copy this for your homework).
6. We do not really have to worry about the postmodel at this point. It is written for us and handles getting data in and out of the database for the Model (but you just need to update the names for your application in that file for the exercise).
7. The break statement get us out of the loop when an HTTP method has been identified.
...
> GET /CI/index.php?/Rest/edit/Motorhead/Hammered/Metal/2 HTTP/1.1
...
< HTTP/1.1 200 O
...
< ?xml version="1.0" encoding="UTF-8"?>
* Connection #0 to host localhost left intact
* Closing connection #0
25Edited!
Listing 11.9. GET updates the service in violation of REST.
You can see that in listing 11.10, we have a uniform interface from HTTP methods. We will verify this below using curl with a PUT interaction.
Note that the querystring goes into the -d option just like for POST and that is what goes into the entity-body. The id is in the URL.
=> curl -v -X PUT -d 'artist=Motorhead&title=hammered&genre=Metal'
https://userpages.umbc.edu/~jianwu/is651/programs/ch9/ci/index.php?/Rest2/index/
[...]
> PUT /CI/index.php?/Rest2/index/7 HTTP/1.1
[...] 7Edited!
So now we have a complete REST API for the cdStore service that meets all the requirements for a RESTful architecture.
function index() //1
{
$request_method = $_SERVER['REQUEST_METHOD']; //2
$id = (int) $this->uri->segment("3");
if($id == NULL)
{
switch($request_method) //3
{
case "GET":
$data = $this->postmodel->get_post();
$this->response($data);
break;
case "POST":
$this->postmodel->create_post($_POST);//4
$message = array('message' => 'Added!');
$this->response($message);
break;
}
}
else
{
switch($request_method)
{
case "GET":
$data = $this->postmodel->get_post($id);
$this->response($data);
break;
case "PUT":
parse_str(file_get_contents("php://input"), $put_data); //5
$this->postmodel->update_post($put_data, $id); //6
$message = array('id' => $id, 'message' => 'Edited!');
$this->response($message);
break;
case "DELETE":
$this->postmodel->delete_post($id);
$message = array('message' => 'Deleted!');
$this->response($message);
break; //7
}
}
}
Listing 11.10. The fixed REST service.
This CodeIgniter example was to show you the difference between a correct and incorrect rest api. We will not use CodeIgniter in the hw, but create our own rest api from scratch that returns json. So first we will review mysql, then review some basics about php, and finally write our own rest api.
####REST API from Scratch
MySQL on gl is reviewed here. It is the same stuff we did in chapter 9.
- Your mysql account is on the host *studentdb-maria.gl.umbc.edu*.
- I use the web user interface at *https://mysql-admin.umbc.edu/index.php* which we will primarily use for all database admin via the vpn.
- In addition to using the graphical *mysqladmin*, you can interact with your database on the command line. It is exactly the same as using the graphical tool. Use these steps:
- ssh to your gl account.
- At the gl prompt: *mysql -h studentdb-maria.gl.umbc.edu -u username -p* where you use your own username. You will be prompted for your password. Then you will get the mysql prompt.
- At the mysql prompt: use *your_username*. This changes the current database. You only have one database, but in regular mysql (not this one multiply hosted for students) there can be many databases.
- See the interaction with my database which is on a different host than you use and my gl prompt is `=>`:
=>mysql -h studentdb-maria.gl -u jianwu -p
...
(jianwu@studentdb-maria.gl) [(none)]> use jianwu;
Database changed
(jianwu@studentdb-maria.gl) [jianwu]> show tables;
+--------------------+
| Tables_in_jianwu |
+--------------------+
| test |
| test2 |
| test3 |
+--------------------+
23 rows in set (0.00 sec)
(jianwu@studentdb-maria.gl) [jianwu]> select * from test;
+------+-----+
| name | age |
+------+-----+
| tom | 25 |
| sue | 19 |
+------+-----+
2 rows in set (0.01 sec)
(jianwu@studentdb-maria.gl) [jianwu]> describe test;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| name | varchar(10) | NO | PRI | | |
| age | int(11) | NO | | 0 | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
- You can see the basic commands for the mysql prompt [here](http://www.pantz.org/software/mysql/mysqlcommands.html).
- You can get out of mysql by typing `\q` and you will return to the gl prompt. If you type *help* at the mysql prompt, you can see all the backslash commands.
Before we start on the an application, we need to make sure that we understand how php and mysql work together. Use mysqladmin (or the command line) to create a simple 2-column table with 'name' and 'age' and I showed above. Then use the php file below to access it. You can see it in my account [here](https://userpages.umbc.edu/~jianwu/is651/programs/ch11/mysql/showDB.php).
< ?php
$con = mysql_connect("host","yourdb","yourpw");
if(!$con){die('Could not connect: ' . mysql_error());}
mysql_select_db("jianwu", $con);
$result = mysql_query("SELECT * FROM test");
header("Content-type: text/html");
while($row = mysql_fetch_array($result))
{
echo $row['name'] . " " . $row['age'];
echo " ";
}
mysql_close($con);
?>
This returns simple html that was rendered on the server-side as we expect. For example, you can see all the php mysql commands [here](http://us2.php.net/manual/en/ref.mysql.php). Note that for each command, the docs says it is deprecated in php5 which would use a very similar but slightly different command (msqli). Now lets [do xml](https://userpages.umbc.edu/~jianwu/is651/programs/ch11/mysql/showDBXML.php) and note the use of *mysql\_fetch\_assoc*:
< ?php
$con = mysql_connect("...","...","...");
if (!$con){die('Could not connect: ' . mysql_error());}
mysql_select_db("...", $con);
$result = mysql_query("SELECT * FROM test");
header("Content-type: text/xml");
echo '< ?xml version="1.0" encoding="utf-8"?>';
echo "";
while($data = mysql_fetch_assoc($result)) {
echo "";
foreach($data as $key => $value) {
echo "<$key>$value$key>";
}
echo "";
}
echo "";
mysql_close($con);
?>
So this works fine for html and xml, but we do not want that. We *want to return json* instead of xml. Here is an example:
< ?php
$con = mysql_connect("...","...","...");
if(!$con){die('Could not connect: ' . mysql_error());}
mysql_select_db("...", $con);
$result = mysql_query("SELECT * FROM test");
$json=array(); // create a blank array
while($row = mysql_fetch_assoc($result))
{
$json[]=$row; // add each row to array
}
mysql_close($con);
header("Content-type: text/json");
$a = json_encode($json); // transform array to json
echo $a;
?>
You can see it in my account [here](https://userpages.umbc.edu/~jianwu/is651/programs/ch11/mysql/showDBJSON.php). Go back to the php mysql docs and look up the *mysql\_fetch\_array* and *mysql\_fetch\_assoc* commands. Now with this background, we can create our own rest api for mysql using php.
We need to create a rest api in a php file that responds correctly to the http methods. Typically this is done with a server-side framework (like CodeIgniter) that is adapted to return only json. But we will write our own primitive framework so that we can understand all parts of it. The php program below implements an outline rest api that is easy to understand. This api depends on parsing the incoming url to see what kind of request it is.
< ?php
echo("
Simple REST Demo
\n"); // html tags only needed for browsers
echo("
URL = http://host/index.php?/users or http://host/index.php?/users/id
\n");
$url = $_SERVER['REQUEST_URI']; //get whole url - note that the demo resource is 'users'
$http = $_SERVER['REQUEST_METHOD']; // get http method verb
$p = parse_url( $url ); // http://php.net/manual/en/function.parse-url.php
print_r($p); // print out hash representation of the parsed url
$q=explode('/', $p['query']); // http://www.w3schools.com/php/func_string_explode.asp
print_r($q); // print out array representation of the querystring
$c=count($q); // length of array - lowest count is 2 - http://www.w3schools.com/php/func_array_count.asp
if ($q[1]!="users") { // any string will work but this demo is for 'users'
echo(" \nThe resource is 'users', not '$q[1]'");
}
else if($c<3) { // no id in the url - e.g. http://host/index.php?/users - do not use a trailing slash
if ($http=='GET') { // GET method etc.
echo(" \nGet all users - 200 (OK)"); // correct http return code included
}
else if ($http=='POST') {
echo(" \nCreate a user w/id generated on server - 201 (Created)");
}
else {
echo(" \nImproper verb use - 404 (Not Found)");
}
}
else // has id in the url - note count will be 3 - e.g. http://host/index.php?/users/5
{
if ($http=='GET') {
echo(" \nGet a specific user w/id=$q[2] - 200 (OK)");
}
else if ($http=='PUT') { // PUT and DELETE only available with ajax or curl
echo(" \nUpdate a specific user w/id=$q[2] - 200 (OK)");
} // PATCH is a variation of PUT
else if ($http=='DELETE') {
echo(" \nDelete a specific user w/id=$q[2] - 200 (OK)");
}
else {
echo(" \nImproper verb use - 404 (Not Found)");
}
}
?>
Implement this program in your gl account and experiment with this very simple api program and understand all parts of it using [curl](http://curl.haxx.se/docs/). You can start now with a browser for the http method *GET* as [here](https://userpages.umbc.edu/~jianwu/is651/programs/ch11/mysql/rest/index.php?/users/1), but the other methods will only work with curl.
Every rest api has five cases for CRUD (create, read, update, delete):
- GET with no record id - reads all records - http://url/path/
- GET with a record id - reads a specific record - http://url/path/1
- POST with no record id - creates a new record - http://url/path/
- PUT with a record id - updates a record - http://url/path/1
- DELETE with a record id - deletes a record - http://url/path/1
All other combinations are illegal. The php program detects which http method is used for a request and then parses the url to find out if it has an id or not. The program is heavily commented for you to see the logic and contains links to documentation of the functions. It also has some tutorial print statements to show what is happening such as what the arrays look like. So all requests go to the single file *index.php* and then the program parses the query string (after the *?*) to detect any record ids. Many people call this a *router* because it is a single point of entry to the framework that decides what to call. Typically a [*.htaccess file*](http://en.wikipedia.org/wiki/Rewrite_engine) would be used to hide the index.php and allow the request to have a pretty url like `https://userpages.umbc.edu/~jianwu/is651/programs/ch11/mysql/rest/index.php?/users/1`, but we will not bother with that (because gl has no rewrite module loaded :@ ).
Try each of the *5 method call types*. The message tells you what the api is expecting, what it will do, and what http response code returned. The following shows you two examples of using curl from the gl command line to access the rest api where I have put my index.php inside a directory called *rest*.
- `curl -v -X GET 'https://userpages.umbc.edu/~jianwu/is651/programs/ch11/mysql/rest/index.php?/users'`
- `curl -v -X POST 'https://userpages.umbc.edu/~jianwu/is651/programs/ch11/mysql/rest/index.php?/users' -d 'name=joe&age=35'`
The methods *put* and *post* require a querystring that is put into the '-d' option for curl. This sample api is not a real api, but a structure where it returns a message instead of data for illustrative purposes with no actual database interaction. The message tells you about what should happen. Below, we will replace each message with a relevant SQL query to mysql. First, we need to modify *index.php* to use a database. We will start with a modification of the simple name/age table. Use these steps for the modification:
1. Modify table with an *id* and add the *Salary* field.
2. Remove all the html tags and php tutorial print commands.
3. Make a connection to the database.
4. Decide on a resource name (like table name).
5. Replace each message with an appropriate sql query.
6. Return json.
The previous name/age table has no id. This will not work for our rest api since we need a unique id for each record. Furthermore, we want the database to handle that, not our application. So we need to create another table that has an id with the *auto_increment* parameter in *mysqladmin*. Also make that id field a *primary key* by clicking the little key icon at the right side. This means that whenever we create a record, the database automagically assigns an id and we do not supply one. I made this table called *test2* in *mysqladmin*.
The best way to proceed is use our json php example from above and put the database connection, selection, and library include stuff at the top of the rest api file since it only has to be done only once and then put the appropriate *mysql\_query* and return stuff where the old message is now. Now you should be able to do curl requests and get back json (and json only) from the command line on gl. You can see it working in my account for [GET all](https://userpages.umbc.edu/~jianwu/is651/programs/ch11/mysql/rest/rest2.php?/test2) or [GET one](https://userpages.umbc.edu/~jianwu/is651/programs/ch11/mysql/rest/rest2.php?/test2/1) as defined below. Only these 2 GET requests work for this demo, you need to do them all. Note that I have left a couple of the possibilities for you to do as an exercise.
< ?php
// #1 connect to mysql db
$con = mysql_connect("...","...","...");
if(!$con){die('Could not connect: ' . mysql_error());}
mysql_select_db("...", $con);
// #2 parse the request url
$url = $_SERVER['REQUEST_URI']; //get whole url
$http = $_SERVER['REQUEST_METHOD']; // get http method from request header
$p = parse_url( $url ); // http://php.net/manual/en/function.parse-url.php
$q=explode('/', $p['query']); // http://www.w3schools.com/php/func_string_explode.asp
$c=count($q); // length of array - lowest count is 2 - http://www.w3schools.com/php/func_array_count.asp
// #3 all legal combinations of http method and url type for a rest api
if ($q[1]!="test2") { // lockdown the mysql table
header("Content-type: text/json", TRUE, 404); // http://php.net/manual/en/function.header.php
echo("{error: Incorrect table name}");
}
else if($c<3) { // no id in the url - e.g. http://host/index.php?/users - do not use a trailing slash
if ($http=='GET') { // GET method etc.
$result = mysql_query("SELECT * FROM $q[1]"); // sql query using table name from url
$json=array(); // create a blank array
while($row = mysql_fetch_assoc($result)) {$json[]=$row; } // add each row to array
mysql_close($con); // close the db connection
if(!$result) { // $result returns false if the insert fails
header("Content-type: text/json", TRUE, 500); // 500 is server error of any kind
echo("{error: Select failed}");
} else {
header("Content-type: text/json", TRUE, 200);
$a = json_encode($json); // transform array to json using library function
echo $a; // return the json string
}
}
else if ($http=='POST') {
// http://www.w3schools.com/sql/sql_insert.asp - autoincrement
// we are ignoring sql injection here - http://www.w3schools.com/php/func_mysql_real_escape_string.asp
$name=$_POST['Name'];
$age=$_POST['Age'];
$salary=$_POST['Salary'];
$result = mysql_query("INSERT INTO $q[1] (ID, Name, Age, Salary) VALUES (NULL, '$name', '$age', '$salary')");
mysql_close($con);
if(!$result) { // $result returns false if the insert fails
header("Content-type: text/json", TRUE, 500);
echo("{error: Insert failed}");
} else {header("Content-type: text/json", TRUE, 201);} // 201 means 'created'
}
else {
header("Content-type: text/json", TRUE, 500);
echo("{error: Improper verb use}");
}
}
else // has id ($q[2]) in the url - note count will be 3 - e.g. http://host/index.php?/users/3
{
if ($http=='GET') {
// you need to add this
}
else if ($http=='PUT') { // PUT and DELETE require ajax or curl
// http://www.w3schools.com/sql/sql_update.asp - must send all fields even if changing only one
$input = file_get_contents('php://input'); // there is no $_PUT in php
parse_str($input, $params); // all the input vars are in the params hash
$name=$params['Name'];
$age=$params['Age'];
$salary=$params['Salary'];
$result = mysql_query("UPDATE $q[1] SET Name='$name', Age='$age', Salary='$salary' WHERE ID=$q[2]");
mysql_close($con);
if(!$result) { // $result returns false if the update fails
header("Content-type: text/json", TRUE, 500);
echo("{error: Update failed}");
} else {header("Content-type: text/json", TRUE, 200); }
}
else if ($http=='DELETE') {
// http://www.w3schools.com/sql/sql_delete.asp
// you need to add this
}
else {
header("Content-type: text/json", TRUE, 404);
echo("{error: Improper verb use}");
}
}
?>
You need to understand that the rest api above is just for educational purposes. *You would not use it in real life.* But it is very simple and allows us to fully understand rest apis. It is written for clarity and simplicity (not efficiency or security). Specifically, it is subject to sql injection (we learn about this in is672). In real life, you would use a framework on the server-side, not program one yourself. For example, the php framework [slim](http://www.slimframework.com/) is good for creating rest apis. Also, most all server-side frameworks can also act as simple rest api servers if so configured. For example, [Ruby on Rails](http://rubyonrails.org/) typically renders pages on the server-side, but can be easily configured to return only json with a rest api for a client-side framework like [backbone.js](http://backbonejs.org/). We are using this simple php rest api here so that there is no *magic*, you can understand everything.
There are three major parts to this simple rest api.
1. Do stuff that needs to be done only once like include the json library and make a connection to the database.
2. Parse the incoming request url. We use some php functions (see the comments) to find the http method, the table name, and the record id if it exists. It is helpful to go back to our previous 'message only' version of this api where I printed out these arrays so you can see what they look like.
3. Detect and handle all possibilities of http method and url type. There are 4 http methods (get, post, put, delete) and 2 url types (with an id or not). Make sure you understand this logic.
Implement this in your account and try all the possibilities with curl from the command line. For example (available as a live demo):
=> curl -v -X GET 'https://userpages.umbc.edu/~jianwu/is651/programs/ch11/mysql/rest/rest2.php?/test2/1'
* About to connect() to userpages.umbc.edu port 443 (#0)
* Trying 130.85.12.173... connected
* Connected to userpages.umbc.edu (130.85.12.173) port 443 (#0)
...
> GET /~jianwu/is651/programs/ch11/mysql/rest/rest2.php?/test2/1 HTTP/1.1
> User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.21 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2
> Host: userpages.umbc.edu
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Wed, 10 May 2017 15:33:45 GMT
< Server: Apache/2.4.6 (CentOS)
< X-Powered-By: PHP/5.4.16
< Vary: Accept-Encoding
< Content-Length: 109
< Connection: close
< Content-Type: text/html
<
* Closing connection #0
[{"id":"1","name":"tom","age":"25","salary":"3000"}]
Note that there is no need to always call the router *index.php*, you can use any name - it is *rest2.php* here. Also, some of the methods do not return anything such as *post*, so you have to do a subsequent *get* to see if the record was actually added (although you should get an error message if it fails). It looks like this (but *not* available as a live demo - do it in your account):
=> curl -v -X POST 'https://userpages.umbc.edu/~jianwu/is651/programs/ch11/mysql/rest/rest2.php?/test2' -d 'Name=joe&Age=35&Salary=89000'
* About to connect() to userpages.umbc.edu port 443 (#0)
* Trying 130.85.12.173... connected
* Connected to userpages.umbc.edu (130.85.12.173) port 443 (#0)
...
> POST /~jianwu/is651/programs/ch11/mysql/rest/rest2.php?/test2 HTTP/1.1
> User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.21 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2
> Host: userpages.umbc.edu
> Accept: */*
> Content-Length: 28
> Content-Type: application/x-www-form-urlencoded
>
< HTTP/1.1 201 Created
< Date: Wed, 10 May 2017 15:31:39 GMT
< Server: Apache/2.4.6 (CentOS)
< X-Powered-By: PHP/5.4.16
< Content-Length: 0
< Connection: close
< Content-Type: text/json
<
* Closing connection #0
###Chapter 11 Exercises
Do the end-of-chapter exercises for each chapter of the book by following the link in the on-line syllabus.