Skip to content

Commit

Permalink
feat: mimicker report
Browse files Browse the repository at this point in the history
  • Loading branch information
Amazia Gur authored and Amazia Gur committed Dec 16, 2024
1 parent d0fcc47 commit f5de746
Show file tree
Hide file tree
Showing 5 changed files with 690 additions and 2 deletions.
17 changes: 17 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Use the official Python image
FROM python:3.12-slim

# Set the working directory in the container
WORKDIR /app

# Copy the project files into the container
COPY . .

# Install Poetry and the project dependencies
RUN pip install poetry && poetry install --no-dev

# Expose the default port for the server
EXPOSE 8080

# Start the MimickerServer using poetry and Python
CMD ["poetry", "run", "python", "-c", "from mimicker.server import MimickerServer; MimickerServer().start()"]
23 changes: 22 additions & 1 deletion mimicker/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
import json
from typing import Any, Tuple, Optional, Dict, Callable, List

from mimicker.request_log import RequestLog
from mimicker.stub_group import StubGroup


class MimickerHandler(http.server.SimpleHTTPRequestHandler):
def __init__(self, stub_matcher: StubGroup, *args, **kwargs):
def __init__(self, stub_matcher: StubGroup, request_logs: Dict[str, List[RequestLog]], *args, **kwargs):
self.stub_matcher = stub_matcher
self.request_logs = request_logs
super().__init__(*args, **kwargs)

def do_GET(self):
Expand All @@ -28,11 +30,30 @@ def _handle_request(self, method: str):
method, self.path, request_headers=request_headers
)

body = None
if method in ["POST", "PUT"]:
body = self._get_request_body()

RequestLog.log_request(self.request_logs, method, self.path, body)

if matched_stub:
self._send_response(matched_stub, path_params)
else:
self._send_404_response(method)

def _get_request_body(self):
"""Read the body of the request for POST and PUT requests."""
content_length = int(self.headers.get('Content-Length', 0))
if content_length > 0:
body = self.rfile.read(content_length)
try:
# Try to parse as JSON if the body is JSON
return json.loads(body)
except json.JSONDecodeError:
# If it's not JSON, return as a string
return body.decode('utf-8')
return None

def _send_response(
self,
matched_stub: Tuple[
Expand Down
169 changes: 169 additions & 0 deletions mimicker/request_log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import json
from datetime import datetime


class RequestLog:
def __init__(self, method, path, timestamp, body):
self.method = method
self.path = path
self.timestamp = timestamp
self.body = body

@staticmethod
def log_request(request_logs, method, path, body):
"""Log requests to the server."""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
if path not in request_logs:
request_logs[path] = []
request_logs[path].append(RequestLog(method, path, timestamp, body))

@staticmethod
def generate_html_report(request_logs):
"""Generate an HTML report of routes and requests with better styling and grouping by method."""
html_content = """
<html>
<head>
<title>Routes and Requests Report</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f9;
color: #333;
margin: 20px;
}
h1 {
color: #4CAF50;
text-align: center;
}
ul {
list-style-type: none;
padding: 0;
}
li {
margin-bottom: 20px;
}
.route {
font-size: 22px;
font-weight: bold;
color: #2c3e50;
margin-bottom: 10px;
cursor: pointer;
}
.method-group {
background-color: #ecf0f1;
border-radius: 5px;
padding: 10px;
margin-bottom: 10px;
display: none;
}
.method-title {
font-size: 18px;
font-weight: bold;
color: #2980b9;
cursor: pointer;
}
.hit-count {
font-size: 14px;
color: #27ae60;
font-weight: normal;
}
.request-log {
background-color: #fff;
border-radius: 5px;
padding: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
margin-bottom: 10px;
}
.log-item {
padding: 8px;
border-bottom: 1px solid #ddd;
}
.log-item:last-child {
border-bottom: none;
}
.timestamp {
color: #888;
font-size: 14px;
}
.method {
font-weight: bold;
color: #2980b9;
}
.body {
font-style: italic;
color: #7f8c8d;
}
</style>
</head>
<body>
<h1>Routes and Requests Report</h1>
<ul>
"""

# Loop through the routes and group logs by HTTP method (GET, POST, etc.)
for route, logs in request_logs.items():
html_content += f"<li><div class='route' onclick='toggleMethodGroup(\"{route}\")'>{route}</div>"

# Group logs by method (GET, POST, etc.)
method_logs = {}
for log in logs:
if log.method not in method_logs:
method_logs[log.method] = []
method_logs[log.method].append(log)

# Add the method groups with the number of hits
for method, logs in method_logs.items():
html_content += f"""
<div class='method-group' id='{route}_{method}'>
<div class='method-title' onclick='toggleMethodLogs(\"{route}_{method}\")'>
{method} <span class='hit-count'>Hits: {len(logs)}</span>
</div>
<div class='request-log'>
<ul>
"""

# List all logs for that method
for log in logs:
body = log.body if isinstance(log.body, str) else json.dumps(log.body, indent=2)
html_content += f"""
<li class='log-item'>
<div><span class='timestamp'>{log.timestamp}</span> -
<span class='method'>{log.method}</span> -
<span class='body'>{body}</span></div>
</li>
"""

html_content += """
</ul>
</div>
</div>
"""

html_content += "</li>"

html_content += """
</ul>
<script>
// Function to toggle visibility of method groups (GET, POST, etc.)
function toggleMethodGroup(route) {
var groups = document.querySelectorAll('.method-group');
groups.forEach(function(group) {
if (group.id.startsWith(route)) {
group.style.display = group.style.display === 'block' ? 'none' : 'block';
}
});
}
// Function to toggle visibility of logs under each method
function toggleMethodLogs(groupId) {
var methodGroup = document.getElementById(groupId);
methodGroup.style.display = methodGroup.style.display === 'block' ? 'none' : 'block';
}
</script>
</body>
</html>
"""

return html_content
7 changes: 6 additions & 1 deletion mimicker/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,21 @@
import threading

from mimicker.handler import MimickerHandler
from mimicker.request_log import RequestLog
from mimicker.route import Route
from mimicker.stub_group import StubGroup


class MimickerServer:
def __init__(self, port: int = 8080):
self.stub_matcher = StubGroup()
self.request_logs = {}
self.server = socketserver.TCPServer(("", port), self._handler_factory)
self._thread = threading.Thread(target=self.server.serve_forever, daemon=True)
atexit.register(self.shutdown)

def _handler_factory(self, *args):
return MimickerHandler(self.stub_matcher, *args)
return MimickerHandler(self.stub_matcher, self.request_logs, *args)

def routes(self, *routes: Route):
for route in routes:
Expand All @@ -38,6 +40,9 @@ def start(self):
return self

def shutdown(self):
html_report = RequestLog.generate_html_report(self.request_logs)
with open("request_log_report.html", "w") as f:
f.write(html_report)
self.server.shutdown()
if self._thread.is_alive():
self._thread.join()
Loading

0 comments on commit f5de746

Please sign in to comment.