#!/usr/bin/env python3

import argparse

import sys
import socket
import itertools
from socket import socket as Socket

BUFFER_SIZE = 4096

def main():

    # Command line arguments. Use port 8080 by default: widely used for proxys
    # and >1024 so we don't need sudo to run.
    parser = argparse.ArgumentParser()
    parser.add_argument('--port', '-p', default=8080, type=int,
                        help='Port to use')
    args = parser.parse_args()


    # Create the server socket (to handle tcp requests using ipv4), make sure
    # it is always closed by using with statement.
    with Socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:

        # The socket stays connected even after this script ends. So in order
        # to allow the immediate reuse of the socket (so that we can kill and
        # re-run the server while debugging) we set the following option. This
        # is potentially dangerous in real code: in rare cases you may get junk
        # data arriving at the socket.
        server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

        server_socket.bind(('', args.port))
        server_socket.listen(1)
        # no multithreaded yet, would need to set up atomic updates to dict.
        # Might be automatic in python?

        # Create empty dict for cached pages
        cache_dict = {}

        print("Proxy server ready")

        while True:
            # Accept TCP connection from client
            with server_socket.accept()[0] as connection_socket:
                
                request_string = connection_socket.recv(BUFFER_SIZE).decode().replace('\r\n', '\n')
                http_dict = http_parse(request_string)
                full_url = http_dict['Host'] + http_dict['Url']

                # Check cache for page
                cached_page = cache_dict.get(full_url)

                # If it's not in the cache then get the page
                if cached_page is None:
                    cached_page = get_page(http_dict['Host'], request_string).replace('\r\n', '\n').encode()
                    cache_dict[full_url] = cached_page
                    
                    print("Serving page", full_url, "from cache")
                    print(cached_page)
                    
                else:
                    print("Got page from", full_url, "and cached it")

                connection_socket.send(cached_page)

                # handle updating old cached pages here?
    
    return 0


# Taken from the web server code
def http_parse(request_string):
    """Given a request return a dict containing the request data

    Members of dict are: Method, Url, Version, Body, and any other
    headers supplied by the request.
    """

    try:
        [header, body] = request_string.split('\n\n')
    except ValueError:
        header = request_string
        body = 'nothing'

    header_lines = header.rstrip().split('\n')

    # Get the first line into a dict
    method, url, version = header_lines[0].split()
    firstline_dict = {'Method' : method,
                      'Url' : url,
                      'Version' : version,
                      'Body' : body}

    # And the rest
    headers = [l.split(': ') for l in header_lines[1:]]

    # Make the dict combining all these parts
    return dict(itertools.chain(firstline_dict.items(),
                                headers,
                                [('Body', body)]))

def http_port():
    return 80

def get_page(server_address, request):

    with Socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:

        # Connect to server
        client_socket.connect((server_address, http_port()))

        # Get the page
        client_socket.send(request.encode())
        reply = client_socket.recv(BUFFER_SIZE).decode()
        reply2 = client_socket.recv(BUFFER_SIZE).decode()

    return reply + reply2


def http_format_request(request_dict): 
    return """GET {1} HTTP/1.1
Host: {2}""".format(request_dict['Url'], request_dict['Host'])


if __name__ == "__main__":
    sys.exit(main())

@@ -117,27 +117,8 @@
 et%0A%0A
-BUFFER_SIZE = 4096%0A
 %0Adef
@@ -1506,32 +1506,33 @@
                %0A
+%0A
                 
@@ -1535,795 +1535,197 @@
     
-request_string = connection_socket.recv(BUFFER_SIZE).decode().replace('%5Cr%5Cn', '%5Cn')%0A                http_dict = http_parse(request_string)%0A                full_url = http_dict%5B'Host'%5D + http_dict%5B'Url'%5D%0A%0A                # Check cache for page%0A                cached_page = cache_dict.get(full_url)%0A%0A                # If it's not in the cache then get the page%0A                if cached_page is None:%0A                    cached_page = get_page(http_dict%5B'Host'%5D, request_string).replace('%5Cr%5Cn', '%5Cn').encode()%0A                    cache_dict%5Bfull_url%5D = cached_page%0A                    %0A                    print(%22Serving page%22, full_url, %22from cache%22)%0A                    print(cached_page)%0A                    %0A                else:%0A                    print(%22Got page from%22, full_url, %22
+# Fill in the code to recive the request, check if the url is%0A                # in cache_dict and either serve the cached version or request%0A                # the page from the real server 
 and 
@@ -1733,367 +1733,122 @@
 ache
-d
  it
-%22)%0A%0A                connection_socket.send(cached_page)%0A%0A                # handle updating old cached pages here?%0A    %0A    return 0%0A%0A%0A# Taken from the web server code%0Adef http_parse(request_string):%0A    %22%22%22Given a request return a dict containing the request data%0A%0A    Members of dict are: Method, Url, Version, Body, and any other%0A    headers supplied by
+.%0A%0A                # You may want to use code from the web server to extract%0A                # information from
  the
@@ -1861,1303 +1861,224 @@
 st.%0A
+%0A
     
-%22%22%22%0A%0A    try:%0A        %5Bheader, body%5D = request_string.split('%5Cn%5Cn')%0A    except ValueError:%0A        header = request_string%0A        body = 'nothing'%0A%0A    header_lines = header.rstrip().split('%5Cn')%0A%0A    # Get the first line into a dict%0A    method, url, version = header_lines%5B0%5D.split()%0A    firstline_dict = %7B'Method' : method,%0A                      'Url' : url,%0A                      'Version' : version,%0A                      'Body' : body%7D%0A%0A    # And the rest%0A    headers = %5Bl.split(': ') for l in header_lines%5B1:%5D%5D%0A%0A    # Make the dict combining all these parts%0A    return dict(itertools.chain(firstline_dict.items(),%0A                                headers,%0A                                %5B('Body', body)%5D))%0A%0Adef http_port():%0A    return 80%0A%0Adef get_page(server_address, request):%0A%0A    with Socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:%0A%0A        # Connect to server%0A        client_socket.connect((server_address, http_port()))%0A%0A        # Get the page%0A        client_socket.send(request.encode())%0A        reply = client_socket.recv(BUFFER_SIZE).decode()%0A        reply2 = client_socket.recv(BUFFER_SIZE).decode()%0A%0A    return reply + reply2%0A%0A%0Adef http_format_request(request_dict): %0A    return %22%22%22GET %7B1%7D HTTP/1.1%0AHost: %7B2%7D%22%22%22.format(request_dict%5B'Url'%5D, request_dict%5B'Host'%5D)
+            # If you want to do more after that you could try to handle%0A                # updating cached pages, and then try to convert the server to%0A                # a multithreaded version.%0A    %0A    return 0
 %0A%0A%0Ai

Convert proxy server to exercise version