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

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

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

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

object 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 کند، اتصال رد می‌شود.
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 ارسال می‌شود.
  • هر 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-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، این 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-Settings
  • Upgrade: h2c
  • HTTP2-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 است.
  • انتظار اینکه HttpClient header‌های HTTP را در اختیار node قبلی بگذارد؛ فقط payloadِ body عبور داده می‌شود.
  • انتظار multiplex شدن تعداد زیادی stream در HTTP/2؛ برای multiplexing در سطح WaterWall، پیش از HTTP از MuxClient استفاده کنید.
  • فرستادن extension سفارشی WebSocket و انتظار پیاده‌سازی آن در تونل.

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

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

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