Server API¶
API reference for the Mototli Gopher server.
GopherServer¶
GopherServer
¶
High-level Gopher server.
This class provides a simple interface for creating and running a Gopher server with static file serving, CGI support, and Gopher+ extensions.
Attributes:
| Name | Type | Description |
|---|---|---|
config |
Server configuration. |
|
router |
Request router. |
|
server |
Server | None
|
The asyncio server object when running. |
Examples:
>>> config = ServerConfig(
... host="localhost",
... port=70,
... document_root=Path("/var/gopher"),
... )
>>> server = GopherServer(config)
>>> await server.start()
Initialize the Gopher server.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
ServerConfig
|
Server configuration. |
required |
Source code in src/mototli/server/server.py
serve_forever
async
¶
Run the server forever.
This method blocks until the server is stopped.
Source code in src/mototli/server/server.py
start
async
¶
Start the server.
Creates the asyncio server and starts listening for connections.
Raises:
| Type | Description |
|---|---|
OSError
|
If unable to bind to the specified host/port. |
Source code in src/mototli/server/server.py
stop
async
¶
ServerConfig¶
ServerConfig
dataclass
¶
ServerConfig(
host: str = "localhost",
port: int = DEFAULT_PORT,
document_root: Path = (lambda: Path("."))(),
hostname: str | None = None,
enable_directory_listing: bool = True,
default_indices: list[str] = (
lambda: ["index.gph", "gophermap", "index.txt"]
)(),
cgi_extensions: list[str] = (
lambda: [".cgi", ".sh", ".py", ".pl"]
)(),
cgi_directories: list[str] = (lambda: ["cgi-bin"])(),
max_file_size: int = 100 * 1024 * 1024,
request_timeout: float = REQUEST_TIMEOUT,
cgi_timeout: float = 30.0,
gopher_plus: bool = True,
admin_name: str | None = None,
admin_email: str | None = None,
)
Configuration for the Gopher server.
Attributes:
| Name | Type | Description |
|---|---|---|
host |
str
|
The host address to bind to. |
port |
int
|
The port to listen on (default 70). |
document_root |
Path
|
Path to the directory containing files to serve. |
hostname |
str | None
|
Public hostname for generating menu items. If None, uses host. |
enable_directory_listing |
bool
|
Whether to generate directory listings. |
default_indices |
list[str]
|
List of index filenames to try for directory requests. |
cgi_extensions |
list[str]
|
File extensions that indicate CGI scripts. |
cgi_directories |
list[str]
|
Directory names that contain CGI scripts. |
max_file_size |
int
|
Maximum file size to serve in bytes. |
request_timeout |
float
|
Timeout for receiving requests in seconds. |
cgi_timeout |
float
|
Timeout for CGI script execution in seconds. |
gopher_plus |
bool
|
Whether Gopher+ is enabled. |
admin_name |
str | None
|
Administrator name for Gopher+ ADMIN block. |
admin_email |
str | None
|
Administrator email for Gopher+ ADMIN block. |
Examples:
>>> config = ServerConfig(
... host="localhost",
... port=70,
... document_root=Path("/var/gopher"),
... )
__post_init__
¶
Validate and normalize configuration after initialization.
Source code in src/mototli/server/config.py
from_toml
classmethod
¶
Load configuration from a TOML file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Path | str
|
Path to the TOML configuration file. |
required |
Returns:
| Type | Description |
|---|---|
ServerConfig
|
A ServerConfig instance with values from the file. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If the config file doesn't exist. |
ValueError
|
If the config file is invalid. |
Examples:
Source code in src/mototli/server/config.py
to_toml
¶
Serialize the configuration to a TOML string.
Returns:
| Type | Description |
|---|---|
str
|
The configuration as a TOML-formatted string. |
Source code in src/mototli/server/config.py
validate
¶
Validate the configuration.
Raises:
| Type | Description |
|---|---|
ValueError
|
If the configuration is invalid. |
Source code in src/mototli/server/config.py
Router¶
Router
¶
Routes incoming requests to appropriate handlers.
The Router supports two types of route matching: - Exact: Selector must match exactly - Prefix: Selector must start with the pattern
Routes are matched in the order they were registered.
Examples:
>>> router = Router()
>>> router.add_route("/", index_handler)
>>> router.add_route("/files/", file_handler, route_type=RouteType.PREFIX)
Initialize an empty router.
Source code in src/mototli/server/router.py
add_route
¶
add_route(
pattern: str,
handler: Callable[[GopherRequest], GopherResponse],
route_type: RouteType = RouteType.EXACT,
) -> None
Register a new route.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pattern
|
str
|
The selector pattern to match. |
required |
handler
|
Callable[[GopherRequest], GopherResponse]
|
Callable that takes a GopherRequest and returns a GopherResponse. |
required |
route_type
|
RouteType
|
Type of matching to perform (default: EXACT). |
EXACT
|
Examples:
>>> router.add_route("/", index_handler)
>>> router.add_route("/cgi-bin/", cgi_handler, RouteType.PREFIX)
Source code in src/mototli/server/router.py
route
¶
Route a request to the appropriate handler.
Routes are matched in the order they were registered. If no route matches, the default handler is called (if set), otherwise an error response is returned.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
GopherRequest
|
The incoming request to route. |
required |
Returns:
| Type | Description |
|---|---|
GopherResponse
|
The response from the matched handler. |
Source code in src/mototli/server/router.py
set_default_handler
¶
Set the default handler for unmatched routes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
handler
|
Callable[[GopherRequest], GopherResponse]
|
Callable that handles requests not matching any route. Typically returns an error response. |
required |
Source code in src/mototli/server/router.py
RouteType¶
RouteType
¶
Convenience Functions¶
run_server¶
run_server
async
¶
Run a Gopher server with the given configuration.
This is a convenience function that creates a server and runs it until interrupted (Ctrl+C).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
ServerConfig
|
Server configuration. |
required |
Examples:
>>> config = ServerConfig(
... host="localhost",
... port=70,
... document_root=Path("/var/gopher"),
... )
>>> asyncio.run(run_server(config))
Source code in src/mototli/server/server.py
start_server¶
start_server
async
¶
start_server(
host: str = "localhost",
port: int = 70,
document_root: Path | str = ".",
hostname: str | None = None,
enable_directory_listing: bool = True,
gopher_plus: bool = True,
admin_name: str | None = None,
admin_email: str | None = None,
) -> None
Start a Gopher server with the given options.
This is a convenience function that creates a configuration and runs the server.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
host
|
str
|
Host address to bind to. |
'localhost'
|
port
|
int
|
Port to listen on. |
70
|
document_root
|
Path | str
|
Path to the document root directory. |
'.'
|
hostname
|
str | None
|
Public hostname for menu items. If None, uses host. |
None
|
enable_directory_listing
|
bool
|
Whether to generate directory listings. |
True
|
gopher_plus
|
bool
|
Whether Gopher+ is enabled. |
True
|
admin_name
|
str | None
|
Administrator name for Gopher+ ADMIN block. |
None
|
admin_email
|
str | None
|
Administrator email for Gopher+ ADMIN block. |
None
|
Examples:
>>> asyncio.run(start_server(
... host="0.0.0.0",
... port=70,
... document_root="/var/gopher",
... hostname="gopher.example.com",
... ))
Source code in src/mototli/server/server.py
Handlers¶
RequestHandler¶
RequestHandler
¶
Bases: ABC
Abstract base class for request handlers.
All request handlers should inherit from this class and implement the handle() method.
handle
abstractmethod
¶
Handle a Gopher request and return a response.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
GopherRequest
|
The incoming request to handle. |
required |
Returns:
| Type | Description |
|---|---|
GopherResponse
|
A GopherResponse object. |
StaticFileHandler¶
StaticFileHandler
¶
StaticFileHandler(
document_root: Path | str,
hostname: str,
port: int = DEFAULT_PORT,
default_indices: list[str] | None = None,
enable_directory_listing: bool = True,
max_file_size: int = 100 * 1024 * 1024,
gopher_plus: bool = True,
admin_name: str | None = None,
admin_email: str | None = None,
)
Bases: RequestHandler
Handler for serving static files from a document root.
This handler serves files from a specified directory with path traversal protection, automatic directory listings, and Gopher+ attribute support.
Attributes:
| Name | Type | Description |
|---|---|---|
document_root |
Path to the directory containing files to serve. |
|
hostname |
Server hostname for generating menu items. |
|
port |
Server port for generating menu items. |
|
default_indices |
List of index filenames to try for directory requests. |
|
enable_directory_listing |
Whether to generate directory listings. |
|
max_file_size |
Maximum file size to serve in bytes. |
|
gopher_plus |
Whether Gopher+ is enabled. |
|
admin_name |
Administrator name for Gopher+ ADMIN block. |
|
admin_email |
Administrator email for Gopher+ ADMIN block. |
Examples:
>>> handler = StaticFileHandler(
... Path("/var/gopher"),
... hostname="gopher.example.com",
... )
>>> request = GopherRequest(selector="/readme.txt")
>>> response = handler.handle(request)
Initialize the static file handler.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document_root
|
Path | str
|
Path to the directory containing files to serve. |
required |
hostname
|
str
|
Server hostname for generating menu items. |
required |
port
|
int
|
Server port for generating menu items. |
DEFAULT_PORT
|
default_indices
|
list[str] | None
|
List of index filenames to try for directory requests. |
None
|
enable_directory_listing
|
bool
|
Whether to generate directory listings. |
True
|
max_file_size
|
int
|
Maximum file size to serve in bytes. |
100 * 1024 * 1024
|
gopher_plus
|
bool
|
Whether Gopher+ is enabled. |
True
|
admin_name
|
str | None
|
Administrator name for Gopher+ ADMIN block. |
None
|
admin_email
|
str | None
|
Administrator email for Gopher+ ADMIN block. |
None
|
Source code in src/mototli/server/handler.py
handle
¶
Handle a request for a static file or directory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
GopherRequest
|
The incoming request. |
required |
Returns:
| Type | Description |
|---|---|
GopherResponse
|
A GopherResponse with the file contents, directory listing, |
GopherResponse
|
or Gopher+ attributes. |
Source code in src/mototli/server/handler.py
CGIHandler¶
CGIHandler
¶
CGIHandler(
document_root: Path | str,
hostname: str,
port: int = DEFAULT_PORT,
cgi_extensions: list[str] | None = None,
cgi_directories: list[str] | None = None,
timeout: float = 30.0,
gopher_plus: bool = True,
)
Bases: RequestHandler
Handler for executing CGI scripts.
This handler executes CGI scripts and returns their output as Gopher responses. Scripts can be identified by file extension (e.g., .cgi) or by being located in a CGI directory (e.g., cgi-bin/).
The handler sets up standard CGI environment variables plus Gopher-specific variables like SELECTOR and GOPHER_PLUS.
Attributes:
| Name | Type | Description |
|---|---|---|
document_root |
Path to the document root. |
|
hostname |
Server hostname for environment variables. |
|
port |
Server port for environment variables. |
|
cgi_extensions |
File extensions that indicate CGI scripts. |
|
cgi_directories |
Directory names that contain CGI scripts. |
|
timeout |
Timeout for CGI script execution in seconds. |
|
gopher_plus |
Whether Gopher+ is enabled. |
Examples:
>>> handler = CGIHandler(
... document_root=Path("/var/gopher"),
... hostname="gopher.example.com",
... cgi_extensions=[".cgi", ".sh"],
... cgi_directories=["cgi-bin"],
... )
Initialize the CGI handler.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document_root
|
Path | str
|
Path to the document root. |
required |
hostname
|
str
|
Server hostname for environment variables. |
required |
port
|
int
|
Server port for environment variables. |
DEFAULT_PORT
|
cgi_extensions
|
list[str] | None
|
File extensions that indicate CGI scripts. |
None
|
cgi_directories
|
list[str] | None
|
Directory names that contain CGI scripts. |
None
|
timeout
|
float
|
Timeout for CGI script execution in seconds. |
30.0
|
gopher_plus
|
bool
|
Whether Gopher+ is enabled. |
True
|
Source code in src/mototli/server/cgi.py
can_handle
¶
Check if this handler can handle a selector.
This is useful for routing decisions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
selector
|
str
|
The selector to check. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if this handler can handle the selector. |
Source code in src/mototli/server/cgi.py
execute_cgi_async
async
¶
Execute a CGI script asynchronously.
This method uses asyncio subprocess for non-blocking execution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
script_path
|
Path
|
Path to the CGI script. |
required |
request
|
GopherRequest
|
The incoming request. |
required |
Returns:
| Type | Description |
|---|---|
GopherResponse
|
A GopherResponse with the script output. |
Source code in src/mototli/server/cgi.py
handle
¶
Handle a request by executing the CGI script.
This method runs synchronously but calls an async helper to execute the CGI script.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
GopherRequest
|
The incoming request. |
required |
Returns:
| Type | Description |
|---|---|
GopherResponse
|
A GopherResponse with the CGI script output. |
Source code in src/mototli/server/cgi.py
ErrorHandler¶
ErrorHandler
¶
Bases: RequestHandler
Handler that returns error responses.
Useful for handling 404 Not Found and other error cases.
Examples:
Initialize the error handler.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str
|
The error message to return. |
'Error'
|
Source code in src/mototli/server/handler.py
handle
¶
Return an error response.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
GopherRequest
|
The incoming request (ignored). |
required |
Returns:
| Type | Description |
|---|---|
GopherResponse
|
A GopherResponse with an error item. |
Source code in src/mototli/server/handler.py
Usage Examples¶
Quick Start¶
import asyncio
from mototli.server import run_server
# Serve current directory on port 7070
asyncio.run(run_server(document_root=".", port=7070))
With Configuration¶
import asyncio
from mototli.server import GopherServer, ServerConfig
from pathlib import Path
config = ServerConfig(
host="0.0.0.0",
port=7070,
hostname="gopher.example.com",
document_root=Path("/var/gopher"),
gopher_plus=True,
admin_name="Admin",
admin_email="admin@example.com"
)
async def main():
server = GopherServer(config)
await server.start()
try:
await asyncio.Event().wait() # Run forever
finally:
await server.stop()
asyncio.run(main())
From TOML File¶
import asyncio
from mototli.server import GopherServer, ServerConfig
config = ServerConfig.from_toml("server.toml")
async def main():
server = GopherServer(config)
await server.start()
await asyncio.Event().wait()
asyncio.run(main())
Custom Routing¶
from mototli.server import Router, RouteType
router = Router()
# Exact match
router.add_route("/about", about_handler, RouteType.EXACT)
# Prefix match
router.add_route("/files/", file_handler, RouteType.PREFIX)
Custom Handler¶
from mototli.server import RequestHandler
from mototli.protocol import GopherRequest, GopherResponse
class CustomHandler(RequestHandler):
async def handle(self, request: GopherRequest) -> GopherResponse:
# Generate custom response
content = f"You requested: {request.selector}"
return GopherResponse(content=content.encode())
See Also¶
- Your First Gopherhole - Tutorial
- Configure Server - Configuration guide
- Configuration Reference - TOML options