Spune ce vrei să spui
Un API REST comunică prin două vocabulare. Metoda spune ce vrea clientul: GET citește, POST creează, PUT înlocuiește, PATCH modifică o parte, DELETE șterge. Codul de stare spune cum a decurs: 2xx succes, 4xx greșeala clientului, 5xx greșeala serverului.
Un detaliu bun de știut: PUT este idempotent — dacă îl trimiți de două ori, rezultatul final este același ca după o singură dată. POST nu este: două cereri POST creează două resurse.
Metoda potrivită
Clasa handler direcționează după verb în locul tău: serverul apelează do_GET pentru un GET, do_POST pentru un POST, do_DELETE pentru un DELETE — câte o metodă pentru fiecare verb. Crearea răspunde cu 201 Created și indică noua resursă printr-un antet Location; ștergerea răspunde cu 204 No Content — succes, fără nimic de spus.
GET, POST și DELETE pe o singură resursă
Trei verbe, trei răspunsuri: lista vine înapoi cu 200, crearea cu 201 și un antet Location, iar ștergerea cu 204 și un corp gol.
Ieșire: program Python complet
import threading
import urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
class NotesHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.end_headers()
self.wfile.write("notițe: shopping, ideas\n".encode())
def do_POST(self):
length = int(self.headers.get("Content-Length", 0))
self.rfile.read(length) # JSON-ul noii notițe
self.send_response(201) # creat
self.send_header("Location", "/notes/3")
self.end_headers()
self.wfile.write(b"created\n")
def do_DELETE(self):
self.send_response(204) # succes, nimic de spus
self.end_headers()
def log_message(self, fmt, *args):
pass
server = ThreadingHTTPServer(("127.0.0.1", 0), NotesHandler)
base = f"http://127.0.0.1:{server.server_address[1]}"
threading.Thread(target=server.serve_forever, daemon=True).start()
with urllib.request.urlopen(base + "/notes") as resp:
print("GET /notes ->", resp.status, resp.read().decode().strip())
req = urllib.request.Request(base + "/notes", data=b'{"text": "call Ben"}',
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req) as resp:
print("POST /notes ->", resp.status, "Location:", resp.headers["Location"])
req = urllib.request.Request(base + "/notes/3", method="DELETE")
with urllib.request.urlopen(req) as resp:
print("DELETE /notes/3 ->", resp.status, "(fără conținut)")
server.shutdown()
Codul de stare potrivit
Și erorile merită precizie: o resursă care lipsește înseamnă 404 Not Found (cu un corp JSON de eroare, ca să-l poată citi programele), iar un verb greșit pe o rută doar pentru citire înseamnă 405 Method Not Allowed — aici trimis de o metodă do_DELETE foarte scurtă. (Dacă omiți complet această metodă, http.server răspunde singur cu 501 Unsupported method — să scrii tu răspunsul 405 este varianta REST mai precisă.)
Pe partea de client, urllib ridică HTTPError pentru fiecare răspuns 4xx și 5xx, în loc să-l returneze în liniște — obiectul de eroare conține totuși codul de stare și corpul.
200, 404 și 405
Notița existentă returnează 200 cu date, cea care lipsește returnează 404 cu o eroare JSON, iar cererea DELETE este refuzată cu 405. Clientul prinde HTTPError și citește din el corpurile de eroare.
Ieșire: program Python complet
import json
import threading
import urllib.error
import urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
notes = {"1": "shopping list"}
class NoteHandler(BaseHTTPRequestHandler):
def do_GET(self):
note_id = self.path.rsplit("/", 1)[-1]
text = notes.get(note_id)
if text is None:
self.reply(404, {"error": "note not found"})
else:
self.reply(200, {"id": note_id, "text": text})
def do_DELETE(self):
# Acest API doar citește notițe - refuză cinstit orice alt verb.
self.reply(405, {"error": "method not allowed"})
def reply(self, status, data):
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write((json.dumps(data) + "\n").encode())
def log_message(self, fmt, *args):
pass
server = ThreadingHTTPServer(("127.0.0.1", 0), NoteHandler)
base = f"http://127.0.0.1:{server.server_address[1]}"
threading.Thread(target=server.serve_forever, daemon=True).start()
def show(method, path):
req = urllib.request.Request(base + path, method=method)
try:
with urllib.request.urlopen(req) as resp:
status, body = resp.status, resp.read().decode()
except urllib.error.HTTPError as err: # ridicată pentru orice 4xx și 5xx
status, body = err.code, err.read().decode()
print(f"{method:<6} {path} -> {status} {body.strip()}")
show("GET", "/notes/1")
show("GET", "/notes/99")
show("DELETE", "/notes/1")
server.shutdown()