يقول مؤلف المادة ، التي ننشر ترجمتها اليوم ، أن تجربته الأخيرة في مجال هندسة مشاريع البرامج كانت إنشاء تطبيق ويب يعمل باستخدام لغة Rust فقط وبأقل استخدام ممكن لرمز القالب. في هذه المقالة ، يريد أن يشارك مع القراء ما اكتشفه أثناء تطوير تطبيق والإجابة على
سؤال ما إذا كان Rust جاهزًا لاستخدامه في مختلف مجالات تطوير الويب.

نظرة عامة على المشروع
يمكن العثور على رمز المشروع الذي سيتم مناقشته هنا في
GitHub . توجد أجزاء العميل والخادم للتطبيق في نفس المستودع ، ويتم ذلك لتبسيط صيانة المشروع. وتجدر الإشارة إلى أن Cargo ستحتاج إلى تجميع تطبيقات الواجهة الأمامية والخلفية مع تبعيات مختلفة.
هنا يمكنك إلقاء نظرة على تطبيق يعمل.
مشروعنا هو عرض بسيط لآلية المصادقة. يسمح لك بتسجيل الدخول باستخدام اسم المستخدم وكلمة المرور المحددين (يجب أن يكونا متطابقين).
إذا كان اسم المستخدم وكلمة المرور مختلفين ، فستفشل المصادقة. بعد المصادقة الناجحة ، يتم تخزين رمز
JWT المميز (JSON Web Token) على كل من العميل والخادم. عادة ما يكون تخزين الرمز المميز على الخادم في هذه التطبيقات غير مطلوب ، لكنني فعلت ذلك فقط لأغراض العرض التوضيحي. يمكن استخدام هذا ، على سبيل المثال ، لمعرفة عدد المستخدمين الذين قاموا بتسجيل الدخول. يمكن تكوين التطبيق بالكامل باستخدام ملف
Config.toml واحد ، على سبيل المثال ، تحديد بيانات الاعتماد للوصول إلى قاعدة البيانات ، أو عنوان ورقم منفذ الخادم. فيما يلي شكل الرمز القياسي لهذا الملف لتطبيقنا.
[server] ip = "127.0.0.1" port = "30080" tls = false [log] actix_web = "debug" webapp = "trace" [postgres] host = "127.0.0.1" username = "username" password = "password" database = "database"
تطوير تطبيق العميل
لتطوير جانب العميل من التطبيق ، قررت استخدام
الطقسوس . هذا هو إطار الصدأ الحديث المستوحى من Elm و Angular و React. تم تصميمه لإنشاء أجزاء العميل من تطبيقات الويب متعددة الخيوط باستخدام
WebAssembly (Wasm). حاليًا ، هذا المشروع قيد التطوير النشط ، في حين لا يوجد الكثير من الإصدارات المستقرة.
يعتمد إطار
yew
على أداة
ويب الشحن ، والتي تم تصميمها لتجميع التعليمات البرمجية في Wasm.
أداة
الشحن على شبكة الإنترنت هي اعتماد مباشر على
yew
يبسط التجميع المتقاطع لرمز الصدأ في Wasm. فيما يلي ثلاثة أهداف رئيسية لتجميع Wasm متوفرة من خلال هذه الأداة:
asmjs-unknown-emscripten
- يستخدم asm.js من خلال Emscripten.wasm32-unknown-emscripten
- يستخدم WebAssembly من خلال Emscriptenwasm32-unknown-unknown
- يستخدم WebAssembly مع الواجهة الخلفية Rust الأصلية لـ WebAssembly
تجميع ويبقررت استخدام الخيار الأخير ، والذي يتطلب استخدام التجميع "الليلي" لمترجم Rust ، ولكن في أفضل حالاته يوضح قدرات Wasm الأصلية من Rust.
إذا تحدثنا عن WebAssembly ، فإن الحديث عن Rust اليوم هو الموضوع الأكثر سخونة. يتم إجراء قدر كبير من العمل على تجميع الصدأ في Wasm ودمجها في النظام البيئي Node.js (باستخدام حزم npm). قررت تنفيذ المشروع دون أي تبعيات JavaScript.
عند تشغيل الواجهة الأمامية لتطبيق ويب (يتم ذلك في مشروعي باستخدام الأمر
make frontend
) ، يقوم
cargo-web
بتجميع التطبيق في Wasm وحزمه ، مع إضافة بعض المواد الثابتة.
cargo-web
بتشغيل خادم ويب محلي ، مما يسمح لك بالتفاعل مع التطبيق لأغراض التطوير. إليك ما يحدث في وحدة التحكم عند تشغيل الأمر أعلاه:
> make frontend Compiling webapp v0.3.0 (file:///home/sascha/webapp.rs) Finished release [optimized] target(s) in 11.86s Garbage collecting "app.wasm"... Processing "app.wasm"... Finished processing of "app.wasm"! If you need to serve any extra files put them in the 'static' directory in the root of your crate; they will be served alongside your application. You can also put a 'static' directory in your 'src' directory. Your application is being served at '/app.js'. It will be automatically rebuilt if you make any changes in your code. You can access the web server at `http://0.0.0.0:8000`.
يحتوي إطار
yew
على بعض الميزات المثيرة للاهتمام. من بينها دعم بنى مكونات قابلة لإعادة الاستخدام. قامت هذه الميزة بتبسيط تقسيم تطبيقي إلى ثلاثة مكونات رئيسية:
RootComponent . يتم تثبيت هذا المكون مباشرةً على علامة
<body>
الخاصة بموقع الويب. يقرر أي مكون فرعي يجب تحميله بعد ذلك. إذا تم العثور على رمز JWT المميز عند المدخل الأول للصفحة ، فإنه يحاول تحديث هذا الرمز المميز عن طريق الاتصال بجزء الخادم من التطبيق. إذا فشل ذلك ، يتم تنفيذ الانتقال إلى مكون
LoginComponent
.
مكون الدخول . هذا المكون هو سليل لمكون
RootComponent
؛ يحتوي على نموذج يحتوي على حقول لإدخال بيانات الاعتماد. بالإضافة إلى ذلك ، يتفاعل مع الواجهة الخلفية للتطبيق لتنظيم مخطط مصادقة بسيط يعتمد على التحقق من اسم المستخدم وكلمة المرور ، وفي حالة نجاح المصادقة ، يحفظ JWT في ملف تعريف ارتباط. بالإضافة إلى ذلك ، إذا كان المستخدم قادرًا على المصادقة ، فإنه ينتقل إلى مكون
ContentComponent
.
ظهور مكون LoginComponentالمحتوى هذا المكون هو سليل آخر لمكون
RootComponent
. يحتوي على ما يتم عرضه على الصفحة الرئيسية للتطبيق (في الوقت الحالي هو مجرد عنوان وزر للخروج من النظام). يمكن الحصول على الوصول إليها من خلال
RootComponent
(إذا تمكن التطبيق ، عند بدء التشغيل ، من العثور على رمز جلسة عمل صالح) ، أو من خلال
LoginComponent
(في حالة المصادقة الناجحة). يقوم هذا المكون بتبادل البيانات مع الواجهة الخلفية عندما ينقر المستخدم على زر تسجيل الخروج.
مكون ContentComponentجهاز التوجيه يخزن هذا المكون جميع المسارات الممكنة بين المكونات التي تحتوي على محتوى. بالإضافة إلى ذلك ، فإنه يحتوي على الحالة الأولية
loading
التطبيق
error
. يتصل مباشرة بـ
RootComponent
.
الخدمات هي واحدة من المفاهيم الرئيسية التالية
yew
التي سنناقشها الآن. أنها تسمح لك بإعادة استخدام نفس المنطق في مكونات مختلفة. لنفترض أن هذه يمكن أن تكون واجهات تسجيل أو أدوات لدعم استخدام
ملفات تعريف الارتباط . لا تخزن الخدمات بعض الحالات العامة ، بل يتم إنشاؤها عند تهيئة المكونات. بالإضافة إلى الخدمات ، فإن
yew
يدعم مفهوم الوكلاء. يمكن استخدامها لتنظيم مشاركة البيانات بين المكونات المختلفة ، للحفاظ على الحالة العامة للتطبيق ، مثل المكون المطلوب للعامل المسؤول عن التوجيه. لتنظيم نظام التوجيه لتطبيقنا ، الذي يغطي جميع المكونات ، تم تنفيذ
وكيلنا الخاص وخدمة التوجيه هنا. لا يوجد جهاز توجيه قياسي في
yew
، ولكن في مستودع إطار العمل يمكنك العثور
على مثال على تنفيذ جهاز التوجيه الذي يدعم مجموعة متنوعة من عمليات URL.
يسعدني أن أشير إلى أن
yew
يستخدم
Web Webers API لتشغيل الوكلاء على سلاسل المحادثات المختلفة ويستخدم جدولة محلية متصلة بالخيط لحل المهام المتوازية. هذا يجعل من الممكن تطوير تطبيقات المستعرض بدرجة عالية من سلاسل المحادثات المتعددة على Rust.
يقوم كل مكون بتنفيذ
سمة Renderable الخاصة به ، والتي تسمح لنا بتضمين شفرة HTML مباشرة في شفرة مصدر Rust باستخدام
html! {} Macro .
هذه ميزة رائعة ، وبالطبع ، يتم التحكم في استخدامها الصحيح من قبل المترجم. إليك
Renderable
تنفيذ
LoginComponent
مكون
LoginComponent
.
impl Renderable<LoginComponent> for LoginComponent { fn view(&self) -> Html<Self> { html! { <div class="uk-card uk-card-default uk-card-body uk-width-1-3@s uk-position-center",> <form onsubmit="return false",> <fieldset class="uk-fieldset",> <legend class="uk-legend",>{"Authentication"}</legend> <div class="uk-margin",> <input class="uk-input", placeholder="Username", value=&self.username, oninput=|e| Message::UpdateUsername(e.value), /> </div> <div class="uk-margin",> <input class="uk-input", type="password", placeholder="Password", value=&self.password, oninput=|e| Message::UpdatePassword(e.value), /> </div> <button class="uk-button uk-button-default", type="submit", disabled=self.button_disabled, onclick=|_| Message::LoginRequest,>{"Login"}</button> <span class="uk-margin-small-left uk-text-warning uk-text-right",> {&self.error} </span> </fieldset> </form> </div> } } }
يعتمد الاتصال بين الواجهة الأمامية والخلفية على اتصالات
WebSocket التي يستخدمها كل عميل. تتمثل قوة تقنية WebSocket في حقيقة أنها مناسبة لنقل الرسائل الثنائية ، بالإضافة إلى حقيقة أن الخادم ، إذا لزم الأمر ، يمكنه إرسال إشعارات الدفع للعملاء. لدى
yew
خدمة WebSocket قياسية ، لكنني قررت إنشاء نسختها
الخاصة لأغراض العرض التوضيحي ، ويرجع ذلك أساسًا إلى التهيئة "البطيئة" للاتصالات مباشرة داخل الخدمة. إذا تم إنشاء خدمة WebSocket أثناء تهيئة المكون ، فسيتعين علي مراقبة العديد من الاتصالات.
بروتوكول Cap'n Protoقررت استخدام بروتوكول
Cap'n Proto (بدلاً من استخدام شيء مثل
JSON أو
MessagePack أو
CBOR ) كطبقة لنقل بيانات التطبيق لأسباب تتعلق بالسرعة والضغط. تجدر الإشارة إلى أنني لم أستخدم واجهة بروتوكول
RPC التي تمتلكها Cap'n Proto ، حيث لا يتم ترجمة تطبيق Rust الخاص بها إلى WebAssembly (بسبب
تبعيات Unixx-rs Unix). وقد أدى ذلك إلى حد ما إلى تعقيد اختيار الطلبات والاستجابات من الأنواع الصحيحة ، ولكن يمكن حل هذه المشكلة باستخدام
واجهة برمجة تطبيقات جيدة التنظيم . هنا هو إعلان بروتوكول Cap'n Proto للتطبيق.
@0x998efb67a0d7453f; struct Request { union { login :union { credentials :group { username @0 :Text; password @1 :Text; } token @2 :Text; } logout @3 :Text; # The session token } } struct Response { union { login :union { token @0 :Text; error @1 :Text; } logout: union { success @2 :Void; error @3 :Text; } } }
يمكنك أن ترى أنه لدينا هنا نسختان مختلفتان من طلب تسجيل الدخول.
واحد لـ
LoginComponent
(هنا ، للحصول على رمز مميز ، يتم استخدام اسم وكلمة مرور) ، والآخر لـ
RootComponent
(يتم استخدامه لتحديث رمز مميز موجود). كل ما هو مطلوب لعمل البروتوكول معبأ في خدمة
البروتوكول ، وبفضله من المناسب إعادة استخدام القدرات المقابلة في أجزاء مختلفة من الواجهة الأمامية.
UIkit - إطار أمامي معياري صغير الحجم لتطوير واجهات ويب سريعة وقويةتعتمد واجهة المستخدم الخاصة بجزء العميل من التطبيق على إطار عمل
UIkit ، وسيتم إصدار الإصدار 3.0.0 في المستقبل القريب.
يقوم البرنامج النصي
build.rs المُعد خصيصًا بتنزيل جميع تبعيات UIkit الضرورية وتجميع ورقة الأنماط الناتجة. هذا يعني أنه يمكنك إضافة أنماطك الخاصة إلى ملف
style.scss واحد ، والذي يمكن تطبيقه عبر التطبيق بأكمله. إنه مريح للغاية.
end اختبار الواجهة
أعتقد أن هناك بعض المشكلات في اختبار حلنا. والحقيقة هي أنه من السهل جدًا اختبار الخدمات الفردية ، ولكن
yew
لا
yew
المطور طريقة مناسبة لاختبار المكونات والوكلاء. الآن ، في إطار الصدأ النقي ، لا يتوفر التكامل والاختبار الشامل للواجهة الأمامية. هنا يمكنك استخدام مشاريع مثل
Cypress أو
Protractor ، ولكن مع هذا النهج ، سيكون عليك تضمين الكثير من كود JavaScript / TypeScript في المشروع ، لذلك قررت التخلي عن تنفيذ مثل هذه الاختبارات.
بالمناسبة ، إليك فكرة لمشروع جديد: إطار للاختبار الشامل مكتوب بلغة Rust.
تطوير جانب خادم التطبيق
لتنفيذ جانب الخادم من التطبيق ، اخترت إطار
الويب actix . هذا هو إطار
نموذج ممثل صغير وعملي وسريع جدًا قائم على الصدأ. وهو يدعم جميع التقنيات اللازمة ، مثل WebSockets و TLS و
HTTP / 2.0 . يدعم هذا الإطار معالجات وموارد مختلفة ، ولكن في تطبيقنا تم استخدام اثنين فقط من الطرق الرئيسية:
/ws
هو المورد الرئيسي لاتصالات WebSocket./
- المعالج الرئيسي الذي يتيح الوصول إلى تطبيق الواجهة الأمامية الثابت.
بشكل افتراضي ، يبدأ
actix-web
سير العمل بالمبلغ المقابل لعدد نوى المعالج المتوفرة على الكمبيوتر المحلي. هذا يعني أنه إذا كان التطبيق يحتوي على حالة ، فيجب مشاركته بأمان بين جميع سلاسل الرسائل ، ولكن بفضل قوالب الحوسبة المتوازية الموثوقة Rust ، هذه ليست مشكلة. مع ذلك ، يجب أن تكون الواجهة الخلفية نظامًا عديم الجنسية ، حيث يمكن نشر العديد من نسخه بالتوازي في بيئة سحابية (مثل
Kubernetes ). ونتيجة لذلك ، يجب فصل البيانات التي تشكل حالة التطبيق عن الخلفية. على سبيل المثال ، قد تكون داخل نسخة منفصلة من حاوية
Docker .
PostgreSQL DBMS ومشروع الديزلبصفتي مستودع البيانات الرئيسي ، قررت استخدام نظام
PostgreSQL DBMS. لماذا؟ حدد هذا الاختيار وجود مشروع
ديزل رائع يدعم بالفعل PostgreSQL ويقدم نظام ORM آمن وقابل للتوسيع وأداة بناء استعلام له. كل هذا يتوافق تمامًا مع احتياجات مشروعنا ، لأن
actix-web
تدعم بالفعل الديزل. ونتيجة لذلك ، هنا ، لإجراء عمليات CRUD بمعلومات حول الجلسات في قاعدة البيانات ، يمكنك استخدام لغة خاصة تأخذ في الاعتبار تفاصيل Rust. فيما يلي مثال لمعالج
UpdateSession
لـ
actix-web
استنادًا إلى Diesel.rs.
impl Handler<UpdateSession> for DatabaseExecutor { type Result = Result<Session, Error>; fn handle(&mut self, msg: UpdateSession, _: &mut Self::Context) -> Self::Result {
يستخدم مشروع
r2d2 لإنشاء اتصال بين
actix-web
و Diesel. هذا يعني أن لدينا (بالإضافة إلى التطبيق مع سير العمل) الحالة المشتركة للتطبيق ، والتي تدعم العديد من اتصالات قاعدة البيانات كمجموعة اتصال واحدة. هذا يبسط إلى حد كبير التدرج الخطير للواجهة الخلفية ، مما يجعل هذا الحل مرنًا.
هنا يمكنك العثور على الرمز المسؤول عن إنشاء مثيل الخادم.
end اختبار الخلفية
يتم
إجراء اختبار تكامل الواجهة الخلفية في مشروعنا من خلال إطلاق مثيل خادم اختبار والاتصال بقاعدة بيانات قيد التشغيل بالفعل. ثم يمكنك استخدام عميل WebSocket القياسي (استخدمت
التنغستن ) لإرسال البيانات التي تم إنشاؤها باستخدام بروتوكول Cap'n Proto إلى الخادم ومقارنة النتائج بالنتائج المتوقعة. لقد أثبت إعداد الاختبار هذا أنه ممتاز. لم أستخدم
خوادم الاختبار الخاصة
بشركة actix-web ، حيث لا يتطلب الأمر الكثير من العمل لتكوين خادم حقيقي وتشغيله. تبين أن اختبار وحدة الواجهة الخلفية ، كما هو متوقع ، مهمة بسيطة إلى حد ما ؛ إجراء مثل هذه الاختبارات لا يسبب أي مشاكل.
نشر المشروع
التطبيق سهل للغاية لنشره باستخدام صورة Docker.
عامل ميناءباستخدام
make deploy
يمكنك إنشاء صورة تسمى
webapp
تحتوي على ملفات تنفيذية للخلفية مرتبطة بشكل ثابت وملف
Config.toml
الحالي وشهادات TLS ومحتوى الواجهة الأمامية الثابتة. يتم تنفيذ تجميع الملفات التنفيذية المرتبطة بشكل ثابت في Rust باستخدام نسخة معدلة من صورة Docker
rust-musl-builder Docker. يمكن اختبار تطبيق ويب منتهي باستخدام الأمر
make run
الذي يقوم بتشغيل حاوية ممكّنة للشبكة. يجب تشغيل حاوية PostgreSQL بالتوازي مع حاوية التطبيق لضمان عمل النظام. بشكل عام ، فإن عملية نشر نظامنا بسيطة للغاية ، بالإضافة إلى ذلك ، وبفضل التقنيات المستخدمة هنا ، يمكننا التحدث عن مرونتها الكافية ، وتبسيط تكيفها المحتمل مع احتياجات التطبيق النامي.
التقنيات المستخدمة في تطوير المشروع
هنا هو مخطط تبعية التطبيق.
التقنيات المستخدمة لتطوير تطبيق ويب في Rustالمكون الوحيد الذي تستخدمه الواجهة الأمامية والخلفية هو إصدار Rust من Cap'n Proto ، والذي يتطلب إنشاء مترجم Cap'n Proto المثبت محليًا.
النتائج. هل الصدأ جاهز لإنتاج الويب؟
هذا سؤال كبير. إليك ما يمكنني الإجابة عليه. من وجهة نظر الخوادم ، أميل إلى الإجابة بـ "نعم" ، حيث أن النظام البيئي Rust ، بالإضافة إلى
actix-web
، يحتوي على
حزمة HTTP ناضجة للغاية والعديد من
الأطر المختلفة للتطوير السريع لواجهات برمجة التطبيقات والخدمات.
إذا تحدثنا عن الواجهة الأمامية ، فبفضل الاهتمام العام بـ WebAssembly ، يجري الكثير من العمل الآن. ومع ذلك ، يجب أن تصل المشاريع التي تم إنشاؤها في هذا المجال إلى نفس النضج الذي وصلت إليه مشروعات الخادم. هذا ينطبق بشكل خاص على استقرار API وقدرات الاختبار. لذا أقول الآن "لا" لاستخدام الصدأ في الواجهة الأمامية ، ولكن لا يسعني إلا أن أشير إلى أنه يتحرك في الاتجاه الصحيح.
أعزائي القراء! هل تستخدم Rust في تطوير الويب؟