mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-09-20 14:27:42 +00:00
Use ruff linter to fix issues and provide consistent formatting
This commit is contained in:
@@ -14,8 +14,7 @@ from core.config import ALLOW_SPOTTING, ALLOW_UPSTREAM_SPOTTING, RECAPTCHA_SECRE
|
||||
from core.constants import UNKNOWN_BAND
|
||||
from core.prometheus_metrics_handler import api_requests_counter
|
||||
from core.sig_utils import get_ref_regex_for_sig
|
||||
from core.utils import infer_band_from_freq
|
||||
from core.utils import safe_json_dumps
|
||||
from core.utils import infer_band_from_freq, safe_json_dumps
|
||||
from data.spot import Spot
|
||||
from providers.spot.spot_provider import SpotProvider
|
||||
|
||||
@@ -25,7 +24,12 @@ RECAPTCHA_VERIFY_URL = "https://www.google.com/recaptcha/api/siteverify"
|
||||
class APISpotHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v2/spot (POST)"""
|
||||
|
||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
application: "Application",
|
||||
request: httputil.HTTPServerRequest,
|
||||
**kwargs: Any,
|
||||
):
|
||||
self._spots = None
|
||||
self._web_server_metrics = None
|
||||
self._spot_providers = None
|
||||
@@ -53,7 +57,7 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
return
|
||||
|
||||
# Reject if format not json
|
||||
if not self.request.headers.get('Content-Type', '').startswith("application/json"):
|
||||
if not self.request.headers.get("Content-Type", "").startswith("application/json"):
|
||||
self.set_status(415)
|
||||
self.write(safe_json_dumps("Error - request Content-Type must be application/json"))
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
@@ -82,13 +86,10 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
upstream_credentials = handling.get("upstream_credentials", {})
|
||||
captcha_token = handling.get("captcha_token", None)
|
||||
|
||||
|
||||
# Spothole v2.0 release only: deny upstream spotting. Spothole API breaking changes were in v2.0 but
|
||||
# functionality is not ready yet. TODO
|
||||
submit_upstream = False
|
||||
|
||||
|
||||
|
||||
# Verify CAPTCHA if required
|
||||
if RECAPTCHA_SECRET_KEY:
|
||||
if not captcha_token:
|
||||
@@ -111,7 +112,8 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
if not spot.time or not spot.dx_call or not spot.freq or not spot.de_call:
|
||||
self.set_status(422)
|
||||
self.write(
|
||||
safe_json_dumps("Error - 'time', 'dx_call', 'freq' and 'de_call' must be provided as a minimum."))
|
||||
safe_json_dumps("Error - 'time', 'dx_call', 'freq' and 'de_call' must be provided as a minimum.")
|
||||
)
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
self.set_header("Content-Type", "application/json")
|
||||
return
|
||||
@@ -133,29 +135,37 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
# Reject if frequency not in a known band
|
||||
if infer_band_from_freq(spot.freq) == UNKNOWN_BAND:
|
||||
self.set_status(422)
|
||||
self.write(
|
||||
safe_json_dumps(f"Error - Frequency of {spot.freq / 1000.0!s}kHz is not in a known band."))
|
||||
self.write(safe_json_dumps(f"Error - Frequency of {spot.freq / 1000.0!s}kHz is not in a known band."))
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
self.set_header("Content-Type", "application/json")
|
||||
return
|
||||
|
||||
# Reject if grid formatting incorrect
|
||||
if spot.dx_grid and not re.match(
|
||||
r"^([A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}[A-X]{2}|[A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}|[A-R]{2}[0-9]{2}[A-X]{2}|[A-R]{2}[0-9]{2})$",
|
||||
spot.dx_grid.upper()):
|
||||
r"^([A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}[A-X]{2}|[A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}|[A-R]{2}[0-9]{2}[A-X]{2}|[A-R]{2}[0-9]{2})$",
|
||||
spot.dx_grid.upper(),
|
||||
):
|
||||
self.set_status(422)
|
||||
self.write(
|
||||
safe_json_dumps(f"Error - '{spot.dx_grid}' does not look like a valid Maidenhead grid."))
|
||||
self.write(safe_json_dumps(f"Error - '{spot.dx_grid}' does not look like a valid Maidenhead grid."))
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
self.set_header("Content-Type", "application/json")
|
||||
return
|
||||
|
||||
# Reject if sig_ref format incorrect for sig
|
||||
if spot.sig and spot.sig_refs and len(spot.sig_refs) > 0 and spot.sig_refs[0].id and get_ref_regex_for_sig(
|
||||
spot.sig) and not re.match(get_ref_regex_for_sig(spot.sig), spot.sig_refs[0].id):
|
||||
if (
|
||||
spot.sig
|
||||
and spot.sig_refs
|
||||
and len(spot.sig_refs) > 0
|
||||
and spot.sig_refs[0].id
|
||||
and get_ref_regex_for_sig(spot.sig)
|
||||
and not re.match(get_ref_regex_for_sig(spot.sig), spot.sig_refs[0].id)
|
||||
):
|
||||
self.set_status(422)
|
||||
self.write(safe_json_dumps(
|
||||
f"Error - '{spot.sig_refs[0].id}' does not look like a valid reference for {spot.sig}."))
|
||||
self.write(
|
||||
safe_json_dumps(
|
||||
f"Error - '{spot.sig_refs[0].id}' does not look like a valid reference for {spot.sig}."
|
||||
)
|
||||
)
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
self.set_header("Content-Type", "application/json")
|
||||
return
|
||||
@@ -185,7 +195,8 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
if not spot.dx_grid and upstream_provider_name == "Tiles":
|
||||
self.set_status(422)
|
||||
self.write(
|
||||
safe_json_dumps("Error - a grid reference is required to submit upstream to Tiles on the Air."))
|
||||
safe_json_dumps("Error - a grid reference is required to submit upstream to Tiles on the Air.")
|
||||
)
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
self.set_header("Content-Type", "application/json")
|
||||
return
|
||||
@@ -210,7 +221,9 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
upstream_warning = str(e)
|
||||
except Exception:
|
||||
logging.exception(f"Failed to submit spot upstream to {upstream_provider_name}")
|
||||
upstream_warning = f"Spot was saved locally but upstream submission to {upstream_provider_name} failed."
|
||||
upstream_warning = (
|
||||
f"Spot was saved locally but upstream submission to {upstream_provider_name} failed."
|
||||
)
|
||||
else:
|
||||
upstream_warning = f"No enabled provider named '{upstream_provider_name}' supports upstream submission for {spot.sig if spot.sig else ''} spots."
|
||||
|
||||
@@ -250,10 +263,12 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
"""Verify a Google reCAPTCHA v2 token. Returns True if valid."""
|
||||
|
||||
try:
|
||||
response = requests.post(RECAPTCHA_VERIFY_URL,
|
||||
data={"secret": RECAPTCHA_SECRET_KEY, "response": token},
|
||||
timeout=(5, 10))
|
||||
response = requests.post(
|
||||
RECAPTCHA_VERIFY_URL,
|
||||
data={"secret": RECAPTCHA_SECRET_KEY, "response": token},
|
||||
timeout=(5, 10),
|
||||
)
|
||||
return response.ok and response.json().get("success", False)
|
||||
except Exception:
|
||||
logging.exception(f"reCAPTCHA verification request failed")
|
||||
logging.exception("reCAPTCHA verification request failed")
|
||||
return False
|
||||
|
||||
@@ -17,7 +17,12 @@ from data.lookup_credentials import extract_credentials
|
||||
class APIAlertsHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v2/alerts"""
|
||||
|
||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
application: "Application",
|
||||
request: httputil.HTTPServerRequest,
|
||||
**kwargs: Any,
|
||||
):
|
||||
self._alerts = None
|
||||
self._web_server_metrics = None
|
||||
super().__init__(application, request, **kwargs)
|
||||
@@ -82,8 +87,7 @@ class APIAlertsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
|
||||
def custom_headers(self):
|
||||
"""Custom headers to avoid e.g. nginx reverse proxy from buffering SSE data"""
|
||||
|
||||
return {"Cache-Control": "no-store",
|
||||
"X-Accel-Buffering": "no"}
|
||||
return {"Cache-Control": "no-store", "X-Accel-Buffering": "no"}
|
||||
|
||||
def open(self):
|
||||
try:
|
||||
@@ -142,10 +146,10 @@ def get_alert_list_with_filters(all_alerts, query):
|
||||
a = all_alerts.get(k)
|
||||
if a is not None:
|
||||
alerts.append(a)
|
||||
alerts = sorted(alerts, key=lambda alert: (alert.start_time if alert and alert.start_time else 0))
|
||||
alerts = sorted(alerts, key=lambda alert: alert.start_time if alert and alert.start_time else 0)
|
||||
alerts = list(filter(lambda alert: alert_allowed_by_query(alert, query), alerts))
|
||||
if "limit" in query.keys():
|
||||
alerts = alerts[:int(query.get("limit"))]
|
||||
alerts = alerts[: int(query.get("limit"))]
|
||||
return alerts
|
||||
|
||||
|
||||
@@ -164,8 +168,11 @@ def alert_allowed_by_query(alert, query):
|
||||
# Check the duration if end_time is provided. If end_time is not provided, assume the activation is
|
||||
# "short", i.e. it always passes this check. If dxpeditions_skip_max_duration_check is true and
|
||||
# the alert is a dxpedition, it also always passes the check.
|
||||
if alert.is_dxpedition and (query.get(
|
||||
"dxpeditions_skip_max_duration_check").upper() == "TRUE" if "dxpeditions_skip_max_duration_check" in query.keys() else False):
|
||||
if alert.is_dxpedition and (
|
||||
query.get("dxpeditions_skip_max_duration_check").upper() == "TRUE"
|
||||
if "dxpeditions_skip_max_duration_check" in query.keys()
|
||||
else False
|
||||
):
|
||||
continue
|
||||
if alert.end_time and alert.start_time and alert.end_time - alert.start_time > max_duration:
|
||||
return False
|
||||
@@ -192,8 +199,10 @@ def alert_allowed_by_query(alert, query):
|
||||
return False
|
||||
case "text_includes":
|
||||
text_includes = query.get(k).strip()
|
||||
if (not alert.dx_call or text_includes.upper() not in alert.dx_call.upper()) \
|
||||
and (not alert.comment or text_includes.upper() not in alert.comment.upper()) \
|
||||
and (not alert.freqs_modes or text_includes.upper() not in alert.freqs_modes.upper()):
|
||||
if (
|
||||
(not alert.dx_call or text_includes.upper() not in alert.dx_call.upper())
|
||||
and (not alert.comment or text_includes.upper() not in alert.comment.upper())
|
||||
and (not alert.freqs_modes or text_includes.upper() not in alert.freqs_modes.upper())
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -21,7 +21,12 @@ BANDS_SET = frozenset(BANDS)
|
||||
class APIDxStatsHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v2/dxstats"""
|
||||
|
||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
application: "Application",
|
||||
request: httputil.HTTPServerRequest,
|
||||
**kwargs: Any,
|
||||
):
|
||||
self._spots = None
|
||||
self._web_server_metrics = None
|
||||
super().__init__(application, request, **kwargs)
|
||||
@@ -46,12 +51,15 @@ class APIDxStatsHandler(tornado.web.RequestHandler):
|
||||
continue
|
||||
if not spot.time or spot.time < one_hour_ago:
|
||||
continue
|
||||
if spot.de_continent in CONTINENTS_SET and spot.dx_continent in CONTINENTS_SET and spot.band in BANDS_SET:
|
||||
if (
|
||||
spot.de_continent in CONTINENTS_SET
|
||||
and spot.dx_continent in CONTINENTS_SET
|
||||
and spot.band in BANDS_SET
|
||||
):
|
||||
counts[spot.de_continent, spot.dx_continent, spot.band] += 1
|
||||
|
||||
result = {
|
||||
de: {dx: {band: counts[de, dx, band] for band in BANDS} for dx in CONTINENTS}
|
||||
for de in CONTINENTS
|
||||
de: {dx: {band: counts[de, dx, band] for band in BANDS} for dx in CONTINENTS} for de in CONTINENTS
|
||||
}
|
||||
|
||||
self.write(json.dumps(result))
|
||||
|
||||
@@ -10,7 +10,11 @@ from tornado.web import Application
|
||||
|
||||
from core.call_lookup_helper import get_call_info
|
||||
from core.constants import SIGS
|
||||
from core.geo_utils import lat_lon_for_grid_sw_corner_plus_size, lat_lon_to_cq_zone, lat_lon_to_itu_zone
|
||||
from core.geo_utils import (
|
||||
lat_lon_for_grid_sw_corner_plus_size,
|
||||
lat_lon_to_cq_zone,
|
||||
lat_lon_to_itu_zone,
|
||||
)
|
||||
from core.prometheus_metrics_handler import api_requests_counter
|
||||
from core.sig_lookup_helper import populate_missing_sig_ref_info
|
||||
from core.sig_utils import get_ref_regex_for_sig
|
||||
@@ -22,7 +26,12 @@ from data.sig_ref import SIGRef
|
||||
class APILookupCallHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v2/lookup/call"""
|
||||
|
||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
application: "Application",
|
||||
request: httputil.HTTPServerRequest,
|
||||
**kwargs: Any,
|
||||
):
|
||||
self._web_server_metrics = None
|
||||
super().__init__(application, request, **kwargs)
|
||||
|
||||
@@ -42,7 +51,7 @@ class APILookupCallHandler(tornado.web.RequestHandler):
|
||||
query_params = {k: v[0].decode("utf-8") for k, v in self.request.arguments.items()}
|
||||
|
||||
# The "call" query param must exist and look like a callsign
|
||||
if "call" in query_params.keys():
|
||||
if "call" in query_params:
|
||||
call = str(query_params.get("call")).upper()
|
||||
if re.match(r"^[A-Z0-9/\-]*$", call):
|
||||
credentials = extract_credentials(self.request.headers)
|
||||
@@ -68,7 +77,12 @@ class APILookupCallHandler(tornado.web.RequestHandler):
|
||||
class APILookupSIGRefHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v2/lookup/sigref"""
|
||||
|
||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
application: "Application",
|
||||
request: httputil.HTTPServerRequest,
|
||||
**kwargs: Any,
|
||||
):
|
||||
self._web_server_metrics = None
|
||||
super().__init__(application, request, **kwargs)
|
||||
|
||||
@@ -89,7 +103,7 @@ class APILookupSIGRefHandler(tornado.web.RequestHandler):
|
||||
|
||||
# "sig" and "id" query params must exist, SIG must be known, and if we have a reference regex for that SIG,
|
||||
# the provided id must match it.
|
||||
if "sig" in query_params.keys() and "id" in query_params.keys():
|
||||
if "sig" in query_params and "id" in query_params:
|
||||
sig = str(query_params.get("sig")).upper()
|
||||
ref_id = str(query_params.get("id")).upper()
|
||||
if sig in list(map(lambda p: p.name.upper(), SIGS)):
|
||||
@@ -98,8 +112,9 @@ class APILookupSIGRefHandler(tornado.web.RequestHandler):
|
||||
self.write(safe_json_dumps(data))
|
||||
|
||||
else:
|
||||
self.write(safe_json_dumps(
|
||||
f"Error - '{ref_id}' does not look like a valid reference ID for {sig}."))
|
||||
self.write(
|
||||
safe_json_dumps(f"Error - '{ref_id}' does not look like a valid reference ID for {sig}.")
|
||||
)
|
||||
self.set_status(422)
|
||||
else:
|
||||
self.write(safe_json_dumps(f"Error - sig '{sig}' is not known."))
|
||||
@@ -120,7 +135,12 @@ class APILookupSIGRefHandler(tornado.web.RequestHandler):
|
||||
class APILookupGridHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v2/lookup/grid"""
|
||||
|
||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
application: "Application",
|
||||
request: httputil.HTTPServerRequest,
|
||||
**kwargs: Any,
|
||||
):
|
||||
self._web_server_metrics = None
|
||||
super().__init__(application, request, **kwargs)
|
||||
|
||||
@@ -140,7 +160,7 @@ class APILookupGridHandler(tornado.web.RequestHandler):
|
||||
query_params = {k: v[0].decode("utf-8") for k, v in self.request.arguments.items()}
|
||||
|
||||
# "grid" query param must exist.
|
||||
if "grid" in query_params.keys():
|
||||
if "grid" in query_params:
|
||||
grid = str(query_params.get("grid")).upper()
|
||||
lat, lon, lat_cell_size, lon_cell_size = lat_lon_for_grid_sw_corner_plus_size(grid)
|
||||
if lat is not None and lon is not None and lat_cell_size is not None and lon_cell_size is not None:
|
||||
@@ -154,7 +174,7 @@ class APILookupGridHandler(tornado.web.RequestHandler):
|
||||
"latitude": center_lat,
|
||||
"longitude": center_lon,
|
||||
"cq_zone": center_cq_zone,
|
||||
"itu_zone": center_itu_zone
|
||||
"itu_zone": center_itu_zone,
|
||||
},
|
||||
"southwest": {
|
||||
"latitude": lat,
|
||||
@@ -163,7 +183,8 @@ class APILookupGridHandler(tornado.web.RequestHandler):
|
||||
"northeast": {
|
||||
"latitude": lat + lat_cell_size,
|
||||
"longitude": lon + lon_cell_size,
|
||||
}}
|
||||
},
|
||||
}
|
||||
self.write(safe_json_dumps(response))
|
||||
|
||||
else:
|
||||
|
||||
@@ -7,8 +7,15 @@ import tornado
|
||||
from tornado import httputil
|
||||
from tornado.web import Application
|
||||
|
||||
from core.config import MAX_SPOT_AGE, ALLOW_SPOTTING
|
||||
from core.constants import BANDS, ALL_MODES, MODE_TYPES, SIGS, CONTINENTS, PROPAGATION_MODES
|
||||
from core.config import ALLOW_SPOTTING, MAX_SPOT_AGE
|
||||
from core.constants import (
|
||||
ALL_MODES,
|
||||
BANDS,
|
||||
CONTINENTS,
|
||||
MODE_TYPES,
|
||||
PROPAGATION_MODES,
|
||||
SIGS,
|
||||
)
|
||||
from core.prometheus_metrics_handler import api_requests_counter
|
||||
from core.utils import safe_json_dumps
|
||||
|
||||
@@ -16,7 +23,12 @@ from core.utils import safe_json_dumps
|
||||
class APIOptionsHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v2/options"""
|
||||
|
||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
application: "Application",
|
||||
request: httputil.HTTPServerRequest,
|
||||
**kwargs: Any,
|
||||
):
|
||||
self._status_data = None
|
||||
self._web_server_metrics = None
|
||||
self._spot_providers = None
|
||||
@@ -49,13 +61,35 @@ class APIOptionsHandler(tornado.web.RequestHandler):
|
||||
# Spot/alert sources are filtered for only ones that are enabled in config, no point letting the user toggle
|
||||
# things that aren't even available.
|
||||
spot_providers: list = list(
|
||||
map(lambda p: p["name"], filter(lambda p: p["enabled"], self._status_data["spot_providers"])))
|
||||
map(
|
||||
lambda p: p["name"],
|
||||
filter(lambda p: p["enabled"], self._status_data["spot_providers"]),
|
||||
)
|
||||
)
|
||||
alert_providers = list(
|
||||
map(lambda p: p["name"], filter(lambda p: p["enabled"], self._status_data["alert_providers"])))
|
||||
map(
|
||||
lambda p: p["name"],
|
||||
filter(lambda p: p["enabled"], self._status_data["alert_providers"]),
|
||||
)
|
||||
)
|
||||
callsign_data_providers = list(
|
||||
map(lambda p: p["name"], filter(lambda p: p["enabled"], self._status_data["callsign_data_providers"])))
|
||||
map(
|
||||
lambda p: p["name"],
|
||||
filter(
|
||||
lambda p: p["enabled"],
|
||||
self._status_data["callsign_data_providers"],
|
||||
),
|
||||
)
|
||||
)
|
||||
spot_providers_enabled_by_default = list(
|
||||
map(lambda p: p["name"], filter(lambda p: p["enabled"] and p["enabled_by_default_in_web_ui"], self._status_data["spot_providers"])))
|
||||
map(
|
||||
lambda p: p["name"],
|
||||
filter(
|
||||
lambda p: p["enabled"] and p["enabled_by_default_in_web_ui"],
|
||||
self._status_data["spot_providers"],
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
# If spotting to this server is enabled, "API" is another valid spot source even though it does not come from
|
||||
# one of our providers.
|
||||
@@ -63,19 +97,21 @@ class APIOptionsHandler(tornado.web.RequestHandler):
|
||||
spot_providers.append("API")
|
||||
spot_providers_enabled_by_default.append("API")
|
||||
|
||||
options = {"bands": BANDS,
|
||||
"modes": ALL_MODES,
|
||||
"mode_types": MODE_TYPES,
|
||||
"sigs": SIGS,
|
||||
"spot_providers": spot_providers,
|
||||
"spot_providers_enabled_by_default": spot_providers_enabled_by_default,
|
||||
"alert_providers": alert_providers,
|
||||
"callsign_data_providers": callsign_data_providers,
|
||||
"continents": CONTINENTS,
|
||||
"propagation_modes": list(PROPAGATION_MODES.values()),
|
||||
"max_spot_age": MAX_SPOT_AGE,
|
||||
"spot_allowed": ALLOW_SPOTTING,
|
||||
"spot_submit_providers": spot_submit_providers}
|
||||
options = {
|
||||
"bands": BANDS,
|
||||
"modes": ALL_MODES,
|
||||
"mode_types": MODE_TYPES,
|
||||
"sigs": SIGS,
|
||||
"spot_providers": spot_providers,
|
||||
"spot_providers_enabled_by_default": spot_providers_enabled_by_default,
|
||||
"alert_providers": alert_providers,
|
||||
"callsign_data_providers": callsign_data_providers,
|
||||
"continents": CONTINENTS,
|
||||
"propagation_modes": list(PROPAGATION_MODES.values()),
|
||||
"max_spot_age": MAX_SPOT_AGE,
|
||||
"spot_allowed": ALLOW_SPOTTING,
|
||||
"spot_submit_providers": spot_submit_providers,
|
||||
}
|
||||
|
||||
self.write(safe_json_dumps(options))
|
||||
self.set_status(200)
|
||||
|
||||
@@ -14,7 +14,12 @@ from core.utils import safe_json_dumps
|
||||
class APISolarConditionsHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v2/solar"""
|
||||
|
||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
application: "Application",
|
||||
request: httputil.HTTPServerRequest,
|
||||
**kwargs: Any,
|
||||
):
|
||||
self._solar_conditions = None
|
||||
self._web_server_metrics = None
|
||||
super().__init__(application, request, **kwargs)
|
||||
|
||||
@@ -17,7 +17,12 @@ from data.lookup_credentials import extract_credentials
|
||||
class APISpotsHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v2/spots"""
|
||||
|
||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
application: "Application",
|
||||
request: httputil.HTTPServerRequest,
|
||||
**kwargs: Any,
|
||||
):
|
||||
self._spots = None
|
||||
self._web_server_metrics = None
|
||||
super().__init__(application, request, **kwargs)
|
||||
@@ -82,8 +87,7 @@ class APISpotsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
|
||||
def custom_headers(self):
|
||||
"""Custom headers to avoid e.g. nginx reverse proxy from buffering SSE data"""
|
||||
|
||||
return {"Cache-Control": "no-store",
|
||||
"X-Accel-Buffering": "no"}
|
||||
return {"Cache-Control": "no-store", "X-Accel-Buffering": "no"}
|
||||
|
||||
def open(self):
|
||||
"""Called once on the client opening a connection, set things up"""
|
||||
@@ -145,10 +149,10 @@ def get_spot_list_with_filters(all_spots, query):
|
||||
s = all_spots.get(k)
|
||||
if s is not None:
|
||||
spots.append(s)
|
||||
spots = sorted(spots, key=lambda spot: (spot.time if spot and spot.time else 0), reverse=True)
|
||||
spots = sorted(spots, key=lambda spot: spot.time if spot and spot.time else 0, reverse=True)
|
||||
spots = list(filter(lambda spot: spot_allowed_by_query(spot, query), spots))
|
||||
if "limit" in query.keys():
|
||||
spots = spots[:int(query.get("limit"))]
|
||||
spots = spots[: int(query.get("limit"))]
|
||||
|
||||
# Ensure only the latest spot of each callsign-SSID combo is present in the list. This relies on the
|
||||
# list being in reverse time order, so if any future change allows re-ordering the list, that should
|
||||
@@ -245,8 +249,9 @@ def spot_allowed_by_query(spot, query):
|
||||
return False
|
||||
case "text_includes":
|
||||
text_includes = query.get(k).strip()
|
||||
if (not spot.dx_call or text_includes.upper() not in spot.dx_call.upper()) \
|
||||
and (not spot.comment or text_includes.upper() not in spot.comment.upper()):
|
||||
if (not spot.dx_call or text_includes.upper() not in spot.dx_call.upper()) and (
|
||||
not spot.comment or text_includes.upper() not in spot.comment.upper()
|
||||
):
|
||||
return False
|
||||
case "allow_qrt":
|
||||
# If false, spots that are flagged as QRT are not returned.
|
||||
|
||||
@@ -14,7 +14,12 @@ from core.utils import safe_json_dumps
|
||||
class APIStatusHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v2/status"""
|
||||
|
||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
application: "Application",
|
||||
request: httputil.HTTPServerRequest,
|
||||
**kwargs: Any,
|
||||
):
|
||||
self._status_data = None
|
||||
self._web_server_metrics = None
|
||||
super().__init__(application, request, **kwargs)
|
||||
|
||||
@@ -12,14 +12,19 @@ from core.config import ALLOW_SPOTTING
|
||||
from core.constants import UNKNOWN_BAND
|
||||
from core.prometheus_metrics_handler import api_requests_counter
|
||||
from core.sig_utils import get_ref_regex_for_sig
|
||||
from core.utils import safe_json_dumps, infer_band_from_freq
|
||||
from core.utils import infer_band_from_freq, safe_json_dumps
|
||||
from data.spot import Spot
|
||||
|
||||
|
||||
class V1APISpotHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v1/spot (POST). Included in early Spothole v2 for backwards compatibility."""
|
||||
|
||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
application: "Application",
|
||||
request: httputil.HTTPServerRequest,
|
||||
**kwargs: Any,
|
||||
):
|
||||
self._spots = None
|
||||
self._web_server_metrics = None
|
||||
super().__init__(application, request, **kwargs)
|
||||
@@ -45,7 +50,7 @@ class V1APISpotHandler(tornado.web.RequestHandler):
|
||||
return
|
||||
|
||||
# Reject if format not json
|
||||
if not self.request.headers.get('Content-Type', '').startswith("application/json"):
|
||||
if not self.request.headers.get("Content-Type", "").startswith("application/json"):
|
||||
self.set_status(415)
|
||||
self.write(safe_json_dumps("Error - request Content-Type must be application/json"))
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
@@ -68,7 +73,9 @@ class V1APISpotHandler(tornado.web.RequestHandler):
|
||||
# Reject if no timestamp, frequency, dx_call or de_call
|
||||
if not spot.time or not spot.dx_call or not spot.freq or not spot.de_call:
|
||||
self.set_status(422)
|
||||
self.write(safe_json_dumps("Error - 'time', 'dx_call', 'freq' and 'de_call' must be provided as a minimum."))
|
||||
self.write(
|
||||
safe_json_dumps("Error - 'time', 'dx_call', 'freq' and 'de_call' must be provided as a minimum.")
|
||||
)
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
self.set_header("Content-Type", "application/json")
|
||||
return
|
||||
@@ -97,8 +104,9 @@ class V1APISpotHandler(tornado.web.RequestHandler):
|
||||
|
||||
# Reject if grid formatting incorrect
|
||||
if spot.dx_grid and not re.match(
|
||||
r"^([A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}[A-X]{2}|[A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}|[A-R]{2}[0-9]{2}[A-X]{2}|[A-R]{2}[0-9]{2})$",
|
||||
spot.dx_grid.upper()):
|
||||
r"^([A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}[A-X]{2}|[A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}|[A-R]{2}[0-9]{2}[A-X]{2}|[A-R]{2}[0-9]{2})$",
|
||||
spot.dx_grid.upper(),
|
||||
):
|
||||
self.set_status(422)
|
||||
self.write(safe_json_dumps(f"Error - '{spot.dx_grid}' does not look like a valid Maidenhead grid."))
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
@@ -106,11 +114,20 @@ class V1APISpotHandler(tornado.web.RequestHandler):
|
||||
return
|
||||
|
||||
# Reject if sig_ref format incorrect for sig
|
||||
if spot.sig and spot.sig_refs and len(spot.sig_refs) > 0 and spot.sig_refs[0].id and get_ref_regex_for_sig(
|
||||
spot.sig) and not re.match(get_ref_regex_for_sig(spot.sig), spot.sig_refs[0].id):
|
||||
if (
|
||||
spot.sig
|
||||
and spot.sig_refs
|
||||
and len(spot.sig_refs) > 0
|
||||
and spot.sig_refs[0].id
|
||||
and get_ref_regex_for_sig(spot.sig)
|
||||
and not re.match(get_ref_regex_for_sig(spot.sig), spot.sig_refs[0].id)
|
||||
):
|
||||
self.set_status(422)
|
||||
self.write(safe_json_dumps(
|
||||
f"Error - '{spot.sig_refs[0].id}' does not look like a valid reference for {spot.sig}."))
|
||||
self.write(
|
||||
safe_json_dumps(
|
||||
f"Error - '{spot.sig_refs[0].id}' does not look like a valid reference for {spot.sig}."
|
||||
)
|
||||
)
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
self.set_header("Content-Type", "application/json")
|
||||
return
|
||||
|
||||
@@ -35,7 +35,11 @@ class V1RedirectHandler(tornado.web.RequestHandler):
|
||||
if isinstance(response.headers, HTTPHeaders):
|
||||
for name, value in response.headers.get_all():
|
||||
# Let Tornado recompute these for the outgoing response
|
||||
if name.lower() not in ("content-length", "transfer-encoding", "connection"):
|
||||
if name.lower() not in (
|
||||
"content-length",
|
||||
"transfer-encoding",
|
||||
"connection",
|
||||
):
|
||||
self.add_header(name, value)
|
||||
if response.body:
|
||||
self.write(response.body)
|
||||
@@ -54,4 +58,4 @@ class V1RedirectHandler(tornado.web.RequestHandler):
|
||||
await self._proxy(path)
|
||||
|
||||
async def patch(self, path):
|
||||
await self._proxy(path)
|
||||
await self._proxy(path)
|
||||
|
||||
@@ -13,6 +13,7 @@ class V1APISpotsHandler(APISpotsHandler):
|
||||
chunk = _GRID_SOURCE_RE.sub('"dx_location_source": "SPOT"', chunk)
|
||||
super().write(chunk)
|
||||
|
||||
|
||||
class V1APISpotsStreamHandler(APISpotsStreamHandler):
|
||||
"""API request handler for /api/v1/spots/stream (SSE). Included in early Spothole v2 for backwards compatibility."""
|
||||
|
||||
@@ -24,4 +25,4 @@ class V1APISpotsStreamHandler(APISpotsStreamHandler):
|
||||
for k, v in kwargs.items():
|
||||
if isinstance(v, str) and '"dx_location_source"' in v:
|
||||
kwargs[k] = _GRID_SOURCE_RE.sub('"dx_location_source": "SPOT"', v)
|
||||
super().write_message(*args, **kwargs)
|
||||
super().write_message(*args, **kwargs)
|
||||
|
||||
@@ -10,4 +10,4 @@ class PrometheusMetricsHandler(tornado.web.RequestHandler):
|
||||
def get(self):
|
||||
self.write(get_metrics())
|
||||
self.set_status(200)
|
||||
self.set_header('Content-Type', CONTENT_TYPE_LATEST)
|
||||
self.set_header("Content-Type", CONTENT_TYPE_LATEST)
|
||||
|
||||
@@ -6,7 +6,7 @@ import tornado
|
||||
from tornado import httputil
|
||||
from tornado.web import Application
|
||||
|
||||
from core.config import ALLOW_SPOTTING, WEB_UI_OPTIONS, BASE_URL, SERVER_OWNER_CALLSIGN
|
||||
from core.config import ALLOW_SPOTTING, BASE_URL, SERVER_OWNER_CALLSIGN, WEB_UI_OPTIONS
|
||||
from core.constants import SOFTWARE_VERSION
|
||||
from core.prometheus_metrics_handler import page_requests_counter
|
||||
|
||||
@@ -14,7 +14,12 @@ from core.prometheus_metrics_handler import page_requests_counter
|
||||
class PageTemplateHandler(tornado.web.RequestHandler):
|
||||
"""Handler for all HTML pages generated from templates"""
|
||||
|
||||
def __init__(self, application: "Application", request: httputil.HTTPServerRequest, **kwargs: Any):
|
||||
def __init__(
|
||||
self,
|
||||
application: "Application",
|
||||
request: httputil.HTTPServerRequest,
|
||||
**kwargs: Any,
|
||||
):
|
||||
self._template_name = None
|
||||
self._web_server_metrics = None
|
||||
super().__init__(application, request, **kwargs)
|
||||
@@ -31,6 +36,12 @@ class PageTemplateHandler(tornado.web.RequestHandler):
|
||||
page_requests_counter.inc()
|
||||
|
||||
# Load named template, and provide variables used in templates
|
||||
self.render(f"{self._template_name}.html", software_version=SOFTWARE_VERSION,
|
||||
server_owner_callsign=SERVER_OWNER_CALLSIGN, allow_spotting=ALLOW_SPOTTING,
|
||||
web_ui_options=WEB_UI_OPTIONS, baseurl=BASE_URL, current_path=self.request.path)
|
||||
self.render(
|
||||
f"{self._template_name}.html",
|
||||
software_version=SOFTWARE_VERSION,
|
||||
server_owner_callsign=SERVER_OWNER_CALLSIGN,
|
||||
allow_spotting=ALLOW_SPOTTING,
|
||||
web_ui_options=WEB_UI_OPTIONS,
|
||||
baseurl=BASE_URL,
|
||||
current_path=self.request.path,
|
||||
)
|
||||
|
||||
@@ -25,7 +25,7 @@ class SSEBroadcaster:
|
||||
self._handlers.discard(handler)
|
||||
|
||||
def publish(self, value):
|
||||
self._loop.add_callback(self._broadcast, value)
|
||||
self._loop.add_callback(self._broadcast, value)
|
||||
|
||||
def _broadcast(self, value):
|
||||
with self._lock:
|
||||
@@ -36,4 +36,4 @@ class SSEBroadcaster:
|
||||
except Exception:
|
||||
# Connection probably dropped, ignore and de-register the handler to stop getting future items.
|
||||
logging.debug("Failed to push to an SSE client; dropping it")
|
||||
self.unregister(handler)
|
||||
self.unregister(handler)
|
||||
|
||||
+131
-36
@@ -5,13 +5,23 @@ import os
|
||||
import tornado
|
||||
from tornado.web import StaticFileHandler
|
||||
|
||||
from core.config import ALLOW_SPOTTING, WEB_SERVER_PORT, API_ONLY_MODE, LOG_WEB_REQUESTS, BASE_URL
|
||||
from core.config import (
|
||||
ALLOW_SPOTTING,
|
||||
API_ONLY_MODE,
|
||||
BASE_URL,
|
||||
LOG_WEB_REQUESTS,
|
||||
WEB_SERVER_PORT,
|
||||
)
|
||||
from core.data_providers import DATA_PROVIDERS
|
||||
from core.data_store import DATA_STORE
|
||||
from server.handlers.api.addspot import APISpotHandler
|
||||
from server.handlers.api.alerts import APIAlertsHandler, APIAlertsStreamHandler
|
||||
from server.handlers.api.dxstats import APIDxStatsHandler
|
||||
from server.handlers.api.lookups import APILookupCallHandler, APILookupSIGRefHandler, APILookupGridHandler
|
||||
from server.handlers.api.lookups import (
|
||||
APILookupCallHandler,
|
||||
APILookupGridHandler,
|
||||
APILookupSIGRefHandler,
|
||||
)
|
||||
from server.handlers.api.options import APIOptionsHandler
|
||||
from server.handlers.api.solar_conditions import APISolarConditionsHandler
|
||||
from server.handlers.api.spots import APISpotsHandler, APISpotsStreamHandler
|
||||
@@ -45,7 +55,7 @@ class WebServer:
|
||||
"last_api_access_time": None,
|
||||
"page_access_counter": 0,
|
||||
"api_access_counter": 0,
|
||||
"status": "Starting"
|
||||
"status": "Starting",
|
||||
}
|
||||
|
||||
def setup(self):
|
||||
@@ -76,30 +86,73 @@ class WebServer:
|
||||
|
||||
# API endpoints are always enabled
|
||||
api_routes = [
|
||||
(r"/api/v2/spots", APISpotsHandler, {"spots": self._data_store.spots, **handler_opts}),
|
||||
(r"/api/v2/alerts", APIAlertsHandler, {"alerts": self._data_store.alerts, **handler_opts}),
|
||||
(r"/api/v2/spots/stream", APISpotsStreamHandler,
|
||||
{"sse_spot_broadcaster": self._spot_broadcaster, **handler_opts}),
|
||||
(r"/api/v2/alerts/stream", APIAlertsStreamHandler,
|
||||
{"sse_alert_broadcaster": self._alert_broadcaster, **handler_opts}),
|
||||
(r"/api/v2/solar", APISolarConditionsHandler, {"solar_conditions": self._data_store.solar_conditions,
|
||||
**handler_opts}),
|
||||
(r"/api/v2/dxstats", APIDxStatsHandler, {"spots": self._data_store.spots, **handler_opts}),
|
||||
(r"/api/v2/options", APIOptionsHandler, {"status_data": self._data_store.status_data, **handler_opts}),
|
||||
(r"/api/v2/status", APIStatusHandler, {"status_data": self._data_store.status_data, **handler_opts}),
|
||||
(
|
||||
r"/api/v2/spots",
|
||||
APISpotsHandler,
|
||||
{"spots": self._data_store.spots, **handler_opts},
|
||||
),
|
||||
(
|
||||
r"/api/v2/alerts",
|
||||
APIAlertsHandler,
|
||||
{"alerts": self._data_store.alerts, **handler_opts},
|
||||
),
|
||||
(
|
||||
r"/api/v2/spots/stream",
|
||||
APISpotsStreamHandler,
|
||||
{"sse_spot_broadcaster": self._spot_broadcaster, **handler_opts},
|
||||
),
|
||||
(
|
||||
r"/api/v2/alerts/stream",
|
||||
APIAlertsStreamHandler,
|
||||
{"sse_alert_broadcaster": self._alert_broadcaster, **handler_opts},
|
||||
),
|
||||
(
|
||||
r"/api/v2/solar",
|
||||
APISolarConditionsHandler,
|
||||
{"solar_conditions": self._data_store.solar_conditions, **handler_opts},
|
||||
),
|
||||
(
|
||||
r"/api/v2/dxstats",
|
||||
APIDxStatsHandler,
|
||||
{"spots": self._data_store.spots, **handler_opts},
|
||||
),
|
||||
(
|
||||
r"/api/v2/options",
|
||||
APIOptionsHandler,
|
||||
{"status_data": self._data_store.status_data, **handler_opts},
|
||||
),
|
||||
(
|
||||
r"/api/v2/status",
|
||||
APIStatusHandler,
|
||||
{"status_data": self._data_store.status_data, **handler_opts},
|
||||
),
|
||||
(r"/api/v2/lookup/call", APILookupCallHandler, {**handler_opts}),
|
||||
(r"/api/v2/lookup/sigref", APILookupSIGRefHandler, {**handler_opts}),
|
||||
(r"/api/v2/lookup/grid", APILookupGridHandler, {**handler_opts}),
|
||||
(r"/api/v2/spot", APISpotHandler,
|
||||
{"spots": self._data_store.spots, "spot_providers": self._data_providers, **handler_opts}),
|
||||
(
|
||||
r"/api/v2/spot",
|
||||
APISpotHandler,
|
||||
{
|
||||
"spots": self._data_store.spots,
|
||||
"spot_providers": self._data_providers,
|
||||
**handler_opts,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
# v1 API redirects. Most v1 enpoints are unchanged in v2, and get an HTTP 308 redirect to the v2 API. The ones
|
||||
# that have the major breaking changes get a bespoke handler.
|
||||
v1_compat_routes = [
|
||||
(r"/api/v1/spots", V1APISpotsHandler, {"spots": self._data_store.spots, **handler_opts}),
|
||||
(r"/api/v1/spots/stream", V1APISpotsStreamHandler,
|
||||
{"sse_spot_broadcaster": self._spot_broadcaster, **handler_opts}),
|
||||
(
|
||||
r"/api/v1/spots",
|
||||
V1APISpotsHandler,
|
||||
{"spots": self._data_store.spots, **handler_opts},
|
||||
),
|
||||
(
|
||||
r"/api/v1/spots/stream",
|
||||
V1APISpotsStreamHandler,
|
||||
{"sse_spot_broadcaster": self._spot_broadcaster, **handler_opts},
|
||||
),
|
||||
(r"/api/v1/spot", V1APISpotHandler),
|
||||
(r"/api/v1/(.*)", V1RedirectHandler),
|
||||
]
|
||||
@@ -108,38 +161,82 @@ class WebServer:
|
||||
if self._api_only_mode:
|
||||
logging.info("API-only mode is enabled. Web UI will not be served.")
|
||||
ui_routes = [
|
||||
(r"/", PageTemplateHandler, {"template_name": "api_only_home", **handler_opts})
|
||||
(
|
||||
r"/",
|
||||
PageTemplateHandler,
|
||||
{"template_name": "api_only_home", **handler_opts},
|
||||
)
|
||||
]
|
||||
else:
|
||||
ui_routes = [
|
||||
(r"/", PageTemplateHandler, {"template_name": "spots", **handler_opts}),
|
||||
(r"/map", PageTemplateHandler, {"template_name": "map", **handler_opts}),
|
||||
(r"/bands", PageTemplateHandler, {"template_name": "bands", **handler_opts}),
|
||||
(r"/alerts", PageTemplateHandler, {"template_name": "alerts", **handler_opts}),
|
||||
(r"/conditions", PageTemplateHandler, {"template_name": "conditions", **handler_opts}),
|
||||
(r"/status", PageTemplateHandler, {"template_name": "status", **handler_opts}),
|
||||
(r"/about", PageTemplateHandler, {"template_name": "about", **handler_opts})
|
||||
(
|
||||
r"/map",
|
||||
PageTemplateHandler,
|
||||
{"template_name": "map", **handler_opts},
|
||||
),
|
||||
(
|
||||
r"/bands",
|
||||
PageTemplateHandler,
|
||||
{"template_name": "bands", **handler_opts},
|
||||
),
|
||||
(
|
||||
r"/alerts",
|
||||
PageTemplateHandler,
|
||||
{"template_name": "alerts", **handler_opts},
|
||||
),
|
||||
(
|
||||
r"/conditions",
|
||||
PageTemplateHandler,
|
||||
{"template_name": "conditions", **handler_opts},
|
||||
),
|
||||
(
|
||||
r"/status",
|
||||
PageTemplateHandler,
|
||||
{"template_name": "status", **handler_opts},
|
||||
),
|
||||
(
|
||||
r"/about",
|
||||
PageTemplateHandler,
|
||||
{"template_name": "about", **handler_opts},
|
||||
),
|
||||
]
|
||||
# Only allow the Add Spot page if spotting is allowed
|
||||
if ALLOW_SPOTTING:
|
||||
ui_routes += [(r"/add-spot", PageTemplateHandler, {"template_name": "add_spot", **handler_opts})]
|
||||
ui_routes += [
|
||||
(
|
||||
r"/add-spot",
|
||||
PageTemplateHandler,
|
||||
{"template_name": "add_spot", **handler_opts},
|
||||
)
|
||||
]
|
||||
|
||||
# API docs, Prometheus metrics, webapp manifest and static assets are always available regardless of API-only
|
||||
# mode.
|
||||
misc_routes = [
|
||||
(r"/apidocs", PageTemplateHandler, {"template_name": "apidocs", **handler_opts}),
|
||||
(
|
||||
r"/apidocs",
|
||||
PageTemplateHandler,
|
||||
{"template_name": "apidocs", **handler_opts},
|
||||
),
|
||||
(r"/metrics", PrometheusMetricsHandler),
|
||||
(r"/manifest.webmanifest", ManifestHandler),
|
||||
# If e.g. nginx is configured as a reverse proxy with a hard-coded path to static files, as per the README,
|
||||
# this will never have to handle anything, but having it here allows Spothole to work without nginx for
|
||||
# testing.
|
||||
(r"/static/(.*)", StaticFileHandler, {"path": os.path.join(_HERE, "../static")})
|
||||
(
|
||||
r"/static/(.*)",
|
||||
StaticFileHandler,
|
||||
{"path": os.path.join(_HERE, "../static")},
|
||||
),
|
||||
]
|
||||
|
||||
app = tornado.web.Application(api_routes + v1_compat_routes + ui_routes + misc_routes,
|
||||
template_path=os.path.join(_HERE, "../templates"),
|
||||
log_function=request_log,
|
||||
debug=False)
|
||||
app = tornado.web.Application(
|
||||
api_routes + v1_compat_routes + ui_routes + misc_routes,
|
||||
template_path=os.path.join(_HERE, "../templates"),
|
||||
log_function=request_log,
|
||||
debug=False,
|
||||
)
|
||||
app.listen(self._port, xheaders=True)
|
||||
logging.info(f"Web server running on port {WEB_SERVER_PORT!s}")
|
||||
logging.info(f"You can access your copy of Spothole at {BASE_URL}")
|
||||
@@ -162,9 +259,7 @@ def request_log(handler):
|
||||
user_agent = request.headers.get("User-Agent", "-")
|
||||
|
||||
log_method(
|
||||
f'{client_ip} - "{request.method} {request.uri}" '
|
||||
f'{handler.get_status()} {request.request_time():.2f}ms | '
|
||||
f'Ref: {referrer} | UA: {user_agent}'
|
||||
f'{client_ip} - "{request.method} {request.uri}" {handler.get_status()} {request.request_time():.2f}ms | Ref: {referrer} | UA: {user_agent}'
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user