عنوان:

‫بررسی تفاوت رفتار HttpClient در دات‌نت 4.8 و NET Core.


نویسنده: وحید نصیری
تاریخ: ۱۴۰۵/۰۶/۱۸ ۰۸:۵۵
آدرس: www.dntips.ir
تفاوت رفتار HttpClient در دات‌نت 4.8 و .NET Core (یا .NET 5+) ناشی از پیاده‌سازی کاملاً متفاوت لایه شبکه در زیرساخت آن‌هاست: در .NET Framework 4.8 کلاس HttpClient روی HttpWebRequest و نهایتاً WinHTTP / Schannel ویندوز سوار است، در حالی که در .NET Core روی هندلر اختصاصی و کراس‌پلتفرم SocketsHttpHandler اجرا می‌شود. این تفاوت در معماری باعث ایجاد اختلاف در بایت‌های ارسالی روی شبکه می‌شود که اغلب منجر به خطای سمت سرور (مانند 500) می‌گردد:

۱. پروتکل TLS و ناهماهنگی Cipher Suites
در .NET Framework 4.8، مقدار پیش‌فرض پروتکل‌های امنیتی به تنظیمات سیستم‌عامل ویندوز یا نسخه Runtime وابسته است:
  • اگر سیستم به صورت سراسری به TLS 1.3 مجهز نباشد یا اپلیکیشن برای استفاده از SecurityProtocol پیش‌فرض تنظیم نشده باشد، Handshake ممکن است با TLS 1.2 قدیمی یا سایفرهایی انجام شود که سرور مقصد (یا Reverse Proxy جلوی آن مثل Cloudflare/Nginx) در پردازش آن با خطا مواجه می‌شود و خطای 500 برمی‌گرداند.

راه‌حل تست: قبل از ارسال درخواست در 4.8 کد زیر را درج کنید:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13;

۲. هدرExpect: 100-continue
در .NET Framework، مقدار پیش‌فرض هدر Expect100Continue در لایه ServicePointManager به صورت پیش‌فرض true است (در درخواست‌های POST و PUT).
  • این رفتار باعث می‌شود ابتدا هدر Expect: 100-continue فرستاده شود و کلاینت منتظر تأیید سرور بماند. بسیاری از سرورهای مدرن، API Gatewayها یا روترها این هدر را به درستی هندل نکرده و خطای 500 یا 417 صادر می‌کنند.
  • در .NET Core این رفتار به طور پیش‌فرض غیرفعال است.

راه‌حل تست:
// یا از طریق ServicePoint:
ServicePointManager.FindServicePoint(uri).Expect100Continue = false;

// یا مستقیماً روی HttpClient:
httpClient.DefaultRequestHeaders.ExpectContinue = false;

۳. انکودینگ و فرمت هدرها (به‌ویژه User-Agent)
  • در .NET Core اگر مقدار User-Agent را تنظیم نکنید، ممکن است درخواستی تمیز ارسال شود، اما در دات‌نت 4.8 گاهی هدرهای پیش‌فرضی از استک ویندوز یا مقادیر خالی تزریق می‌شوند. برخی سرورها (مانند Cloudflare، WAFها یا کنترلرهای خاص ASP.NET) در صورت نبود User-Agent یا دریافت فرمت غیراستاندارد، کرش می‌کنند.
  • مطمئن شوید یک هدر معتبر تعریف شده است:
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64)");

۴. نحوه Serialization بدنه و هدر Content-Type (Charset)
نحوه اضافه شدن خودکار پارامتر charset در دات‌نت 4.8 و .NET Core تفاوت دارد:
  • در دات‌نت 4.8 وقتی از StringContent با Encoding.UTF8 استفاده می‌کنید: application/json; charset=utf-8 ارسال می‌شود.
  • در .NET Core مدرن، بسیاری از متدها مانند PostAsJsonAsync صرفاً application/json را بدون پسوند charset ارسال می‌کنند. برخی از Serializerهای سخت‌گیر سمت سرور با وجود charset=utf-8 در Content-Type در Deserialize کردن دچار Exception شده و خطای 500 برمی‌گردانند.

راه‌حل تست:
var content = new StringContent(jsonString, Encoding.UTF8);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); // حذف پارامتر charset

۵. مکانیزم اتصال و چرخه سوکت‌ها (Keep-Alive و Connection)
  • در .NET Framework هدرهای اتصال توسط ServicePoint مدیریت می‌شوند که ممکن است هدر Connection: Keep-Alive را با تنظیمات خاص ارسال کند.
  • همچنین در .NET Framework برای هاست‌های مختلف ممکن است محدودیت DefaultConnectionLimit = 2 اعمال شود که باعث صف‌بندی یا قطع نابهنگام کانکشن توسط سرور گردد:
ServicePointManager.DefaultConnectionLimit = 50;

روش قطعی برای مقایسه و کشف تفاوت
سریع‌ترین راه برای پیدا کردن بایت دقیق مشکل‌ساز، بررسی ترافیک با یک ابزار شنود شبکه (مثل Fiddler Classic یا Wireshark) است:
  • Fiddler را باز کنید.
  • کد را یک‌بار با .NET Framework 4.8 و یک‌بار با .NET Core اجرا کنید.
  • در تب Inspectors -> Raw هر دو درخواست (دقیقاً هدرها و بایت‌های Payload) را در کنار هم مقایسه کنید. تفاوت در یکی از هدرهای Expect، Content-Type یا Transfer-Encoding: chunked خواهد بود.