-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproxy.py
More file actions
207 lines (170 loc) · 7.25 KB
/
proxy.py
File metadata and controls
207 lines (170 loc) · 7.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
import socket
import sys
from datetime import datetime
class Proxy:
# Define the proxy class with required parameters
def __init__(self, port, timeout, max_object_size, max_cache_size):
self.port = port
self.timeout = timeout
self.maxObjectSize = max_object_size
self.maxCacheSize = max_cache_size
self.cache = {} # Initialize an empty cache
self.zId = "z5648617"
# Function to run the proxy
def runProxy(self):
host = "127.0.0.1"
# Create a socket
socketToClient = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socketToClient.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # Allow reuse of the address while testing and debugging
socketToClient.bind((host, self.port))
# Listen for the connection
socketToClient.listen(5)
# Listens in a loop
try:
while True:
# Accept a connection
clientSocket, clientAddress = socketToClient.accept()
clientSocket.settimeout(self.timeout)
# Test the connection
print(f"Received connection from client {clientAddress} at {datetime.now()}")
# Get request info in the client socket
self.handleRequest(clientSocket, clientAddress)
# Stop the server using Crtl+C
except KeyboardInterrupt:
print("\nServer stopped.")
sys.exit(0)
# Function to handle the request from the client
def handleRequest(self, clientSocket, clientAddress):
while True:
# Receive data from the client
requestData = clientSocket.recv(4096)
# If no data is received, break the loop
if not requestData:
print("No data received.")
break
# Decode the request data
requestText = requestData.decode('utf-8')
print(f"This is requestText: {requestText}")
# Split the request text into lines
lines = requestText.splitlines()
# Get the start line of text
startLine = lines[0]
# Parse the start line to get the method, URL and HTTP version
if startLine:
startLineElements = startLine.split()
method = startLineElements[0]
requestTarget = startLineElements[1]
protocalVersion = startLineElements[2]
print(f"Request Method:{method}")
print(f"Request URL:{requestTarget}")
print(f"Http Version:{protocalVersion}")
# Parse the absolute-form request target
if requestTarget.startswith("http://"):
target = requestTarget[7:]
if '/' in target:
hostPort, path = target.split('/', 1)
originPath = '/' + path
else:
hostPort = target
originPath = '/'
if ':' in hostPort:
host, portStr = hostPort.split(':')
port = int(portStr)
else:
host = hostPort
# Default port 80
port = 80
print(f"Hostname: {host}")
print(f"Port: {port}")
print(f"Path: {originPath}")
# Parse the headers
headers = {}
for line in lines[1:]:
# Parse until an empty line
if line.strip() == "":
break
headerParts = line.split(":", 1)
if len(headerParts) == 2:
headerName = headerParts[0].strip()
headerValue = headerParts[1].strip()
headers[headerName] = headerValue
print(f"{headerName}:{headerValue}")
# Parse the request target to get the host and port
host = ''
port = 80
path = '/'
self.requestToServer(method, host, port, path, headers, clientSocket)
# Gnerate the log
# Only GET method has cache status of cache hit or miss
if method == "GET":
cacheStatus = "H"
else:
cacheStatus = "-"
self.generateLog(clientAddress, cacheStatus, startLine, "200", len(requestData))
return requestData
# Function to generate request to the server
def requestToServer(self, method, host, port, path, headers, clientSocket):
# Create a socket to the server
socketToServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socketToServer.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # Allow reuse of the address while testing and debugging
socketToServer.bind((host, port))
# Generate a request to the server
requestLine = f"{method} {path} HTTP/1.1\r\n"
headers["Host"] = host
headers["Connection"] = "close"
headers.pop("Proxy-Connection", None)
via = f"1.1 {self.zId}"
if "Via" in headers:
headers["Via"] += f", {via}"
else:
headers["Via"] = via
headerText = ''.join(f"{k}: {v}\r\n" for k, v in headers.items())
fullRequest = (requestLine + headerText + "\r\n").encode('utf-8')
socketToServer.sendall(fullRequest)
# Receive response
response = b''
while True:
data = socketToServer.recv(4096)
if not data:
break
response += data
clientSocket.sendall(data)
# Function to generate the log
def generateLog(self, clientAddress, cacheStatus, request, status, bodySize):
# Log format with the following syntax:
# host port cache date request status bytes
clientIp, clientPort = clientAddress
currentTime = datetime.now().astimezone().strftime("[%d/%b/%Y:%H:%M:%S %z]")
log = f"{clientIp} {clientPort} {cacheStatus} {currentTime} \"{request}\" {status} {bodySize}\n"
print(log)
def parseCommandLine():
# Parse command line arguments
if len(sys.argv) != 5:
print("Usage: python proxy.py <port> <timeout> <max_object_size> <max_cache_size>")
sys.exit(1)
try:
port = int(sys.argv[1])
timeout = int(sys.argv[2])
max_object_size = int(sys.argv[3])
max_cache_size = int(sys.argv[4])
except ValueError:
print("Error: All arguments must be integers.")
sys.exit(1)
# Validate port number
if port < 49152 or port > 65535:
print("Warning: Port number between 49152 and 65535 is recommended.")
sys.exit(1)
# Validate size
if max_cache_size < max_object_size:
print("Warning: max_cache_size must be greater than or equal to max_object_size.")
sys.exit(1)
return port, timeout, max_object_size, max_cache_size
# Main entrance for command line arguments
def main():
# Parse command line
port, timeout, max_object_size, max_cache_size = parseCommandLine()
# Run the proxy
proxy = Proxy(port, timeout, max_object_size, max_cache_size)
proxy.runProxy()
# Run main function
main()