CS637 Programming Project 2: Web Services for B to B

Ordering Pizza Supplies using Web Services

Due Sunday midnight, May 7, in your /var/www/html/cs637/username/proj2 directory on topcat.

Students may work in pairs for this project. Put a README in both top-level project directories (/var/www/html/cs637/username/proj2) stating both names and usernames, and whether this directory should be graded or the partner's. A partnership should provide a good README to go with the project, with a line of text on each modified file.

Our pizza shop needs lots of flour and cheese, obtainable by a relationship with a local supplier and web services. When the pizza shop needs more flour, it sends a web service request to the supplier. The supplier sends back a promise to deliver the order, on the next day, or possibly the day after. The actual delivery day is not known at the time of the order.

The supplier’s website proj2_server will be a modified ch24_guitar_shop.  We have added a category for pizza supplies, with products flour and cheese, and you will implement web services as follows.

Supplied web services

POST /proj2_server/rest/day              specifies the current day number for the server. This is a kludge to make the two servers have the same days. Posting 0 should reinitialize the server-side system: delete the orders and their items. You need to code the database actions to make this work.

GET /proj2_server/rest/day                returns the current day for the server, once you've written the database access code.

GET /proj2_server/rest/products/2    returns info on product 2 (just for demo, not done in normal execution)

POST /proj2_server/rest/products      adds a new product (just for demo, not done in normal execution)

Note: Here "/proj2_server/" will actually be /cs637/username/proj2/proj2_server/. Although the /day services are supplied as web services, they don't really work right until you add code for holding and accessing the day number in the database.

You will add

POST /proj2_server/rest/orders            creates a new order, returns new URI

GET /proj2_server/rest/orders              returns all orders with status of order (i.e., delivered or not)

GET /proj2_server/rest/orders/123      returns the one order of id=123, including status of order (i.e., delivered or not)

The pizza project, now pizza2, will be a client to proj2_server, calling on the above web services. You will add to pizza2 the ability to track inventory of supplies, and order as necessary. For simplicity, a unit of flour is enough for one pizza, and flour is sold in 40-unit bags.  Similarly, a unit of cheese is enough for one pizza, and cheese is sold in 20-unit containers. However, you can only store 250 units of each. You should be able to handle 50 pizzas/day by ordering carefully. Set up the tables so that you initially have 100 units of each, just enough for 2 days. Make sure that the "Initialize DB" button of the day manager initializes the inventory and undelivered orders tables, returning the system properly to day 1.

The supplied pizza2_setup has an added field to the pizza-order page allowing orders of multiple pizzas of the same kind, using a new parameter of n, so n=30 will order 30 pizzas.

Add inventory checking to the advance-day code. Assume a maximum of 50 orders/day for the shop. Because of the possible 2-day lag for orders, you need to aim to have at least 3 days worth of supplies (150 units) on hand. So originally, with 100 units, you should order (at least) 50 units, so that if nothing is delivered tomorrow, and 50 units are used today and tomorrow, the 50 units just ordered will be available for day after tomorrow. This means that the "advance-day" logic should be run when the system is initialized, i.e., at the beginning of day 1. We will require that the admin explicitly reinitializes the system before it is used for customers, so the day manager is run at the right time for this initial inventory checking.

In the advance-day logic of pizza2:

During the day: decrease inventory by 1 unit flour, 1 unit cheese for each order, with no output to HTML (the HTML is going to a customer, not an admin).

The day manager now additionally lists inventory information as follows, whenever it sends back its page:

Supplies on Order
  order 21: flour 20 cheese 40
  order 22: flour 80 cheese 40
Current Inventory
  flour 92
  cheese 112

Here, the inventory manager saw 100 units of flour at day start (after deliveries added), and so decided to order 80 units to bring the level up past 150 units. A previous order (not yet delivered) had ordered 20 units. Similarly, it saw 120 units of cheese at day start, and ordered 40 to bring it up beyond 150. A previous order had ordered 40 units. After day start, 8 pizza orders have been placed, bringing the inventory down to (92,112).


Product:  JSON serialization of $product from product_db.php.

Day:  simple text number

Order: two variants of order are needed, as detailed below.

$item1 = array(‘productID’=>11, ‘quantity’=>40);
$item2 = array(‘productID’=>12, ‘quantity’=>60);
$order = array(
‘customerID’=> 1, 'items' => array($item1, $item2));

The representation is just the JSON serialization of this.

$item1 = array(‘productID’=>11, ‘quantity’=>40);
$item2 = array(‘productID’=>12, ‘quantity’=>60);
$order = array(
‘customerID’=> 1, ‘orderID’ =>22, ‘delivered’ => false, 'items' => array($item1, $item2)); $order0, $order1 as above, with status:
$orders = array($order0, $order1);

Server details (proj2/proj2_server project)

When the server hears of a new day, it stores the new day number in the new single-row table systemDay. Note that for server-side web services, we can’t use session variables, because there is no “user” to track. Whenever the current_day is needed, read this row for its value.

When a new order comes in to the server via POST, use a random number generator to decide its delivery day: 1 or 2 days in the future, equally likely. Put the delivery day in the data for the order. Note the new column deliveryDay in orders. The delivery day itself is not sent back to the client, only the current status of an order as delivered or not, when queried.

When a GET for an order comes in, look up the order in the database and if delivery_day <= current_day, return ‘delivered’=>true, else false.

Client details (proj2/pizza2 project)

Note that the client needs to be able to determine and remember the orderIDs and flour/cheese quantities of the one or two undelivered orders, as well as the inventory levels.  Tracking undelivered orders is best done using a database table so that the client doesn’t forget things if the system reboots.  Simply set up a table named undelivered_orders with int columns orderid, flour_qty, cheese_qty, with orderid as primary key. When you submit an order, insert its info into undelivered_orders, and when you see it delivered, delete its row. Add a new file inventory_db.php to model for the needed database access code (inventory and undelivered_orders tables).

When the client analyzes inventory needs, it looks at the undelivered order numbers it has saved, and queries the server for their status, either individually by orderID, or collectively with GET to orders. The newly discovered deliveries are added to the inventory.

A student order may now fail because there is not enough inventory to make the pizzas. This should cause an exception in the model function, handled by the controller, which composes an appropriate error message (containing the word “inventory”) that shows in the returned HTML (the order form being redisplayed). We will test this by ordering 101 pizzas just after initializing the system.

Web Service client requests

The needed GET and POST requests from the web services client (pizza2) can be done using the curl library described in Chap. 22, but it is quite difficult, especially to recover the Location header of the response when a new resource is created. This Location header contains the newly assigned URL, which itself contains the new id of the resource. Luckily we can use the Guzzle PHP Component to greatly simplify this programming. The code for Guzzle is provided in the vendor directory of the project. Like all proper PHP Components, the various classes of the component can be autoloaded, and all we need to do is include the provided autoloader.php. See the sample code in pizza2/restclient/index.php. But the delivered code for this should be in the day directory, with the basic services in web_services.php.

Web Service server-side request handling

Unlike the client side, the server-side code can be done in a straightforward manner, once we see how to talk to the incoming and outgoing data streams at all. Previously, all our PHP code has worked with GETs and POSTs from forms and links, and sent back only HTML or REDIRECTs. Now we need to use streams of JSON data, incoming (in a POST body) or outgoing (in a response body). The provided code in rest/index.php shows how to do this.

Testing the server using Curl

The web services can be tested independently of PHP by command-line curl. For example, to find out the current day for the server:

curl localhost/cs637/username/proj2/proj2_server/rest/day/

and to POST a new day number, 9 for example, as plain text, and get back status info (-i):

curl -i -d 9 -H Content-Type:text/plain  http://localhost/cs637/username/proj2/proj2_server/rest/day/

There will be Linux/Mac and Windows shell programs provided for testing in the proj2_tests directory. It is somewhat difficult to install curl on Windows, so consider early deliveries and testing on topcat.The final grading-run testing will involve such curl commands, as well as Selenium tests.

Note that the two projects are delivered as subdirectories of the top-level directory. It's important to keep them this way so that the client can easily find the server.  Just unzip the zipfile to XAMMP's  htdocs/cs637/username/proj2_setup and also to htdocs/cs637/username/proj2, and then work on proj2.  In fact, you can install it at another directory level if you want, and it will work, but then the curl test scripts will need to be modified.

Changes to ch24_guitar_shop to create proj2/proj2_server (as in proj2_setup)

 Changes from pizza1 to proj2/pizza2 (as in proj2_setup)

Important files for development

pizza2/database/createdb.sql, dropdb.sql: needs new code to set up/drop inventory table, undelivered_orders table.

pizza2/pizza/index.php:  needs to fail an order if there is insufficient inventory. Deducts from inventory for successful order.

pizza2/day/index.php: when doing next_day action, needs to do the inventory management and ordering. On any access, needs to find out inventory levels, etc.

pizza2/day/order_list.php: now needs to displays inventory information and undelivered supply orders

pizza2/day/web_services.php: functions for each web service, using Guzzle to do the actual GET and POSTs. As in model, let the caller handle exceptions.

pizza2/model/inventory_db.php: database actions for inventory management (also need to edit initial.php)

proj2_server/rest/index.php:  top-level web service code

proj2_server/rest/.htaccess:  needed to get the Apache web server to execute index.php for any incoming URL .../rest/... (no edits needed)

proj2_server/model/order_db.php: provided code has new add_order(), this may be sufficient.

proj2_server/model/day.php: needs to retrieve and update the system day on the server

Getting Started

Unzip the supplied double-project zip file to htdocs/cs637/username/proj2_setup and also to htdocs/cs637/username/proj2, for your version.  On Linux or Mac, "chmod 777 */*.log" in proj2, to make all 3 log files writable by PHP (unnecessary on Windows).

Set up a project in Netbeans for pizza2 and another for proj2_server.  The easiest way is just to create each project in htdocs/cs637/username/proj2/pizza 2 and htdocs/cs637/username/proj2/proj2_server. Change model/database.php to use your username. See pizza2/README for more info.

You can use the same pizza database as before to get started, or drop it and recreate it to be sure. Use proj2_server/database/dev_setup.sql, then createdb.sql to create a proj2_server database with slightly modified tables from the guitar-shop series.

Run pizza2/restclient/index.php to see the supplied web services happen.

Note: Don't try to run proj2_server/rest/index.php by browsing to it: it only works as a web service handler

You can use a browser to generate GETs to REST URLs. Try browsing to http://localhost/cs637/username/proj2/proj2_server/rest/day to see the server's current day. Browse to http://localhost/cs637/username/proj2/proj2_server/rest/products/1 to see JSON for product 1. You can try other numbers too, but remember that product 2 has some bad chars in its description that prevent its conversion to JSON.

Browse to http://localhost/cs637/username/proj2/pizza2/restclient/index.php. This sample program should output the following as its response HTML (using the project as originally provided): replace eoneil with your username here:

app_path: /cs637/eoneil/proj2/pizza2/
base_url: localhost/cs637/eoneil/proj2/proj2_server/rest
POST day = 3 to http://localhost/cs637/eoneil/proj2/proj2_server/rest/day/
Post of day result: 200 
GET of day to http://localhost/cs637/eoneil/proj2/proj2_server/rest/day/
Back from GET: day = 6 (wrong until server coded right) 

GET of product 1 to http://localhost/cs637/eoneil/proj2/proj2_server/rest/products/1
Returned result of GET of product 1: 

The php-errors.log in pizza2 under xampp should have something like the following. If there is nothing there, on Linux or Mac, "chmod 777 *.log" in pizza2. If you have deleted the log file, recreate it with "touch php-errors.log" in pizza2, then chmod it.

[16-Apr-2017 14:08:59 America/New_York] =====Starting request: /cs637/eoneil/proj2/pizza2/restclient/index.php
[16-Apr-2017 14:08:59 America/New_York] ...... restclient: POST day = 3 to http://localhost/cs637/eoneil/proj2/proj2_server/rest/day/
[16-Apr-2017 14:08:59 America/New_York] ...... restclient: GET day
[16-Apr-2017 14:08:59 America/New_York] ...... restclient: GET product
[16-Apr-2017 14:08:59 America/New_York] ...... restclient: POST product

The php-server-errors.log in proj2_server under xampp should have something like the following. If there is nothing there, on Linux or Mac, "chmod 777 *.log" in proj2_server. If you have deleted the server log file, recreate it with "touch php-server-errors.log" in proj2_server, then chmod it.
[16-Apr-2017 14:08:59 America/New_York] starting REST server request, method=POST, uri = .../rest/day/
[16-Apr-2017 14:08:59 America/New_York] request at case day
[16-Apr-2017 14:08:59 America/New_York] rest server in handle_post_day
[16-Apr-2017 14:08:59 America/New_York] Server saw POSTed day = 3
[16-Apr-2017 14:08:59 America/New_York] starting REST server request, method=GET, uri = .../rest/day/
[16-Apr-2017 14:08:59 America/New_York] request at case day
[16-Apr-2017 14:08:59 America/New_York] rest server in handle_get_day, day = 6
[16-Apr-2017 14:08:59 America/New_York] starting REST server request, method=GET, uri = .../rest/products/1
[16-Apr-2017 14:08:59 America/New_York] request at case product
[16-Apr-2017 14:08:59 America/New_York] hi from handle_get_product
[16-Apr-2017 14:08:59 America/New_York] starting REST server request, method=POST, uri = .../rest/products/
[16-Apr-2017 14:08:59 America/New_York] request at case product

Nothing from this run will show in php-errors.log in proj2_server, since only web service code was executed there.

Once this is all working, start on on making the day web services work properly. Start using the proj2_tests to test the web services. See its README and the usage information at the start of the files. First test, on topcat: "servertest0.sh eoneil" to run against the supplied project deployed as "student" user eoneil on topcat (which is localhost when we're logged in on topcat). On your Windows system, "servertest0 username" should work similarly. On a Mac, use "servertest0.sh username", just like on topcat.

Client-side development can start immediately too. You need to setup up a table for inventory and another for undelivered orders, and code to access them, for example.