Re-upload: CI infrastructure issue resolved, all tests verified passing
This commit is contained in:
196
http_log_explorer/analyzers/traffic_analyzer.py
Normal file
196
http_log_explorer/analyzers/traffic_analyzer.py
Normal file
@@ -0,0 +1,196 @@
|
||||
"""Traffic analyzer for filtering HTTP entries."""
|
||||
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
|
||||
from http_log_explorer.models import FilterCriteria, HTTPEntry
|
||||
|
||||
|
||||
class TrafficAnalyzer:
|
||||
"""Analyzer for filtering and searching HTTP entries."""
|
||||
|
||||
def __init__(self, entries: list[HTTPEntry]) -> None:
|
||||
"""Initialize with HTTP entries.
|
||||
|
||||
Args:
|
||||
entries: List of HTTPEntry objects to analyze
|
||||
"""
|
||||
self.entries = entries
|
||||
|
||||
def filter(self, criteria: FilterCriteria) -> list[HTTPEntry]:
|
||||
"""Filter entries based on criteria.
|
||||
|
||||
Args:
|
||||
criteria: FilterCriteria object with filtering rules
|
||||
|
||||
Returns:
|
||||
Filtered list of HTTPEntry objects
|
||||
"""
|
||||
predicates: list[Callable[[HTTPEntry], bool]] = []
|
||||
|
||||
if criteria.methods:
|
||||
predicates.append(lambda e: e.request.method in criteria.methods)
|
||||
|
||||
if criteria.status_codes:
|
||||
predicates.append(lambda e: e.response.status in criteria.status_codes)
|
||||
|
||||
if criteria.url_pattern:
|
||||
pattern = re.compile(criteria.url_pattern)
|
||||
predicates.append(lambda e: bool(pattern.search(e.request.url)))
|
||||
|
||||
if criteria.content_types:
|
||||
predicates.append(lambda e: bool(e.content_type and any(ct in e.content_type for ct in criteria.content_types)))
|
||||
|
||||
if criteria.start_time:
|
||||
predicates.append(lambda e: bool(e.timestamp and e.timestamp >= criteria.start_time))
|
||||
|
||||
if criteria.end_time:
|
||||
predicates.append(lambda e: bool(e.timestamp and e.timestamp <= criteria.end_time))
|
||||
|
||||
if criteria.min_response_time_ms is not None:
|
||||
predicates.append(lambda e: bool(e.duration_ms and e.duration_ms >= criteria.min_response_time_ms))
|
||||
|
||||
if criteria.max_response_time_ms is not None:
|
||||
predicates.append(lambda e: bool(e.duration_ms and e.duration_ms <= criteria.max_response_time_ms))
|
||||
|
||||
if criteria.request_body_contains:
|
||||
predicates.append(
|
||||
lambda e: bool(e.request.body and criteria.request_body_contains in e.request.body)
|
||||
)
|
||||
|
||||
if criteria.response_body_contains:
|
||||
predicates.append(
|
||||
lambda e: bool(e.response.body and criteria.response_body_contains in e.response.body)
|
||||
)
|
||||
|
||||
if not predicates:
|
||||
return list(self.entries)
|
||||
|
||||
return [entry for entry in self.entries if all(pred(entry) for pred in predicates)]
|
||||
|
||||
def by_method(self, methods: list[str]) -> list[HTTPEntry]:
|
||||
"""Filter by HTTP methods.
|
||||
|
||||
Args:
|
||||
methods: List of methods (GET, POST, PUT, DELETE, etc.)
|
||||
|
||||
Returns:
|
||||
Filtered entries
|
||||
"""
|
||||
criteria = FilterCriteria(methods=methods)
|
||||
return self.filter(criteria)
|
||||
|
||||
def by_status(self, status_codes: list[int]) -> list[HTTPEntry]:
|
||||
"""Filter by status codes.
|
||||
|
||||
Args:
|
||||
status_codes: List of status codes to include
|
||||
|
||||
Returns:
|
||||
Filtered entries
|
||||
"""
|
||||
criteria = FilterCriteria(status_codes=status_codes)
|
||||
return self.filter(criteria)
|
||||
|
||||
def by_url(self, url_pattern: str) -> list[HTTPEntry]:
|
||||
"""Filter by URL pattern.
|
||||
|
||||
Args:
|
||||
url_pattern: Regular expression pattern to match URLs
|
||||
|
||||
Returns:
|
||||
Filtered entries
|
||||
"""
|
||||
criteria = FilterCriteria(url_pattern=url_pattern)
|
||||
return self.filter(criteria)
|
||||
|
||||
def by_content_type(self, content_types: list[str]) -> list[HTTPEntry]:
|
||||
"""Filter by content types.
|
||||
|
||||
Args:
|
||||
content_types: List of content type substrings to match
|
||||
|
||||
Returns:
|
||||
Filtered entries
|
||||
"""
|
||||
criteria = FilterCriteria(content_types=content_types)
|
||||
return self.filter(criteria)
|
||||
|
||||
def by_status_range(self, min_status: int, max_status: int) -> list[HTTPEntry]:
|
||||
"""Filter by status code range.
|
||||
|
||||
Args:
|
||||
min_status: Minimum status code (inclusive)
|
||||
max_status: Maximum status code (inclusive)
|
||||
|
||||
Returns:
|
||||
Filtered entries
|
||||
"""
|
||||
all_in_range = list(range(min_status, max_status + 1))
|
||||
return self.by_status(all_in_range)
|
||||
|
||||
def successful_requests(self) -> list[HTTPEntry]:
|
||||
"""Get all 2xx responses.
|
||||
|
||||
Returns:
|
||||
Entries with 2xx status codes
|
||||
"""
|
||||
return self.by_status_range(200, 299)
|
||||
|
||||
def client_errors(self) -> list[HTTPEntry]:
|
||||
"""Get all 4xx responses.
|
||||
|
||||
Returns:
|
||||
Entries with 4xx status codes
|
||||
"""
|
||||
return self.by_status_range(400, 499)
|
||||
|
||||
def server_errors(self) -> list[HTTPEntry]:
|
||||
"""Get all 5xx responses.
|
||||
|
||||
Returns:
|
||||
Entries with 5xx status codes
|
||||
"""
|
||||
return self.by_status_range(500, 599)
|
||||
|
||||
def search(self, query: str, case_sensitive: bool = False) -> list[HTTPEntry]:
|
||||
"""Search across URL, request body, and response body.
|
||||
|
||||
Args:
|
||||
query: Search string
|
||||
case_sensitive: Whether search should be case sensitive
|
||||
|
||||
Returns:
|
||||
Entries matching the query
|
||||
"""
|
||||
search_query = query if case_sensitive else query.lower()
|
||||
|
||||
def matches(entry: HTTPEntry) -> bool:
|
||||
url = entry.request.url if case_sensitive else entry.request.url.lower()
|
||||
if search_query in url:
|
||||
return True
|
||||
if entry.request.body:
|
||||
body = entry.request.body if case_sensitive else entry.request.body.lower()
|
||||
if search_query in body:
|
||||
return True
|
||||
if entry.response.body:
|
||||
body = entry.response.body if case_sensitive else entry.response.body.lower()
|
||||
if search_query in body:
|
||||
return True
|
||||
return False
|
||||
|
||||
return [e for e in self.entries if matches(e)]
|
||||
|
||||
def get_entry_by_id(self, entry_id: str) -> HTTPEntry | None:
|
||||
"""Get a specific entry by its ID.
|
||||
|
||||
Args:
|
||||
entry_id: The entry ID to find
|
||||
|
||||
Returns:
|
||||
The HTTPEntry or None if not found
|
||||
"""
|
||||
for entry in self.entries:
|
||||
if entry.id == entry_id:
|
||||
return entry
|
||||
return None
|
||||
Reference in New Issue
Block a user