Pengembangan Aplikasi Web di Rust

Penulis materi, terjemahan yang kami terbitkan hari ini, mengatakan bahwa eksperimen terbarunya di bidang arsitektur proyek perangkat lunak adalah pembuatan aplikasi web yang berfungsi hanya menggunakan bahasa Rust dan dengan penggunaan kode templat seminimal mungkin. Dalam artikel ini, dia ingin berbagi dengan pembaca apa yang dia temukan saat mengembangkan aplikasi dan menjawab pertanyaan apakah Rust siap menggunakannya di berbagai bidang pengembangan web.



Tinjauan Proyek


Kode untuk proyek yang akan dibahas di sini dapat ditemukan di GitHub . Bagian klien dan server aplikasi terletak di repositori yang sama, ini dilakukan untuk menyederhanakan pemeliharaan proyek. Perlu dicatat bahwa Cargo perlu mengkompilasi aplikasi frontend dan backend dengan dependensi berbeda. Di sini Anda dapat melihat aplikasi yang berfungsi.

Proyek kami adalah demonstrasi sederhana dari mekanisme otentikasi. Ini memungkinkan Anda untuk masuk dengan nama pengguna dan kata sandi yang dipilih (harus sama).

Jika nama pengguna dan kata sandi berbeda, otentikasi akan gagal. Setelah otentikasi berhasil, token JWT (JSON Web Token) disimpan di sisi klien dan server. Menyimpan token di server dalam aplikasi seperti itu biasanya tidak diperlukan, tetapi saya hanya melakukan itu untuk tujuan demonstrasi. Ini, misalnya, dapat digunakan untuk mencari tahu berapa banyak pengguna yang login. Seluruh aplikasi dapat dikonfigurasi menggunakan file Config.toml tunggal, misalnya, menentukan kredensial untuk mengakses database, atau alamat dan nomor port server. Seperti apa kode standar untuk file ini untuk aplikasi kita.

[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" 

Pengembangan Aplikasi Klien


Untuk mengembangkan sisi aplikasi klien, saya memutuskan untuk menggunakan yew . Ini adalah kerangka kerja Rust modern yang terinspirasi oleh Elm, Angular dan React. Ini dirancang untuk membuat bagian klien dari aplikasi web multi-threaded menggunakan WebAssembly (Wasm). Saat ini, proyek ini sedang dalam pengembangan aktif, sementara tidak ada rilis stabil sangat banyak.

Kerangka kerja yew bergantung pada alat web-kargo , yang dirancang untuk mengkompilasi silang kode dalam Wasm.

Alat web kargo adalah ketergantungan yew langsung yang menyederhanakan kompilasi silang kode Rust di Wasm. Berikut adalah tiga tujuan utama untuk menyusun Wasm yang tersedia melalui alat ini:

  • asmjs-unknown-emscripten - menggunakan asm.js melalui Emscripten.
  • wasm32-unknown-emscripten - menggunakan WebAssembly melalui Emscripten
  • wasm32-unknown-unknown - menggunakan WebAssembly dengan backend Rust asli untuk WebAssembly


Perakitan web

Saya memutuskan untuk menggunakan opsi terakhir, yang membutuhkan penggunaan perakitan "malam" dari kompiler Rust, tetapi yang terbaik menunjukkan kemampuan Wasm asli Rust.
Jika kita berbicara tentang WebAssembly, maka berbicara tentang Rust hari ini adalah topik terpanas. Sejumlah besar pekerjaan sedang dilakukan pada cross-compiling Rust in Wasm dan mengintegrasikannya ke dalam ekosistem Node.js (menggunakan paket npm). Saya memutuskan untuk mengimplementasikan proyek tanpa ketergantungan JavaScript.

Ketika meluncurkan frontend aplikasi web (dalam proyek saya ini dilakukan dengan perintah make frontend ), cargo-web cross-kompilasi aplikasi di Wasm dan mengemasnya, menambahkan beberapa bahan statis. cargo-web meluncurkan server web lokal, yang memungkinkan Anda untuk berinteraksi dengan aplikasi untuk tujuan pengembangan. Inilah yang terjadi di konsol ketika Anda menjalankan perintah di atas:

 > 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`. 

Kerangka kerja yew memiliki beberapa fitur yang sangat menarik. Diantaranya adalah dukungan untuk arsitektur komponen yang dapat digunakan kembali. Fitur ini telah menyederhanakan penguraian aplikasi saya menjadi tiga komponen utama:

RootComponent . Komponen ini langsung dipasang ke tag <body> situs web. Dia memutuskan komponen anak mana yang harus dimuat selanjutnya. Jika, di pintu masuk pertama ke halaman, token JWT ditemukan, ia mencoba untuk memperbarui token ini dengan menghubungi bagian server dari aplikasi. Jika gagal, transisi ke komponen LoginComponent .

Komponen Login . Komponen ini adalah turunan dari komponen RootComponent , ini berisi formulir dengan bidang untuk memasukkan kredensial. Selain itu, ia berinteraksi dengan bagian belakang aplikasi untuk mengatur skema otentikasi sederhana berdasarkan verifikasi nama pengguna dan kata sandi, dan, jika otentikasi berhasil, menyimpan JWT dalam cookie. Selain itu, jika pengguna dapat mengautentikasi, ia melakukan transisi ke komponen ContentComponent .


Penampilan Komponen LoginComponent

ContentComponent Komponen ini adalah turunan lain dari komponen RootComponent . Ini berisi apa yang ditampilkan di halaman utama aplikasi (saat ini hanya judul dan tombol untuk keluar dari sistem). Akses ke sana dapat diperoleh melalui RootComponent (jika aplikasi, saat startup, berhasil menemukan token sesi yang valid), atau melalui LoginComponent (jika otentikasi berhasil). Komponen ini bertukar data dengan backend ketika pengguna mengklik tombol logout.


Komponen ContentComponent

Komponen Router Komponen ini menyimpan semua rute yang mungkin antara komponen yang mengandung konten. Selain itu, ini berisi keadaan awal loading dan error aplikasi. Ini terhubung langsung ke RootComponent .

Salah satu konsep kunci yew yang akan kita bahas sekarang adalah layanan. Mereka memungkinkan Anda untuk menggunakan kembali logika yang sama di berbagai komponen. Katakanlah ini bisa berupa antarmuka atau alat logging untuk mendukung penggunaan cookie . Layanan tidak menyimpan beberapa keadaan global, mereka dibuat ketika komponen diinisialisasi. Selain layanan, yew mendukung konsep agen. Mereka dapat digunakan untuk mengatur berbagi data antara berbagai komponen, untuk mempertahankan keadaan umum aplikasi, seperti yang diperlukan untuk agen yang bertanggung jawab untuk routing. Untuk mengatur sistem perutean aplikasi kami, yang mencakup semua komponen, agen kami dan layanan perutean diterapkan di sini. Tidak ada router standar di yew , tetapi dalam repositori framework Anda dapat menemukan contoh implementasi router yang mendukung berbagai operasi URL.

Saya senang mencatat bahwa Anda menggunakan API Pekerja Web untuk menjalankan agen pada berbagai utas dan menggunakan penjadwal lokal yang terpasang pada utas untuk menyelesaikan tugas paralel. Hal ini memungkinkan untuk mengembangkan aplikasi browser dengan multithreading tingkat tinggi di Rust.

Setiap komponen mengimplementasikan sifat Renderable -nya sendiri, yang memungkinkan kita untuk memasukkan kode HTML secara langsung dalam kode sumber Rust menggunakan html! {} Makro .

Ini adalah fitur yang hebat, dan, tentu saja, penggunaannya yang tepat dikendalikan oleh kompiler. Berikut adalah Renderable implementasi LoginComponent komponen 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>       }   } } 

Koneksi antara frontend dan backend didasarkan pada koneksi WebSocket yang digunakan oleh setiap klien. Kekuatan teknologi WebSocket adalah kenyataan bahwa itu cocok untuk mengirimkan pesan biner, serta fakta bahwa server, jika perlu, dapat mengirim pemberitahuan push ke klien. yew memiliki layanan WebSocket standar, tetapi saya memutuskan untuk membuat versinya sendiri untuk tujuan demonstrasi, terutama karena inisialisasi koneksi "malas" langsung di dalam layanan. Jika layanan WebSocket akan dibuat selama inisialisasi komponen, saya harus memantau banyak koneksi.


Protokol Cap'n Proto

Saya memutuskan untuk menggunakan protokol Cap'n Proto (bukan sesuatu seperti JSON , MessagePack atau CBOR ) sebagai lapisan untuk mengirimkan data aplikasi untuk alasan kecepatan dan kekompakan. Perlu dicatat bahwa saya tidak menggunakan antarmuka protokol RPC yang dimiliki Cap'n Proto, karena implementasi Rustnya tidak dapat dikompilasi untuk WebAssembly (karena dependensi toxio-rs Unix). Ini agak rumit dalam pemilihan permintaan dan tanggapan dari jenis yang benar, tetapi masalah ini dapat diselesaikan dengan menggunakan API yang terstruktur dengan baik . Berikut ini adalah deklarasi protokol Proto Cap'n untuk aplikasi tersebut.

 @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;       }   } } 

Anda dapat melihat bahwa di sini kami memiliki dua versi berbeda dari permintaan login.

Salah satunya adalah untuk LoginComponent (di sini, untuk mendapatkan token, nama dan kata sandi digunakan), dan yang lainnya untuk RootComponent (digunakan untuk memperbarui token yang ada). Segala sesuatu yang diperlukan untuk protokol untuk bekerja dikemas dalam layanan protokol , karena itu nyaman untuk menggunakan kembali kemampuan yang sesuai di berbagai bagian frontend.


UIkit - kerangka front-end modular yang ringkas untuk mengembangkan antarmuka web yang cepat dan tangguh

Antarmuka pengguna bagian klien dari aplikasi didasarkan pada kerangka UIkit , versi 3.0.0 akan dirilis dalam waktu dekat. Skrip build.rs yang disiapkan khusus secara otomatis mengunduh semua dependensi UIkit yang diperlukan dan mengkompilasi lembar gaya yang dihasilkan. Ini berarti bahwa Anda dapat menambahkan gaya Anda sendiri ke file style.scss tunggal, yang dapat diterapkan di seluruh aplikasi. Sangat nyaman.

FrontMenguji frontend


Saya percaya bahwa ada beberapa masalah dengan pengujian solusi kami. Faktanya adalah sangat mudah untuk menguji layanan individu, tetapi Anda tidak memberikan pengembang dengan cara yang nyaman untuk menguji komponen dan agen. Sekarang, dalam kerangka Rust murni, integrasi dan pengujian ujung ke ujung tidak tersedia. Di sini Anda dapat menggunakan proyek-proyek seperti Cypress atau Protractor , tetapi dengan pendekatan ini, Anda harus memasukkan banyak templat kode JavaScript / TypeScript dalam proyek, jadi saya memutuskan untuk meninggalkan implementasi tes tersebut.

Omong-omong, inilah ide untuk proyek baru: kerangka kerja untuk pengujian ujung-ke-ujung yang ditulis dalam Rust.

Pengembangan sisi server aplikasi


Untuk mengimplementasikan sisi server aplikasi, saya memilih kerangka kerja actix-web . Ini adalah kerangka model aktor berbasis Rust yang ringkas, praktis dan sangat cepat. Ini mendukung semua teknologi yang diperlukan, seperti WebSockets, TLS dan HTTP / 2.0 . Kerangka kerja ini mendukung berbagai penangan dan sumber daya, tetapi dalam aplikasi kami hanya beberapa rute utama yang digunakan:

  • /ws adalah sumber daya utama untuk komunikasi WebSocket.
  • / - pengendali utama yang memberikan akses ke aplikasi front-end statis.

Secara default, actix-web memulai alur kerja dalam jumlah yang sesuai dengan jumlah inti prosesor yang tersedia di komputer lokal. Ini berarti bahwa jika aplikasi memiliki status, aplikasi harus dibagikan dengan aman di antara semua utas, tetapi berkat template komputasi paralel Rust yang andal, ini bukan masalah. Meskipun demikian, backend seharusnya merupakan sistem tanpa kewarganegaraan, karena banyak salinannya dapat digunakan secara paralel di lingkungan cloud (seperti Kubernetes ). Akibatnya, data yang membentuk keadaan aplikasi harus dipisahkan dari backend. Misalnya, mereka mungkin berada di dalam instance terpisah dari wadah Docker .


DBMS dan Proyek Diesel PostgreSQL

Sebagai gudang data utama, saya memutuskan untuk menggunakan DBMS PostgreSQL . Mengapa Pilihan ini menentukan keberadaan proyek Diesel yang luar biasa yang sudah mendukung PostgreSQL dan menawarkan sistem ORM yang aman dan dapat dikembangkan serta alat bantu permintaan untuknya. Semua ini sangat sesuai dengan kebutuhan proyek kami, karena actix-web sudah mendukung Diesel. Sebagai hasilnya, di sini, untuk melakukan operasi CRUD dengan informasi tentang sesi dalam basis data, Anda dapat menggunakan bahasa khusus yang mempertimbangkan spesifikasi Rust. Berikut ini adalah contoh handler actix-web untuk actix-web berdasarkan Diesel.rs.

 impl Handler<UpdateSession> for DatabaseExecutor {   type Result = Result<Session, Error>;   fn handle(&mut self, msg: UpdateSession, _: &mut Self::Context) -> Self::Result {       //         debug!("Updating session: {}", msg.old_id);       update(sessions.filter(id.eq(&msg.old_id)))           .set(id.eq(&msg.new_id))           .get_result::<Session>(&self.0.get()?)           .map_err(|_| ServerError::UpdateToken.into())   } } 

Proyek r2d2 digunakan untuk membuat koneksi antara actix-web dan Diesel. Ini berarti bahwa kami memiliki (selain aplikasi dengan alur kerjanya) keadaan bersama aplikasi, yang mendukung banyak koneksi basis data sebagai kumpulan koneksi tunggal. Ini sangat menyederhanakan penskalaan serius backend, membuat solusi ini fleksibel. Di sini Anda dapat menemukan kode yang bertanggung jawab untuk membuat instance server.

▍Menguji backend


Pengujian integrasi backend dalam proyek kami dilakukan dengan meluncurkan instance server pengujian dan menyambungkan ke database yang sudah berjalan. Kemudian Anda dapat menggunakan klien WebSocket standar (saya menggunakan tungstenite ) untuk mengirim data yang dihasilkan menggunakan protokol Cap'n Proto ke server dan membandingkan hasilnya dengan yang diharapkan. Pengaturan tes ini telah terbukti sangat baik. Saya tidak menggunakan server uji khusus actix-web , karena lebih banyak pekerjaan tidak diperlukan untuk mengkonfigurasi dan menjalankan server nyata. Pengujian unit backend ternyata, seperti yang diharapkan, menjadi tugas yang cukup sederhana, melakukan tes seperti itu tidak menimbulkan masalah.

Penempatan proyek


Aplikasi ini sangat mudah digunakan menggunakan gambar Docker.


Docker

Dengan make deploy Anda dapat membuat gambar bernama webapp yang berisi executable backend yang terhubung secara statis, file Config.toml saat ini, sertifikat TLS, dan konten frontend statis. Perakitan executable yang sepenuhnya terhubung secara statis di Rust diimplementasikan menggunakan versi modifikasi dari gambar Docker rust-musl-builder . Aplikasi web yang sudah selesai dapat diuji menggunakan perintah make run , yang meluncurkan wadah yang mendukung jaringan. Wadah PostgreSQL harus dijalankan secara paralel dengan wadah aplikasi untuk memastikan sistem bekerja. Secara umum, proses penerapan sistem kami cukup sederhana, di samping itu, berkat teknologi yang digunakan di sini, kami dapat berbicara tentang fleksibilitasnya yang cukup, menyederhanakan adaptasi yang mungkin untuk kebutuhan aplikasi yang sedang berkembang.

Teknologi yang digunakan dalam pengembangan proyek


Berikut adalah diagram ketergantungan aplikasi.


Teknologi yang digunakan untuk mengembangkan aplikasi web di Rust

Satu-satunya komponen yang digunakan frontend dan backend adalah versi Rust dari Cap'n Proto, yang membutuhkan kompiler Cap'n Proto yang diinstal secara lokal untuk dibuat.

Hasilnya. Apakah Rust Siap Untuk Produksi Web?


Ini pertanyaan besar. Inilah yang bisa saya jawab. Dari sudut pandang server, saya cenderung menjawab β€œya”, karena ekosistem Rust, selain actix-web , memiliki tumpukan HTTP yang sangat matang dan banyak kerangka kerja berbeda untuk pengembangan API dan layanan server yang cepat.

Jika kita berbicara tentang front-end, maka, berkat perhatian umum ke WebAssembly, banyak pekerjaan sedang berlangsung sekarang. Namun, proyek yang dibuat di area ini harus mencapai kematangan yang sama dengan proyek server. Ini terutama berlaku untuk stabilitas API dan kemampuan pengujian. Jadi sekarang saya mengatakan "tidak" untuk menggunakan Rust di ujung depan, tetapi saya tidak bisa tidak mencatat bahwa itu bergerak ke arah yang benar.

Pembaca yang budiman! Apakah Anda menggunakan Rust dalam pengembangan web?

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


All Articles