-
HTTP Client. Write a simple client that can make HTTP requests. An HTTP
request consists of three elements: a
method, URI and a protocol name. The
request header may be followed by one or more lines of header
fields. RFC 2068 has all of the details of this protocol.
The OPTIONS request returns information about the
server.
OPTIONS * HTTP/1.1
The GET request returns a particular HTML page from a
server.
GET https://www.python.org/index.html HTTP/1.1
The following request is two lines, with a header field that
specifies the host.
GET /pub/WWW/ HTTP/1.1
Host: www.w3.org
Your client will need to open a socket on port 80. It will
send the request line, and then read and print all of the reply
information.
-
FTP Client. Write a simple, special-purpose FTP client that establishes
a connection with an FTP server, gets a directory and ends the
connection. The FTP directory commands are "DIR" and "LS". The
responses may be long and complex, so this program must be
prepared to read many lines of a response.
For more information, RFC 959 has complete information on all
of the commands an FTP server should handle. Generally, the DIR or
LS command, the GET and PUT commands are sufficient to do simple FTP
transfers.
Your client will need to open a socket on port 21. It will
send the command line, and then read and print all of the reply
information. In many cases, you will need to provide additional
header fields in order to get a satisfactory response from a web
server.
-
Stand-Alone Web Application. You can easily write a desktop application that uses web
technology, but doesn't use the Internet. Here's how it would
work.
-
Your application is built as a very small web server,
based on BaseHTTPServer.HTTPServer
. This
application prepares HTML pages and forms for the user.
-
The user will interact with the application through a
standard browser like Firefox, Opera or Safari. Rather than
connect to a remote web server somewhere in the Internet, this
browser will connect to a small web server running on your
desktop.
-
You can package your application with a simple shell
script (or .BAT
file) which does two
things. (This can also be done as a simple Python program using
the subprocess
module.)
-
It starts a subprocess to run the HTTP server side of
your application.
-
It starts another subprocess to run the browser,
providing an initial link of
'https://localhost:8008'
to point the
browser at your application server.
This assures that your desktop application has a web browser
look and feel.
The Input Form. While the full power of HTML is beyond the scope of this
book, we'll provide a simple form using the
<form>
, <label>
,
<button>
and
<input>
tags. Here's an example of a
simple form. This form will send a POST request to the path
/response
when the user clicks the
Submit button. The input will include two
name-value pairs with keys of field
(from the
<input type="text">
) and
action
(from the <button
type="submit">
).
<html><head><title>Test</title><head>
<body><form action="/response" method="POST">
<label>Field</label> <input type="text" name="field"/>
<br/>
<button type="submit" name="action" value="submit">Submit</button>
</form>
</body>
</html>
An Overview of the Server. You'll create a subclass of
BaseHTTPServer.BaseHTTPRequestHandler
which
provides definitions for do_GET
and
do_POST
methods.
Your main script will create an instance of
BaseHTTPServer.HTTPServer
, provide it an
address like ('', 8008)
, and the name of your
subclass of BaseHTTPRequestHandler
. This
object's serve_forever
method will then
handle HTTP requests on port 8008.
Your subclass of BaseHTTPRequestHandler
will have do_GET
and
do_POST
methods. Both methods need to
examine the path to determine the name of the form they are
responding to. For example, if you are going to do a celsius to
farenheit conversion, a GET for a path of
/farenheit
would present a form that accepts a
celsius temperature. A POST for a path of
/farenheit
would check that the input is a
number, and would produce a form with the farenheit value.
Handling Form Input. By making our form's method is "POST"
,
we've assured that the do_POST
method
will be called when someone clicks the
Submit button. The user's input is made
available to your program in the form of a data stream, available
from the socket through a file object,
self.rfile
. The
do_POST
method must read the data waiting
in this file.
Because of the HTTP protocol rules, this socket is left open.
It cannot be simply read to end of file. The
do_POST
method must first determine how
much data is there, then read just that number of bytes and no
more.
The formText
received from the browser will
contain the user's input encoded as a single, long string that
includes all of the form's field (and button) names and input
values. The cgi
module has functions for
parsing this. Specifically cgi.parse_qs
and
cgi.parse_multipart
functions handle this
parsing nicely.
length= int( self.headers['content-length'] )
formText= self.rfile.read( length )
formInput= cgi.parse_qs( formText )
The resulting formInput
object is a
dictionary with all of the various input fields and button names as
keys.
Sending HTML Output. The response from a web server is a kind of MIME message. It
has a status line, some headers, and the "payload" or content from
the web server. There are some methods defined in
BaseHTTPRequestHandler
that help us create
this response correctly. Specifically,
send_response
,
send_header
and
end_headers
are used to create the
message header, including the proper MIME type.
Here's an example of the correct way to send an HTML page back
to a browser. It shows just two headers,
content-type
and
content-length
. This example also shows where any
cookie information would be included in the headers. The
Cookie
module helps to encode and decode
cookies.
self.send_response( 200 )
self.send_header( "Content-type", type )
self.send_header( "Content-length", str(len(body)) )
self.wfile.write( theCookie.output() + "\n" )
self.end_headers()
self.wfile.write( body )
When the content type is "text/html", the body is expected to
be an HTML document; the browser will format the document using the
HTML markup. For a content type of "text/plain" the browser will not
format the document. Other content types will lead to other kinds of
processing. For example, a content type of "image/jpeg" would
describe a JPEG file being sent as the response, the browser will
display the image. A content type of "audio/x-aiff" would describe
an AIFF file being sent; most browsers will start playing the audio
file.
-
Roulette Server. We'll create an alternative implementation of the simple
Roulette server shown in the section called “Web Services Server”. Rather than use web
services, we'll create a new protocol and use sockets to interact
with the server.
Our new protocol will involve a stream of commands from the
client to the server. This protocol will be limited to a single
client at a time -- it isn't appropriate for games like chess or
poker where there will be multiple, interacting clients.
We'll define three commands that a client can send to the
server.
The bet
command will define a bet placed by
a client. It will save the proposition and the amount being bet. The
proposition is any of the available Roulette bets: a specific
number, a color, high or low, even or odd. If the bet and amount are
valid, the server should respond with a "201" message. For example,
"201 Bet $5.00 BLACK accepted"
. If the bet or
amount are invalid, the server should respond with a "501" message.
For example, "501 'BET asd EVEN' has invalid
amount"
. Additionally, a bet over the table maximum will
also get a "501" message.
The client can send as many bet requests as necessary. For
example "BET BLACK 5"
and "BET EVEN
5"
. Each bet will receive a confirmation. The table
minimum and maximum might be $10 and $500.
The command to spin the wheel will be spin
.
There are two possible responses. If the total value of the bets is
less than the table minimum, a "401" status is sent with a message
that includes the table minimum and the amount bet. For example
"401 only $8.00 bet at a $10.00 table"
. If the
total of the bets is greater than the table minimum, a "203" status
code is sent. This message includes the result, and the list of bets
placed and the result for each bet. This can be quite a long
message. For example, it might be "203 Spin (14, RED, LOW,
EVEN). Bet $5.00 on BLACK: lose. Bet $5 on EVEN:
win."
Checking the status of bets will be show
.
This command will get an 202 status code with all of the possible
bets and the amounts placed. This can be a single long line of data,
or multiple lines using indentation to show that this is a
multiple-line response. For example "202 EVEN 0, BLACK 5,
ODD 0, EVEN 5, HIGH 0, LOW 0"
.
We can leverage the handle
method of a
handler to parse the input command. We would send a "505" response
if the message cannot be interpreted.
class RouletteHandler( StreamRequestHandler ):
def handle():
input= self.rfile.read()
if input[:3] == "bet":
reply= self.placeBet(input)
elif input[:4] == "show":
reply= self.showBets(input)
elif input[:4] == "spin":
reply= self.spinWheel()
else:
reply= "505 unknown request: " + input
self.wfile.write(reply)
You'll need to assign this to some suitable port number, for
example, 36000.
-
Roulette Client. Write a simple client which places a number of bets on the
Roulette server, using the Martingale betting strategy. The
strategy is relatively simple. First, each bet will be placed on
just one proposition (Red, Black, Even, Odd, High or Low). A base
betting amount (the table minimum) is used. If a spin is a winner,
the betting amount is reset to the base. If a spin is a loser, the
betting amount is doubled.
Note that bet doubling will lead to situations where the ideal
bet is beyond the table maximum. In that case, your simulation can
simply stop playing, or adjust the bets to be the table
maximum.
-
Roulette Web Application. We can write a web application which uses our Roulette
Server. This will lead to a fairly complex (but typical)
architecture, with two servers and a client.
-
We'll have the Roulette Server from exercise 4, running on
port 36000. This server accepts bets and spins the wheel on
behalf of a client process. It has no user interaction, it
simply maintains state, in the form of bets placed.
-
We'll have a web server, similar to exercise 3, running on
port 8008. This application can present a simple form for
placing a bet or spinning the wheel. If the user filled in the
fields and clicked the Bet button, the
web application will make a request to the Roulette Server and
present the results in the HTML page that is returned to the
user. If the user clicked the Spin
button, the web application will make a request to the Roulette
Server and present th eresults in the HTML page. This
application will include a large amount of fancy HTML
presentation.
-
We'll use a browser to contact our web server. This client
will browse "https://localhost:8008" to get a web page with a
simple form for placing a bet or spinning the wheel.
A simple HTML form might look like the following.
<html><head><title>Roulette</title></head>
<body>
<p>Results from previous request go here</p>
<form action="/roulette" method="POST">
<label>Amount</label> <input type="text" name="amount"/>
<br/>
<label>Proposition</label> <select name="proposition">
<option>Red</option>
<option>Black</option>
<option>Even</option>
<option>Odd</option>
<option>High</option>
<option>Low</option>
</select>
<br/>
<button type="submit" name="action" value="bet">Bet</button>
<button type="submit" name="action" value="spin">Spin</button>
</form>
</body>
</html>
Your web server will see requests with a path of
"/roulette"
, the action on the form. The input
will include three name/value pairs, named
amount
, proposition
and
action
. When the action
field
is "bet", then the amount
and
proposition
will define the bet request sent to
the Roulette server. When the action field is "spin", then the spin
request should be sent to the Roulette server.
-
Chess Server. In the section called “Chessboard Locations” we
described some of the basic mechanics of chess play. A chess
server would allow exactly two clients to establish a connection.
It would then a chess moves from each client and respond to both
clients with the new board position.
We'll create a web service, using XML-RPC, that has a number
of methods for handling chess moves. To do this, we'll need to
create a basic ChessBoard class which has a number of methods that
establish players, move pieces, and report on the board's
status.
It's essential that the ChessBoard be a single object that
maintains the state of the game. When two players are connected,
each will need to see a common version of the chessboard.
Here are some of the methods that are essential to making this
work.
-
__ini__
-
This method will initialize the chessboard with all
pieces in the starting position. It will also create two
variables to store the player names. These two player names
will initially be None, since no player has connected.
-
connect
(
name
)
→ message string
-
This method will allow a player to connect to the chess
server. If two players are already connected, this will return
an Error message. Otherwise, this will return an acceptance
message that includes the player's assigned color (White or
Black.)
-
move
(
player
,
from
,
to
)
→ message string
-
A request by a player to move a piece from one location
to another location. Both locations are simply file (a-h) and
rank (1-8). If the move is allowed, the response is an
acknowledgement. Otherwise, an error response is sent. The
chess server will need to track the moves to be sure they
players are alternating moves properly.
-
board
→
position string
-
The response is a 128-character string that reports the
state of the board. It has each rank in order from 8 to 1;
within each rank is each file from a to h. Each position is
two characters with a piece code or spaces if the position is
empty.
A client process for this web service will attempt a
connection. If that succeeds, the client will provide moves from the
given player. Periodically, the client will also ask for the status
of the board.
If the client program is a desktop application, it may poll
the chess server's board
method fairly
often to provide a response to one player as soon as the other
player has moved.
If the client program is a web application, there may be a
JavaScript prorgam which is polling the board periodically. The HTML
may include an automatic refresh via <meta
http-equiv="refresh" content="120">
with the
<head>
tags. Or the user may be expected to
refresh the page manually to check for any change to the
board.