iT邦幫忙

2

以Rust開發一個網站,不是網頁喔!

  • 分享至 

  • xImage
  •  

前言

上一篇【Rust程式語言兼具Python與C優點】介紹Rust程式語言特點,這次我們透過程式實際見證它的威力,以Rust開發一個網站,可以提供各式的網頁(Html、CSS、JS、圖片...)服務。

簡單的網站

開發簡單的網站步驟如下:

  1. 新增一個專案:在終端機或DOS視窗輸入下列指令,Windows使用者只需在檔案總管的路徑列輸入cmd即可。
cargo new simple_web_server
  1. 修改simple_web_server\src\main.rs檔案內容。首先引進套件,相當於Python的import,以下均為內建模組。
use std::{
    fs,
    io::{prelude::*, BufReader},
    net::{TcpListener, TcpStream},
    thread,
    time::Duration,
};
  1. 主程式:建立TCP Listener,監聽8000埠(port),一旦收到請求就交由handle_connection函數處理。監聽的埠可任意指定,大於1024即可,瀏覽器輸入的URL需與本程式設定一致。
fn main() {
    // 建立 TCP Listener, 監聽 8000 埠(port)
    let listener = TcpListener::bind("127.0.0.1:8000").unwrap();

    // 接收到請求,由 handle_connection 處理
    for stream in listener.incoming() {
        let stream = stream.unwrap();
        handle_connection(stream);
    }
}
  1. 定義handle_connection函數:上一步驟有呼叫handle_connection函數,進行連線處理,這裡僅單純顯示瀏覽器提出的請求(Request)內容。
fn handle_connection(mut stream: TcpStream) {
    // 讀取請求(Request)內容
    let buf_reader = BufReader::new(&mut stream);
    let request_line = buf_reader.lines().next().unwrap().unwrap();
    let mut file_name = "";
    let mut status_line = "";
    // 判斷第一行內容
    match request_line.as_str() {
        "GET / HTTP/1.1" | "GET /index.html HTTP/1.1" => {
            // 準備回應內容
            status_line = "HTTP/1.1 200 OK";
            // 讀取 index.html 內容
            file_name = "index.html";
            },
        "GET /register.html HTTP/1.1" => {
            // 準備回應內容
            status_line = "HTTP/1.1 200 OK";
            // 讀取 index.html 內容
            file_name = "register.html";
            },
        _ => {
            // 準備回應內容:404 無此網頁
            status_line = "HTTP/1.1 404 NOT FOUND";
            // 讀取 404.html 內容
            file_name = "404.html";
            },
    }
    let contents = fs::read_to_string(file_name).unwrap();
    // 計算 index.html 內容長度
    let length = contents.len();
    // 回應要求
    let response =
        format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");    
    stream.write_all(response.as_bytes()).unwrap();
}

說明如下:
. 讀取瀏覽器提出的請求(Request)內容:逐行讀取。

    // 讀取請求(Request)內容
    let buf_reader = BufReader::new(&mut stream);
    let request_line = buf_reader.lines().next().unwrap().unwrap();

. 判斷第一行內容:如為【/】即傳回首頁(index.html),如為【/register.html】傳回register.html,其他則傳回404.html,表無此網頁。

    // 判斷第一行內容
    match request_line.as_str() {
        "GET / HTTP/1.1" | "GET /index.html HTTP/1.1" => {
            ...
            file_name = "index.html";
            },
        "GET /register.html HTTP/1.1" => {
            ...
            file_name = "register.html";
            },
        _ => {
            ...
            file_name = "404.html";
            },
    }

. 回應要求,回傳Html檔案內容。

    let contents = fs::read_to_string(file_name).unwrap();
    // 計算 index.html 內容長度
    let length = contents.len();
    // 回應要求
    let response =
        format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");    
    stream.write_all(response.as_bytes()).unwrap();
  1. 程式就這麼簡單,接著在專案資料夾放入Html檔案,如下:
    https://ithelp.ithome.com.tw/upload/images/20240819/20001976OB2cI4i1A5.png

  2. 進行建置與執行。

cd simple_web_server
cargo run
  1. 在瀏覽器輸入http://localhost:8000 ,畫面如下。
    https://ithelp.ithome.com.tw/upload/images/20240819/200019768rjJcNcGi2.png

  2. 可點選【註冊】,可連結到register.html。
    https://ithelp.ithome.com.tw/upload/images/20240819/200019769mA68n8BIn.png!

  3. 可輸入http://localhost:8000/1 ,會得到404.html內容。
    https://ithelp.ithome.com.tw/upload/images/20240819/20001976zYqssJUIi6.png

加入並行處理(Concurrency)

以上程式有個缺點,每個請求都要依序處理,前面的請求未處理完,後面的請求就必須等待,因此,我們可以使用Rust強項,加入並行處理(Concurrency),讓每個請求(Request)都可以獨立執行,不互相影響。Amazon開發一個套件Tokio,可以讓我們輕鬆開發並行處理的應用程式。
步驟如下:

  1. 新增專案。
cargo new tokio_web_server
  1. 加入Tokio套件。
cargo add tokio -F full
  1. 引用tokio套件。
use tokio::net::TcpListener;
use tokio::net::TcpStream;
use tokio::fs;
use tokio::io::{self, BufReader, AsyncBufReadExt};
use tokio::io::AsyncReadExt; // for read_to_end()
  1. 將主程式改用Tokio並行處理的寫法:main要加註解#[tokio::main],且改為非同步(async)。
#[tokio::main]
async fn main() {
    // 建立 TCP Listener, 監聽 8000 埠(port)
    let listener = TcpListener::bind("127.0.0.1:8000").await.unwrap();

    // 接收到請求,由 handle_connection 處理
    loop {
        let (stream, _) = listener.accept().await.unwrap();
        
        tokio::spawn(async {
            handle_connection(stream).await;
        });
    }
}
  1. 其他內容就大同小異了,請參閱GitHub範例。

  2. 筆者把reveal.js的展示網頁放入專案資料夾,在瀏覽器輸入http://localhost:8000 ,畫面如下,瞬間就變成簡報工具了。
    https://ithelp.ithome.com.tw/upload/images/20240819/20001976AGQZtj12C5.png

結語

使用Rust開發生產力雖不及Python,但是,效能、記憶體的掌控及安全性,就不是Python可以比擬的,大家一起來玩玩吧。

工商廣告:)

Rust雖然具備諸多優點,但學習曲線陡峭,因此筆者最近剛完成【Rust 最佳入門與實戰】一書的撰寫,希望能與讀者分享Rust開發心得,內容除了Rust語言的入門、設計典範(Design patterns)外,也著重應用的探討,包括網頁、WebAssembly、桌面程式、資料庫、機器學習/深度學習、區塊鏈…等。
https://ithelp.ithome.com.tw/upload/images/20240817/20001976QxDOTVaiEa.jpg

本文相關範例放在這裡,【Rust 最佳入門與實戰】還有各式各樣的範例供大家下載。


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言