transportهای API
APIServer یک stream بایتی مرتب و قابل اعتماد مصرف میکند. تمام transportها همان frameهای API را حمل میکنند: طول چهار بایتی big-endian و سپس همان تعداد بایت envelope JSON. طول مرز پیام را مشخص میکند؛ transport مسئول تحویل و ترتیب است.
پیکربندیهای زیر، مثال کامل کلاینت محلی را تغییر میدهند. auth-db،
management-api، tokenها و core.json آن را نگه دارید. قطعههای node این صفحه
باید داخل آرایهٔ nodes همان مثال اضافه یا جایگزین شوند و config کامل مستقلی
نیستند. پیش از ویرایش نمونه را بهصورت عادی متوقف کنید و پس از هر تغییر transport
دوباره راه بیندازید.
TCP مستقیم
مثال اولیه از همین مسیر استفاده میکند:
Python client -> TcpListener -> APIServer
listener روی 127.0.0.1:9080 bind میشود و next آن management-api است. فرمان
بدون تغییر کلاینت را از مثال اول اجرا کنید. readهای TCP ممکن است
prefix و body را تکهتکه یا چند frame را ترکیب کنند؛ کلاینت باید دقیقاً طول prefix
و body را جمعآوری کند.
تونل body در HTTP/1.1
مقدار api-listener.next را به http-body تغییر دهید و این node را اضافه کنید:
{
"name": "http-body",
"type": "HttpServer",
"settings": {
"http-version": "1.1",
"http1-mode": "single",
"upgrade": false,
"path": "/management",
"method": "POST"
},
"next": "management-api"
}
زنجیره چنین میشود:
HTTP caller -> TcpListener -> HttpServer -> APIServer
بایتهای کامل frame API را در body درخواست HTTP با Content-Length یا encoding
معتبر chunked قرار دهید. HttpServer method و path تنظیمشده را بررسی میکند و
سپس بایتهای body را میفرستد. احراز هویت API در هر envelope JSON باقی میماند؛
header و URL در HTTP به selector API یا بدنهٔ operation تبدیل نمیشوند. این یک
تونل body است، نه مسیریابی REST یا HTTP status جدا برای هر operation.
در این تنظیم HTTP/1.1، body پاسخ chunked است. پیش از خواندن طول API آن را dechunk کنید. هر chunk HTTP یک frame API نیست. prefix چهار بایتی API و دقیقاً تعداد بایت body اعلامشده را بخوانید؛ منتظر EOF یا chunk نهایی با طول صفر HTTP نمانید. حالت single تونل را پس از پایان body درخواست زنده نگه میدارد.
کلاینت عمومی api_client.py و CLI مدیریت فقط TCP مستقیم را میشناسند و درخواست HTTP نمیسازند.
برای یک درخواست status روی این config HTTP، کتابخانهٔ استاندارد http.client
headerها و dechunking را مدیریت میکند:
import http.client
import json
from pathlib import Path
import struct
def exact(body, length):
data = bytearray()
while len(data) < length:
part = body.read(length - len(data))
if not part:
raise RuntimeError("HTTP body ended before the API frame was complete")
data.extend(part)
return bytes(data)
request_id = "http-status-1"
envelope = {
"id": request_id,
"auth": {"client": "monitor", "token": Path("monitor-token").read_text(encoding="ascii").strip()},
"selector": {"config": "central", "node": "auth-db"},
"operation": "status", "content_type": "application/json",
"body_encoding": "utf8", "body": "{}"
}
payload = json.dumps(envelope, separators=(",", ":")).encode("utf-8")
frame = struct.pack("!I", len(payload)) + payload
connection = http.client.HTTPConnection("127.0.0.1", 9080, timeout=10)
try:
connection.request("POST", "/management", body=frame,
headers={"Content-Type": "application/octet-stream"})
response = connection.getresponse()
if response.status != 200:
raise RuntimeError("HTTP tunnel request was rejected")
length = struct.unpack("!I", exact(response, 4))[0]
if not 0 < length <= 1024 * 1024:
raise RuntimeError("API response exceeded the client limit")
reply = json.loads(exact(response, length).decode("utf-8"))
if reply["id"] != request_id or reply["error"] is not None:
raise RuntimeError("API correlation or gateway error")
result = reply["result"]
if result["body_encoding"] != "utf8":
raise RuntimeError("Unexpected status response encoding")
print(result["body"])
if result["code"] != 0:
raise SystemExit(1)
finally:
connection.close()
پس از تغییر config، قطعهکد را از api-demo اجرا کنید. یک پاسخ میخواند و بدون
retry خودکار اتصالش را میبندد. حالتهای HTTP و تنظیمات مستقلشان در
HttpServer آمدهاند.
stream قابل اعتماد روی UDP
اتصال مستقیم UdpListener -> APIServer پشتیبانی نمیشود. prefix طول، datagram
گمشده را بازیابی یا مرتب نمیکند و پاسخ قابل اعتماد بزرگتر از یک datagram را
بازسازی نمیکند. از nodeهای جفت موجود استفاده کنید:
TCP caller -> TcpListener -> TcpOverUdpClient -> UdpConnector
|
UDP transport
|
APIServer <- TcpOverUdpServer <- UdpListener
دوباره از مثال TCP مستقیم شروع کنید و api-listener را با این پنج node جایگزین
کنید. auth-db و management-api را در همان آرایهٔ nodes نگه دارید:
[
{"name":"api-listener","type":"TcpListener",
"settings":{"address":"127.0.0.1","port":9080},"next":"reliable-client"},
{"name":"reliable-client","type":"TcpOverUdpClient","next":"udp-out"},
{"name":"udp-out","type":"UdpConnector",
"settings":{"address":"127.0.0.1","port":9081}},
{"name":"udp-in","type":"UdpListener",
"settings":{"address":"127.0.0.1","port":9081},"next":"reliable-server"},
{"name":"reliable-server","type":"TcpOverUdpServer","next":"management-api"}
]
اگر نسخهٔ HTTP را تغییر میدهید، node مربوط به HTTP را حذف کنید تا خارج از زنجیره باقی نماند.
کلاینت Python همچنان با همان فرمان به port محلی TCP شمارهٔ ۹۰۸۰ وصل میشود.
UDP، KCP یا FEC را پیادهسازی نمیکند. TcpOverUdpClient و TcpOverUdpServer
stream را حمل و بازسازی میکنند؛ prefix چهار بایتی APIServer همچنان برای تشخیص
پیامهای کامل API در آن stream لازم است.
برای مستقلبودن مثال، هر دو سمت در یک فرایند و روی loopback اجرا میشوند. برای دو ماشین جدا، TCP listener، reliable client و UDP connector را سمت caller و UDP listener، reliable server، APIServer و دیتابیس هدف را سمت سرور قرار دهید. آدرسها را متناسب تغییر دهید. selector منطقی API به configهای بارشده روی سرور اشاره میکند. هنگام فعالکردن FEC تنظیمات دو طرف را یکسان کنید؛ قواعد MTU و تنظیمات بیشتر در TcpOverUdpClient و TcpOverUdpServer آمدهاند.
این مثالها transport را رمزنگاری نمیکنند. مثال محلی را روی loopback نگه دارید و برای دسترسی راه دور از transport حفاظتشده استفاده کنید. سقف منابع gateway، deadline و قواعد نتیجهٔ نامعلوم نوشتن در تمام ترکیبها یکساناند.