Serve neo4j-browser with Starlette and uvicorn (the optional browser extra) instead of a hand-written HTTP server, and handle Bolt over WebSocket with the websockets library's Sans-I/O protocol. Rename Dockerfile, .dockerignore and docker/ to Containerfile, .containerignore and container/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
55 lines
2.0 KiB
Python
55 lines
2.0 KiB
Python
# SPDX-FileCopyrightText: 2026 Bart Broere
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
"""HTTP server for the neo4j-browser static files and Neo4j discovery.
|
|
|
|
Needs the ``browser`` extra: ``pip install 'peo[browser]'``.
|
|
"""
|
|
|
|
from starlette.applications import Starlette
|
|
from starlette.datastructures import MutableHeaders
|
|
from starlette.middleware import Middleware
|
|
from starlette.responses import RedirectResponse, Response
|
|
from starlette.routing import Mount, Route
|
|
from starlette.staticfiles import StaticFiles
|
|
|
|
from .discovery import discovery_document
|
|
|
|
|
|
class BrowserFiles(StaticFiles):
|
|
"""neo4j-browser's build: index.html must be revalidated, the hashed assets can be cached."""
|
|
|
|
def file_response(self, full_path, *args, **kwargs):
|
|
response = super().file_response(full_path, *args, **kwargs)
|
|
response.headers["Cache-Control"] = ("no-cache" if str(full_path).endswith(".html")
|
|
else "public, max-age=86400")
|
|
return response
|
|
|
|
|
|
def nosniff(app):
|
|
async def wrapped(scope, receive, send):
|
|
async def send_with_header(message):
|
|
if message["type"] == "http.response.start":
|
|
MutableHeaders(scope=message)["X-Content-Type-Options"] = "nosniff"
|
|
await send(message)
|
|
await app(scope, receive, send_with_header)
|
|
return wrapped
|
|
|
|
|
|
def create_app(browser_dir: str, bolt_port: int) -> Starlette:
|
|
async def root(request):
|
|
if "text/html" in request.headers.get("accept", ""):
|
|
return RedirectResponse("/browser/", status_code=302)
|
|
return Response(discovery_document(request.headers, bolt_port), media_type="application/json")
|
|
|
|
async def browser(request):
|
|
return RedirectResponse("/browser/", status_code=301)
|
|
|
|
return Starlette(
|
|
routes=[
|
|
Route("/", root),
|
|
Route("/browser", browser),
|
|
Mount("/browser", BrowserFiles(directory=browser_dir, html=True)),
|
|
],
|
|
middleware=[Middleware(nosniff)],
|
|
)
|