ACET




REST tutorial

Bids: More POST

Now that we can manage items in our auction house, we also need to be able to bid on things.

We only need one function in bidding: POST, to place a bid against an item. We will also need to modify the client user interface to allow bids to be placed.

Making a bid: server side

We will submit bids to the system by using POST to add a new bid resource to the /item/1729/bid collection.

First, we add an extra piece to the URI parsing in the service() method in your server code. If there’s an item ID specified, this will check whether the next component in the URI is bid, and sets a flag.

	// If we have an item ID, check to see if the request is for a bid on this item
	boolean bid = false;
	if(item_id >= 0) {
		if(path_components.length >= 4
			&& path_components[3].equals("bid")) {
			bid = true;
		}
	}

Secondly, modify the code that selects the handler, to use different functions for handling POST requests to an item and to a bid:

	if(verb.equals("GET")) {
		handleGetItem(item_id, response);
	} else if(verb.equals("POST") && item_id >= 0) {
		ServletInputStream input = request.getInputStream();
		if(bid) {
			handlePostBid(item_id, input, response);
		} else {
			handlePostItem(input, response);
		}
	} else if(verb.equals("DELETE") && item_id >= 0 <b>&& !bid</b>) {
		handleDeleteItem(item_id, response);
	}

The POST handler for bids

Finally, add the handlePostBid() method to update the database:

public void handlePostBid(int item_id,
	  ServletInputStream input,
	  HttpServletResponse response)
	throws IOException, ServletException
{
	try {

We have to do some additional checking, to make sure that we aren’t adding a bid to an item that doesn’t exist. We also check that the bid isn’t past the finish time of the auction:

		// Bid time is now
		Timestamp now = new Timestamp(System.currentTimeMillis());

		// Do some checking: does the item requested actually exist? Is it still live?
		String check_sql = "SELECT * FROM item WHERE id = ? AND expiry >= ?";
		PreparedStatement check_stmt = db_connection.prepareStatement(check_sql);
		check_stmt.setInt(1, item_id);
		check_stmt.setTimestamp(2, now);
		ResultSet results = check_stmt.executeQuery();
		if(!results.next()) {
			sendNotFoundError(response, "Item not found, or is closed");
			return;
		}

The input parser looks very similar to the one in the handlePostItem() method we wrote earlier. A good design should probably have only a single parser instance for each data format being used. In our case, it’s a little easier simply to cut and paste the main bits:

		// Read and parse the input
		Map<String, String> parsed_input = new HashMap<String, String>();

		byte[] buffer = new byte[1024];

		int	bytes = input.readLine(buffer, 0, 1023);
		while(bytes >= 0) {
			// Warning: This code doesn't handle lines longer than
			// 1024 bytes very well.
			String line = new String(buffer, 0, bytes);
			String[] parts = line.split(":", 2);
			// Error checking: input line doesn't contain a colon: ignore it
			if(parts.length < 2) {
				continue;
			}
			// Store the results of parsing this line
			parsed_input.put(parts[0].trim(), parts[1].trim());
			// Get the next line of input
			bytes = input.readLine(buffer, 0, 1023);
		}

		// Check that we have both required parts of the bid
		if(!parsed_input.containsKey("bidder")) {
			sendNotAllowedError(response, "No bidder");
			return;
		}
		if(!parsed_input.containsKey("value")) {
			sendNotAllowedError(response, "No value");
			return;
		}

Finally, we can create a new bid in the database:

		// Now post a new item into the database
		String post_sql = "INSERT INTO bid"
			+ " (user, itemid, value, timestamp)"
			+ " VALUES (?, ?, ?, ?)";
		PreparedStatement stmt = db_connection.prepareStatement(post_sql);
		stmt.setString(1, parsed_input.get("bidder"));
		stmt.setInt(2, item_id);
		double value = Double.parseDouble(parsed_input.get("value"));
		stmt.setDouble(3, value);
		stmt.setTimestamp(4, now);

		stmt.execute();

		// Ensure that the update gets sent to the disk
		flush_db.execute();

	} catch(SQLException ex) {
		ex.printStackTrace(System.err);
		sendInternalError(response, "SQL Exception");
		return;
	}
}

Client-side bidding interface

The bidding interface simply adds a field and a “bid” button to the list of items. In a real application, the username would be made a part of the HTTP authentication process, and could be extracted by the servlet from the authentication details. In our case, it is simply provided by a field on the web form.

We first extend the items table with the field and button on each row:

	for(var idx in items)
	{
	    var item = items[idx];

	    if(item['id'])
	    {
	        newlist += "  <tr>\n";
		newlist += "    <td>" + item['title'] + "</td>\n";
		newlist += "    <td>" + item['description'] + "</td>\n";
		newlist += "    <td>&pound;" + item['reserve'] + "</td>\n";
		newlist += "    <td>" + item['expiry'] + "</td>\n";
		newlist += "    <td>\n";
		newlist += "      <button onclick='delete_item(" + item['id'] + ")'>Delete</button>\n";
		newlist += "    </td>\n";
		newlist += "    <td>\n";
		newlist += "      <input id='bid_value_" + item['id'] + "'/>\n";
		newlist += "      <button onclick='bid_item(" + item['id'] + ")'>Bid</button>\n";
		newlist += "    </td>\n";
		newlist += "  </tr>\n";
	    }
	}

We also add a field to the “list items” section for the username, and a <div> to put user feedback into:

<div id='bid_result'></div>
<br/>Username: <input id='username'>derek</input>

Finally, we add a function to handle the bidding process:

function bid_item(item)
{
    var request = get_request_object();
    var uri = "http://localhost:8080/auction/item/" + item + "/bid";
 
    request.onreadystatechange = function() {
	  if(request.readyState == 4)
        {
	      var bid_result = document.getElementById("bid_result");

	      if(request.status == 200)
	      {
	          bid_result.innerHTML = "Successful bid";
	      }
	      else
	      {
	          bid_result.innerHTML = "Failed bid! Result was " + request.statusText + ": " + request.responseText;
	      }
        }        
    }

    var content = "";
    content += "value:" + document.getElementById("bid_value_" + item).value + "\n";
    content += "bidder:" + document.getElementById("username").value + "\n";
 
    request.open("POST", uri, true);
    request.send(content);
}

Top bid

We should also add a “top bid” field to the resource, so that the user interface can show it. On the server side, we need to modify the SQL statement in handleGetItem():

			// Construct a query to get data out of the database
			String sql = "SELECT item.id, title, description, reserve, expiry, MAX(value)"
				+ " FROM item LEFT JOIN bid ON item.id = bid.itemid";

			// If we were asked for /item/123, limit the query to that
			// particular ID.
			if(item_id >= 0) {
				sql += " WHERE id = ?";
			}
 
			sql += " GROUP BY item.id, title, description, reserve, expiry";

Also add a line into the output:

			out.print("high-bid:" + results.getfloat(6) + "\n");

On the client side, you only need to add a new column to the table:

	newlist += "    <th>High bid</th>";

and a corresponding output line for each item:

		newlist += "    <td>" + item['high-bid'] + "</td>\n";

Rebuild your server, and test your new client code to see the high bids. See the next page for ideas on further work that could be done on this service.

Valid XHTML | Copyright | Last Modified: 1/Apr/2009 |