ORT BRAUDE COLLEGE OF ENGINEERING Electronic and Electrical Engineering Department Client Server Systems 31261 PROJECT 4: BFTP - Braude File Transfer Protocol * Work in pairs is OK. * Due date is: Thursday, June 13, 2013 See below how to submit the project * According to the course program, projects are mandatory, and will form an integral part of the course grade (30%) For motivation, each project will include extra bonus problems that will be taken into account in case that the final exam grade is unsatidfactory (I will use my personnal judgements for such cases) * You have to design three classes in this project: Bserver, ResponseThread, Bclient * The precise requirements and characterization is specified in the following problems. * Make sure to include a documentation before each class and each method that clearly states what you are trying to do. Please be short and precise. * IMPORTANT: Please stick to the required names of classes, methods, and functions !!! Do not invent you own name or try to modify the names in any way !!! Your work will be checked and graded by automatic scripts on top of your code. So if you do not use the correct names for classes, methods, and functions, you may loose valuable points for your grade! * Before you start, please review the class lecture notes and any other required materials from the course web site, or Google search. Use tutorials on the internet - read all you need about the IP datagram and TCP segment structure. * How to submit your work? You should send your work to: samyz.ort.braude@gmail.com as one zip file: proj4.zip The zip file should contain: proj4/README.txt proj4/proj4.py proj4/test.py proj4/socket_utils.py README.txt: This file should contain your names (one or two partners), course name, id, email, and phones, and any other special information that you think ought to be there. proj4.py: This should contain the Bserver, Bclient, and ResponseThread classes socket_utils.py: This is the socket utilities library which we have used in class and lab. It should be identical to the file which is included in: http://tinyurl.com/samyz/cliserv/projects/proj4/socket.zip If you find a bug or an important improvement in this library, please report to me at: samyz.ort.braude@gmail.com, and share your imporvemnts with all students test.py: This file should include all your tests of the classes and functions in the file proj4.py This file is not going to be checked ar graded, but it maybe needed for reference on how you are using your classes and functions. * Please report any error or missing items in this assignment to me. It may be updated several times, so do not save a copy and rely on it. You are encouraged and welcome to submit any errors or comments to: samyz.ort.braude@gmail.com Please ask any question that bothers you and report anything which is not completely clear. Just state the line number in this file and what is the error or unclear part. * Make sure to insert clear and short comments that explain what you do. * Use consistent coding style. Make sure your code is short, clear, and readable ! * Except for accuracy and code correctness, your code will also be checked and credited according to readabilty, simplicity, function length, run time, efficiency, and beauty! - Bad function or variable names are against Python philosophy! - Complicated and very long lines or paragraphs are a clear indication of design problems! --------------------------- BFTP PROTOCOL SPECIFICATION --------------------------- Command argument notation: Mandatory argument (must be supplied or an error is issued) [arg] Optional argument. If not supplied, a default value is chosen instead (depending on the command) --------------- CLIENT COMMANDS --------------- GET Get from a BFTP server to current directory in client - mandatory argument can be an absolute file path like: "c:/braude/python/proj3.txt" or a relative file path like "oliver.txt", or "books/alice.txt" In case of a relative path, then it is relative to the current working directory of the server Examples: GET oliver.txt GET books/alice.txt GET c:/braude/books/bob.txt PWD Get Working Directory in server side CD Change directory to in server side can be an absolute path or relative path. In case of a relative path, the move will be done relative to the current working directory of the server Examples: CD c:/braude/os # absolute path CD tutorials # relative path CD ../projects # relative path LIST List files in current working directory of server side Server should return the list of files as a one line list of files delimited by a newline character. DELETE Delete file from server If is a relative path, then the action will be done from the current working directory of the server. QUIT Signal end of session (Disconnect) from the BFTP session ++++++++++++++++ BONUS CHALLENGES ++++++++++++++++ + A special grade protection will be given to students that solve this more challenging + features. The grade value of this part will be determined later. + + PUT [destination] + PUT file from the client in the server [destination] + Default value of [destination] is the server current directory + [destination] can be one of: + - absolute directory path + - relative directory path + + GETDIR [destination] + Get a full directory tree from the server! (recursively) + [destination] is an optional directory path to which to copy the source directory + Again, [destination] can be a relative or an absolute path as before. + This can be done the hard way (recursive GET command) or a clever way (e.g., zipfile module?) + This is a particular + The optional [port] argument is the same as in the PORT command + + PORT [udp] + Use alternate for subsequent GET requests + If udp is indicated, use UDP (e.g., for voice or video files) + This way we can issue several GET requests by several Threads + on several ports ... + + FIND + Find list of files matching in the directory tree (at the server). + This is also not present in the official FTP protocol but with Python + it is easy to implement. + Example: + FIND c:/braude/books "G*.txt" + FIND c:/braude/tutorials "tcp*.doc" + + AUTH + Authentication + Example: AUTH dany may1985 + This is actually the "handshaking" stage. + Without passing this step the client cannot request anything. + Server should close connection if authentication fails (?) + In the implementation phase you should make sure to assign an authentication stamp + on each client that connects or else he will not be able perform any action + what to do if a clients sends a request without passing authentication? + +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -------------------------- SERVER RETURN STATUS CODES -------------------------- WARNING: This is just an initial suggestion! The precise specification should be decided by your implementation. Feel free to change and add status numbers if your solutions need them, but make sure to document all the details so your work can be checked correctly. 200 Authentication successful fulfilled 201 GET File OK: sending now 202 PWD Request successful. Follows the current directory of server. 205 CD request was accepted and done 206 LIST request done. File list follows 207 DELETE file request done 208 BYE! Closing connection 400 Bad request 401 File unreadable 402 File type not normal (directory, socket, pipe, system file, device file) 403 Forbidden request (file not public or outside server scope) 404 File not found 405 Directory not found 407 DELETE file failed 411 Authentication failed 500 Server Error 501 Not implemented yet (reserved command which is not ready yet) ------------------------------------- API - APPLICATION PROGRAM INTERFACE ------------------------------------- Your work: ---------- You should implement the following three classes Here is a suggested implementation plan. You may add more methods and helper utilities, but try to stick to this schema. You need to define three classes: 1. class Bserver 2. class ResponseThread 3. class Bclient class Bserver: def __init__(self, port): # server initialization # server should be ready and start working in this method def close(self): self.sock.close() def accept_clients(self): # This is the servel loop for accepting clients # After a client connects, you must assign a ResponseThread to take care # of it and leave this method free to handle more clients while True: # ... thread = ResponseThread(client_socket) thread.start() print "Launched Response Thread =", thread.getName() class ResponseThread(Thread): def __init__(self, client_socket): Thread.__init__(self) self.sock = client_socket def run(self): while True: message = recv_size(self.sock) # * In this method you must dispatch all the client requests: # GET, PWD, CD, LIST, DELETE, QUIT # * Each request is dispatched to one of the following do_ methods: # do_GET, do_PWD, do_LIST, ... # SUGGESTION: always send your status line before the data # so the client first gets the status line and then knows what to expect next # (in case of error status, the client may not get any data at all, but he will know that ...) # * Make sure you do not accidentally exit this infinite Response loop! That's what keeps # the connection with the client alive! (if your client freezes, most likely this is the reason) def do_GET(self): # Send requested file to client # Must check here if file exists, readable, etc... # If file is OK, then you send it to the client in a thread (so the server stay responsive) # Note that here you should use the simple send_file from socket_utils.py ! not the threaded one! # Since we are already inside a thread, so no need to start a new thread !! def do_PWD(self): # report current working directory at server side # Here is a small example of how these do methods look like: cwd = os.getcwd() response = "202 PWD Request successful. Follows the current directory of server.\n" self.send(response) # see the "def send" method below self.send(cwd) def do_CD(self): # Change directory to dir (server side) def do_LIST(self): # List files in current directory of server def do_DELETE(self): # Delete file (at server side) def do_QUIT(self): # Send BYE and close connection # Our private send method is a simple wrapper around the send_size function # this is useful in case we want to change the implementation in the future def send(self, message): send_size(self.sock, message) class Bclient: def __init__(self, server_host, server_port): self.server = server_host self.port = server_port self.sock = self.connect() self.status = None self.args = None def connect(self): sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) address = (self.server, self.port) sock.connect(address) print "Connected to Server: " + self.server return sock def close(self): self.sock.close() def send(self, message): # message is a request string (like: "GET file") without a newline! # We add the newline in this method ! # The client sends a request to the server (like: "GET file") and then receives # the server response. The handling of the server response (like getting the file # and copying it to a proper place is done by a handler method (like: handler_GET) send_size(self.sock, message) self.status = recv_size(self.sock) # status line (like: "404 File not found") # All command: GET, PWD, CD, LIST, DELETE, QUIT # should be dispatched to one of the following handle_ methods: def handle_GET(self, args): # Simple get file - no thread required. must wait untill all bytes arrive # Do not bother with buffer size and modes (just use the defaults) # The threaded version is on the BONUS list if not self.status[0] == '2': # success status is: '2xx' (200, 201, ...) self.sock.close() return None def handle_PWD(self): # Receive server current working directory (as a delimited line) s = self.status[0] if s == '2': dir = recv_size(self.sock) print "PWD =", dir def handle_CD(self): pass def handle_LIST(self): # Receive list of files in server current working directory (as a delimited line) def handle_DELETE(self): # Delete remote file at the server ----------------- USE CASE EXAMPLES ----------------- How you should use your classes? The following examples show how your code should work. 1. It should be easy for you to create three different file servers that work in parallel on the same host! 2. Similarly, it will be easy to create several clients and make them work simultaneously BFTP SERVER: # Start a BFTP servers at host: shark.braude.ac.il ! serv1 = Bserver(12345) # Server should start right away ... # After 5 hours ... we close server 1 serv1.close() BFTP CLIENT: # Start three BFTP clients from a different host client1 = Bclient("shark.braude.ac.il", 12345) client2 = Bclient("shark.braude.ac.il", 12345) client3 = Bclient("shark.braude.ac.il", 12345) # Note that clients 1, 2, 3 are connected to serv1, client1.send("PWD") client1.send("LIST") client1.send("GET c:/workspace/oliver.txt") client1.send("QUIT") client1.close() --------------------- Your work: BFTP SHELL --------------------- You need to implement a BFTP Shell! A BFTP is a command line application similar to cmd.exe in which you connect to a BFTP server and can run a CLIENT BFTP commands from the shell. The following example should make the idea clear: >> GET c:/braude/books/Gulliver.txt 404 File not found >> GET c:/braude/books/oliver.txt 201 GET File OK: sending now >> LIST 206 LIST request done. File list follows oliver.txt Alice.txt Tom_Sawyer.txt Cinderella.txt Treasure_Island.txt C_for_kids.txt >> DELETE c:/braude/books/Cinderella.txt 207 DELETE file request done A BFTP Shell should be implemented with a Bclient class roughly as follows def bftp_shell(): bclient = Bclient("shark.braude.ac.il", 12345) while True: cmdline = raw_input(">> ") bclient.send(cmdline) You should complete the rest ... ----------------------- HOW TO TEST YOUR CODE ? ----------------------- To test your code efficiently we suggest that you create 3 Python files in the same directory where your proj4.py, and socket_utils.py files are in. File 1: bserver.py ------------------ from proj4 import * server = Bserver(12345) File 2: bclient.py ------------------ from proj4 import * client = Bclient("localhost", 12345) status = client.send("PWD") print status status = client.send("LIST") print status status = client.send("GET c:/workspace/oliver.txt") print status status = client.send("QUIT") print status client.close() File 3: bshell.py ------------------ from proj4 import * def bshell(host, port): bclient = Bclient(host, port) # Shell Loop Running these three programs ---------------------------- 1. Open a cmd.exe console, and run the command: > bserver.py 2. Open a second cmd.exe console, and run the command: > bclient.py - You should be able to see the prints you made in bclient.py - Make sure to check that you can really handle multiple clients simultaneously! - You can start more cmd.exe consoles and run more clients from them ... 3. Open a third cmd.exe console, and run the command: > bshell.py You should be able to communicate with the server that you started in stage 1