تعيين طلبات Netty

ذات مرة في مشروع واحد بعيد ، ... ، كنت بحاجة إلى القيام بمعالجة طلبات http على Netty . لسوء الحظ ، لم تكن هناك آليات ملائمة قياسية لتعيين طلبات http في Netty (وهذا الإطار ليس كذلك على الإطلاق) ، وبالتالي ، فقد تقرر تنفيذ آليتنا الخاصة.


إذا بدأ القارئ بالقلق بشأن مصير المشروع ، فلا يستحق ذلك ، فكل شيء على ما يرام معه ، لأن في المستقبل ، تقرر إعادة كتابة خدمة الويب على إطار أكثر حدة للخدمات RESTful ، دون استخدام الدراجات الخاصة بك. لكن الإنجازات بقيت ، ويمكن أن تكون مفيدة لشخص ما ، لذلك أود مشاركتها.

Netty هو إطار لتطوير تطبيقات الشبكات عالية الأداء. يمكنك قراءة المزيد عنها على موقع المشروع.
يوفر Netty وظائف مريحة للغاية لإنشاء خوادم مأخذ توصيل ، ولكن في رأيي ، هذه الوظيفة ليست مريحة للغاية لإنشاء خوادم REST.


طلب المعالجة باستخدام محرك Netty القياسي


لمعالجة الطلبات في Netty ، يجب أن ترث من فئة ChannelInboundHandlerAdapter وتجاوز طريقة channelRead .


 public class HttpMappingHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { } } 

للحصول على المعلومات اللازمة لمعالجة طلبات HTTP ، يمكن إرسال كائن msg إلى HttpRequest .


 HttpRequest request = (HttpRequest) msg; 

بعد ذلك ، يمكنك الحصول على أي معلومات من هذا الطلب. على سبيل المثال ، عنوان URL للطلب.


 String uri = request.uri(); 

نوع الطلب.


 HttpMethod httpMethod = request.method(); 

والمحتوى.


 ByteBuf byteBuf = ((HttpContent) request).content(); 

قد يكون المحتوى ، على سبيل المثال ، تمرير json في نص طلب POST . ByteBuf فئة من مكتبة Netty ، لذلك من غير المحتمل أن يعمل محللو json ، ولكن يمكن بسهولة تحويلها إلى سلسلة.


 String content = byteBuf.toString(StandardCharsets.UTF_8); 

هذا ، بشكل عام ، هو كل شيء. باستخدام الطرق المذكورة أعلاه ، يمكنك معالجة طلبات HTTP. صحيح ، يجب معالجة كل شيء في مكان واحد ، أي في طريقة channelRead . حتى إذا قمنا بفصل منطق معالجة الطلبات في طرق وفئات مختلفة ، فلا يزال يتعين عليك تعيين عنوان URL لهذه الأساليب في مكان ما في مكان واحد.


طلب رسم الخرائط


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


  • @Get
  • @Post
  • @Put
  • @Delete

يشير اسم التعليق التوضيحي إلى نوع الطلب الذي سيتم استدعاؤه. يتم دعم أربعة أنواع من الطلبات: GET و POST و PUT و DELETE . كمعلمة value في التعليق التوضيحي ، يجب عليك تحديد عنوان URL ، عند الوصول إليه ، سيتم استدعاء الطريقة المطلوبة.


مثال على ما يبدو GET معالج GET ، والذي يُرجع السلسلة Hello, world! .


 public class HelloHttpHandler extends AbstractHttpMappingHandler { @Get("/test/get") public DefaultFullHttpResponse test() { return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, OK, Unpooled.copiedBuffer("Hello, world!", StandardCharsets.UTF_8)); } } 

طلب المعلمات


يتم أيضًا تنفيذ تمرير المعلمات من الطلب إلى أسلوب المعالج باستخدام التعليقات التوضيحية. للقيام بذلك ، استخدم أحد التعليقات التوضيحية التالية:


  • @PathParam
  • @QueryParam
  • @RequestBody

تضمين التغريدة


لتمرير معلمات المسار ، يتم @PathParam التعليق التوضيحي @PathParam . عند استخدامه كمعلمة value التعليق التوضيحي ، يجب عليك تحديد اسم المعلمة. بالإضافة إلى ذلك ، يجب أيضًا تحديد اسم المعلمة في عنوان URL للطلب.


مثال عن كيفية قيام معالج GET بالبحث في تمرير معلمة مسار id وإرجاع هذه المعلمة.


 public class HelloHttpHandler extends AbstractHttpMappingHandler { @Get("/test/get/{id}") public DefaultFullHttpResponse test(@PathParam(value = "id") int id) { return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, OK, Unpooled.copiedBuffer(id, StandardCharsets.UTF_8)); } } 

تضمين التغريدة


لتمرير معلمات الاستعلام ، يتم @QueryParam التعليق التوضيحي @QueryParam . عند استخدامه كمعلمة value التعليق التوضيحي ، يجب عليك تحديد اسم المعلمة. يمكن التحكم في ربط المعلمة باستخدام المعلمة التوضيحية required .


مثال عن الشكل الذي سيبدو GET معالج GET ، والذي message تمرير message معلمة الاستعلام إليه والتي تُرجع هذه المعلمة.


 public class HelloHttpHandler extends AbstractHttpMappingHandler { @Get("/test/get") public DefaultFullHttpResponse test(@QueryParam(value = "message") String message) { return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, OK, Unpooled.copiedBuffer(message, StandardCharsets.UTF_8)); } } 

تضمين التغريدة


لنقل نص طلبات POST ، يتم @RequestBody التعليق التوضيحي @RequestBody . لذلك ، يُسمح باستخدامه فقط في طلبات POST . من المفترض أن بيانات تنسيق json سيتم إرسالها كنص الطلب. لذلك ، لاستخدام @RequestBody من الضروري تمرير تطبيق واجهة JsonParser إلى مُنشئ فئة المعالج ، والتي ستشارك في تحليل البيانات من نص الطلب. أيضًا ، تحتوي المكتبة بالفعل على تطبيق افتراضي لـ JsonParserDefault . يستخدم هذا التطبيق jackson كمحلل.


مثال على الشكل الذي سيبدو عليه معالج طلب POST والذي يوجد به نص طلب.


 public class HelloHttpHandler extends AbstractHttpMappingHandler { @Post("/test/post") public DefaultFullHttpResponse test(@RequestBody Message message) { return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, OK, Unpooled.copiedBuffer("{id: '" + message.getId() +"', msg: '" + message.getMessage() + "'}", StandardCharsets.UTF_8)); } } 

فئة Message على النحو التالي.


 public class Message { private int id; private String message; public Message() { } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } } 

خطأ في التعامل


في حالة حدوث أي Exception أثناء معالجة الطلبات ولن يتم اعتراضه في رمز أساليب المعالج ، فسيتم إرجاع الإجابة التي تحتوي على الكود 500. من أجل كتابة بعض المنطق لمعالجة الأخطاء ، يكفي إعادة تعريف طريقة exceptionCaught في فئة المعالج.


 public class HelloHttpHandler extends AbstractHttpMappingHandler { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { super.exceptionCaught(ctx, cause); ctx.writeAndFlush(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR)); } } 

الخاتمة


هذا ، بشكل عام ، هو كل شيء. آمل أن يكون هذا مثيرًا للاهتمام وسيكون مفيدًا لشخص ما.




يتوفر هنا مثال لرمز خادم Netty http باستخدام مكتبة الأسطوانات .

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


All Articles