Skip to content

Latest commit

 

History

History
254 lines (197 loc) · 5.55 KB

hello-moturus.md

File metadata and controls

254 lines (197 loc) · 5.55 KB

Hello Motūrus

In this example, we will compile and run the final project (a multithreaded web server) from Rust Book inside a Motūrus VM, with minor changes.

First, make sure that you can build and run Motūrus OS.

Then run

$ cd ~
$ cargo new hello
     Created binary (application) `hello` project
$ cd hello

Replace src/main.rs with this:

use hello::ThreadPool;
use std::fs;
use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;
use std::thread;
use std::time::Duration;

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

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

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

    println!("Shutting down.");
}

fn handle_connection(mut stream: TcpStream) {
    let mut buffer = [0; 1024];
    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", "hello.html")
    } else if buffer.starts_with(sleep) {
        thread::sleep(Duration::from_secs(5));
        ("HTTP/1.1 200 OK", "hello.html")
    } else {
        ("HTTP/1.1 404 NOT FOUND", "404.html")
    };

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

    let response = format!(
        "{}\r\nContent-Length: {}\r\n\r\n{}",
        status_line,
        contents.len(),
        contents
    );

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

This code is the same as in Rust Book, with a single change: instead of TcpListener::bind("127.0.0.1:7878") we call TcpListener::bind("0.0.0.0:5542").

The port is changed because our vm_images/release/run-qemu-full.sh script forwards port 5542 from inside the VM to the host port 10023. The address is changed because we would like to bind the listener not only to the loopback address, but also to the IP address that Qemu assigns to the VM.

Then create src/lib.rs:

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

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

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

impl ThreadPool {
    /// Create a new ThreadPool.
    ///
    /// The size is the number of threads in the pool.
    ///
    /// # Panics
    ///
    /// The `new` function will panic if the size is zero.
    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: Some(sender),
        }
    }

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

        self.sender.as_ref().unwrap().send(job).unwrap();
    }
}

impl Drop for ThreadPool {
    fn drop(&mut self) {
        drop(self.sender.take());

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

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

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

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

            match message {
                Ok(job) => {
                    println!("Worker {id} got a job; executing.");

                    job();
                }
                Err(_) => {
                    println!("Worker {id} disconnected; shutting down.");
                    break;
                }
            }
        });

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

This code is exactly the same as in Rust Book.

Now build the web server for Motūrus OS:

cargo +dev-x86_64-unknown-moturus build --release \
  --target x86_64-unknown-moturus

Now add the web server to the Motūrus OS image:

mkdir $MOTORH/img_files/full/hello

cp ./target/x86_64-unknown-moturus/release/hello \
    $MOTORH/img_files/full/hello/

Now add the following two files to the $MOTORH/img_files/full/hello:

Filename: hello.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Hello!</title>
  </head>
  <body>
    <h1>Hello!</h1>
    <p>Hi from Motūrus OS</p>
  </body>
</html>

Filename: 404.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Hello!</title>
  </head>
  <body>
    <h1>Oops!</h1>
    <p>Sorry, I don't know what you're asking for.</p>
  </body>
</html>

Now build a new Motūrus OS VM image:

cd $MOTORH
cargo make boot_img_release

Run Motūrus OS:

$ cd $MOTORH/motor-os/vm_images/release
$ ./run-qemu-web.sh

Inside Motūrus OS shell:

rush /$: cd hello
rush /hello$: hello

This will start the hello web server. On your host/desktop, navigate to http://localhost:10023, you should see the hello.html from above.