Code exercise of the rust programming language (Part 3 simple web server)

Time:2021-5-2
My relationship with rust started when I found such a modern functional system programming language for system security when I was wandering in the Programming Forum. However, at that time, I only had a general understanding and did not have in-depth study, so I studied it carefully this time.
The learning content includes all the contents of the book "the rust programming language" (completed), all the contents of "the way of rust programming" (unfinished) and some contents of "the rustomicon" (unfinished).

1、 Content overview

I will “rust programming language” learning content is divided into basic learning (1 to 9 chapters) and advanced learning (10 to 19 chapters), these two parts are a rough abbreviation of my learning content. Then it is a simple web server program construction based on the last chapter (Chapter 20) of the book. Finally, it is a simple example comparing the existing Actix web framework of rust community.
This paper is the conclusion of the last chapter of the rust programming languageWeb projectPractice, this part of the learning practice code has been sent to the open source platformGiteeandGitHubOn the platform

To view the previous section, pleaseGo to

4. Construction of rust simple web server

4.1 build a single threaded web server with rust:

After three simple refactorings, the rust code is as follows:

src/main.rs:

use multithreaded_server::ThreadPool;
use std::time::Duration;
use std::fs;
use std::thread;
use std::io::prelude::*;
use std::net::{TcpListener,TcpStream};

fn main() {
    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
    let pool = ThreadPool::new(4); // Create a thread pool with 4 threads

    for stream in listener.incoming() {
        let stream = stream.unwrap();

        pool.execute(|| {
            handle_connection(stream);
        });
    }
}

fn handle_connection(mut stream: TcpStream) {
    let mut buffer = [0;  512]; // Stack creates a 512 byte buffer

    stream.read(&mut buffer).unwrap(); // Read bytes from tcpstream and put them into buffer
    println!("Request: {}", String::from_utf8_lossy(&buffer[..]));

    let get = b"GET / HTTP/1.1\r\n";
    let sleep = b"GET /sleep HTTP/1.1\r\n";

    let (status_line, filename) = if buffer.starts_with(get) {
        ("HTTP/1.1 200 OK\r\n\r\n", "test.html")
    } else if buffer.starts_with(sleep) {
        thread::sleep(Duration::from_secs(5));
        ("HTTP/1.1 200 OK\r\n\r\n", "test.html")
    } else {
        ("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html")
    };

    let contents = fs::read_to_string(filename).unwrap();

    let response = format!("{}{}", status_line, contents);

    stream.write(response.as_bytes()).unwrap();
    stream.flush().unwrap();

    println!("Response: ");
    println!("{}", response);
}

Of course, the basic TCP socket can only be implemented by the official standard library of rust.

In the simple single thread web server implementation, TCP socket is created, and port 7878 is bound and monitored. The TCP socket flow connection and request / response process between each client and server are processed, and the iterator composed of connection is obtained. After error handling for each connection, call the custom connection handler function handle_ After processing the request to place the buffer, connection responds to the corresponding resources of the web browser.

In order to facilitate debugging, the contents of the request and response are output to the console.

The following is the resource code for testing:

test.html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <title>test web server</title>
</head>

<body>
    <h1>test</h1>
    <p>this is test for little web server</p>
</body>

</html>

404.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

    <meta charset="UTF-8" http-equiv="Content-Type" content="text/html; charset=utf-8" />

    < title > 404 - sorry! The page you visited does not exist < / Title >

    <style type="text/css">
        .head404 {
            width: 580px;
            height: 234px;
            margin: 50px auto 0 auto;
            background: url(https://www.daixiaorui.com/Public/images/head404.png) no-repeat;
        }

        .txtbg404 {
            width: 499px;
            height: 169px;
            margin: 10px auto 0 auto;
            background: url(https://www.daixiaorui.com/Public/images/txtbg404.png) no-repeat;
        }

        .txtbg404 .txtbox {
            width: 390px;
            position: relative;
            top: 30px;
            left: 60px;
            color: #eee;
            font-size: 13px;
        }

        .txtbg404 .txtbox p {
            margin: 5px 0;
            line-height: 18px;
        }

        .txtbg404 .txtbox .paddingbox {
            padding-top: 15px;
        }

        .txtbg404 .txtbox p a {
            color: #eee;
            text-decoration: none;
        }

        .txtbg404 .txtbox p a:hover {
            color: #FC9D1D;
            text-decoration: underline;
        }
    </style>

</head>



<body bgcolor="#494949">

    <div class="head404"></div>

    <div class="txtbg404">

        <div class="txtbox">

            <p>Sorry, the page you requested does not exist, has been deleted, or is temporarily unavailable</p>

            < p class = "paddingbox" > please click the link below to continue browsing</p>

            <p>》<a style=" cursor:pointer "Onclick =" history. Back() "> returns to the previous page</a></p>

            <p>》<a href=" https://www.daixiaorui.com "> back to home page</a></p>

        </div>

    </div>

</body>

</html>

</html>

After running the simple web server, the browser requests127.0.0.1:7878/orhttp://127.0.0.1:7878/sleep:

Code exercise of the rust programming language (Part 3 simple web server)

The console also outputs the corresponding request / responses:
Code exercise of the rust programming language (Part 3 simple web server)

If you access an undefined route, for example: 127.0.0.1:7878 / test, 404.html will be returned as follows:
Code exercise of the rust programming language (Part 3 simple web server)

Thus, a simple single thread web server is completed.

Although this single threaded web server can also process and respond to resources, the advanced features of rust are not reflected in it, such as multi-threaded fearless concurrency and advanced trait. Moreover, because the server is running in a single thread, it can only process one request at a time, which means that the second connection will not be processed before the first processing is completed. I added sleep in the code to simulate slow requests. If the request / sleep is within five seconds (that is, the current sleep request is processing) request /, I will find that it can only appear after the sleep has been dormant for five seconds.

That is, if the server is receiving more and more requests, this kind of serial operation will make the performance worse and worse. If a request takes a long time to process, subsequent requests will have to wait for the end of the long request, even if the new requests can be processed quickly. So I plan to use rust’s concurrency to improve the server.

4.2 build multi thread web server with rust

A common way to build a multithreaded web server is to implement a thread pool for it. Similarly, a simple thread pool will be implemented here, which contains a fixed number of threads. After new requests are sent to the thread pool for processing, the thread pool will maintain the received request queue, and each thread will take a request from the queue for processing, Then ask the queue for another request, that is, the concurrent number n of the thread pool is the number of threads in the thread pool. If each thread is responding to slow requests, it will still block the queue. However, compared with the single thread web server above, the performance is much higher, and the slow requests it can handle are also more.

The code to build a thread pool is as follows:

src/lib.rs:

use std::sync::{mpsc, Arc, Mutex};
use std::thread;

struct Worker {
    id: usize,
    thread: Option<thread::JoinHandle<()>>,
}

impl Worker {
    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Message>>>) -> Worker {
        let thread = thread::spawn(move || loop {
            let message = receiver.lock().unwrap().recv().unwrap();

            match message {
                Message::NewJob(job) => {
                    println!("Worker {} got a job; executing.", id);

                    job();
                }
                Message::Terminate => {
                    println!("Worker {} was told to terminate.", id);

                    break;
                }
            }
        });

        Worker {
            id,
            thread: Some(thread),
        }
    }
}

type Job = Box<dyn FnOnce() + Send + 'static>;

enum Message {
    NewJob(Job),
    Terminate,
}

pub struct ThreadPool {
    workers: Vec<Worker>,
    sender: mpsc::Sender<Message>,
}

impl ThreadPool {
    pub fn new(size: usize) -> ThreadPool {
        assert!(size > 0);

        let (sender, receiver) = mpsc::channel();

        let receiver = Arc::new(Mutex::new(receiver));

        let mut workers = Vec::with_capacity(size);

        for id in 0..size {
            workers.push(Worker::new(id, Arc::clone(&receiver)));
        }
        ThreadPool { workers, sender }
    }

    pub fn execute<F>(&self, f: F)
    where
        F: FnOnce() + Send + 'static,
    {
        let job = Box::new(f);

        self.sender.send(Message::NewJob(job)).unwrap();
    }
}

//Discard thread pool
impl Drop for ThreadPool {
    fn drop(&mut self) {
        println!("Sending terminate message to all workers.");

        for _ in &mut self.workers {
            self.sender.send(Message::Terminate).unwrap();
        }

        println!("Shutting down all workers.");

        for worker in &mut self.workers {
            println!("Shutting down worker {}", worker.id);

            if let Some(thread) = worker.thread.take() {
                thread.join().unwrap();
            }
        }
    }
}

Here, we create the ThreadPool correlation function new, which is used to create a thread pool. New establishes the sender and receiver through the channel. The receiver is actually a waiting thread, so it is cloned into the waiting queue works several times in the new, and then each thread of the waiting thread queue works realizes the shared task queue through the thread safe intelligent pointer, That is, the same sender.

In the worker’s correlation function new, a loop is used to implement the request task for the receiver of the channel. If the task is not obtained, the thread itself will be blocked and executed when it is obtained, and mutex is used to achieve secure sharing. When the thread pool is discarded, it will call its drop function to clean up the thread queue and clean up all the threads in the thread pool.

Thus, a simple thread pool of asynchronous execution connection is implemented, and its corresponding main function is as follows:

use multithreaded_server::ThreadPool;
use std::time::Duration;
use std::fs;
use std::thread;
use std::io::prelude::*;
use std::net::{TcpListener,TcpStream};

fn main() {
    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
    let pool = ThreadPool::new(4);

    for stream in listener.incoming() {
        let stream = stream.unwrap();

        pool.execute(|| {
            handle_connection(stream);
        });
    }

    println!("Shutting down.");
}

fn handle_connection(mut stream: TcpStream) {
    let mut buffer = [0; 512];
    stream.read(&mut buffer).unwrap();

    let get = b"GET / HTTP/1.1\r\n";
    let sleep = b"GET /sleep HTTP/1.1\r\n";

    let (status_line, filename) = if buffer.starts_with(get) {
        ("HTTP/1.1 200 OK\r\n\r\n", "test.html")
    } else if buffer.starts_with(sleep) {
        thread::sleep(Duration::from_secs(5));
        ("HTTP/1.1 200 OK\r\n\r\n", "test.html")
    } else {
        ("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html")
    };

    let contents = fs::read_to_string(filename).unwrap();

    let response = format!("{}{}", status_line, contents);

    stream.write(response.as_bytes()).unwrap();
    stream.flush().unwrap();
}

After running, the slow request test and concurrency test are carried out in the browser. It is found that the current multi-threaded server can handle more concurrent connections than the single threaded version, and it is not affected by slow requests to a certain extent. For the four tests, the output of console is as follows after 6 / and / sleep requests
Code exercise of the rust programming language (Part 3 simple web server)
That is, the number of concurrent requests is 4. When new requests are established, the threads in the thread pool are still processed in the form of queues, and the throughput increases, which is in line with the expectation.

If the number of requests of the web server is set to 4, the web server program will stop running after 4 requests, and the console output is as follows:
Code exercise of the rust programming language (Part 3 simple web server)

You can see that the thread queue is cleaned up after message processing, which is in line with the expectation.

Similarly, after running the multithreaded web server, the browser can display and obtain resources correctly.

4.3 rust acitx Web Framework

In fact, rust community has implemented many different web frameworks, the most famous of which are Actix and rocket. Taking Actix web framework as an example, we have implemented the corresponding HTTP server, request handler, type safe information extractor, error handling, URL scaling, etc., and also implemented http2 protocol.

Here’s a simple Actix web application:

The corresponding items are as follows:

Code exercise of the rust programming language (Part 3 simple web server)

main.rs:

use actix_files as fs;
use actix_session::{CookieSession, Session};
use actix_utils::mpsc;
use actix_web::http::{header, Method, StatusCode};
use actix_web::{
    error, get, guard, middleware, web, App, Error, HttpRequest, HttpResponse,
    HttpServer, Result,
};
use std::{env, io};

/// favicon handler
#[get("/favicon")]
async fn favicon() -> Result<fs::NamedFile> {
    Ok(fs::NamedFile::open("static/favicon.ico")?)
}

/// simple index handler
#[get("/welcome")]
async fn welcome(session: Session, req: HttpRequest) -> Result<HttpResponse> {
    println!("{:?}", req);

    // session
    let mut counter = 1;
    if let Some(count) = session.get::<i32>("counter")? {
        println!("SESSION value: {}", count);
        counter = count + 1;
    }

    // set counter to session
    session.set("counter", counter)?;

    // response
    Ok(HttpResponse::build(StatusCode::OK)
        .content_type("text/html; charset=utf-8")
        .body(include_str!("../static/welcome.html")))
}

/// 404 handler
async fn p404() -> Result<fs::NamedFile> {
    Ok(fs::NamedFile::open("static/404.html")?.set_status_code(StatusCode::NOT_FOUND))
}

/// response body
async fn response_body(path: web::Path<String>) -> HttpResponse {
    let text = format!("Hello {}!", *path);

    let (tx, rx_body) = mpsc::channel();
    let _ = tx.send(Ok::<_, Error>(web::Bytes::from(text)));

    HttpResponse::Ok().streaming(rx_body)
}

/// handler with path parameters like `/user/{name}/`
async fn with_param(
    req: HttpRequest,
    web::Path((name,)): web::Path<(String,)>,
) -> HttpResponse {
    println!("{:?}", req);

    HttpResponse::Ok()
        .content_type("text/plain")
        .body(format!("Hello {}!", name))
}

#[actix_web::main]
async fn main() -> io::Result<()> {
    env::set_var("RUST_LOG", "actix_web=debug,actix_server=info");
    env_logger::init();

    HttpServer::new(|| {
        App::new()
            // cookie session middleware
            .wrap(CookieSession::signed(&[0; 32]).secure(false))
            // enable logger - always register actix-web Logger middleware last
            .wrap(middleware::Logger::default())
            // register favicon
            .service(favicon)
            // register simple route, handle all methods
            .service(welcome)
            // with path parameters
            .service(web::resource("/user/{name}").route(web::get().to(with_param)))
            // async response body
            .service(
                web::resource("/async-body/{name}").route(web::get().to(response_body)),
            )
            .service(
                web::resource("/test").to(|req: HttpRequest| match *req.method() {
                    Method::GET => HttpResponse::Ok(),
                    Method::POST => HttpResponse::MethodNotAllowed(),
                    _ => HttpResponse::NotFound(),
                }),
            )
            .service(web::resource("/error").to(|| async {
                error::InternalError::new(
                    io::Error::new(io::ErrorKind::Other, "test"),
                    StatusCode::INTERNAL_SERVER_ERROR,
                )
            }))
            // static files
            .service(fs::Files::new("/static", "static").show_files_listing())
            // redirect
            .service(web::resource("/").route(web::get().to(|req: HttpRequest| {
                println!("{:?}", req);
                HttpResponse::Found()
                    .header(header::LOCATION, "static/welcome.html")
                    .finish()
            })))
            // default
            .default_service(
                // 404 for GET request
                web::resource("")
                    .route(web::get().to(p404))
                    // all requests that are not `GET`
                    .route(
                        web::route()
                            .guard(guard::Not(guard::Get()))
                            .to(HttpResponse::MethodNotAllowed),
                    ),
            )
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

The example after operation is as follows:
Code exercise of the rust programming language (Part 3 simple web server)

Of course, this is just a simple application example.

summary

Compared with the multi-threaded asynchronous web server, Actix has perfect asynchronous processing, routing control and resource control, which shows that Actix is a simple and fast web framework.

This work adoptsCC agreementReprint must indicate the author and the link of this article

Author: Chen 0adapter

Recommended Today

Looking for frustration 1.0

I believe you have a basic understanding of trust in yesterday’s article. Today we will give a complete introduction to trust. Why choose rust It’s a language that gives everyone the ability to build reliable and efficient software. You can’t write unsafe code here (unsafe block is not in the scope of discussion). Most of […]