• A Web Application Completely in Rust
  • Sascha Grunert
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: Raoul1996
  • Proofread by: 7Ethan, Calpa

My latest attempt at software architecture is to build a real Web application in Rust with as few template files as possible. In this article I’ll share my findings to answer the question of how many sites actually use Rust.

The projects mentioned in this article can be found on GitHub. To improve the maintainability of the project, I put the front end (client) and back end (server) in one repository. This required Cargo to compile the front-end and back-end binaries with different dependencies separately for the entire project.

Note that the project is currently in rapid iteration and all the relevant code can be found on the rev1 branch. You can read part 2 of this blog series here.

This application is a simple authentication demonstration that allows you to select a user name and password (which must be the same) to log in and fail when they are different. After successful authentication, a JSON Web Token (JWT) is saved on both the client and server. Usually the server does not need to store tokens, but for demonstration purposes, we do. For example, this token can be used to track how many users actually log in. The entire project can be configured using a config.toml file, for example to set database connection credentials, or server host and port.

[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"
Copy the code

The default config. toml file for WebApp

Front end – client side

I decided to use Yew to build the application client. Yew is a modern Rust application framework that uses WebAssembly(Wasm) to create multithreaded front-end applications inspired by Elm, Angular, and ReactJS. The project is in a highly active phase of development, with not many stable releases.

The cargo- Web tool is one of Yew’s direct dependencies and can cross-compile Wasm directly. In fact, there are three main goals for using Wasm in the Rust compiler:

  • _asmjs-unknown-emscripten _ — Use asM.js with emscripten
  • wasm32-unknown-emscripten— Use WebAssembly via Emscripten
  • _WASm32-unknown-unknown _ – Uses the WebAssembly with the Rust native WebAssembly back end

I’ve decided to use the last one and need a nightly Rust compiler. In fact, demonstrating Rust native Wasm is probably the best.

WebAssembly is currently one of Rust’s hottest topics at 🔥. There are a number of developers around the world who are working on building Rust into Wasm and integrating it into NodeJS (NPM packaging). I decided to take a direct approach and not introduce any JavaScript dependencies.

When starting the frontend of a web application (in my case make frontend), cargo web compiles the application into Wasm and packages it with static resources. Cargo – Web then launches a local Web server to facilitate application development.

> make frontend Compiling webapp v0.3.0 (file:///home/sascha/webapp.rs) Finished release/optimized target (s)in11.86 s Garbage collecting"app.wasm". Processing"app.wasm". Finished processing of"app.wasm"! If you need to start the service on any other files, place them in the project root directory'static'Directory; They will then be provided to the user along with your application. Static resource directories can also be placed in the 'SRC' directory. Your application passes'/app.js'Start. Any code changes will trigger an automatic rebuild. You can access the web server at http://0.0.0.0:8000Copy the code

Yew has some nice features, like a reusable component architecture, that make it easy to divide my application into three main components:

  • The root component: mounted directly to the web page<body>Tag to determine which child component to load next. If the JWT is found when entering the page, an attempt will be made to communicate with the back end to update the token, and if the update fails, route toLog on to the component
  • Log on to the component: The root componentContains a login form field. It also performs basic username and password authentication with the back end and saves the JWT to a cookie upon success. After successful authentication, route toContent components

  • Content components: The root of the componentAnother child component, including a main page content (currently just a header and a logout button). It can go throughThe root componentAccess (if a valid session token is already available) or passLog on to the component(Successful authentication) access. When the user presses the logout button, this component communicates with the back end.

  • Routing component: Saves all possible routes between components containing content. It also contains an initial “loading” state and an “error” state of the application, and is directly attached toThe root componentOn.

Service was one of Yew’s next key concepts. It allows reuse of the same logic between components, such as logging or cookie handling. The services in the component are stateless and are created when the component is initialized. In addition to services, Yew also included the concept of agents. Proxies can be used to share data between components, providing a global application state, just as routing proxies require. To route the sample application between all the components, a custom set of routing agents and services is implemented. Yew does not actually have independent routing, but their example provides a reference implementation that supports all types of URL modification.

Surprisingly, Yew uses the Web Workers API to generate proxies in a separate thread and uses a local task scheduler attached to the thread to perform concurrent tasks. This makes it possible to write highly concurrent applications in a browser using Rust.

Each component implements its own ‘Renderable’ feature, which allows us to use Renderable directly via [HTML! {}] (https://github.com/DenisKolodin/yew#jsx-like-templates-with-html-macro) macro in rust source code contained in the HTML. This is great, and makes sure to check with the editor’s built-in Borrow Checker!

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>
        }
    }
}
Copy the code

Log in to the implementation of the component Renderable

Each client communicates from the front end to the back end (and vice versa) via WebSocket connections. The advantage of WebSocket is that binary information can be used, and the server can also push notifications to the client if needed. Yew has already released a WebSocket service, but I had to create a custom version of the sample program, mainly because of the lazy initialization of connections in the service. If the WebSocket service was created at component initialization, we would have to track multiple socket connections.

For speed and compactness. I decided to use a binary protocol, Cap ‘n Proto, as the application data communication layer (rather than JSON, MessagePack, or CBOR). It is worth mentioning that I did not use Cap ‘n Proto’s RPC interface protocol because its Rust implementation cannot compile to WebAssembly (due to tokio-RS ‘Unix dependencies). This makes it slightly difficult to correctly distinguish between request and response types, but a well-defined API can solve this problem:

@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; }}}Copy the code

Cap ‘n Proto protocol definition for an application

You can see that we have two different variations of the login request here: one is the login component (credential request for username and password) and the other is the root component (existing token refresh request). All required protocol implementations are included in the protocol service, making it easy to reuse across the entire front end.

UIkit – A lightweight modular front-end framework for developing fast and powerful Web interfaces

The front-end user interface is powered by UIkit, version 3.0.0 of which will be released in the near future. The custom build.rs script automatically downloads all the dependencies required by UIkit and compiles the entire stylesheet. This means that you can insert custom styles in a separate style. SCSS file and use them in your application. Arrangement! (PS: Neat!)

Front end test

In my opinion, there may be some minor problems with the test. Testing individual services is easy, but Yew does not yet provide an elegant way to test individual components or proxies. Front-end integration and end-to-end testing is also currently not possible within Rust. Projects like Cypress or Protractor could have been used, but this would have introduced too many JavaScript/TypeScript boilerplates, so I skipped this option.

But maybe it’s a good starting point for a new project: writing an end-to-end testing framework in Rust! What do you think?

Back end – the server side

My back-end framework of choice is Actix-Web: a small, pragmatic and extremely fast Rust Actor framework. It supports all the required technologies, such as WebSockets, TLS, and HTTP/ 2.0.actix-Web supports different handlers and resources, but only two main routes are used in the sample application:

  • **/ws**: Primary Websocket communication resource.
  • / * * * *: main application processing handle (handler) for routing to statically deployed front-end applications

By default, Actix-Web generates as many Works as the number of logical cpus on the local computer. This means that possible application state must be safely shared between threads, but this is not a problem at all with Rust’s fearless concurrency mode. However, the entire back end should be stateless, as multiple replicas may be deployed in parallel on the cloud (such as Kubernetes). So the application state should be outside of the back-end service in a single Docker container instance.

I decided to use PostgreSQL as my primary data store. Why is that? The awesome Diesel project already supports PostgreSQL and provides it with a secure and extensible object relational Mapping (ORM) and Query Builder. This is great because Actix-Web already supports Diesel. In this way, you can customize the customary Rust domain-specific language to create, read, update, or delete (CRUD) sessions in the database, as follows:

impl Handler<UpdateSession> for DatabaseExecutor {
    typeResult = Result<Session, Error>; fn handle(&mut self, msg: UpdateSession, _: &mut Self::Context) -> Self::Result { // Update the session 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())
    }
}
Copy the code

Actix-web UpdateSession handler provided by Dies. rs

For handling the connection between Actix-Web and Diesel, use the R2D2 project. This means that we (the application and its Works) have a shared application state that saves multiple connections to the database as a single connection pool. This makes the whole back end very flexible and easy to scale up. The entire server example can be found here.

The back-end test

Back-end integration testing is done by setting up a test case and connecting to an already running database. You can then use a standard WebSocket client (I used Tungstenite) to send the protocol-specific Cap’n Proto data to the server and verify the expected results. It works! I didn’t use an Actix-Web-specific test server, because setting up a real one wasn’t too much trouble. Unit testing for the rest of the back end is as easy as expected, without any tricky pitfalls.

The deployment of

Docker images make it easy to deploy applications.

The Makefile command make deploy creates a Docker image named webApp containing the staticlly Linked back-end executable, the current config.toml, TLS certificates, and the front-end static resources. Building a fully statically linked executable in Rust is done through a modified rust-Musl-Builder image variant. The generated Webapp can be tested using make run, which starts the container and host network. PostgreSQL containers should now run in parallel. Overall, the overall deployment should not be an important part of the project and should be flexible enough to accommodate future changes.

conclusion

To summarize, the basic dependency stack for an application looks like this:

The only shared component between the front end and back end is the Rust source generated by Cap’n Proto, which requires a locally installed Cap’n Proto compiler.

So, are we done with the Web (for production)?

This is a big problem, and this is my personal opinion:

I tend to say yes to the back end. Because Rust has a wide variety of frameworks with very mature HTTP technology stacks, like Actix-Web. Used to quickly build apis and back-end services.

On the front end, there’s a lot of work in progress, thanks to WebAssembly hype. But the project needs to have the same level of maturity as the back end, especially in terms of stable apis and feasibility of testing. So the front end should be no. But we are still in the right direction.

Thank you so much for reading this. ❤

I will continue to refine my sample application to explore the connection points between Rust and Web applications. Sustainable rusting.

If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.