Говорите, что имеете в виду
REST API общается с помощью двух словарей. Метод говорит, чего хочет клиент: GET — прочитать, POST — создать, PUT — заменить, PATCH — изменить часть, DELETE — удалить. Код состояния говорит, чем всё закончилось: 2xx — успех, 4xx — ошибка клиента, 5xx — ошибка сервера.
Полезная деталь: PUT идемпотентен — если отправить его дважды, результат будет тем же, что и после одного раза. POST таким не является: два POST-запроса создают два ресурса.
Правильный метод
Класс-обработчик сам распределяет запросы по методам: сервер вызывает do_GET для GET, do_POST для POST, do_DELETE для DELETE — по одному методу на каждый HTTP-метод. На создание сервер отвечает 201 Created и указывает на новый ресурс заголовком Location; на удаление — 204 No Content: успех, и сказать больше нечего.
GET, POST и DELETE для одного ресурса
Три метода — три ответа: список возвращается с кодом 200, создание — с кодом 201 и заголовком Location, удаление — с кодом 204 и пустым телом.
Вывод: полная программа на Python
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(b"notes: shopping, ideas\n")
def do_POST(self):
length = int(self.headers.get("Content-Length", 0))
self.rfile.read(length) # JSON новой заметки
self.send_response(201) # создано
self.send_header("Location", "/notes/3")
self.end_headers()
self.wfile.write(b"created\n")
def do_DELETE(self):
self.send_response(204) # успех, сказать нечего
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, "(нет содержимого)")
server.shutdown()
Правильный код состояния
Ошибки тоже требуют точности: отсутствующий ресурс — это 404 Not Found (с телом ошибки в JSON, чтобы его могли прочитать программы), а неподходящий метод на маршруте только для чтения — это 405 Method Not Allowed; здесь его возвращает крошечный do_DELETE. (Если совсем не писать этот метод, http.server сам ответит 501 Unsupported method — но самостоятельно вернуть 405 — более точный ответ в духе REST.)
На стороне клиента urllib выбрасывает HTTPError на каждый ответ 4xx и 5xx, а не возвращает его молча — объект ошибки всё равно содержит код состояния и тело.
200, 404 и 405
Существующая заметка возвращает 200 с данными, отсутствующая — 404 с ошибкой в JSON, а DELETE отклоняется с кодом 405. Клиент перехватывает HTTPError и читает из него тела ошибок.
Вывод: полная программа на Python
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):
# Этот API только читает заметки - честно отказываем на все остальные методы.
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: # возникает при каждом 4xx и 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()