پرش به مطلب اصلی

HttpServer

HttpServer بخش سرور HttpClient است. درخواست ورودی HTTP را تجزیه می‌کند، فریم‌بندی HTTP را برمی‌دارد و فقط body درخواست را به node بعدی می‌دهد. در مسیر برگشت نیز payload دریافتی از node بعدی را به body پاسخ تبدیل می‌کند.

این node از HTTP/1.1، اتصال مستقیم HTTP/2، upgrade از HTTP/1.1 به h2c، token سفارشی upgrade، حالت split در HTTP/1.1 و WebSocket پشتیبانی می‌کند.

HttpServer خودش TLS را terminate نمی‌کند. اگر ترافیک ورودی HTTPS است، باید TlsServer را پیش از HttpServer قرار دهید.

جایگاه رایج​

TcpListener -> HttpServer -> SomeServiceTunnel
TcpListener -> TlsServer -> HttpServer -> SomeServiceTunnel

نمونه یک مسیر کامل:

Client side:
TcpListener -> MuxClient -> HttpClient -> TlsClient -> TcpConnector

Server side:
TcpListener -> TlsServer -> HttpServer -> MuxServer -> TcpConnector

نمونه تنظیم​

{
"name": "http-server",
"type": "HttpServer",
"settings": {
"http-version": "both",
"upgrade": true,
"host": "example.com",
"path": "/api/tunnel",
"method": "POST",
"status": 200,
"headers": {
"server": "waterwall"
}
},
"next": "service"
}

تنظیمات ضروری​

این node تنظیم اختصاصی اجباری ندارد، اما object settings باید وجود داشته باشد و خالی نباشد.

پیش‌فرض‌های مهم:

گزینهپیش‌فرض
http-versionboth
upgradeدر حالت both برابر true
path/
methodPOST
status200
websocketfalse
http1-modesingle

تنظیمات اختیاری​

گزینهپیش‌فرضتوضیح
http-versionbothحالت پروتکل؛ مقدارهای عددی و نام‌های مستعار متنی را می‌پذیرد.
upgradeفقط در حالت both برابر trueدرخواست‌های upgrade در HTTP/1.1 را می‌پذیرد.
upgrade-protocolh2cتوکن upgrade. اگر غیر از h2c باشد، بعد از 101 مسیر raw می‌شود.
upgrade-request-headersنداردheader‌هایی که باید در درخواست ارتقا وجود داشته باشند.
upgrade-response-headersنداردheader‌های اضافه برای پاسخ 101 Switching Protocols.
hostنداردhost مورد انتظار. اگر تنظیم شود، مقدار ناسازگار رد خواهد شد.
path/مسیر مورد انتظار.
methodPOSTمتد مورد انتظار در حالت غیر WebSocket.
status200کد وضعیت پاسخ. بازه معتبر فعلی 100 تا 599 است.
headersنداردheader‌های اضافه پاسخ. مقدارها باید string باشند.
content-typeنداردمقدار Content-type از جدول داخلی واتروال.
websocketfalsehandshakeِ WebSocket را می‌پذیرد و سپس payload را در فریم‌های WebSocket منتقل می‌کند.
websocket-originنداردمقدار مورد انتظار Origin.
websocket-subprotocolنداردsubprotocol مورد انتظار که در پاسخ تکرار می‌شود.
http1-modesingleشکل HTTP/1.1: مقدار single یا split. گزینه قدیمی http1-split هم پذیرفته می‌شود.
splitمقادیر پیش‌فرض داخلیتنظیمات جفت کردن دو درخواست در حالت split.
no-split-upload-buffering-limitfalseگزینه مخصوص تست برای برداشتن سقف buffer سمت upload پیش از متصل شدن download.
verbosefalseلاگ بیشتر برای تشخیص پروتکل، handshake و فریم‌بندی.

حالت‌های HTTP​

مقدار http-versionمعنی
1, 1.1, http1, http1.1اجبار به HTTP/1.1.
2, 2.0, http2, h2اجبار به HTTP/2 مستقیم.
both, any, auto, 1.1+2تشخیص HTTP/2 مستقیم یا HTTP/1.1، همراه با upgrade اختیاری.

HTTP/1.1 معمولی​

با http1-mode: "single" برای هر line در WaterWall یک درخواست و پاسخ HTTP/1.1 استفاده می‌شود:

  • header‌های درخواست در HttpServer خوانده می‌شوند.
  • body درخواست به payloadِ WaterWall تبدیل می‌شود.
  • payload برگشتی از node بعدی به body پاسخ تبدیل می‌شود.
  • پاسخ با Transfer-Encoding: chunked ساخته می‌شود.
  • هنگام Finish در downstream، chunk پایانی فرستاده و سپس Finishِ WaterWall منتشر می‌شود.

Parser درخواست می‌تواند body از نوع chunked، بدنه دارای Content-Length و درخواست بدون body را بخواند. Trailerهای chunked در خود node خوانده می‌شوند.

HttpClient و HttpServer در حالت single همیشه full duplex هستند. Finish در Waterwall کل line را می‌بندد و half-close وجود ندارد؛ بنابراین پایان body درخواست یا پاسخ به‌تنهایی line تونل را نمی‌بندد. تنها استثنا http1-mode: "split" است: نیمه upload یک اتصال جداست و پایان body آن سیگنال توقف ارسال peer محسوب می‌شود.

حالت HTTP/1.1 split​

در حالت split دو درخواست HTTP/1.1 به یک line در WaterWall متصل می‌شوند:

  • درخواست upload: body آن به node بعدی داده می‌شود.
  • درخواست download: body پاسخ آن، payload برگشتی از node بعدی را حمل می‌کند.
  • دو نیمه با id، direction و token اختیاری به هم جفت می‌شوند.

HttpClient برای هر line یک شناسه جفت‌سازی ۱۲۸ بیتی تازه با CSPRNG از دو مقدار fastRand64() (۱۶ بایت از stream ChaCha20 همان thread) تولید می‌کند و آن را به‌صورت ۳۲ کاراکتر hex با حروف کوچک می‌فرستد. سرور از این مقدار به‌عنوان metadata معمولی برای تطبیق بخش‌های upload و download استفاده می‌کند. اگر استقرار به محافظت از ترافیک نیاز دارد، می‌توان TlsServer، EncryptionServer یا یک node امنیتی دیگر را به chain اضافه کرد.

پایان body اتصال جداگانه upload، روی line اصلی یک Finish در upstream منتشر می‌کند.

این حالت باید با حالت split در HttpClient هماهنگ باشد.

{
"name": "http-server",
"type": "HttpServer",
"settings": {
"http-version": 1,
"http1-mode": "split",
"path": "/tunnel",
"split": {
"upload-method": "POST",
"download-method": "GET",
"id-placement": "query",
"id-name": "sid",
"direction-placement": "query",
"direction-name": "part",
"token": "shared-token",
"token-placement": "header",
"token-name": "X-Tunnel-Token"
},
"headers": {
"Cache-Control": "no-store"
}
},
"next": "service"
}

فیلدهای رایج داخل split:

گزینهپیش‌فرضتوضیح
upload-methodمتد سطح اصلیمتد مورد انتظار سمت upload.
download-methodGETمتد مورد انتظار سمت download.
upload-pathpath اصلیمسیر سمت upload.
download-pathpath اصلیمسیر سمت download.
id-placementqueryمحل خواندن شناسه مشترک.
id-namewwidنام فیلد شناسه.
direction-placementqueryمحل خواندن marker مربوط به جهت.
direction-namewwdirنام marker مربوط به جهت.
upload-valueuploadمقدار جهت برای upload.
download-valuedownloadمقدار جهت برای download.
tokenنداردtoken مشترک اختیاری.
token-placementheaderمحل خواندن token.
token-nameX-Waterwall-Tokenنام فیلد token.
هشدار

Split mode فقط با http-version = 1 معتبر است و با websocket ترکیب نمی‌شود.

no-split-upload-buffering-limit سقف ایمنی معمول buffer سمت upload را برمی‌دارد. این گزینه بیشتر برای تست و شرایط کنترل‌شده مناسب است، نه استفاده عادی.

HTTP/2​

در اتصال مستقیم HTTP/2، HttpServer یک stream منطقی برای line در WaterWall می‌پذیرد:

  • فریم‌های DATA درخواست به payloadِ upstream تبدیل می‌شوند.
  • payload برگشتی به فریم DATA پاسخ تبدیل می‌شود.
  • END_STREAM سمت درخواست فقط در state داخلی ثبت می‌شود و line تا بسته شدن transport باز می‌ماند.
  • Finish در downstream به END_STREAM پاسخ تبدیل می‌شود.

جزئیات مهم:

  • مقدار MAX_CONCURRENT_STREAMS برابر 1 است
  • window اولیه stream برابر 1 MiB است.
  • بیشترین اندازه فریم 32 KiB است.
  • stream‌های اضافه با REFUSED_STREAM رد می‌شوند.
  • در حالت WebSocket، تنظیم SETTINGS_ENABLE_CONNECT_PROTOCOL فعال می‌شود.

Upgrade​

با http-version: "both" و upgrade: true، node می‌تواند درخواست‌های upgrade را بپذیرد.

برای h2c، درخواست باید metadata لازم را داشته باشد:

  • Connection شامل Upgrade و HTTP2-Settings
  • Upgrade: h2c
  • HTTP2-Settings

پس از پذیرش upgrade، پاسخ 101 Switching Protocols فرستاده می‌شود و node وارد HTTP/2 می‌شود. درخواست اصلی پس از upgrade stream شماره 1 است و طراحی فعلی برای payload منتظر یک stream تونل پس از upgrade می‌ماند.

اگر upgrade-protocol توکنی سفارشی و غیر از h2c باشد، پس از handshake موفق مسیر به عبور دوطرفه بایت‌های خام تبدیل می‌شود.

پس از ارسال کامل پاسخ 101 و داده‌های خام پذیرفته‌شده، HttpServer وضعیت HTTP خود را آزاد می‌کند و برای آن line به‌صورت دائمی bypass می‌شود. داده‌های خامی که هنگام این گذار به‌صورت بازگشتی وارد می‌شوند، ترتیب FIFO را حفظ می‌کنند؛ حد هر صف جهت‌دار ۲ MiB هزینهٔ تخصیص و ۱٬۰۲۴ بافر است و عبور از حد، اتصال را می‌بندد. حالت‌های بدنهٔ HTTP، HTTP/2، WebSocket و split برای framing یا نگاشت line فعال می‌مانند. اعلان‌های Pause/Resume روی همان line شفاف و تکرارپذیر هستند، حتی در زمان upgrade، و ارسال خروجی آماده را به تعویق نمی‌اندازند.

WebSocket​

با websocket: true، ابتدا handshakeِ WebSocket بررسی می‌شود و سپس payload در فریم‌های WebSocket جابه‌جا می‌شود.

در HTTP/1.1:

  • متد باید GET باشد
  • header‌های upgrade WebSocket باید وجود داشته باشند
  • Sec-WebSocket-Key باید معتبر باشد
  • Sec-WebSocket-Version باید 13 باشد
  • request body پذیرفته نمی‌شود
  • اگر host، path، websocket-origin یا websocket-subprotocol تنظیم شده باشند، باید با درخواست منطبق باشند.

در HTTP/2:

  • request باید extended CONNECT باشد
  • مقدار :protocol باید websocket باشد
  • :path و authority/host در صورت تنظیم باید منطبق باشند.
  • Sec-WebSocket-Version باید 13 باشد

بعد از handshake:

  • فریم‌های سمت کلاینت باید mask شده باشند.
  • فریم‌های سمت سرور بدون mask فرستاده می‌شوند.
  • payload برگشتی WaterWall به فریم binary تبدیل می‌شود.
  • فریم‌های text، binary و continuation از peer به payload خام تبدیل می‌شوند.
  • ping و pong داخل node مدیریت می‌شوند
  • پیش از Finish یک فریم close فرستاده می‌شود.

رفتار Finish و Lifecycle​

HttpServer پیش از انتشار بعضی رویدادهای Finish در WaterWall، بایت‌های پایانی پروتکل را می‌فرستد:

  • HTTP/1.1 آخرین chunk پاسخ را می‌فرستد.
  • HTTP/2 یک END_STREAM برای پاسخ می‌فرستد.
  • WebSocket یک فریم close می‌فرستد.

پیاده‌سازی ابتدا جهت مربوط را finished علامت می‌زند، سپس state محلی HTTP را از بین می‌برد و در پایان Finish واقعی WaterWall را منتشر می‌کند. این ترتیب مانع از آن می‌شود که backpressure ناشی از بایت‌های پایانی پروتکل، callback‌های pause/resume را به سمتی برگرداند که قبلاً finish شده است.

در حالت split، HttpServer همان line منطقی WaterWall را می‌سازد و مدیریت می‌کند که دو نیمه upload و download در HTTP را به هم وصل می‌کند. نیمه‌های transport بسته می‌شوند، بی‌آنکه مانند اتصال‌های مستقل سرویس با آن‌ها رفتار شود.

Buffer Padding​

HttpServer برای مسیرهای فریم‌بندی، 16 بایت left padding اعلام می‌کند. هنگام ترکیب آن با node‌هایی که بایت پروتکل اضافه می‌کنند، این فضا را در نظر بگیرید.

اشتباه‌های رایج​

  • قرار دادن HttpServer مستقیم بعد از TcpListener وقتی ترافیک ورودی TLS رمزنگاری شده است. ابتدا TlsServer بگذارید.
  • انتظار عبور header به node بعدی؛ node بعدی فقط payloadِ body را می‌گیرد.
  • ترکیب حالت split با WebSocket یا HTTP/2.
  • انتظار تبدیل چند stream مستقل HTTP/2 به چند line در WaterWall.
  • استفاده از extension سفارشی WebSocket؛ مسیر WebSocket فقط فریم‌های بدون extension را منتقل می‌کند.

متادیتای نود​

metadata برگرفته از کد منبع:

ویژگیمقدار
node flagskNodeFlagBlocksSplice
can_have_prevtrue
can_have_nexttrue
layer_groupkNodeLayer4
layer_group_prev_nodekNodeLayer4
layer_group_next_nodekNodeLayer4
required_padding_left16 bytes

حد انتظار upload در split برابر 131070 * max(1, ceil(L / 32768)) بایت است و فقط عبور از آن موجب بستن می‌شود. L مبنای مستقل بودجهٔ انتظار در pool مربوط به line است: ۶۴ KiB در S1/S2 و ۱ MiB در پروفایل‌های بالاتر. این مبنا از اندازهٔ buffer‌های عادی و سقف splice مستقل است. header و جفت‌شدن ابتدا پردازش می‌شوند؛ body همراه header جزو حد header نیست. گزینهٔ no-split-upload-buffering-limit همچنان این حد را غیرفعال می‌کند.