عندما معيار HTTP لا يكفي. الالتزام الدقيق

مرحباً بالجميع ، اسمي ديمتري ، واليوم سأتحدث عن كيف جعلني الإنتاج بحاجة إلى المساهمة في إطار عمل Micronaut. بالتأكيد سمع الكثيرون عنه. باختصار ، هذا بديل خفيف الوزن لـ Spring Boot ، حيث لا ينصب التركيز الرئيسي على التفكير ، ولكن على التجميع الأولي لجميع التبعيات اللازمة. يمكن لمعارف أكثر تفصيلا أن يبدأ بالوثائق الرسمية.

يتم استخدام إطار عمل Micronaut في العديد من مشاريع Yandex الداخلية وقد رسخ نفسه جيدًا. إذن ما الذي فقدناه؟ أستطيع أن أقول على الفور: من خارج الصندوق ، يدعم الإطار ، من حيث المبدأ ، جميع الميزات التي قد يحتاج إليها المبرمج نظريًا لتطوير الوصلات الخلفية. ومع ذلك ، هناك حالات نادرة غير مدعومة خارج الصندوق. واحد منهم هو عندما تحتاج إلى العمل ليس عبر HTTP ، ولكن مع امتداد HTTP. على سبيل المثال ، مع طرق إضافية. في الواقع ، مثل هذه الحالات هي أكثر بكثير مما قد يبدو. علاوة على ذلك ، فإن بعض هذه البروتوكولات هي معايير:

  • Webdav هو امتداد للوصول إلى الموارد. بالإضافة إلى الطرق القياسية ، يتطلب HTTP دعمًا للطرق الإضافية مثل LOCK و PROPPATCH وما إلى ذلك.
  • Caldav هو امتداد Webdav للعمل مع أحداث نوع التقويم. يوجد هذا البروتوكول ذو درجة الاحتمال العالية في التطبيقات على هاتفك الذكي: لمزامنة التقويمات والمواعيد وما إلى ذلك.

والقائمة لا تقتصر على هذا. إذا نظرت إلى سجل أساليب HTTP ، فسترى أن أساليب HTTP الموصوفة فقط بواسطة معايير RFC هي 39 حاليًا. وعدد الحالات التي يوجد فيها بروتوكول مكتوب ذاتيًا عبر HTTP. لذا يعد دعم أساليب HTTP غير القياسية أمرًا شائعًا إلى حد ما. كما يحدث غالبًا أن الإطار الذي تستخدمه لا يدعم هذه الأساليب. هنا مناقشة حول Stack Overflow لـ ExpressJS . وهنا هو طلب سحب على جيثب لتورنادو . حسنًا ، نظرًا لأن Micronaut غالبًا ما يتم وضعها كبديل خفيف الوزن لـ Spring - فهذه هي نفس المشكلة بالنسبة لـ Spring .

ليس من المستغرب أنه عندما نحتاج في أحد المشاريع إلى دعم بروتوكول يمتد HTTP من حيث الأساليب ، واجهنا المشكلة نفسها لـ Micronaut التي نستخدمها في هذا المشروع لفترة طويلة. اتضح أن الحصول على Micronaut لمعالجة الأساليب غير القياسية أمر صعب للغاية.

لماذا؟ لأنه إذا نظرت إلى تعريف أساليب HTTP في Micronaut في الوقت الحالي ، ستجد أنها مضبوطة باستخدام Enum ، وليس فئة ، كما يتم ذلك ، على سبيل المثال ، في Netty (لا أذكر Netty بطريق الخطأ ، فسوف يظهر لاحقًا أكثر من مرة). لجعل الأمور أكثر سوءًا ، يتم تصفية جميع مكالمات خادم المطابقة باستخدام التعداد بدلاً من اسم سلسلة الطريقة. هذا يعني أنك إذا كنت بحاجة إلى طريقة HTTP غير قياسية ، فستحتاج إلى كتابتها في Enum ، وهذا في الواقع ليس حلاً جيدًا للمشكلة. أولاً ، سيتطلب الالتزام بالمستودع في كل مرة تحتاج فيها إلى طريقة جديدة. ثانياً ، لا يتم توحيد أساليب HTTP افتراضيًا ولا يتم إصلاح قائمتها في أي مكان ، لذلك من غير الواقعي التنبؤ بجميع المواقف الممكنة. من الضروري إجبار Micronaut على معالجة الأساليب التي لم يتم توفيرها مسبقًا بواسطة المطورين.

الحل الأول: الجبين


صورة

كان الحل الأول والأكثر وضوحًا هو عدم لمس Micronaut على الإطلاق وعدم إعادة كتابة أي شيء فيه. لماذا ، لأنه يمكنك وضع nronx أمام Micronaut ، كما فعلنا ، بدءًا من مثال :

http { upstream other_PROPPATCH { server ...; } upstream other_REPORT { server ...; } server { location /service { proxy_method POST; proxy_pass http://other_$request_method; } } } 

ما هي النقطة؟ يمكننا فرض nginx على الطرق غير القياسية للوصول إلى الخادم الوكيل الذي نحتاجه ، مع استخدام قدرة nginx على تغيير الطريقة: أي أننا سنصل من خلال طريقة POST ، ويمكن لـ Micronaut معالجتها.

ما هو سيء؟ بادئ ذي بدء ، نحن في الواقع تقديم جميع الطلبات من وجهة نظر Micronaut غير عاطفية. لا تنسى أنه بالنسبة للطرق غير القياسية ، يوجد أيضًا فصل كهذا. على سبيل المثال ، REPORT هو idempotent ، بينما PROPPATCH ليس كذلك. نتيجة لذلك ، لا يعرف إطار العمل نوع الطلب ، ولن يتمكن المبرمج الذي ينظر إلى رمز هؤلاء المعالجين من تحديد ذلك. ومع ذلك ، هذا ليس هو الحال. لدينا بالفعل مجموعة من الاختبارات التي تتحقق تلقائيًا من التطبيق من أجل الامتثال للبروتوكول المطلوب. لكي تعمل هذه الاختبارات مع مثل هذا الحل في مشروع ما ، يجب عليك اختيار أحد الخيارين التاليين:

  • ارفع صورة nginx بالإعدادات الضرورية ، بالإضافة إلى التطبيق نفسه ، بحيث تصل الاختبارات إلى nginx ، وليس Micronaut نفسه. على الرغم من أن البنية التحتية لـ Yandex تسمح لك بالتأكيد برفع مكونات إضافية ، إلا أنه في هذه الحالة يبدو أن الهندسة المفرطة هي مجرد اختبارات.
  • أعد كتابة الاختبارات بحيث لا تختبر البروتوكول المطلوب ، لكن راجع المسارات التي يعيد توجيه nginx إليها. هذا هو ، في الواقع ، نحن لا نختبر البروتوكول ، ولكن الشجاعة لتنفيذ عكاز محددة.

كلا الخيارين ليسا جميلين ، لذا فقد طرحت الفكرة: لماذا لا يتم إصلاح Micronaut للغرض الصحيح ، والأهم من ذلك أن هذا التعديل سيكون مفيدًا ليس فقط لنا. وهذا هو ، أردت شيئا مثل هذا:

 @CustomMethod("PROPFIND") public String process( // Provide here HttpRequest or something else, as standard micronaut methods ) { } 

وتولت هذه المهمة بمرح ، لكن ماذا حدث في النهاية؟

الحل الثاني: دعنا نعيد كتابة كل شيء!




في الواقع ، إنه أسهل بكثير مما يبدو للوهلة الأولى. الالتزام يغير ببساطة HttpMethod من التعداد إلى الصف. بعد ذلك ، أنشأنا أساليب ثابتة (في المقام الأول valueOf) داخل الفئة التي تم استدعاؤها للتعداد. و IDEA إلى جانب Gradle تأكد من كسر أي شيء.

أصعب شيء هنا كان مع DefaultUriRouter ، حيث افترض أن المجموعة تم إصلاحها وإنشاء مجموعة من قوائم المسارات للطرق الممكنة. كان لا بد من التخلي عن هذا لتنفيذ جديد. ولكن بشكل عام ، تبين أن كل شيء بسيط للغاية. لاحظ أنه كان عليك إضافة 240 سطرًا وحذف 116 سطرًا.

المشكلة هي أن هذا تغيير كبير. نعم ، في الممارسة العملية ، في مشروع منتظم باستخدام Micronaut ، أنت - على الأرجح - لا تستخدم HttpMethod مباشرة في الكود ، وإذا كنت تستخدمه ، فمن غير المحتمل أن تستخدم الطريقة الترتيبية وغيرها من طرق التعداد المحددة هناك. ومع ذلك ، لا يزال هذا لا يجعل مثل هذا التغيير في الإصدار 1.x مسموحًا به ، خاصة بالنظر إلى حقيقة أن كل هذا بدأ من أجل دعم حالة نادرة إلى حد ما. لكن بالنسبة إلى الإصدار 2.x ، يعد هذا تعديلاً عاديًا ، ولكن لا يزال يتعين عليك ترقيته إلى 2.x. لذلك ، اضطررت إلى كتابة المزيد من التعليمات البرمجية ...

الحل الثالث: التصرف تطوريًا


صورة

في الواقع ، يمكنك رؤية طلب السحب المقابل للإصدار 1.3. كما ترون ، كان علي أن أكتب حوالي خمس مرات رمز أكثر من تغيير كبير ، وهذا ليس من قبيل الصدفة. هنا أود أن أثني على الأساليب الافتراضية في الواجهات المقدمة في جافا الثامنة. بالنسبة إلى إعادة التنظيم هذه التي لا تنفصل عن التوافق مع الإصدارات السابقة ، فإن هذا الشيء لا يمكن الاستغناء عنه ، ولا يمكنني أن أتخيل كيف أجري هذه التعديلات لجافا حتى الإصدار الثامن (على الرغم من الغريب في الأمر ، أنه يمكن إجراء تغيير كبير قبل النسخة الثامنة).

اعتمدت التعديلات الأساسية على حقيقة أن واجهة HttpRequest كان لها طريقة getMethod ، والتي كانت تستخدم للتصفية. عاد ، كما قد تتخيل ، التعداد. لذلك ، تمت إضافة getHttpMethodName بالطريقة الافتراضية إلى الواجهة ، والتي تقوم بإرجاع اسم قيمة التعداد افتراضيًا. ثم وجدوا حيث تم استخدام الطريقة الأصلية في مطابقة المسار ، وهناك تم استبدالها بمكالمات إلى الطريقة الجديدة. وبعد ذلك ، في تطبيقات الواجهة لخادم Netty ، تم إعادة تعريف طريقة الواجهة لاستخدام القيمة الحقيقية لطريقة HTTP.

كانت تحتوي على مجموعة واحدة يمكن رؤيتها في المناقشة ، وهي تتعلق بالعملاء التابعين لـ Micronaut. يستخدمون تحويل اسم قيمة التعداد إلى مثيل لفئة HttpMethod لـ Netty. إذا نظرت إلى وثائق طريقة valueOf في هذه الفئة ، ستلاحظ أن القيمة المخزنة مؤقتًا سيتم إرجاعها للطرق القياسية ، أما بالنسبة للطرق غير القياسية ، فسيتم إرجاع مثيل جديد للفئة في كل مرة. أي إذا كان لديك حمولة كبيرة وقمت بالانتقال إلى الخادم باستخدام طريقة HTTP غير قياسية مليون مرة ، فستقوم في نفس الوقت بإنشاء مليون كائن جديد. بالطبع ، يجب على GCs الحديثة التعامل مع هذا ، ولكن ما زلت لا أريد إنشاء كائنات إضافية دون سبب. ثم ظهرت الفكرة لاستخدام ConcurrentHashMap.computeIfAbsent للتخزين المؤقت ، ولكن الأمر هنا ليس بهذه البساطة: المشكلة في عيب Java 8 ، الأمر الذي سيؤدي إلى حظر التدفقات حتى للحالة عندما لا يتم إجراء تسجيل. نتيجة لذلك ، اتخذنا قرارًا مؤقتًا:

  • بالنسبة للطرق القياسية ، نستخدم التخزين المؤقت للنسخ ، والذي يوفره Netty (في الواقع ، كما كان من قبل).
  • بالنسبة للطرق غير القياسية ، دع إنشاء مثيلات جديدة. يجب على أولئك الذين يختارون أساليب غير قياسية التأكد من أن جامع البيانات المهملة يمكنه هضم إنشاء الكائنات (على سبيل المثال ، نستخدم Shenandoah).

النتائج


صورة

ماذا يمكن أن يقال في النهاية؟

  • يتجلى منحنى تكلفة تصحيح الأخطاء المعروف في المراحل المختلفة لتطوير البرمجيات هنا بوضوح شديد. على وجه التحديد ، نحن نتحدث عن سوء تقدير في المرحلة المبكرة جدًا من تطوير Micronaut ، عندما تقرر استخدام التعداد لطرق HTTP. من الصعب القول كيف يمكن تبرير هذا القرار ، نظرًا لأن Micronaut تدور على Netty ، حيث يتم استخدام الفصل لنفسه. في الأساس ، فإن الحفاظ على الفصل بدلاً من التعداد لا يستحق العمل الإضافي. هذا هو السبب في أنه كان من الأسهل إجراء تغيير كبير في هذه الخطة من إصلاحه مع دعم للتوافق مع الإصدارات السابقة.
  • كعب أخيل المعروف للمشاريع مفتوحة المصدر (ومع ذلك ، يمكن ملاحظة ذلك أيضًا في المشروعات الصناعية ذات الكود المغلق) - ليس لديهم مستندات المشروع. في الوقت نفسه ، لدى Micronaut وثائق جيدة جدًا في الواقع: ما هي الخيارات لاستخدامها وما شابه. ومع ذلك ، نحن هنا نتحدث عن توثيق كيفية اتخاذ قرارات التصميم. نتيجة لذلك ، يصعب على المبرمج من الخارج المشاركة في تطوير المشروع ، حتى لو كان هناك حاجة إلى تحسن طفيف.
  • لا تنسى أن تفكر في حقيقة أن مشروعًا مفتوحًا أو أكثر يستخدم في بيئات متعددة التحميل ومتعددة الارتباطات. هنا كان من الضروري أن تأخذ هذا في الاعتبار حتى بالنسبة لتحسن طفيف.

PS


أثناء إعداد هذه المقالة للنشر ، تم قبول طلب السحب في فرع معالج Micronaut وسيتم إصداره في الإصدار 1.3.

Source: https://habr.com/ru/post/ar466917/


All Articles