HttpClient
HttpClient نودی از نوع stream است که payloadِ WaterWall را در درخواست HTTP سمت کلاینت قرار میدهد. در مسیر رفت، داده را با HTTP فریمبندی میکند و در مسیر برگشت این فریمبندی را از پاسخ برمیدارد تا نود قبلی فقط body پاسخ را بگیرد.
این نود از HTTP/1.1، اتصال مستقیم HTTP/2، ارتقای HTTP/1.1 به h2c، توکن سفارشی 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 انتخاب میکند. این نود ALPN
انتخابشده توسط server را از TlsClient دریافت نمیکند و پس از TLS handshake نیز نمیتواند mode مربوط به HTTP را تغییر
دهد. مسئولیت هماهنگ نگه داشتن تنظیمات دو نود با کاربر است.
وقتی به یک پیشنهاد قطعی تک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 پیروی نمیکند. وقتی نود بعدی 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 استفاده میشود. |
آبجکت 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 کند، اتصال رد میشود. |
full-duplex | false | بیشتر برای هماهنگی با HttpServer است؛ سمت کلاینت در HTTP/1.1 بهصورت chunked و دوطرفه stream میکند. |
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ارسال میشود. - هنگام
Finishدر upstream، آخرین chunk یعنی0فرستاده و سپس Finishِ WaterWall منتشر میشود.
Parser پاسخ میتواند body از نوع chunked، بدنه دارای Content-Length، body تا زمان بسته شدن و پاسخهای بدون body را بخواند.
حالت HTTP/1.1 split
در حالت split، برای هر line در WaterWall دو درخواست HTTP/1.1 ساخته میشود:
- درخواست upload برای فرستادن payload به سمت سرور
- درخواست download برای گرفتن payload برگشتی از سرور
- دو نیمه با id، direction و token اختیاری به هم جفت میشوند
این حالت زمانی مفید است که 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، این نود برای هر line در WaterWall یک stream منطقی باز میکند. طراحی فعلی از چند stream مستقل روی یک line پشتیبانی نمیکند.
جزئیات مهم:
- مقدار
MAX_CONCURRENT_STREAMSبرابر1است - window اولیه stream برابر 1 MiB است.
- بیشترین اندازه فریم 32 KiB است.
- فریمهای DATA دریافتی به payloadِ WaterWall تبدیل میشوند.
Finishدر upstream بهEND_STREAMتبدیل میشود.
اگر میخواهید چند اتصال منطقی را 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 بافرشده بهصورت chunked ارسال میشود.
اگر upgrade-protocol توکنی سفارشی و غیر از h2c باشد، پس از 101 مسیر به عبور دوطرفه بایتهای خام تبدیل میشود.
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 داخل خود نود مدیریت میشوند
- پیش از Finish یک فریم close فرستاده میشود.
در HTTP/1.1 مقدار Sec-WebSocket-Accept اعتبارسنجی میشود و فریمهای سمت سرور نباید mask شده باشند. اگر peer روی extensionای به توافق برسد، اتصال رد میشود، زیرا extensionها در این نود پیادهسازی نشدهاند.
در WebSocket روی HTTP/2، peer باید پیش از فرستادن extended CONNECT مقدار SETTINGS_ENABLE_CONNECT_PROTOCOL = 1 را اعلام کند.
http-version: "both" همراه websocket: true، WebSocket opening handshake را روی HTTP/1.1 نگه میدارد.
وقتی TlsClient بعد از این نود قرار دارد، مسیر 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 شده است.
Buffer Padding
HttpClient برای مسیرهای فریمبندی، 16 بایت left padding اعلام میکند. نودهای اطراف باید این فضا را حفظ کنند.
اشتباههای رایج
- تنظیم
scheme: "https"بدون افزودنTlsClient؛ scheme فقط metadata مربوط به HTTP است. - پیشنهاد چند protocol در ALPN بدون دانستن اینکه target کدام را انتخاب میکند؛ پیشنهاد دوگانه Chrome-like معتبر است،
اما
HttpClientباید برای نتیجه شناختهشده تنظیم شود، زیرا انتخاب ALPN به این نود برگردانده نمیشود. - استفاده از
http-version: "both"بهعنوان negotiation مربوط به TLS؛ این گزینه mode ارتقا از HTTP/1.1 بهh2cاست. - استفاده از
http1-mode: "split"با HTTP/2 یا WebSocket؛ split mode فقط برای HTTP/1.1 است. - انتظار اینکه
HttpClientheaderهای HTTP را در اختیار نود قبلی بگذارد؛ فقط payloadِ body عبور داده میشود. - انتظار multiplex شدن تعداد زیادی stream در HTTP/2؛ برای multiplexing در سطح WaterWall، پیش از HTTP از
MuxClientاستفاده کنید. - فرستادن extension سفارشی WebSocket و انتظار پیادهسازی آن در تونل.