Skip to content

Commit

Permalink
v1.0.0 🐣
Browse files Browse the repository at this point in the history
  • Loading branch information
RobertoPrevato authored Feb 25, 2021
1 parent cf7c1c3 commit 4d61bc9
Show file tree
Hide file tree
Showing 14 changed files with 264 additions and 181 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.0] - 2021-02-25 :hatching_chick:
- Upgrades dependencies
- Improves the internal server error page and the code handling it
- Marks the web framework as stable

## [0.3.2] - 2021-01-24 :grapes:
- Logs handled and unhandled exceptions (fixes: #75)
- Adds support for [Flask Variable Rules syntax](https://flask.palletsprojects.com/en/1.1.x/quickstart/?highlight=routing#variable-rules) (ref. #76) and more granular control on the
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
include LICENSE
include README.md
include blacksheep/server/res/*.html
include blacksheep/server/res/*.css
include build_info.txt
recursive-include blacksheep *.pyx *.pxd *.pxi *.pyi *.py *.c *.h

Expand Down
30 changes: 4 additions & 26 deletions blacksheep/baseapp.pyx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .messages cimport Request, Response
from .contents cimport TextContent, HtmlContent
from .exceptions cimport HTTPException, NotFound
from .contents cimport TextContent
from .exceptions cimport HTTPException


import os
Expand Down Expand Up @@ -94,30 +94,8 @@ cdef class BaseApplication:

async def handle_internal_server_error(self, Request request, Exception exc):
if self.show_error_details:
tb = traceback.format_exception(
exc.__class__,
exc,
exc.__traceback__
)
info = ''
for item in tb:
info += f'<li><pre>{html.escape(item)}</pre></li>'

content = HtmlContent(
self.resources.error_page_html.format_map(
{
'process_id': os.getpid(),
'info': info,
'exctype': exc.__class__.__name__,
'excmessage': str(exc),
'method': request.method,
'path': request.url.value.decode()
}
)
)

return Response(500, content=content)
return Response(500, content=TextContent('Internal server error.'))
return self.server_error_details_handler.produce_response(request, exc)
return Response(500, content=TextContent("Internal server error."))

async def _apply_exception_handler(self, Request request, Exception exc, object exception_handler):
try:
Expand Down
19 changes: 7 additions & 12 deletions blacksheep/server/application.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from blacksheep.server.files import ServeFilesOptions
from blacksheep.server.errors import ServerErrorDetailsHandler
import logging
import os
from typing import (
Any,
Awaitable,
Expand Down Expand Up @@ -33,9 +34,9 @@
from blacksheep.server.bindings import ControllerParameter
from blacksheep.server.controllers import router as controllers_router
from blacksheep.server.cors import CORSPolicy, CORSStrategy, get_cors_middleware
from blacksheep.server.files import ServeFilesOptions
from blacksheep.server.files.dynamic import serve_files_dynamic
from blacksheep.server.normalization import normalize_handler, normalize_middleware
from blacksheep.server.resources import get_resource_file_content
from blacksheep.server.routing import RegisteredRoute, Router, RoutesRegistry
from blacksheep.utils import ensure_bytes, join_fragments
from guardpost.asynchronous.authentication import AuthenticationStrategy
Expand Down Expand Up @@ -63,11 +64,6 @@ async def default_headers_middleware(
return default_headers_middleware


class Resources:
def __init__(self, error_page_html: str):
self.error_page_html = error_page_html


class ApplicationEvent:
def __init__(self, context: Any) -> None:
self.__handlers: List[Callable[..., Any]] = []
Expand Down Expand Up @@ -115,26 +111,24 @@ def __init__(
self,
*,
router: Optional[Router] = None,
resources: Optional[Resources] = None,
services: Optional[Container] = None,
debug: bool = False,
show_error_details: bool = False,
show_error_details: Optional[bool] = None,
):
if router is None:
router = Router()
if services is None:
services = Container()
if show_error_details is None:
show_error_details = bool(os.environ.get("APP_SHOW_ERROR_DETAILS", False))
super().__init__(show_error_details, router)

if resources is None:
resources = Resources(get_resource_file_content("error.html"))
self.services: Container = services
self._service_provider: Optional[Services] = None
self.debug = debug
self.middlewares: List[Callable[..., Awaitable[Response]]] = []
self._default_headers: Optional[Tuple[Tuple[str, str], ...]] = None
self._middlewares_configured = False
self.resources = resources
self._cors_strategy: Optional[CORSStrategy] = None
self._authentication_strategy: Optional[AuthenticationStrategy] = None
self._authorization_strategy: Optional[AuthorizationStrategy] = None
Expand All @@ -144,6 +138,7 @@ def __init__(
self.started = False
self.controllers_router: RoutesRegistry = controllers_router
self.files_handler = FilesHandler()
self.server_error_details_handler = ServerErrorDetailsHandler()

@property
def service_provider(self) -> Services:
Expand Down
15 changes: 15 additions & 0 deletions blacksheep/server/asgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from blacksheep.messages import Request


def get_request_url(request: Request) -> str:
protocol = request.scope.get("type")
host, port = request.scope.get("server")

if protocol == "http" and port == 80:
port_part = ""
elif protocol == "https" and port == 443:
port_part = ""
else:
port_part = f":{port}"

return f"{protocol}://{host}{port_part}{request.url.value.decode()}"
7 changes: 5 additions & 2 deletions blacksheep/server/bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
from blacksheep.url import URL
from dateutil.parser import parse as dateutil_parser
from guardpost.authentication import Identity
from rodi import Services
from rodi import Services, CannotResolveTypeException

T = TypeVar("T")
TypeOrName = Union[Type, str]
Expand Down Expand Up @@ -751,7 +751,10 @@ async def get_value(self, request: Request) -> Optional[T]:
# (across parameters and middlewares)
context = None

return self.services.get(self.expected_type, context)
try:
return self.services.get(self.expected_type, context)
except CannotResolveTypeException:
return None


class ControllerParameter(BoundValue[T]):
Expand Down
48 changes: 48 additions & 0 deletions blacksheep/server/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import html
import traceback

from blacksheep.contents import HtmlContent
from blacksheep.messages import Request, Response
from blacksheep.server.asgi import get_request_url
from blacksheep.server.resources import get_resource_file_content


def _load_error_page_template() -> str:
error_css = get_resource_file_content("error.css")
error_template = get_resource_file_content("error.html")
assert "/*STYLES*/" in error_template

# since later it is used in format_map...
error_css = error_css.replace("{", "{{").replace("}", "}}")
return error_template.replace("/*STYLES*/", error_css)


class ServerErrorDetailsHandler:
"""
This class is responsible of producing a detailed response when the Application is
configured to show error details to the client, and an unhandled exception happens.
"""

def __init__(self) -> None:
self._error_page_template = _load_error_page_template()

def produce_response(self, request: Request, exc: Exception) -> Response:
tb = traceback.format_exception(exc.__class__, exc, exc.__traceback__)
info = ""
for item in tb:
info += f"<li><pre>{html.escape(item)}</pre></li>"

content = HtmlContent(
self._error_page_template.format_map(
{
"info": info,
"exctype": exc.__class__.__name__,
"excmessage": str(exc),
"method": request.method,
"path": request.url.value.decode(),
"full_url": get_request_url(request),
}
)
)

return Response(500, content=content)
89 changes: 89 additions & 0 deletions blacksheep/server/res/error.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
body {
font-family: sans-serif;
margin: 0;
padding: 0;
}

dl {
display: flex;
flex-flow: row;
flex-wrap: wrap;
overflow: visible;
}

dt {
font-weight: bold;
flex: 0 0 20%;
text-overflow: ellipsis;
overflow: hidden;
border-bottom: 1px dotted #eee;
padding: 10px 0;
}

dd {
flex: 0 0 80%;
margin-left: auto;
text-align: left;
text-overflow: ellipsis;
overflow: hidden;
padding: 10px 0;
border-bottom: 1px dotted #eee;
}

dt, dd {
padding: .3rem 0;
}

header {
background-color: #1c6962;
color: white;
padding: .5rem 2rem;
border-bottom: 1px solid #0f5046;
}

#content {
padding: .5rem 2rem;
}

.stack-trace {
font-family: monospace;
background: aliceblue;
padding: 1rem 3.5rem;
border: 1px dashed #366a62;
font-size: 14px;
}

.stack-trace pre {
padding-left: 60px;
word-break: break-word;
white-space: break-spaces;
}

.custom-counter {
margin: 0;
padding: 0 1rem 0 0;
list-style-type: none;
}

.custom-counter li {
counter-increment: step-counter;
margin-bottom: 5px;
}

.custom-counter li::before {
content: counter(step-counter);
margin-right: 20px;
font-size: 80%;
background-color: rgb(54 106 98);
color: white;
font-weight: bold;
padding: 3px 8px;
float: left;
border-radius: 11px;
margin-left: 20px;
}

.notes {
padding: 1rem 0;
font-size: small;
}
47 changes: 30 additions & 17 deletions blacksheep/server/res/error.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,37 @@
<meta charset="utf-8" />
<meta content='text/html; charset=utf-8' http-equiv='Content-Type'>
<style>
dt {{
font-weight: bold;
}}
/*STYLES*/
</style>
</head>
<body>
<h1>Internal Server Error.</h1>
<p>While handling request: {method} {path}</p>
<dl>
<dt>Exception type:</dt>
<dd>{exctype}</dd>
<dt>Exception message:</dt>
<dd>{excmessage}</dd>
</dl>
<hr/>
<p>Stack trace:</p>
<ol>{info}</ol>
<hr/>
<p>Process ID: {process_id}.</p>
<header>
<h1>Internal Server Error.</h1>
<p>While handling request: {method} {path}</p>
</header>
<div id="content">
<div>
<dl>
<dt>Exception type:</dt>
<dd>{exctype}</dd>
<dt>Exception message:</dt>
<dd>{excmessage}</dd>
<dt>Method:</dt>
<dd>{method}</dd>
<dt>URL:</dt>
<dd>{full_url}</dd>
</dl>
</div>
<hr/>
<h3>Stack trace:</h3>
<ol class="stack-trace custom-counter">{info}</ol>
<hr/>
<div class="notes">
<span>
This error is displayed for diagnostic purpose. Error details
should be hidden during normal service operation.
</span>
</div>
</div>
</body>
</html>
</html>
Loading

0 comments on commit 4d61bc9

Please sign in to comment.