http.server and serve()¶
http.server¶
Python's built-in HTTP server, with no other dependencies. It reads the body
itself, so it also has to check the Content-Length header, refuse a body
that's too big and handle bytes that aren't UTF-8:
import json
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from jsonrpcserver import Result, Success, dispatch, method
MAX_BODY = 1_000_000 # bytes
@method
def ping() -> Result:
return Success("pong")
class Handler(BaseHTTPRequestHandler):
def do_POST(self) -> None:
length = self.headers.get("Content-Length", "")
if not length.isdecimal():
self.send_error(411, "Content-Length required")
return
if int(length) > MAX_BODY:
self.send_error(413, "Request body too large")
return
try:
request = self.rfile.read(int(length)).decode("utf-8")
except UnicodeDecodeError:
response = json.dumps(
{
"jsonrpc": "2.0",
"error": {"code": -32700, "message": "Parse error"},
"id": None,
}
)
else:
# max_batch_size: see the Security page.
response = dispatch(request, max_batch_size=100)
if response:
body = response.encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
else:
# A notification. There's nothing to send back.
self.send_response(204)
self.end_headers()
if __name__ == "__main__":
ThreadingHTTPServer(("localhost", 8000), Handler).serve_forever()
A missing Content-Length gets 411, a body over the limit gets 413, and a
body that isn't UTF-8 gets a -32700 "Parse error". http.server is fine for
small internal tools. For anything public, use a framework or put a
production web server in front.
serve()¶
jsonrpcserver also has a small built-in server, serve(). It's for trying
things out, not for production:
from jsonrpcserver import Result, Success, method, serve
@method
def ping() -> Result:
return Success("pong")
if __name__ == "__main__":
serve("localhost", 8000)
It answers POST requests on any path, sends 204 for notifications, and
handles each request in its own thread. It also handles a missing
Content-Length (411) and a body that isn't UTF-8 (-32700).
Know its limits before you use it:
- It listens on every network interface unless you pass a host, as the
example does with
"localhost". Withserve()and no arguments, anyone who can reach your machine on port 5000 can call your methods. - It has no TLS, no authentication and no limit on the body size, and it
doesn't set
max_batch_size. - It says where it's listening when it starts (new in 5.0.10). Each request
is logged on the
jsonrpcserver.serverlogger at INFO level, sologging.basicConfig(level=logging.INFO)shows them.
Changed in 5.0.10
In 5.0.9, serve() answers a notification with 200 and an empty body
instead of 204. It also handles one request at a time. It logs its start on
the root logger at INFO, so you usually see nothing. It drops the
connection for a missing Content-Length or a body that isn't UTF-8.
Try it¶
Save the serve() example as quickstart.py and run it:
python quickstart.py
Then send it a request from another terminal:
curl -s -H 'Content-Type: application/json' -d '{"jsonrpc": "2.0", "method": "ping", "id": 1}' http://localhost:8000/
{"jsonrpc": "2.0", "result": "pong", "id": 1}
The jsonrpcclient quickstart sends the same request from Python.