العميل HttpClient الحديث
العميل HttpClient الحديث
قبل Java 11، كان إجراء طلب HTTP من كود Java يستلزم إما استخدام الواجهة القديمة المرهقة HttpURLConnection (كثيرة الإسهاب، حاجبة، ومليئة بالمزالق) أو الاعتماد على مكتبة خارجية مثل Apache HttpClient أو OkHttp. جاءت Java 11 بحزمة java.net.http — عميل HTTP حديث ومتوافق مع المعايير، مدمج في JDK. يدعم HTTP/1.1 وHTTP/2، وWebSockets، وإدخال/إخراج غير محجوب بالكامل عبر CompletableFuture، فضلًا عن واجهة بناء متدفقة ونظيفة.
يتناول هذا الدرس القطعتين الأساسيتين في هذه الواجهة: بناء HttpClient وإنشاء HttpRequest. أما إرسال الطلبات ومعالجة الاستجابات فهو موضوع الدرس التالي.
التصميم الجوهري: كائنات قيمة غير قابلة للتغيير تُبنى بالبنّاءات
يتمحور التصميم بأكمله حول نوعين غير قابلين للتغيير:
HttpClient— محرك HTTP آمن للخيوط وقابل لإعادة الاستخدام. أنشئ نسخة واحدة لكل تطبيق (أو لكل نطاق منطقي) وأعد استخدامها في جميع الطلبات. تُدفع تكلفة الإنشاء الثقيلة مرة واحدة فقط.HttpRequest— وصف غير قابل للتغيير لطلب واحد (URI، طريقة، رؤوس، جسم). أنشئ نسخة جديدة لكل استدعاء.
يُنشأ كلاهما باستخدام فئات Builder داخلية متشعّبة تتبع نمط البنّاء القياسي في Java الذي تعرفه بالفعل من واجهات Streams وCollections. بمجرد البناء، تصبح النسخ غير قابلة للتغيير وبالتالي آمنة للمشاركة بين الخيوط.
HttpClient تجمّعات الاتصالات وسياق SSL وحالة المنفّذ. إن جعله غير قابل للتغيير يُزيل أخطاء الحالة المشتركة غير المقصودة في الكود المتزامن — تُعدّه مرة واحدة وتثق أنه لن يتغيّر أبدًا.
إنشاء HttpClient
أبسط عميل ممكن يستخدم جميع القيم الافتراضية:
تمنحك القيم الافتراضية HTTP/2 مع الرجوع إلى HTTP/1.1، وسياق SSL القياسي لـ JVM، وتجمّع خيوط في الخلفية. هذا يكفي لكثير من التطبيقات. عندما تحتاج إلى تخصيص السلوك، استخدم البنّاء:
خيارات تهيئة HttpClient الرئيسية
- version() —
Version.HTTP_2(افتراضي) أوVersion.HTTP_1_1. يُعدّد HTTP/2 طلبات متعددة عبر اتصال TCP واحد، مما يُقلّل زمن الاستجابة عند التوسع. يتفاوض العميل عبر ALPN ويرجع بسلاسة. - followRedirects() —
NEVERأوALWAYSأوNORMAL(يتبع جميع إعادات التوجيه باستثناء التخفيض من HTTPS إلى HTTP).NORMALهو الخيار الآمن في الإنتاج. - connectTimeout() — المدة الزمنية للانتظار قبل اتصال TCP الأولي. يمنع تعليق الخيوط إلى أجل غير مسمى عند عناوين IP محجوبة بجدار الحماية. ملاحظة: هذا مهلة الاتصال فقط؛ مهلات القراءة/الكتابة تُضبط لكل طلب.
- executor() — تجمّع الخيوط للعمليات غير المتزامنة. في Java 21+ تمرير
Executors.newVirtualThreadPerTaskExecutor()يتيح آلاف الاستدعاءات المتزامنة بتكلفة منخفضة دون ضبط تجمّع خيوط منصّة. - sslContext() — مرر
SSLContextمخصصًا عند الحاجة إلى TLS المتبادل، أو مخزن ثقة مخصص، أو شهادات موقّعة ذاتيًا في بيئة الاختبار. - authenticator() —
Authenticatorلتحديات HTTP Basic/Digest (نادر في واجهات APIs الحديثة؛ معظمها تستخدم رموز bearer في الرؤوس). - cookieHandler() — أضف
CookieManagerلسير عمل ملفات تعريف الارتباط. الافتراضي: لا تُخزَّن ملفات تعريف. - proxy() — وجّه الحركة عبر وكيل مؤسسي:
ProxySelector.of(new InetSocketAddress("proxy.corp.com", 8080)).
static final إن لزم.
بناء HttpRequest
يجمع HttpRequest كل ما يصف استدعاء HTTP واحدًا: URI الهدف، وطريقة HTTP، والرؤوس، وناشر جسم اختياري، ومهلة اختيارية لكل طلب. أنشئه بـ HttpRequest.newBuilder():
ضبط timeout() على الطلب يعني: إذا لم يُكمل الخادم الاستجابة خلال تلك النافذة الزمنية، يرمي العميل HttpTimeoutException. هذا مختلف عن مهلة الاتصال على العميل — تحتاج عادةً إلى كليهما.
طرق HTTP وناشرو الجسم
استخدم طرق البنّاء الخاصة بكل طريقة. GET وDELETE لا يحملان جسمًا. POST وPUT وPATCH تحمل جسمًا يُعبَّر عنه كـ HttpRequest.BodyPublisher:
أكثر مصانع BodyPublisher المدمجة فائدةً في BodyPublishers:
ofString(String)— جسم نصي بترميز UTF-8. الخيار الأكثر شيوعًا لحمولات JSON.ofFile(Path)— يبثّ ملفًا من القرص مباشرةً دون تخزين مؤقت في الكومة. استخدمه للرفع الكبير.ofByteArray(byte[])— بايتات خام. مفيد عندما تكون الحمولة مُسلسَلة مسبقًا.noBody()— يُعلّم صراحةً الطلب بغياب الجسم. يعادل عدم تمرير ناشر جسم.
ضبط رؤوس متعددة وتجاوز الرؤوس
يوفّر البنّاء كلًا من header(name, value) (الذي يُضيف رأسًا، مع دعم الرؤوس متعددة القيم) وheaders(name, value, name, value, ...) للضبط الجماعي. لاستبدال قيمة مضبوطة سابقًا استخدم setHeader(name, value):
Host أو Content-Length أو Connection يدويًا. يحسبها JDK ويُطبّقها. محاولة ضبطها ترمي IllegalArgumentException وقت بناء الطلب. راجع Javadoc الخاص بـ HttpRequest للقائمة الكاملة للرؤوس المقيّدة.
إعادة استخدام قالب الطلب مع copy()
لأن HttpRequest غير قابل للتغيير، لا يمكن تعديله بعد البناء. غير أنك تستطيع نسخ بنّاء من طلب موجود وتجاوز حقول محددة:
يستنسخ منشئ النسخ HttpRequest.newBuilder(existingRequest, headerFilter) جميع الإعدادات؛ لامدا التصفية تحدد أي الرؤوس تُحتجز (true يحتفظ بالجميع).
تجميع الأجزاء: نمط خدمة بسيط
يفصل هذا النمط وصف الطلب عن إرساله، مما يجعل طرق البنّاء قابلة للاختبار بسهولة دون أي استدعاءات شبكة.
الخلاصة
HttpClient محرك قابل لإعادة الاستخدام وآمن للخيوط — أنشئه مرة واحدة وشاركه. HttpRequest وصف غير قابل للتغيير لكل استدعاء — أنشئ نسخة جديدة في كل مرة. هيّئ العميل بالإصدار وسياسة إعادة التوجيه ومهلة الاتصال والمنفّذ. هيّئ الطلب بـ URI والطريقة والرؤوس وناشر الجسم والمهلة لكل طلب. استخدم BodyPublishers لتوفير الأجسام من نصوص أو ملفات أو مصفوفات بايت. بعد تجهيز كلا الكائنين، الخطوة التالية هي الإرسال ومعالجة الاستجابة.