HttpClient
HttpClient nodeای از نوع stream است که payloadِ WaterWall را در درخواست HTTP سمت کلاینت قرار میدهد. در مسیر رفت، داده را با HTTP فریمبندی میکند و در مسیر برگشت این فریمبندی را از پاسخ برمیدارد تا node قبلی فقط body پاسخ را بگیرد.
این node از HTTP/1.1، اتصال مستقیم HTTP/2، upgrade از HTTP/1.1 به h2c، token سفارشی upgrade، حالت split در HTTP/1.1 و WebSocket پشتیبانی میکند.
HttpClient خودش TLS برقرار نمیکند. اگر روی wire به ترافیک HTTPS نیاز دارید، TlsClient را پس از HttpClient قرار دهید.
جایگاه رایج
SomeTunnel -> HttpClient -> TcpConnector
SomeTunnel -> HttpClient -> TlsClient -> TcpConnector
نمونه همراه با mux و TLS:
TcpListener -> MuxClient -> HttpClient -> TlsClient -> TcpConnector
بدون TLS:
TcpListener -> MuxClient -> HttpClient -> TcpConnector
سازگاری با ALPN در TlsClient
وقتی TlsClient بعد از HttpClient قرار میگیرد، HTTP version تنظیمشده و protocolهایی که از طریق TLS ALPN پیشنهاد
میشوند باید با هم یکسان باشند. HttpClient protocol روی wire را فقط بر اساس http-version انتخاب میکند. این node ALPN
انتخابشده توسط server را از TlsClient دریافت نمیکند و پس از TLS handshake نیز نمیتواند mode مربوط به HTTP را تغییر
دهد. مسئولیت هماهنگ نگه داشتن تنظیمات دو node با کاربر است.
وقتی به یک پیشنهاد قطعی تکprotocol نیاز دارید، از این ترکیبها استفاده کنید:
| protocol موردنظر | تنظیم HttpClient | تنظیم TlsClient |
|---|---|---|
| HTTP/1.1، حالت split در HTTP/1.1 یا WebSocket Upgrade کلاسیک | "http-version": 1 | "alpns": ["http/1.1"] |
HTTP/2 مستقیم یا WebSocket روی extended CONNECT در HTTP/2 | "http-version": 2 | "alpns": ["h2"] |
برای WebSocket روی HTTP/2، مقدار "http-version": 2 را همراه با "websocket": true تنظیم کنید. در این حالت
HttpClient از مسیر extended CONNECT استفاده میکند. فقط زمانی HTTP/1.1 را انتخاب کنید که peer منتظر upgrade کلاسیک
با GET و 101 Switching Protocols باشد.
پیشنهاد همزمان h2 و http/1.1 در TlsClient معتبر و رایج است و ClientHello را به پیشفرض Chrome نزدیکتر نگه
میدارد. هنگام استفاده از این پیشنهاد، کاربر باید بداند target server کدام protocol را انتخاب میکند و این HttpClient
را برای همان version تنظیم کند. برای مثال، یک hostname معمولی پروکسیشده Cloudflare با HTTP/2 فعال، وقتی h2 پیشنهاد
شود آن را انتخاب میکند؛ بنابراین HttpClient باید روی HTTP/2 باشد. اگر انتخاب target نامشخص یا متغیر است، فقط protocol
هماهنگ با HttpClient را پیشنهاد کنید.
http-version: "both" و نامهای مستعار آن نیز ALPN-aware نیستند. این حالت فقط upgrade لایه application از HTTP/1.1 به
h2c را پیادهسازی میکند و از نتیجه TLS ALPN پیروی نمیکند. وقتی node بعدی TlsClient است، یک HTTP version مشخص را
انتخاب کنید.
تا ژوئیه ۲۰۲۶، فرض انتخاب h2 در بالا برای HTTPS معمولی از طریق hostname پروکسیشده Cloudflare با HTTP/2
فعال است و برای WebSocket صدق نمیکند. رفتار
فعلی مستندشده Cloudflare برای WebSocketهای proxyشده از extended
CONNECT مربوط به WebSocket روی HTTP/2 (RFC 8441) پشتیبانی نمیکند.
برای WebSocket پشت Cloudflare، HttpClient را روی WebSocket Upgrade در HTTP/1.1 قرار دهید و در TlsClient بعدی فقط
"alpns": ["http/1.1"] را پیشنهاد کنید. پیشنهاد همزمان هر دو مقدار باعث میشود Cloudflare مقدار h2 را انتخاب کند؛
در این حالت HttpClient روی HTTP/1.1 با protocol مذاکرهشده ناسازگار میشود و HttpClient روی HTTP/2 نیز وارد مسیر
پشتیبانینشده extended CONNECT خواهد شد. چون تنظیمات و پشتیبانی provider ممکن است تغییر کند، پیش از تکیه بر هرکدام از
این رفتارها قابلیتهای فعلی Cloudflare را دوباره بررسی کنید.
نمونه تنظیم
{
"name": "http-client",
"type": "HttpClient",
"settings": {
"host": "example.com",
"path": "/api/tunnel",
"scheme": "https",
"port": 443,
"method": "POST",
"http-version": 2,
"headers": {
"x-client-name": "waterwall"
}
},
"next": "tls-or-transport"
}
تنظیمات ضروری
| فیلد | نوع | توضیح |
|---|---|---|
host | string | اجباری. در HTTP/1.1 بهعنوان Host و در HTTP/2 بهعنوان :authority استفاده میشود. |
object settings باید وجود داشته باشد و خالی نباشد.
تنظیمات اختیاری
| گزینه | پیشفرض | توضیح |
|---|---|---|
path | / | مسیر درخواست. |
scheme | https | فقط metadata مربوط به HTTP است و TLS را فعال نمیکند. |
port | برای https مقدار 443 و در غیر این صورت 80 | پورت داخل metadata HTTP. |
method | POST | متد درخواست در حالت غیر WebSocket. |
user-agent | WaterWall/1.x | مقدار headerِ User-Agent. |
http-version | HTTP/2 | حالت پروتکل؛ مقدارهای عددی و نامهای مستعار متنی را میپذیرد. |
upgrade | فقط در حالت both برابر true | در صورت امکان، کار را با درخواست upgrade در HTTP/1.1 آغاز میکند. |
upgrade-protocol | h2c | توکن upgrade. اگر غیر از h2c باشد، بعد از 101 مسیر به raw forwarding تبدیل میشود. |
upgrade-request-headers | ندارد | headerهای اضافه مخصوص درخواست ارتقا. |
upgrade-response-headers | ندارد | headerهایی که باید در پاسخ 101 وجود داشته باشند. |
headers | ندارد | headerهای اضافه برای درخواست. مقدارها باید string باشند. |
content-type | ندارد | مقدار Content-type از جدول داخلی واتروال انتخاب میشود. |
websocket | false | پس از handshake، payloadها را در فریمهای WebSocket جابهجا میکند. |
websocket-origin | ندارد | مقدار اختیاری Origin. |
websocket-subprotocol | ندارد | subprotocol اختیاری. |
websocket-extensions | ندارد | header مربوط به extension را میفرستد. اگر peer یک extension را negotiate کند، اتصال رد میشود. |
http1-mode | single | شکل HTTP/1.1: مقدار single یا split. گزینه قدیمی http1-split هم پذیرفته میشود. |
split | مقادیر پیشفرض داخلی | تنظیمات حالت split. |
verbose | false | لاگ بیشتر برای handshake، انتخاب پروتکل و فریمبندی. |
متدهای پشتیبانیشده شامل موارد رایجی مانند GET، POST، PUT، PATCH، DELETE، HEAD، CONNECT و OPTIONS و نیز متدهای توسعهیافته سبک WebDAV در جدول داخلی WaterWall هستند.
WaterWall مقدار content-type را در جدول داخلی خود جستوجو میکند. مقادیر رایجی مانند application/json، text/plain و application/octet-stream پشتیبانی میشوند. اگر مقدار کاملاً سفارشی میخواهید، آن را در headers بنویسید.
حالتهای 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/1.1 و در صورت امکان ارتقا به h2c. |
پیشفرض فعلی HttpClient، HTTP/2 مستقیم است.
حالت HTTP/1.1 معمولی
با http1-mode: "single" یک اتصال HTTP/1.1 ترافیک هر دو جهت را حمل میکند:
- payloadِ upstream به body درخواست تبدیل میشود.
- body پاسخ به payloadِ WaterWall تبدیل میشود.
- درخواست با
Transfer-Encoding: chunkedارسال میشود. - هر chunk خروجی حداکثر ۱۶٬۳۷۲ بایت body دارد؛ پیشوند اندازه، body و CRLF انتهایی با هم ارسال میشوند.
- payload خالی چیزی ارسال نمیکند؛ ورودی بزرگتر بدون انتظار برای دادهٔ بیشتر به چند chunk تقسیم میشود.
- هنگام
Finishدر upstream، آخرین chunk یعنی0فرستاده و سپس Finishِ WaterWall منتشر میشود.
Parser پاسخ میتواند body از نوع chunked، بدنه دارای Content-Length، body تا زمان بسته شدن و پاسخهای بدون body را بخواند.
HttpClient و HttpServer در حالت single همیشه full duplex هستند. Finish در Waterwall کل line را میبندد و half-close وجود ندارد؛ بنابراین پایان body درخواست یا پاسخ بهتنهایی line تونل را نمیبندد. تنها استثنا http1-mode: "split" است: نیمه upload یک اتصال جداست و پایان body آن سیگنال توقف ارسال peer محسوب میشود.
اگر peer راهدور پس از تکمیل پاسخ، انتقال HTTP را باز نگه دارد، برای نمونه با HTTP keep-alive، line در Waterwall نیز باز میماند تا peer آن را ببندد یا مهلت بیکاری پیکربندیشدهٔ انتقال downstream منقضی شود. HttpServer متناظر Waterwall معمولاً پس از پاسخ تونلشدهٔ تکی خود اتصال را میبندد.
حالت HTTP/1.1 split
در حالت split، برای هر line در WaterWall دو درخواست HTTP/1.1 ساخته میشود:
- درخواست upload برای فرستادن payload به سمت سرور
- درخواست download برای گرفتن payload برگشتی از سرور
- دو نیمه با id، direction و token اختیاری به هم جفت میشوند
HttpClient برای هر line اصلی یک شناسه جفتسازی ۱۲۸ بیتی تازه با CSPRNG از دو مقدار fastRand64() (۱۶ بایت از stream ChaCha20 همان thread) تولید میکند و آن را دقیقاً بهصورت ۳۲ کاراکتر hex با حروف کوچک میفرستد. این مقدار metadata معمولی برای تطبیق بخشهای upload و download است. اگر استقرار به محافظت از ترافیک نیاز دارد، میتوان TlsClient، EncryptionClient یا یک node امنیتی دیگر را به chain اضافه کرد.
این حالت زمانی مفید است که proxy، CDN یا middlebox با یک اتصال طولانی و دوطرفه HTTP/1.1 خوب کار نمیکند، اما با مسیرهای جداگانه upload و download سازگارتر است.
{
"name": "http-client",
"type": "HttpClient",
"settings": {
"host": "cdn.example",
"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",
"upload-value": "up",
"download-value": "down",
"token": "shared-token",
"token-placement": "header",
"token-name": "X-Tunnel-Token",
"upload-headers": {
"Cache-Control": "no-store"
},
"download-headers": {
"Accept": "application/octet-stream"
}
}
},
"next": "transport"
}
فیلدهای رایج داخل split:
| گزینه | پیشفرض | توضیح |
|---|---|---|
upload-method | متد سطح اصلی | متد درخواست سمت upload. |
download-method | GET | متد درخواست سمت download. |
upload-path | path اصلی | مسیر سمت upload. |
download-path | path اصلی | مسیر سمت download. |
upload-headers | ندارد | headerهای اضافه نیمه upload. |
download-headers | ندارد | headerهای اضافه نیمه download. |
id-placement | query | محل قرارگیری شناسه مشترک. |
id-name | wwid | نام فیلد شناسه. |
direction-placement | query | محل قرار دادن marker جهت. |
direction-name | wwdir | نام marker مربوط به جهت. |
upload-value | upload | مقدار جهت برای upload. |
download-value | download | مقدار جهت برای download. |
cache-bypass | true | metadata متغیر برای کاهش احتمال ذخیره شدن در cache. |
cache-bypass-name | wwcb | نام فیلد cache bypass. |
token | ندارد | token مشترک اختیاری. |
token-placement | header | محل قرارگیری token. |
token-name | X-Waterwall-Token | نام فیلد token. |
برای placement میتوانید query، header، cookie یا path را انتخاب کنید. در template مسیر نیز placeholderهای {id}، {direction}، {cache} و {token} قابل استفادهاند.
تنظیمات split را میتوان با فیلدهای مستقیمی مانند upload-method و download-method نوشت، یا از objectهای تودرتوی upload و download با method، path و headers جداگانه برای هر نیمه استفاده کرد.
Split mode فقط با http-version = 1 معتبر است و با websocket ترکیب نمیشود.
HTTP/2
در حالت HTTP/2، این node برای هر line در WaterWall یک stream منطقی باز میکند. طراحی فعلی از چند stream مستقل روی یک line پشتیبانی نمیکند.
جزئیات مهم:
- پروفایل SETTINGS معمولی از مرجع دسکتاپ Chrome 154 پیروی میکند: جدول header برابر ۶۵٬۵۳۶ بایت، push غیرفعال، window اولیهٔ دریافت stream برابر 6 MiB و محدودیت فهرست header برابر ۲۶۲٬۱۴۴ بایت است.
- بیشترین اندازهٔ فریم دریافتی همان مقدار پیشفرض 16 KiB میماند و window دریافت connection به 15 MiB افزایش مییابد.
- در HTTP/2 مستقیم، preface و SETTINGS اولیه و WINDOW_UPDATE اتصال با هم ارسال میشوند؛ HEADERS درخواست جداگانه فرستاده میشود.
- body هر فریم DATA خروجی حداکثر ۱۶٬۳۷۵ بایت است تا همراه header نهبایتی فریم در یک رکورد plaintext با اندازهٔ 16 KiB جا بگیرد؛ flow controlِ peer میتواند آن را کوچکتر کند.
- حالت WebSocket علاوه بر اینها
ENABLE_CONNECT_PROTOCOL = 1را اعلام میکند؛h2cنیز از همین پروفایل SETTINGS استفاده میکند. - فریمهای DATA دریافتی به payloadِ WaterWall تبدیل میشوند.
Finishدر upstream بهEND_STREAMتبدیل میشود.
برای جمعکردن دادهها تأخیری اضافه نمیشود. وقتی TlsClient مستقیماً بعد از این node باشد، این انتخابها برخی تفاوتهای مرز رکورد را کاهش میدهند؛ اما headerهای مرورگر، زمانبندی درخواستها یا pool اتصالهای مرورگر را بازسازی نمیکنند. تنظیم صریح TLS shaping یا nodeهای واسط میتواند رکوردهای نهایی را تغییر دهد.
اگر میخواهید چند اتصال منطقی را multiplex کنید، پیش از HTTP از MuxClient استفاده کنید.
Upgrade
اگر http-version برابر both باشد و upgrade فعال باشد، ارتباط با یک درخواست upgrade در HTTP/1.1 آغاز میشود.
در حالت پیشفرض h2c، request شامل این موارد است:
Connection: Upgrade, HTTP2-SettingsUpgrade: h2cHTTP2-Settings
اگر سرور پاسخ 101 Switching Protocols بدهد، line وارد HTTP/2 میشود. stream اصلی ارتقایافته لغو میشود و یک stream تازه و واحد HTTP/2 برای payload تونل باز خواهد شد.
اگر پاسخی عادی و غیر از 101 برگردد، مسیر روی HTTP/1.1 میماند و payload bufferشده بهصورت chunked ارسال میشود.
اگر upgrade-protocol توکنی سفارشی و غیر از h2c باشد، پس از 101 مسیر به عبور دوطرفه بایتهای خام تبدیل میشود.
پس از ارسال کامل درخواست آغازین و دادههای نگهداشتهشده در هر دو جهت، HttpClient وضعیت HTTP خود را پاک میکند و برای همان line بهصورت دائمی bypass میشود. ورودیهای بازگشتی تا پایان این انتقال ترتیب FIFO را حفظ میکنند. حالتهای دارای فریمبندی HTTP، HTTP/2، WebSocket و split فعال میمانند، چون هنوز وظیفهٔ پروتکلی یا نگاشت line دارند.
Upgrade در HTTP/1.1 همراه با body درخواست، برای طراحی تکاستریم فعلی حالت امنی نیست؛ به این رفتار تکیه نکنید.
WebSocket
با websocket: true، HTTP فقط برای handshake استفاده میشود:
- HTTP/1.1 از
GETو پاسخ101استفاده میکند - HTTP/2 از extended
CONNECTاستفاده میکند - payloadهای upstream به فریم binary تبدیل میشوند.
- فریمهای text، binary و continuation از peer به payload خام تبدیل میشوند.
- ping و pong داخل خود node مدیریت میشوند
- پیش از Finish یک فریم close فرستاده میشود.
در HTTP/1.1 مقدار Sec-WebSocket-Accept اعتبارسنجی میشود و فریمهای سمت سرور نباید mask شده باشند. اگر peer روی extensionای به توافق برسد، اتصال رد میشود، زیرا extensionها در این node پیادهسازی نشدهاند.
در WebSocket روی HTTP/2، peer باید پیش از فرستادن extended CONNECT مقدار SETTINGS_ENABLE_CONNECT_PROTOCOL = 1 را اعلام کند.
http-version: "both" همراه websocket: true، WebSocket opening handshake را روی HTTP/1.1 نگه میدارد.
وقتی TlsClient بعد از این node قرار دارد، مسیر WebSocket را با ALPNای هماهنگ کنید که target server انتخاب خواهد کرد:
- WebSocket Upgrade روی HTTP/1.1: مقدار
"http-version": 1همراه با"alpns": ["http/1.1"] - WebSocket روی extended
CONNECTدر HTTP/2: مقدار"http-version": 2همراه با"alpns": ["h2"]
پیشنهاد Chrome-like شامل هر دو مقدار نیز وقتی انتخاب target شناختهشده و ثابت است معتبر است، اما HttpClient باید برای
همان نتیجه شناختهشده تنظیم شود، زیرا انتخاب server را نمیبیند. WebSocket پشت Cloudflare استثنای توضیحدادهشده در
بالاست و باید روی HTTP/1.1 اجبار شود.
رفتار Finish و Lifecycle
HttpClient باید پیش از انتشار بعضی رویدادهای Finish در WaterWall، بایتهای پایانی پروتکل را بفرستد:
- HTTP/1.1 آخرین chunk یعنی
0را میفرستد. - HTTP/2 یک
END_STREAMمیفرستد. - WebSocket یک فریم close میفرستد.
پیادهسازی ابتدا جهت مربوط را finished علامت میزند، سپس state محلی HTTP را از بین میبرد و در پایان Finish واقعی WaterWall را منتشر میکند. این ترتیب مانع از آن میشود که callbackهای pause/resume یا close ناشی از فرستادن بایتهای پایانی پروتکل، به سمتی برگردند که قبلاً finish شده است.
در اتصالهای معمولی روی یک line، اعلانهای تکراری Pause و Resume بدون تغییر عبور میکنند و ارسال خروجی آماده را متوقف نمیکنند. اتصالهای split نگاشت میان line اصلی و مسیرهای upload/download را حفظ میکنند. فریمبندی HTTP، ترتیب FIFO و محدودیتهای ورودی نگهداشتهشده همچنان برقرارند.
Buffer Padding
HttpClient برای مسیرهای فریمبندی، 16 بایت left padding اعلام میکند. nodeهای اطراف باید این فضا را حفظ کنند.
اشتباههای رایج
- تنظیم
scheme: "https"بدون افزودنTlsClient؛ scheme فقط metadata مربوط به HTTP است. - پیشنهاد چند protocol در ALPN بدون دانستن اینکه target کدام را انتخاب میکند؛ پیشنهاد دوگانه Chrome-like معتبر است،
اما
HttpClientباید برای نتیجه شناختهشده تنظیم شود، زیرا انتخاب ALPN به این node برگردانده نمیشود. - استفاده از
http-version: "both"بهعنوان negotiation مربوط به TLS؛ این گزینه mode ارتقا از HTTP/1.1 بهh2cاست. - استفاده از
http1-mode: "split"با HTTP/2 یا WebSocket؛ split mode فقط برای HTTP/1.1 است. - انتظار اینکه
HttpClientheaderهای HTTP را در اختیار node قبلی بگذارد؛ فقط payloadِ body عبور داده میشود. - انتظار multiplex شدن تعداد زیادی stream در HTTP/2؛ برای multiplexing در سطح WaterWall، پیش از HTTP از
MuxClientاستفاده کنید. - فرستادن extension سفارشی WebSocket و انتظار پیادهسازی آن در تونل.
متادیتای نود
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 |