Move peo package to a different spot to prepare it for PyPI

This commit is contained in:
Bart Broere committed 2026-10-21 10:59:30 +02:00
1 parent 9a25aa11c9
commit 3e8e498b56
16 files changed
+408 -59

No files matched your search

+86
View File
@@ -0,0 +1,86 @@
# SPDX-FileCopyrightText: 2026 Bart Broere
# SPDX-License-Identifier: GPL-3.0-or-later
"""Tiny HTTP server for the neo4j-browser static files and Neo4j discovery."""
import asyncio
import json
import logging
import mimetypes
import os
from pathlib import Path
from urllib.parse import unquote, urlsplit
from .backend import NEO4J_VERSION
log = logging.getLogger("peo.http")
class HttpServer:
def __init__(self, browser_dir: str, bolt_port: int):
self.root = Path(browser_dir).resolve()
self.bolt_port = bolt_port
async def handle(self, reader, writer):
try:
request = await asyncio.wait_for(reader.readuntil(b"\r\n\r\n"), timeout=30)
lines = request.decode("latin-1").split("\r\n")
method, target, _ = (lines[0].split(" ") + ["", "", ""])[:3]
headers = {}
for line in lines[1:]:
k, _, v = line.partition(":")
headers[k.strip().lower()] = v.strip()
status, extra, body = self.respond(method, target, headers)
head = [f"HTTP/1.1 {status}", f"Content-Length: {len(body)}", "Connection: close",
"X-Content-Type-Options: nosniff"]
head += [f"{k}: {v}" for k, v in extra.items()]
writer.write(("\r\n".join(head) + "\r\n\r\n").encode("latin-1"))
if method != "HEAD":
writer.write(body)
await writer.drain()
except (asyncio.IncompleteReadError, asyncio.LimitOverrunError, asyncio.TimeoutError,
ConnectionResetError, BrokenPipeError):
pass
except Exception: # noqa: BLE001
log.exception("HTTP request failed")
finally:
writer.close()
def respond(self, method, target, headers):
if method not in ("GET", "HEAD"):
return "405 Method Not Allowed", {"Allow": "GET, HEAD"}, b""
path = unquote(urlsplit(target).path)
if path in ("/", ""):
if "text/html" in headers.get("accept", ""):
return "302 Found", {"Location": "/browser/"}, b""
return "200 OK", {"Content-Type": "application/json"}, discovery_document(headers, self.bolt_port)
if path == "/browser":
return "301 Moved Permanently", {"Location": "/browser/"}, b""
if not path.startswith("/browser/"):
return "404 Not Found", {"Content-Type": "text/plain"}, b"Not found\n"
file = (self.root / path[len("/browser/"):]).resolve()
if file.is_dir():
file = file / "index.html"
if not file.is_relative_to(self.root) or not file.is_file():
return "404 Not Found", {"Content-Type": "text/plain"}, b"Not found\n"
ctype = mimetypes.guess_type(file.name)[0] or "application/octet-stream"
cache = "no-cache" if file.name.endswith(".html") else "public, max-age=86400"
return "200 OK", {"Content-Type": ctype, "Cache-Control": cache}, file.read_bytes()
def discovery_document(headers, bolt_port) -> bytes:
"""The JSON Neo4j serves at ``/`` so clients can find the Bolt endpoint."""
host = headers.get("host") or "localhost"
if host.startswith("["): # IPv6 literal, e.g. [::1]:7474
host = host[:host.index("]") + 1]
else:
host = host.split(":", 1)[0]
port = os.environ.get("PEO_ADVERTISED_BOLT_PORT", str(bolt_port))
return json.dumps({
"bolt_routing": f"neo4j://{host}:{port}",
"bolt_direct": f"bolt://{host}:{port}",
"neo4j_version": NEO4J_VERSION,
"neo4j_edition": "community",
}).encode()