143 lines
4.3 KiB
Python
143 lines
4.3 KiB
Python
"""Data models for HTTP entries."""
|
|
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime
|
|
from typing import Any
|
|
|
|
|
|
@dataclass
|
|
class Request:
|
|
"""Represents an HTTP request."""
|
|
|
|
method: str
|
|
url: str
|
|
http_version: str = "HTTP/1.1"
|
|
headers: dict[str, str] = field(default_factory=dict)
|
|
body: str | None = None
|
|
query_params: dict[str, str] = field(default_factory=dict)
|
|
|
|
def __post_init__(self) -> None:
|
|
if isinstance(self.headers, list):
|
|
self.headers = {h.get("name", ""): h.get("value", "") for h in self.headers}
|
|
|
|
|
|
@dataclass
|
|
class Response:
|
|
"""Represents an HTTP response."""
|
|
|
|
status: int
|
|
status_text: str
|
|
http_version: str = "HTTP/1.1"
|
|
headers: dict[str, str] = field(default_factory=dict)
|
|
body: str | None = None
|
|
content_type: str | None = None
|
|
response_time_ms: float | None = None
|
|
|
|
def __post_init__(self) -> None:
|
|
if isinstance(self.headers, list):
|
|
self.headers = {h.get("name", ""): h.get("value", "") for h in self.headers}
|
|
|
|
|
|
@dataclass
|
|
class HTTPEntry:
|
|
"""Represents a complete HTTP request/response pair."""
|
|
|
|
id: str
|
|
request: Request
|
|
response: Response
|
|
timestamp: datetime | None = None
|
|
server_ip: str | None = None
|
|
connection: str | None = None
|
|
raw_size: int | None = None
|
|
source_file: str | None = None
|
|
|
|
@property
|
|
def duration_ms(self) -> float | None:
|
|
"""Get response time in milliseconds."""
|
|
return self.response.response_time_ms
|
|
|
|
@property
|
|
def content_type(self) -> str | None:
|
|
"""Get content type from response headers."""
|
|
if self.response.content_type:
|
|
return self.response.content_type
|
|
for key, value in self.response.headers.items():
|
|
if key.lower() == "content-type":
|
|
return value
|
|
return None
|
|
|
|
@property
|
|
def endpoint(self) -> str:
|
|
"""Extract endpoint path from URL."""
|
|
from urllib.parse import urlparse
|
|
|
|
parsed = urlparse(self.request.url)
|
|
return parsed.path or "/"
|
|
|
|
@property
|
|
def host(self) -> str:
|
|
"""Extract host from URL."""
|
|
from urllib.parse import urlparse
|
|
|
|
parsed = urlparse(self.request.url)
|
|
return parsed.netloc
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
"""Convert to dictionary representation."""
|
|
return {
|
|
"id": self.id,
|
|
"request": {
|
|
"method": self.request.method,
|
|
"url": self.request.url,
|
|
"http_version": self.request.http_version,
|
|
"headers": self.request.headers,
|
|
"body": self.request.body,
|
|
"query_params": self.request.query_params,
|
|
},
|
|
"response": {
|
|
"status": self.response.status,
|
|
"status_text": self.response.status_text,
|
|
"http_version": self.response.http_version,
|
|
"headers": self.response.headers,
|
|
"body": self.response.body,
|
|
"content_type": self.response.content_type,
|
|
"response_time_ms": self.response.response_time_ms,
|
|
},
|
|
"timestamp": self.timestamp.isoformat() if self.timestamp else None,
|
|
"server_ip": self.server_ip,
|
|
"connection": self.connection,
|
|
"raw_size": self.raw_size,
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class FilterCriteria:
|
|
"""Criteria for filtering HTTP entries."""
|
|
|
|
methods: list[str] | None = None
|
|
status_codes: list[int] | None = None
|
|
url_pattern: str | None = None
|
|
content_types: list[str] | None = None
|
|
start_time: datetime | None = None
|
|
end_time: datetime | None = None
|
|
min_response_time_ms: float | None = None
|
|
max_response_time_ms: float | None = None
|
|
request_body_contains: str | None = None
|
|
response_body_contains: str | None = None
|
|
|
|
|
|
@dataclass
|
|
class DiffResult:
|
|
"""Represents the result of comparing two HTTP entries."""
|
|
|
|
entry1_id: str
|
|
entry2_id: str
|
|
request_headers_diff: list[str] = field(default_factory=list)
|
|
request_body_diff: list[str] = field(default_factory=list)
|
|
response_headers_diff: list[str] = field(default_factory=list)
|
|
response_body_diff: list[str] = field(default_factory=list)
|
|
status_changed: bool = False
|
|
status1: int = 0
|
|
status2: int = 0
|
|
url_changed: bool = False
|