#!/usr/bin/python3 # # Skeleton proxy server (in Python 3) for CS 125 # """Web proxy server. Intercepts Web requests, caches them, and optionally modifies them. Usage: proxyserver.py [-d] SERVER-IP PORT Options: -d Enable debugging messages SERVER-IP is the DNS name or IP address to listen on; usually "localhost" will do just fine. PORT is the port to listen on; usually the UID printed by the "id" command will be appropriate.""" import docopt import hashlib from socket import * import sys args = None debug = False port = None def main(): global args global debug args = docopt.docopt(__doc__, version = 'proxyserver version 1.0') # # args['SERVER-IP'] contains the DNS name or IP address to bind to. # args['PORT'] contains the port number (as a string). # if args['-d']: debug = True # # Invoke the proxy # proxy() def proxy(): # # Convert the port number to an integer. # port = int(args['PORT']) # # Create a server socket, bind it to a port, and start listening # tcpServerSocket = socket(AF_INET, SOCK_STREAM) # # The following line allows you to rerun the server right away # after killing it. That's useful for testing; otherwise you may # have to wait 15-30 seconds each time. # tcpServerSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # FILL IN HERE: binding and listening while True: # Start receiving data from the client if debug: print('Ready to serve...') tcpClientSocket, clientAddress = tcpServerSocket.accept() print('Received a connection from:', clientAddress) clientMessage = '' # FILL IN HERE if debug: print('Request:', clientMessage) # # Extract the filename from the given message. Note that we # are very sloppy about error checking; this should be # improved. However, the code will work for syntactically # correct requests and thus is handy for initial development. # Error handling can be added later. # # FILL IN HERE url = clientMessage.split()[1] if debug: print('Parsed URL:', url) # # Again, we are sloppy about parsing the requested URL, and # that should be improved. # # FILL IN HERE head, slash, filename = url.partition('/') if debug: print('Parsed filename:', filename) fileExists = False # # Figure out a name to use in the cache. Note that it is both # dangerous and incorrect to simply use the name requested by # the client. The name provided might include strange # characters that could break your file system, and if the # client requests a file with the same name from two different # Web servers, the proxy will serve up the cached file from # the first server without noticing the problem. (This is # especially troublesome for common names like "index.html".) # Correcting this flaw is left as an exercise for the student # (it can be useful to calculate a SHA-1 hash [see # hashlib.sha1] of the entire URL and use that as the basis # for a file name). # # FILL IN HERE cachedName = "/" + filename if debug: print('Cached name:', cachedName) try: # Check whether the file exists in the cache cachedFile = open(cachedName[1:], "rb") # # It is incorrect to use read() here; if the file is # enormous the proxy server will die trying to swallow it. # However, this code will work for small files. # # FILL IN HERE outputdata = cachedFile.read() fileExists = True # # We found a cache hit, so we can generate a response message. # However, the code below is incorrect because it always # assumes that the result is text/html. A correct # implementation will record the content-type of the # cached data and pass that on to the client. # # FILL IN HERE tcpClientSocket.send("HTTP/1.0 200 OK\r\n".encode()) tcpClientSocket.send("Content-Type: text/html\r\n".encode()) # FILL IN HERE if debug: print('Successfully served data from cache') cachedFile.close() # Error handling for file not found in cache except IOError: if not fileExists: # # Create a socket to connect to the main Web server # socketToServer = '' # FILL IN HERE try: # # Connect to the server on port 80. # # FILL IN HERE socketToServer.send( ("GET " + filename + " HTTP/1.0\r\n\r\n").encode()) # # Read the server's response # response = '' # FILL IN HERE # # Create a new file in the cache for the requested # file. See above regarding file naming. # # FILL IN HERE tmpFile = open("./" + filename, "wb") # # Also send the response the client socket. # # FILL IN HERE except: # # We couldn't connect to the server. Generate an HTTP 500. # FILL IN HERE # pass # # All done! Close the server socket # socketToServer.close() else: # # We failed to read from the cache; generate an HTTP 500 # (internal server error). # # FILL IN HERE pass # Close the client socket tcpClientSocket.close() if __name__ == '__main__': main()