Skip to content

Commit 35f7234

Browse files
authored
Merge pull request #509 from AutomationSolutionz/mitim-proxy
Proxy implementation for capturing browser generated traffic
2 parents a2c28c8 + fcdd959 commit 35f7234

File tree

9 files changed

+219
-8
lines changed

9 files changed

+219
-8
lines changed

Framework/Built_In_Automation/Sequential_Actions/action_declarations/common.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@
131131

132132
{"name": "connect to google service client", "function": "connect_to_google_service_account", "screenshot": "none" },
133133
{"name": "upload to google storage bucket", "function": "upload_to_google_storage_bucket", "screenshot": "none" },
134+
135+
{"name": "proxy server", "function": "proxy_server", "screenshot": "none"}
134136

135137
) # yapf: disable
136138

Framework/Built_In_Automation/Sequential_Actions/common_functions.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6740,3 +6740,72 @@ def stop_ssh_tunnel(data_set):
67406740

67416741
return "passed"
67426742

6743+
@logger
6744+
def proxy_server(data_set):
6745+
import os
6746+
sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME
6747+
6748+
proxy_var = None
6749+
action = None
6750+
port = 8080
6751+
for left, mid, right in data_set:
6752+
if left.lower().strip() == 'action':
6753+
action = 'start' if right.lower().strip() == 'start' else 'stop'
6754+
if left.lower().strip() == 'port':
6755+
port = int(right.strip())
6756+
if left.lower().strip() == 'proxy server':
6757+
proxy_var = right.strip()
6758+
6759+
if action == None:
6760+
CommonUtil.ExecLog(sModuleInfo, "Incorrect dataset", 3)
6761+
return "zeuz_failed"
6762+
6763+
6764+
if action == 'start':
6765+
CommonUtil.ExecLog(sModuleInfo, f"{action.capitalize()}ing proxy server on port {port}", 1)
6766+
6767+
proxy_log_dir = Path(sr.Get_Shared_Variables("zeuz_download_folder")).parent / 'proxy_log'
6768+
os.makedirs(proxy_log_dir, exist_ok=True)
6769+
mitm_proxy_path = Path(__file__).parent / "mitm_proxy.py"
6770+
output_file_path = proxy_log_dir / 'mitm.log' # Output file to save the logs
6771+
CommonUtil.ExecLog(sModuleInfo, f"Proxy Log file: {output_file_path}", 1)
6772+
6773+
captured_network_file_path = proxy_log_dir / 'captured_network_data.csv'
6774+
CommonUtil.ExecLog(sModuleInfo, f"Captured Network file: {output_file_path}", 1)
6775+
# Open the output file in append mode
6776+
with open(r'{}'.format(output_file_path), 'a') as output_file:
6777+
# Start the subprocess
6778+
process = subprocess.Popen(
6779+
[
6780+
"mitmdump",
6781+
"-s",
6782+
f"{mitm_proxy_path}",
6783+
"-p",
6784+
str(port),
6785+
"--set",
6786+
f"output_file_path={captured_network_file_path}",
6787+
],
6788+
stdout=output_file, # Redirect stdout to the file
6789+
stderr=output_file, # Redirect stderr to the file
6790+
)
6791+
6792+
pid = process.pid
6793+
CommonUtil.mitm_proxy_pids.append(pid)
6794+
CommonUtil.ExecLog(sModuleInfo, f"Started process with PID: {pid}", 1)
6795+
6796+
sr.Set_Shared_Variables(proxy_var, {"pid":pid,"captured_network_file_path":captured_network_file_path,"log_file":output_file_path})
6797+
return "passed"
6798+
else:
6799+
import signal
6800+
6801+
if CommonUtil.mitm_proxy_pids:
6802+
try:
6803+
pid = CommonUtil.mitm_proxy_pids[0]
6804+
os.kill(pid, signal.SIGTERM)
6805+
CommonUtil.ExecLog(sModuleInfo,f"Process with PID {pid} has been terminated.",1)
6806+
CommonUtil.mitm_proxy_pids.pop()
6807+
except OSError as e:
6808+
CommonUtil.ExecLog(sModuleInfo,f"Error: {e}", 3)
6809+
6810+
CommonUtil.ExecLog(sModuleInfo, f"{action.capitalize()}ing proxy server on port {port}", 1)
6811+
return "passed"
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from mitmproxy import http, ctx
2+
import time
3+
import csv
4+
import os
5+
6+
# Initialize a list to track request details
7+
requests_data = []
8+
9+
10+
def load(l):
11+
# Define the custom option `output_file_path`
12+
ctx.options.add_option("output_file_path", str, "", "Path to output CSV file")
13+
14+
15+
def request(flow: http.HTTPFlow) -> None:
16+
# Capture request data when it's made
17+
start_time = time.time()
18+
requests_data.append({
19+
'url': flow.request.url,
20+
'start_time': start_time,
21+
'status_code': None,
22+
'end_time': None,
23+
'duration': None,
24+
'content_length': None
25+
})
26+
27+
def response(flow: http.HTTPFlow) -> None:
28+
output_file_path = ctx.options.output_file_path
29+
create_file_if_not_exists(output_file_path)
30+
31+
# print("Flow", flow)
32+
# print("Response", flow.response)
33+
34+
res = flow.response
35+
end_time = time.time()
36+
37+
# Find the matching request based on the URL
38+
for req in requests_data:
39+
if req['url'] == flow.request.url:
40+
req['status_code'] = res.status_code
41+
req['end_time'] = end_time
42+
req['duration'] = end_time - req['start_time']
43+
req['content_length'] = len(res.content)
44+
break
45+
46+
# Create a list to hold the captured details
47+
captured_details = [
48+
flow.request.url,
49+
res.status_code,
50+
req.get('duration', None),
51+
len(res.content),
52+
end_time
53+
]
54+
55+
# Append the captured details as a row in the CSV file
56+
with open(output_file_path, 'a', newline='') as csvfile:
57+
writer = csv.writer(csvfile)
58+
writer.writerow(captured_details) # Write CSV row
59+
60+
# Optionally print captured details for console output
61+
print(f"Captured: {captured_details}")
62+
63+
def create_file_if_not_exists(filepath):
64+
"""
65+
Check if the output CSV file exists.
66+
If it does not exist, create the file and add csv headers.
67+
"""
68+
69+
if not os.path.exists(filepath):
70+
with open(filepath, "w", newline="") as csvfile:
71+
writer = csv.writer(csvfile)
72+
writer.writerow(
73+
[
74+
"url",
75+
"status_code",
76+
"duration_in_seconds",
77+
"content_length_in_bytes",
78+
"timestamp",
79+
]
80+
)
81+
print(f"Created output file: {filepath}")

Framework/Utilities/CommonUtil.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,8 @@
189189
global_sleep = {"selenium":{}, "appium":{}, "windows":{}, "desktop":{}}
190190
zeuz_disable_var_print = {}
191191

192+
mitm_proxy_pids = []
193+
192194
def clear_performance_metrics():
193195
"""reset everything to initial value"""
194196
global browser_perf, action_perf, step_perf, test_case_perf, perf_test_perf, api_performance_data, load_testing, processed_performance_data

Framework/test.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import os
2+
import subprocess
3+
from Utilities import CommonUtil
4+
from pathlib import Path
5+
6+
OUTPUT_FILEPATH = "/Users/sakib/Documents/zeuz/Zeuz_Python_Node/AutomationLog/debug_sakib_dd446e56-a_AaRlG/session_1/TEST-10894/zeuz_download_folder/proxy_log/mitm.log"
7+
CAPTURED_CSV_FILEPATH = "/Users/sakib/Documents/zeuz/Zeuz_Python_Node/AutomationLog/debug_sakib_dd446e56-a_AaRlG/session_1/TEST-10894/zeuz_download_folder/proxy_log/captured_network_data.csv"
8+
MITM_PROXY_PATH = "/Users/sakib/Documents/zeuz/Zeuz_Python_Node/Framework/Built_In_Automation/Sequential_Actions/mitm_proxy.py"
9+
PORT = 8080
10+
11+
12+
print(f"Starting proxy server on port {PORT}")
13+
14+
print(f"MITM Proxy path: {MITM_PROXY_PATH}")
15+
print(f"Proxy Log file: {OUTPUT_FILEPATH}")
16+
17+
print(f"Captured Network file: {CAPTURED_CSV_FILEPATH}")
18+
19+
# Open the output file in append mode
20+
with open(OUTPUT_FILEPATH, 'a') as output_file:
21+
# Start the subprocess
22+
process = subprocess.Popen(
23+
['mitmdump', '-s', MITM_PROXY_PATH, '-w', str(CAPTURED_CSV_FILEPATH), '-p', str(PORT)],
24+
stdout=output_file, # Redirect stdout to the file
25+
stderr=output_file # Redirect stderr to the file
26+
)
27+
28+
pid = process.pid
29+
30+
# Assuming CommonUtil.mitm_proxy_pids is a list, make sure it's initialized properly
31+
if not hasattr(CommonUtil, 'mitm_proxy_pids'):
32+
CommonUtil.mitm_proxy_pids = []
33+
34+
CommonUtil.mitm_proxy_pids.append(pid)
35+
36+
import time
37+
time.sleep(2)
38+
39+
# Verify if the service is running on the specified port
40+
def verify_port_in_use(port):
41+
if os.name == 'posix': # macOS/Linux
42+
result = subprocess.run(['lsof', '-i', f':{port}'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
43+
return result.stdout
44+
elif os.name == 'nt': # Windows
45+
result = subprocess.run(['netstat', '-aon'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
46+
return result.stdout if f':{port}' in result.stdout else ''
47+
48+
# Check if the port is in use
49+
port_status = verify_port_in_use(PORT)
50+
51+
if port_status:
52+
print(f"Service is running on port {PORT}:\n{port_status}")
53+
else:
54+
print(f"Service is NOT running on port {PORT}. Check if the subprocess started correctly.")
55+
56+
# Prevent the script from exiting immediately
57+
input("Press Enter to exit...\n")

requirements-linux.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,6 @@ jinja2
5656
pandas
5757
pyperclip
5858
thefuzz
59-
backports-datetime-fromisoformat; python_version < '3.11'
6059
genson
61-
google-cloud-storage
60+
google-cloud-storage
61+
mitmproxy

requirements-mac.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ configobj
5959
jinja2
6060
pandas
6161
pyperclip
62-
backports-datetime-fromisoformat; python_version < '3.11'
6362
thefuzz
6463
genson
65-
google-cloud-storage
64+
google-cloud-storage
65+
mitmproxy

requirements-win.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ configobj
6969
jinja2
7070
pandas
7171
pyperclip
72-
backports-datetime-fromisoformat; python_version < '3.11'
7372
thefuzz
7473
genson
75-
google-cloud-storage
74+
google-cloud-storage
75+
mitmproxy

requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,6 @@ configobj
5151
boto3
5252
pandas
5353
pyperclip
54-
backports-datetime-fromisoformat; python_version < '3.11'
5554
genson
56-
google-cloud-storage
55+
google-cloud-storage
56+
mitmproxy

0 commit comments

Comments
 (0)