Source code for duck.contrib.responses.errors

"""
Helpers for fast response generation.
"""

from typing import (
    Optional,
    Union,
    Dict,
    Any,
)

from duck.settings import SETTINGS
from duck.http.request import HttpRequest
from duck.http.response import (
    HttpBadGatewayResponse,
    HttpResponse,
    HttpServerErrorResponse,
    HttpMethodNotAllowedResponse,
    HttpNotFoundResponse,
    HttpBadRequestResponse,
    HttpBadRequestSyntaxResponse,
    HttpUnsupportedVersionResponse,
    HttpRequestTimeoutResponse,
)
from duck.shortcuts import (
    simple_response,
    template_response,
)
from duck.exceptions.all import (
    RequestSyntaxError,
    RequestUnsupportedVersionError,
)
from duck.html import escape
from duck.meta import Meta
from duck.routes import RouteRegistry
from duck.logging import logger


# CSS style for debug error
debug_error_style = """
<style>
  h1 {
    color: #d32f2f; /* Deep red for error text */
  }
  .request-info .inner {
     background-color: #fdf2f2; /* Soft red background for better readability */
     border-radius: 12px;
     padding: 4px 4px;
     list-style: none;
  }
  .request-info .inner li {
    margin-left: 5px;
    color: #d32f2f; /* Deep red for error text */
  }
</style>	
"""
 
[docs] def get_debug_error_as_html(exception: Exception, request: Optional = None): """ Returns the exception as html (only if DEBUG=True, else None). """ if not SETTINGS["DEBUG"]: # return None immediately return # Expand exception exception = logger.expand_exception(exception) # Make html from exception host = None # intialize host exception = escape(exception).replace("^\n", "^\n\n").replace("\n", "\n<br>") if not request: body = f""" <div class="request-info"> <h4 class="subheading">Request</h4> <ul class="inner">Failed to retreive request metadata.</ul> </div><br><div class="exception">{exception}</div> """ body = body + debug_error_style return body # return the body and style else: # Retrieve the original host if SETTINGS["USE_DJANGO"]: # If not real https host set, fallback to http host because this request might have been included inDUCK_EXPLICIT_URLS host = request.META.get("REAL_HTTP_HOST") or request.META.get("HTTP_HOST") else: host = request.META.get("HTTP_HOST") body = f""" <div class="request-info"> <h4 class="subheading" >Request</h4> <ul class="inner"> <li><p>Path: {request.path}</p> <li><p>Method: {request.method}</p> <li><p>Host: {host}</p> <li><p>HTTP-Version: {request.http_version if request.request_store.get("h2_handling") != True else "HTTP/2"}</p> <li><p>Content-Length: {request.content_obj.size}</p> </ul> </div><br><div class="exception">{exception}</div> """ return body + debug_error_style
[docs] def get_timeout_error_response(timeout: Optional[Union[int, float]]) -> HttpRequestTimeoutResponse: """ Returns the request timeout error response. Args: timeout (Union[int, float]): The timeout in seconds. """ if SETTINGS["DEBUG"]: body = "<p>Client sent nothing in expected time it was suppose to!</p>" if timeout: body = "<p>Client sent nothing in expected time it was suppose to!</p><div>Timeout: ({timeout} seconds)</div>" response = template_response(HttpRequestTimeoutResponse, body=body) else: response = simple_response(HttpRequestTimeoutResponse) # Finally return response. return response
[docs] def get_server_error_response(exception: Exception, request: Optional = None): """ Returns HttpServerError Response for the provided exception. """ if request: request.META["DEBUG_MESSAGE"] = f"Internal Server Error: {request.path}" if SETTINGS["DEBUG"]: body = get_debug_error_as_html(exception, request) response = template_response(HttpServerErrorResponse, body=body) else: response = simple_response(HttpServerErrorResponse) return response
[docs] def get_bad_gateway_error_response(exception: Optional[Exception], request: Optional = None): """ Returns the appropriate Bad Gateway response. Args: exception (Optional[Exception]): The exception which might have caused this. The exception may be included in response. request (Optional): The current http request. """ if request: request.META["DEBUG_MESSAGE"] = f"Bad Gateway: {request.path}" if SETTINGS["DEBUG"]: body = get_debug_error_as_html(exception, request) response = template_response(HttpBadGatewayResponse, body=body) else: response = simple_response(HttpBadGatewayResponse) return response
[docs] def get_404_error_response(request: HttpRequest): """ Returns HttpNotFoundError Response for the request. """ if request: request.META["DEBUG_MESSAGE"] = f"Not Found: {request.path}" if SETTINGS["DEBUG"]: if request: body = f'<p>Specified URI not found: {request.path}</p>' else: body = f'<p>Specified URI not found</p>' if SETTINGS["USE_DJANGO"]: body += "<p>It seems like <code>USE_DJANGO=True</code> in settings, maybe this URL is registered and only known to Django.</p>" body += "<p>You may add this URL to <code>DJANGO_SIDE_URLS</code> if that is the case.</p>" # Add a list of registered routes body += "<h3>Duck tried the following routes</h3>" body += "<div class='registered-routes'>" for route, info in RouteRegistry.url_map.items(): name = list(info.items())[0][0] # Replace < and > in route with allowed html signs (if present) route = route.replace('>', "&gt;").replace('<', "&lt;") body += f"""\n <div class='route' > <strong>{route}</strong> [name='{name}'] </div> """ # Close the div tag and add style body += "</div>" style = """ <style> /* Other styles are derived from base_template_response.html */ :root { --primary-color: #FF8C00; } div.registered-routes { border-radius: var(--border-radius); background-color: #fdf2f2; /* Soft red background for better readability */ padding: 5px; } div.registered-routes .route { margin-top: 2px; color: var(--text-color); } </style> """ body += style # Create an http template response response = template_response(HttpNotFoundResponse, body=body) else: body = None response = simple_response(HttpNotFoundResponse, body=body) return response
[docs] def get_method_not_allowed_error_response(request: HttpRequest, route_info: Optional[Dict[str, Any]] = None): """ Returns HttpMethodNotAllowed Response for the request. Args: request (HttpRequest): The http request. route_info (Optional[Dict[str, Any]]): The route info for the request obtained by RouteRegistry. """ if request: request.META["DEBUG_MESSAGE"] = f"Method Not Allowed: {request.method}" if SETTINGS["DEBUG"]: if route_info: body = f'<p>Specified Method not allowed</p><div class="allowed-methods"> Allowed Methods: {[m.upper() for m in route_info["methods"]]}' else: body = "<p>Specified Method not allowed</p>" response = template_response(HttpMethodNotAllowedResponse, body=body) else: body = None response = simple_response(HttpMethodNotAllowedResponse, body=body) return response
[docs] def get_bad_request_error_response(exception: Exception, request: Optional[HttpRequest] = None): """ Returns HttpBadRequest Response for the request. Args: exception (Exception): The appropriate exception. request (Optional[HttpRequest]): The http request. """ if request: request.META["DEBUG_MESSAGE"] = f"Bad Request: {request.path}" response_cls = HttpBadRequestResponse ref = f"<p><b>Reference:</b> {exception}</p>" body = ( "<p>Bad request, there is an error in request.</p><p>" "You might need to reconstruct request in right format</p>" ) if isinstance(exception, RequestSyntaxError): response_cls = HttpBadRequestSyntaxResponse body = "<p>Bad request syntax.</p><p>You might need to reconstruct request in right format</p>" if request: request.META["DEBUG_MESSAGE"] = f"Bad Request Syntax: {request.path}" elif isinstance(exception, RequestUnsupportedVersionError): response_cls = HttpUnsupportedVersionResponse body = "<p>Unsupported HTTP version.</p><p>You might need to switch to supported protocol.</p>" if request: request.META["DEBUG_MESSAGE"] = f"Unsupported HTTP Version: {request.http_version}" else: # General request error if "'utf-8' codec can't decode byte" in str(exception) and not SETTINGS['ENABLE_HTTPS']: # This might be a client sending https traffic to our http server response_cls = HttpBadRequestResponse body = "<p>Bad request, there is an error in request.</p><p>This might be a client sending https encrypted data to our http server.</p>" if request: request.META["DEBUG_MESSAGE"] = "Bad Request: Client might be trying to send HTTPs traffic to our HTTP server" if SETTINGS["DEBUG"]: response = template_response(response_cls, body=body + ref) else: body = None response = simple_response(response_cls, body=body) return response