-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #165 from vkottler/dev/3.5.0
Starting new HTTP server implementation
- Loading branch information
Showing
18 changed files
with
355 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
--- | ||
major: 3 | ||
minor: 4 | ||
patch: 1 | ||
minor: 5 | ||
patch: 0 | ||
entry: runtimepy |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
""" | ||
A module implementing a server interface for this package. | ||
""" | ||
|
||
# built-in | ||
from io import StringIO | ||
from typing import Optional | ||
|
||
# third-party | ||
from vcorelib.io import JsonObject | ||
from vcorelib.paths import find_file | ||
|
||
# internal | ||
from runtimepy import PKG_NAME | ||
from runtimepy.net.http.header import RequestHeader | ||
from runtimepy.net.http.response import ResponseHeader | ||
from runtimepy.net.server.html import ( | ||
HtmlApp, | ||
HtmlApps, | ||
default_html_app, | ||
html_handler, | ||
) | ||
from runtimepy.net.server.json import json_handler | ||
from runtimepy.net.tcp.http import HttpConnection | ||
|
||
|
||
class RuntimepyServerConnection(HttpConnection): | ||
"""A class implementing a server-connection interface for this package.""" | ||
|
||
# Can register application methods to URL paths. | ||
apps: HtmlApps = {} | ||
default_app: HtmlApp = default_html_app | ||
|
||
# Can load additional data into this dictionary for easy HTTP access. | ||
json_data: JsonObject = {"test": {"a": 1, "b": 2, "c": 3}} | ||
|
||
favicon_data: bytes | ||
|
||
def init(self) -> None: | ||
"""Initialize this instance.""" | ||
|
||
super().init() | ||
|
||
# Load favicon if necessary. | ||
if not hasattr(type(self), "favicon_data"): | ||
favicon = find_file("favicon.ico", package=PKG_NAME) | ||
assert favicon is not None | ||
with favicon.open("rb") as favicon_fd: | ||
type(self).favicon_data = favicon_fd.read() | ||
|
||
async def get_handler( | ||
self, | ||
response: ResponseHeader, | ||
request: RequestHeader, | ||
request_data: Optional[bytes], | ||
) -> Optional[bytes]: | ||
"""Sample handler.""" | ||
|
||
result = None | ||
|
||
with StringIO() as stream: | ||
if request.target.origin_form: | ||
path = request.target.path | ||
|
||
# Handle favicon (for browser clients). | ||
if path.startswith("/favicon"): | ||
response["Content-Type"] = "image/x-icon" | ||
return self.favicon_data | ||
|
||
# Handle raw data queries. | ||
if path.startswith("/json"): | ||
json_handler( | ||
stream, | ||
request, | ||
response, | ||
request_data, | ||
self.json_data, | ||
) | ||
|
||
# Serve the application. | ||
else: | ||
await html_handler( | ||
self.apps, | ||
stream, | ||
request, | ||
response, | ||
request_data, | ||
default_app=type(self).default_app, | ||
) | ||
|
||
result = stream.getvalue().encode() | ||
|
||
return result |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
""" | ||
A module implementing HTML interfaces for web applications. | ||
""" | ||
|
||
# built-in | ||
from typing import Awaitable, Callable, Optional, TextIO | ||
|
||
# third-party | ||
from svgen.attribute import attributes | ||
from svgen.element import Element | ||
from vcorelib import DEFAULT_ENCODING | ||
|
||
# internal | ||
from runtimepy.net.http.header import RequestHeader | ||
from runtimepy.net.http.response import ResponseHeader | ||
from runtimepy.net.tcp.http import HttpConnection | ||
|
||
HtmlApp = Callable[ | ||
[Element, Element, RequestHeader, ResponseHeader, Optional[bytes]], | ||
Awaitable[None], | ||
] | ||
HtmlApps = dict[str, HtmlApp] | ||
|
||
|
||
async def default_html_app( | ||
head: Element, | ||
body: Element, | ||
request: RequestHeader, | ||
response: ResponseHeader, | ||
request_data: Optional[bytes], | ||
) -> None: | ||
"""A simple 'Hello, world!' application.""" | ||
|
||
del head | ||
del request | ||
del response | ||
del request_data | ||
|
||
body.children.append(Element(tag="div", text="Hello, world!")) | ||
|
||
|
||
# A default 'head' section to use in the HTML document. | ||
HEAD = Element( | ||
tag="head", | ||
children=[ | ||
Element( | ||
tag="meta", | ||
attrib=attributes({"charset": DEFAULT_ENCODING}), | ||
), | ||
Element( | ||
tag="meta", | ||
attrib=attributes( | ||
{ | ||
"name": "viewport", | ||
"content": "width=device-width, initial-scale=1", | ||
} | ||
), | ||
), | ||
Element(tag="title", text=HttpConnection.identity), | ||
], | ||
) | ||
|
||
|
||
async def html_handler( | ||
apps: HtmlApps, | ||
stream: TextIO, | ||
request: RequestHeader, | ||
response: ResponseHeader, | ||
request_data: Optional[bytes], | ||
default_app: HtmlApp = default_html_app, | ||
) -> None: | ||
"""Render an HTML document in response to an HTTP request.""" | ||
|
||
# Set response headers. | ||
response["Content-Type"] = f"text/html; charset={DEFAULT_ENCODING}" | ||
|
||
# Create a copy at some point? | ||
head = HEAD | ||
|
||
body = Element(tag="body") | ||
|
||
# Create the application. | ||
await apps.get(request.target.path, default_app)( | ||
head, body, request, response, request_data | ||
) | ||
|
||
stream.write("<!DOCTYPE html>\n") | ||
html = Element( | ||
tag="html", attrib=attributes({"lang": "en"}), children=[head, body] | ||
) | ||
html.encode(stream) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
""" | ||
A module implementing basic JSON-object response handling. | ||
""" | ||
|
||
# built-in | ||
from typing import Any, Optional, TextIO | ||
|
||
# third-party | ||
from vcorelib import DEFAULT_ENCODING | ||
from vcorelib.io import ARBITER, JsonObject | ||
|
||
# internal | ||
from runtimepy.net.http.header import RequestHeader | ||
from runtimepy.net.http.response import ResponseHeader | ||
|
||
|
||
def json_handler( | ||
stream: TextIO, | ||
request: RequestHeader, | ||
response: ResponseHeader, | ||
request_data: Optional[bytes], | ||
data: JsonObject, | ||
) -> None: | ||
"""Create an HTTP response from some JSON object data.""" | ||
|
||
del request_data | ||
|
||
response_type = "json" | ||
response["Content-Type"] = ( | ||
f"application/{response_type}; charset={DEFAULT_ENCODING}" | ||
) | ||
|
||
error: dict[str, Any] = {"path": {}} | ||
|
||
# Traverse path. | ||
curr_path = [] | ||
for part in request.target.path.split("/")[2:]: | ||
if not part: | ||
continue | ||
|
||
curr_path.append(part) | ||
|
||
# Handle error. | ||
if not isinstance(data, dict): | ||
error["path"]["part"] = part | ||
error["path"]["current"] = ".".join(curr_path) | ||
error["error"] = f"Can't index '{data}' by string key!" | ||
data = error | ||
break | ||
|
||
# Handle 'key not found' error. | ||
if part not in data: | ||
error["path"]["part"] = part | ||
error["path"]["current"] = ".".join(curr_path) | ||
error["error"] = f"Key not found! {data.keys()}" | ||
data = error | ||
break | ||
|
||
data = data[part] # type: ignore | ||
|
||
# Use a convention for indexing data to non-dictionary leaf nodes. | ||
if not isinstance(data, dict): | ||
data = {"__raw__": data} | ||
|
||
ARBITER.encode_stream(response_type, stream, data) | ||
stream.write("\n") |
Oops, something went wrong.