ذات مرة في مشروع واحد بعيد ، ... ، كنت بحاجة إلى القيام بمعالجة طلبات 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
. كمعلمة 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 باستخدام مكتبة الأسطوانات .