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

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"
}

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

فیلدنوعتوضیح
hoststringاجباری. در HTTP/1.1 به‌عنوان Host و در HTTP/2 به‌عنوان :authority استفاده می‌شود.

آبجکت settings باید وجود داشته باشد و خالی نباشد.

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

گزینهپیش‌فرضتوضیح
path/مسیر درخواست.
schemehttpsفقط metadata مربوط به HTTP است و TLS را فعال نمی‌کند.
portبرای https مقدار 443 و در غیر این صورت 80پورت داخل metadata HTTP.
methodPOSTمتد درخواست در حالت غیر WebSocket.
user-agentWaterWall/1.xمقدار headerِ User-Agent.
http-versionHTTP/2حالت پروتکل؛ مقدارهای عددی و نام‌های مستعار متنی را می‌پذیرد.
upgradeفقط در حالت both برابر trueدر صورت امکان، کار را با درخواست upgrade در HTTP/1.1 آغاز می‌کند.
upgrade-protocolh2cتوکن upgrade. اگر غیر از h2c باشد، بعد از 101 مسیر به raw forwarding تبدیل می‌شود.
upgrade-request-headersنداردheaderهای اضافه مخصوص درخواست ارتقا.
upgrade-response-headersنداردheaderهایی که باید در پاسخ 101 وجود داشته باشند.
headersنداردheaderهای اضافه برای درخواست. مقدارها باید string باشند.
content-typeنداردمقدار Content-Type از جدول داخلی واتروال انتخاب می‌شود.
websocketfalseپس از handshake، payloadها را در فریم‌های WebSocket جابه‌جا می‌کند.
websocket-originنداردمقدار اختیاری Origin.
websocket-subprotocolنداردsubprotocol اختیاری.
websocket-extensionsنداردheader مربوط به extension را می‌فرستد. اگر peer یک extension را negotiate کند، اتصال رد می‌شود.
full-duplexfalseبیشتر برای هماهنگی با HttpServer است؛ سمت کلاینت در HTTP/1.1 به‌صورت chunked و دوطرفه stream می‌کند.
http1-modesingleشکل HTTP/1.1: مقدار single یا split. گزینه قدیمی http1-split هم پذیرفته می‌شود.
splitمقادیر پیش‌فرض داخلیتنظیمات حالت split.
verbosefalseلاگ بیشتر برای 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-methodGETمتد درخواست سمت download.
upload-pathpath اصلیمسیر سمت upload.
download-pathpath اصلیمسیر سمت download.
upload-headersنداردheaderهای اضافه نیمه upload.
download-headersنداردheaderهای اضافه نیمه download.
id-placementqueryمحل قرارگیری شناسه مشترک.
id-namewwidنام فیلد شناسه.
direction-placementqueryمحل قرار دادن marker جهت.
direction-namewwdirنام marker مربوط به جهت.
upload-valueuploadمقدار جهت برای upload.
download-valuedownloadمقدار جهت برای download.
cache-bypasstruemetadata متغیر برای کاهش احتمال ذخیره شدن در cache.
cache-bypass-namewwcbنام فیلد cache bypass.
tokenنداردtoken مشترک اختیاری.
token-placementheaderمحل قرارگیری token.
token-nameX-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-Settings
  • Upgrade: h2c
  • HTTP2-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 است.
  • انتظار اینکه HttpClient headerهای HTTP را در اختیار نود قبلی بگذارد؛ فقط payloadِ body عبور داده می‌شود.
  • انتظار multiplex شدن تعداد زیادی stream در HTTP/2؛ برای multiplexing در سطح WaterWall، پیش از HTTP از MuxClient استفاده کنید.
  • فرستادن extension سفارشی WebSocket و انتظار پیاده‌سازی آن در تونل.