0
0
Lập trình
Admin Team
Admin Teamtechmely

Hướng Dẫn Sử Dụng HTTP/3 Trong Python

Đăng vào 2 ngày trước

• 12 phút đọc

Giới Thiệu

HTTP/3 là phiên bản mới nhất của giao thức HTTP, được phát triển để cải thiện tốc độ và độ tin cậy của việc truyền tải dữ liệu trên internet. Trong bài viết này, chúng ta sẽ tìm hiểu cách sử dụng HTTP/3 trong Python, mặc dù chưa được hỗ trợ chính thức trong thư viện chuẩn của Python. Tuy nhiên, với các thư viện bên thứ ba như aioquic, bạn hoàn toàn có thể triển khai HTTP/3 cho ứng dụng của mình.

Python Có Hỗ Trợ HTTP/3 Không?

Có, nhưng không phải là hỗ trợ chính thức trong thư viện chuẩn. Các thư viện như http.client và các framework WSGI phổ biến như Flask, Django chủ yếu được xây dựng dựa trên HTTP/1.1 hoặc HTTP/2. Để sử dụng HTTP/3, bạn cần phải dựa vào các thư viện bên ngoài.

Thư Viện Để Làm Việc Với HTTP/3 Trong Python

  • aioquic: Thư viện triển khai QUIC và HTTP/3 trong Python. Bạn có thể xây dựng cả client và server với nó.
  • hypercorn: Máy chủ ASGI hỗ trợ HTTP/1.1, HTTP/2 và HTTP/3 thử nghiệm thông qua aioquic.
  • httpx: Client HTTP cho Python. Chính thức hỗ trợ HTTP/1.1 và HTTP/2, nhưng HTTP/3 là thử nghiệm với aioquic.

Chúng ta sẽ xây dựng một ví dụ đơn giản về máy chủ HTTP/3 sử dụng thư viện aioquic.

Giải Thích Về Thư Viện aioquic

**aioquic** là một thư viện QUIC và HTTP/3 hoàn toàn bằng Python, dựa trên thiết kế “bring your own I/O”. Nó hỗ trợ QUIC (RFC 9000 & 9369), HTTP/3 (RFC 9114), TLS 1.3, server push, WebSocket, và WebTransport. Thư viện này đi kèm với một ví dụ máy chủ HTTP/3 (và client) nằm trong examples/http3_server.py, phục vụ một ứng dụng ASGI, phục vụ các tệp tĩnh, các điểm cuối echo và xử lý các tính năng HTTP/3 như server push.

Thực Hiện Máy Chủ HTTP/3

Những Gì Bạn Sẽ Xây Dựng

Chúng ta sẽ xây dựng một máy chủ HTTP/3 tối giản mà trả lời "hello" cho yêu cầu GET / và trả về JSON cho POST /echo. Chúng ta sẽ đề cập đến tại sao mỗi lớp/tham số tồn tại và cách mở rộng nó thành một ứng dụng lớn hơn.

Bước 0: Điều Kiện Tiên Quyết

  • Python: aioquic hiện tại nhắm đến các phiên bản Python hiện đại (3.8+).
  • TLS: HTTP/3 chạy trên QUIC + TLS 1.3, vì vậy bạn cần một chứng chỉ và khóa (chứng chỉ tự ký là đủ cho phát triển cục bộ). Cấu hình của aioquic xử lý việc tải chúng.
  • Cài đặt:
bash Copy
pip install aioquic

Bước 1: Mô Hình Tâm Lý

  • Giao Thức QUIC: Được quản lý bởi QuicConnection và trợ giúp asyncio serve() cho UDP I/O.
  • Lớp HTTP/3: H3Connection nằm trên QUIC. Bạn cung cấp các sự kiện QUIC và nó trả về các sự kiện HTTP/3 (tiêu đề, dữ liệu, v.v.). Bạn phản hồi bằng send_headers() / send_data().
  • Gắn Kết Giao Thức: Kế thừa QuicConnectionProtocol và ghi đè quic_event_received() để định tuyến các sự kiện vào H3Connection.

Bước 2: Tạo Chứng Chỉ Tự Ký (Chỉ Dành Cho Phát Triển)

bash Copy
openssl req -x509 -newkey rsa:2048 -nodes -days 365 \
  -keyout key.pem -out cert.pem -subj "/CN=localhost"

Bước 3: Chọn ALPN Phù Hợp

HTTP/3 được đàm phán thông qua ALPN. aioquic cung cấp H3_ALPN (các token phù hợp cho HTTP/3). Bạn chỉ cần truyền vào QuicConfiguration(alpn_protocols=H3_ALPN).

Bước 4: Máy Chủ Tối Thiểu

Tạo server.py:

python Copy
import argparse
import asyncio
import json
import logging
from typing import Dict, Optional
from aioquic.asyncio import QuicConnectionProtocol, serve
from aioquic.h3.connection import H3_ALPN, H3Connection
from aioquic.h3.events import DataReceived, HeadersReceived, H3Event
from aioquic.quic.configuration import QuicConfiguration

LOG = logging.getLogger("h3server")

class Http3ServerProtocol(QuicConnectionProtocol):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._http: Optional[H3Connection] = None
        self._bodies: Dict[int, bytearray] = {}
        self._req_meta: Dict[int, tuple[str, str]] = {}

    def handle_h3_event(self, event: H3Event) -> None:
        if isinstance(event, HeadersReceived):
            headers = {k.lower(): v for k, v in event.headers}
            method = headers.get(b":method", b"").decode()
            path = headers.get(b":path", b"/").decode()
            sid = event.stream_id
            LOG.info("Headers on stream %d: %s %s", sid, method, path)
            self._req_meta[sid] = (method, path)
            if method in ("POST", "PUT", "PATCH"):
                self._bodies[sid] = bytearray()
                if event.stream_ended:
                    payload = bytes(self._bodies.pop(sid))
                    self._respond(sid, method, path, payload)
                    self._req_meta.pop(sid, None)
            else:
                self._respond(sid, method, path, body=b"")
                self._req_meta.pop(sid, None)
        elif isinstance(event, DataReceived):
            sid = event.stream_id
            if sid not in self._bodies:
                return
            self._bodies[sid].extend(event.data)
            if event.stream_ended:
                method, path = self._req_meta.get(sid, ("", "/"))
                payload = bytes(self._bodies.pop(sid, bytearray()))
                self._respond(sid, method, path, payload)
                self._req_meta.pop(sid, None)

    def _respond(self, stream_id: int, method: Optional[str], path: Optional[str], body: bytes) -> None:
        status = b"200"
        content_type = b"text/plain"
        if method == "GET" and path == "/":
            payload = b"hello from aioquic h3 \n"
        elif method == "POST" and path == "/echo":
            try:
                obj = json.loads(body.decode() or "{}")
                payload = (json.dumps(obj, indent=2) + "\n").encode()
                content_type = b"application/json"
            except Exception:
                payload = body or b"(empty)\n"
                content_type = b"application/octet-stream"
        else:
            payload = b"ok\n"
        headers = [
            (b":status", status),
            (b"server", b"aioquic-tutorial"),
            (b"content-type", content_type),
            (b"alt-svc", b'h3=":4433"; ma=3600'),
        ]
        self._http.send_headers(stream_id, headers)
        self._http.send_data(stream_id, payload, end_stream=True)
        LOG.info("Responded on stream %d (%s bytes)", stream_id, len(payload))

async def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--host", default="::1")
    parser.add_argument("--port", type=int, default=4433)
    parser.add_argument("--certificate", default="cert.pem")
    parser.add_argument("--private-key", dest="private_key", default="key.pem")
    args = parser.parse_args()
    logging.basicConfig(level=logging.INFO)
    config = QuicConfiguration(
        is_client=False,
        alpn_protocols=H3_ALPN,
    )
    config.load_cert_chain(args.certificate, args.private_key)
    server = await serve(
        host=args.host,
        port=args.port,
        configuration=config,
        create_protocol=Http3ServerProtocol,
    )
    logging.info("HTTP/3 server listening on %s:%d", args.host, args.port)
    try:
        await asyncio.Future()
    except KeyboardInterrupt:
        pass
    finally:
        server.close()

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        pass

Bước 5: Chạy & Kiểm Tra

Bắt đầu máy chủ:

bash Copy
python server.py --host ::1 --port 4433 --certificate cert.pem --private-key key.pem

Sau khi chạy lệnh trên, bạn sẽ thấy thông báo trên console:

Copy
INFO:root:HTTP/3 server listening on ::1:4433

Gửi yêu cầu với curl (cục bộ, bỏ qua kiểm tra chứng chỉ):

bash Copy
curl -v --http3 -k https://localhost:4433/
curl -v --http3 -k https://localhost:4433/echo -d '{"hello": "world"}' \
     -H 'content-type: application/json'

Lưu Ý

-k bỏ qua xác thực TLS vì chúng ta đang sử dụng chứng chỉ tự ký.

Bước 6: Hiểu Luồng Sự Kiện

  1. Handshake → bạn nhận HandshakeCompleted(alpn_protocol=...). Nếu ALPN là một trong H3_ALPN, tạo H3Connection(self._quic).
  2. Yêu CầuHeadersReceived (phương thức/đường dẫn trong pseudo-headers), sau đó là các chunk DataReceived cho đến stream_ended=True.
  3. Phản Hồi → gọi send_headers() sau đó send_data(..., end_stream=True).
  4. Flush → gọi self.transmit() để thực sự đẩy các datagram ra ngoài.

Bước 7: Những Cái Bẫy Thường Gặp

  • Headers phải là bytes (tên và giá trị).
  • Phản hồi trên mỗi stream: HTTP/3 là đa luồng; luôn giữ stream_id với trạng thái yêu cầu/đáp ứng của bạn.
  • Kết thúc stream: đặt end_stream=True khi bạn đã gửi xong dữ liệu.

Triển Khai Client

Tương tự như máy chủ, client cũng hoạt động theo nhiều lớp:

  • Giao Thức QUIC: được xử lý bởi QuicConnection bên trong QuicConnectionProtocol.
  • HTTP/3: H3Connection mã hóa các yêu cầu và phân tích các phản hồi.

Bước 1: Mô Hình Tâm Lý Client

  1. Tạo cấu hình QUIC: QuicConfiguration(is_client=True, alpn_protocols=H3_ALPN).
  2. Mở kết nối QUIC: async with connect(...) trả về một đối tượng protocol.
  3. Xây dựng yêu cầu: gọi h3.send_headers(stream_id, headers) và tùy chọn send_data().
  4. Đọc các sự kiện phản hồi: lặp qua handle_event(event) cho đến khi bạn nhận được phản hồi đầy đủ.

Bước 2: Mã Client Tối Thiểu

Tạo client.py:

python Copy
import argparse
import asyncio
import logging
import json
from typing import Optional
from aioquic.asyncio import connect
from aioquic.asyncio.protocol import QuicConnectionProtocol
from aioquic.h3.connection import H3_ALPN, H3Connection
from aioquic.h3.events import HeadersReceived, DataReceived
from aioquic.quic.configuration import QuicConfiguration
from aioquic.quic.events import QuicEvent

LOG = logging.getLogger("h3client")

class H3ClientProtocol(QuicConnectionProtocol):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._http = H3Connection(self._quic)
        self._stream_id: Optional[int] = None
        self._response_headers = None
        self._response_body = bytearray()
        self._done = asyncio.Event()

    def request(self, method: str, path: str, body: bytes = b""):
        self._stream_id = self._quic.get_next_available_stream_id()
        headers = [
            (b":method", method.encode()),
            (b":scheme", b"https"),
            (b":authority", b"localhost"),
            (b":path", path.encode()),
        ]
        self._http.send_headers(self._stream_id, headers)
        self._http.send_data(self._stream_id, body or b"", end_stream=True)
        self.transmit()

    def quic_event_received(self, event: QuicEvent):
        for h3_event in self._http.handle_event(event):
            if isinstance(h3_event, HeadersReceived):
                self._response_headers = h3_event.headers
            elif isinstance(h3_event, DataReceived):
                self._response_body.extend(h3_event.data)
                if h3_event.stream_ended:
                    self._done.set()

    async def wait_response(self):
        await self._done.wait()
        return self._response_headers, bytes(self._response_body)

async def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--host", default="localhost")
    parser.add_argument("--port", type=int, default=4433)
    parser.add_argument("--path", default="/")
    parser.add_argument("--insecure", action="store_true", default=True)
    args = parser.parse_args()
    logging.basicConfig(level=logging.INFO)
    config = QuicConfiguration(is_client=True, alpn_protocols=H3_ALPN)
    if args.insecure:
        config.verify_mode = False
    async with connect(
        args.host,
        args.port,
        configuration=config,
        create_protocol=H3ClientProtocol,
    ) as protocol:
        protocol.request("GET", args.path)
        headers, body = await protocol.wait_response()
        LOG.info("Response headers: %s", headers)
        LOG.info("Response body:\n%s", body.decode(errors="ignore"))

if __name__ == "__main__":
    asyncio.run(main())

Kết Luận

Trong bài viết này, chúng ta đã khám phá cách triển khai một máy chủ và client HTTP/3 đơn giản bằng Python. Với aioquic, bạn có thể dễ dàng xây dựng các ứng dụng web hiện đại sử dụng giao thức HTTP/3 với hiệu suất tốt hơn. Hãy thử triển khai ứng dụng của riêng bạn và khám phá thêm về các tính năng của HTTP/3!

Câu Hỏi Thường Gặp

1. HTTP/3 là gì?
HTTP/3 là phiên bản tiếp theo của giao thức HTTP, sử dụng QUIC để cải thiện tốc độ và độ tin cậy.

2. Tại sao tôi nên sử dụng HTTP/3?
HTTP/3 cung cấp hiệu suất tốt hơn, đặc biệt trong các mạng không ổn định, và giảm độ trễ đáng kể.

3. Tôi có thể sử dụng HTTP/3 trong ứng dụng của mình không?
Có, bạn có thể sử dụng thư viện aioquic để triển khai HTTP/3 trong ứng dụng Python của mình.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào