Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions

  




 

 

Client-Server Exercises

  1. 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 http://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.

  2. 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.

  3. 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.)

      1. It starts a subprocess to run the HTTP server side of your application.

      2. It starts another subprocess to run the browser, providing an initial link of 'http://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.

  4. 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.

  5. 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.

  6. 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 "http://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.

  7. 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.


 
 
  Published under the terms of the Open Publication License Design by Interspire