create-caspian-app 0.2.0-beta.98 → 0.2.0-beta.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.py CHANGED
@@ -1,11 +1,13 @@
1
1
  from casp.components_compiler import transform_components
2
2
  from casp.scripts_type import transform_scripts
3
+ import asyncio
3
4
  import inspect
4
5
  import os
5
6
  import importlib.util
6
7
  import secrets
7
8
  import traceback
8
9
  import json
10
+ import time
9
11
  from pathlib import Path
10
12
  from fastapi import FastAPI, Request, Response
11
13
  from fastapi.responses import RedirectResponse, FileResponse, HTMLResponse
@@ -81,6 +83,10 @@ MAX_CONTENT_LENGTH_MB = int(os.getenv('MAX_CONTENT_LENGTH_MB', 16))
81
83
  IS_PRODUCTION = os.getenv('APP_ENV') == 'production'
82
84
  CACHE_ENABLED = os.getenv('CACHE_ENABLED', 'false').lower() == 'true'
83
85
  DEFAULT_TTL = int(os.getenv('CACHE_TTL', 600))
86
+ REQUEST_TIMEOUT_SECONDS = max(
87
+ 1.0,
88
+ float(os.getenv('CASPIAN_REQUEST_TIMEOUT_SECONDS', 20)),
89
+ )
84
90
 
85
91
 
86
92
  def _client_error_message(exc: Exception) -> str:
@@ -299,6 +305,57 @@ class RPCMiddleware:
299
305
  return
300
306
  await self.app(scope, receive, send)
301
307
 
308
+
309
+ class RequestDiagnosticsMiddleware:
310
+ """Log request start/end in dev and fail visibly when a route stalls."""
311
+
312
+ def __init__(self, app: ASGIApp): self.app = app
313
+
314
+ async def __call__(self, scope: Scope, receive: Receive, send: Send):
315
+ if scope["type"] != "http":
316
+ await self.app(scope, receive, send)
317
+ return
318
+
319
+ method = scope.get("method", "GET")
320
+ path = scope.get("path", "")
321
+ should_log = not path.startswith(('/css/', '/js/', '/assets/', '/favicon.ico'))
322
+ started = time.perf_counter()
323
+
324
+ if should_log and not IS_PRODUCTION:
325
+ print(f"[request:start] {method} {path}", flush=True)
326
+
327
+ try:
328
+ await asyncio.wait_for(
329
+ self.app(scope, receive, send),
330
+ timeout=REQUEST_TIMEOUT_SECONDS,
331
+ )
332
+ except asyncio.TimeoutError:
333
+ elapsed_ms = int((time.perf_counter() - started) * 1000)
334
+ print(
335
+ f"[request:timeout] {method} {path} exceeded "
336
+ f"{REQUEST_TIMEOUT_SECONDS:g}s after {elapsed_ms}ms",
337
+ flush=True,
338
+ )
339
+ response = HTMLResponse(
340
+ content=(
341
+ "<h1>504 - Request Timeout</h1>"
342
+ "<p>The route took too long to respond. "
343
+ "Check the development terminal for the stalled path.</p>"
344
+ ),
345
+ status_code=504,
346
+ )
347
+ await response(scope, receive, send)
348
+ return
349
+ except Exception:
350
+ if should_log and not IS_PRODUCTION:
351
+ elapsed_ms = int((time.perf_counter() - started) * 1000)
352
+ print(f"[request:error] {method} {path} after {elapsed_ms}ms", flush=True)
353
+ raise
354
+ finally:
355
+ if should_log and not IS_PRODUCTION:
356
+ elapsed_ms = int((time.perf_counter() - started) * 1000)
357
+ print(f"[request:end] {method} {path} {elapsed_ms}ms", flush=True)
358
+
302
359
  # ====
303
360
  # Route Registration
304
361
  # ====
@@ -659,6 +716,7 @@ app.add_middleware(
659
716
  path='/',
660
717
  )
661
718
  app.add_middleware(SecurityHeadersMiddleware)
719
+ app.add_middleware(RequestDiagnosticsMiddleware)
662
720
 
663
721
  if __name__ == '__main__':
664
722
  port = int(os.getenv('PORT', 5091))