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-version | both |
upgrade | در حالت both برابر true |
path | / |
method | POST |
status | 200 |
websocket | false |
http1-mode | single |
تنظیمات اختیاری
| گزینه | پیشفرض | توضیح |
|---|---|---|
http-version | both | حالت پروتکل؛ مقدارهای عددی و نامهای مستعار متنی را میپذیرد. |
upgrade | فقط در حالت both برابر true | درخواستهای upgrade در HTTP/1.1 را میپذیرد. |
upgrade-protocol | h2c | توکن upgrade. اگر غیر از h2c باشد، بعد از 101 مسیر raw میشود. |
upgrade-request-headers | ندارد | headerهایی که باید در درخواست ارتقا وجود داشته باشند. |
upgrade-response-headers | ندارد | headerهای اضافه برای پاسخ 101 Switching Protocols. |
host | ندارد | host مورد انتظار. اگر تنظیم شود، مقدار ناسازگار رد خواهد شد. |
path | / | مسیر مورد انتظار. |
method | POST | متد مورد انتظار در حالت غیر WebSocket. |
status | 200 | کد وضعیت پاسخ. بازه معتبر فعلی 100 تا 599 است. |
headers | ندارد | headerهای اضافه پاسخ. مقدارها باید string باشند. |
content-type | ندارد | مقدار Content-type از جدول داخلی واتروال. |
websocket | false | handshakeِ WebSocket را میپذیرد و سپس payload را در فریمهای WebSocket منتقل میکند. |
websocket-origin | ندارد | مقدار مورد انتظار Origin. |
websocket-subprotocol | ندارد | subprotocol مورد انتظار که در پاسخ تکرار میشود. |
http1-mode | single | شکل HTTP/1.1: مقدار single یا split. گزینه قدیمی http1-split هم پذیرفته میشود. |
split | مقادیر پیشفرض داخلی | تنظیمات جفت کردن دو درخواست در حالت split. |
no-split-upload-buffering-limit | false | گزینه مخصوص تست برای برداشتن سقف buffer سمت upload پیش از متصل شدن download. |
verbose | false | لاگ بیشتر برای تشخیص پروتکل، 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-method | GET | متد مورد انتظار سمت download. |
upload-path | path اصلی | مسیر سمت upload. |
download-path | path اصلی | مسیر سمت download. |
id-placement | query | محل خواندن شناسه مشترک. |
id-name | wwid | نام فیلد شناسه. |
direction-placement | query | محل خواندن marker مربوط به جهت. |
direction-name | wwdir | نام marker مربوط به جهت. |
upload-value | upload | مقدار جهت برای upload. |
download-value | download | مقدار جهت برای download. |
token | ندارد | token مشترک اختیاری. |
token-placement | header | محل خواندن token. |
token-name | X-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-SettingsUpgrade: h2cHTTP2-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 flags | kNodeFlagBlocksSplice |
can_have_prev | true |
can_have_next | true |
layer_group | kNodeLayer4 |
layer_group_prev_node | kNodeLayer4 |
layer_group_next_node | kNodeLayer4 |
required_padding_left | 16 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 همچنان این حد را غیرفعال میکند.