Rustで簡単なフレームワークを自作したい5

main.rs

use std::collections::HashMap;
mod ares;

fn main() {
    
    let mut content = HashMap::new();
    content.insert(
        "title",
        "index page",
    );
    content.insert(
        "data1",
        "10000",
    );

    let _ = ares::Router::new("index", Some(content));
    
}

### フレームワーク側
ares.rs

use std::fs;
use std::io;
use std::io::prelude::*;
use std::net::{TcpListener, TcpStream};
use std::collections::HashMap;

pub struct Router;

impl Router {
    pub fn new(path: &str, content: Option<HashMap<&str, &str>>) {

        let mut routes = HashMap::new();
        let temp_path = format!("./templates/{}.html", path);
        let mut html = fs::read_to_string(temp_path).unwrap();

        match content {
            Some(data) => {
                for (key, value) in data {
                    let k = format!("{{{{ {} }}}}", key);
                    html = html.replace(&k, value);
                }
            },
            None => {},
        }
        let route_path = format!("/{}", path);
        routes.insert(
            route_path, html,
        );
        println!("{:?}", routes);


        let listenr = TcpListener::bind("192.168.33.10:8000").unwrap();
        for stream in listenr.incoming() {
            match stream {
                Ok(stream) => {
                    let _ = handle_connection(stream, routes.clone());
                }
                Err(e) => {
                    println!("Connection failed: {}", e);
                }
            }
        }
    }
}

fn handle_connection(mut stream: TcpStream, routes: HashMap<String, String>)  {
    let mut buffer = [0; 1024];
    stream.read(&mut buffer).unwrap();
 
    let request = String::from_utf8_lossy(&buffer);
    let request_line = request.lines().next().unwrap_or("");
    println!("Received request:\n{}", request);
 
    let path = request_line
         .split_whitespace()
         .nth(1)
         .unwrap_or("/");
    
    let response_body = routes.get(path)
         .cloned()
         .unwrap_or_else(|| "<h1>404 Not Found</h1>".to_string());
 
    let response = http_response(200, "text/html", &response_body);
 
    stream.write_all(response.as_bytes()).unwrap();
    stream.flush().unwrap();
 }
 
 fn http_response(status_code: u16, content_type: &str, body: &str) -> String {
    format!(
        "HTTP/1.1 {} {}\r\nContent-Type: {}\r\nContent-Length: {}\r\n\r\n{}",
        status_code,
        get_status_text(status_code),
        content_type,
        body.len(),
        body
    )
 }
 
 fn get_status_text(code: u16) -> &'static str {
    match code {
        200 => "OK",
        404 => "Not Found",
        _ => "Unknown",
    }
 }

複数パスに対応できるようにしたい。

### 複数パスに対応
hashmapにパスとコンテンツを追加するように実装する

use std::collections::HashMap;
mod ares;

fn main() {
    let mut router = ares::Router::new();

    let mut content1 = HashMap::new();
    content1.insert(
        "title",
        "index page",
    );

    let mut content2 = HashMap::new();
    content2.insert(
        "title",
        "this is title",
    );

    router.add("index", Some(content1));
    router.add("hello", None);
    router.add("world", Some(content2));

    router.up("192.168.33.10:8000");   
}
pub struct Router {
    route: HashMap<String, String>,
}

impl Router {

    pub fn new() -> Self {
        Router {
            route: HashMap::new(),
        }
    }

    pub fn add(&mut self, path: &str, content: Option<HashMap<&str, &str>>){
        let temp_path = format!("./templates/{}.html", path);
        let mut html = fs::read_to_string(temp_path).unwrap();

        match content {
            Some(data) => {
                for (key, value) in data {
                    let k = format!("{{{{ {} }}}}", key);
                    html = html.replace(&k, value);
                }
            },
            None => {},
        }
        let route_path = format!("/{}", path);
        self.route.insert(route_path, html);
    }

    pub fn up(&self, ip: &str) {
        let listenr = TcpListener::bind(ip).unwrap();
        for stream in listenr.incoming() {
            match stream {
                Ok(stream) => {
                    let _ = handle_connection(stream, self.route.clone());
                }
                Err(e) => {
                    println!("Connection failed: {}", e);
                }
            }
        }
    }
}

MVCモデルにしたい…

フレームワークを自作したい4

templatesというディレクトリに、ルート名と同じファイル名でhtmlファイルを作成するというルールにする。

fn main() {

   let address = "192.168.33.10:8000";

   let mut routes = HashMap::new();

   let mut paths: Vec<&str> = Vec::new();
   paths.push("index");
   paths.push("hello");

   for path in paths {
        let temp_path = format!("./templates/{}.html", path);
        let content = fs::read_to_string(temp_path).unwrap();

        let route_path = format!("/{}", path);
        routes.insert(
            route_path, content,
        );
   }
   println!("{:?}", routes);
}

{“/index”: “

Hello from Rust!

“, “/hello”: “

Welcome to the Rust server!

“}

teraの場合は、tera.renderとして、任意のファイル名を指定できるようにしている。これも、hashmapで、ルート名とファイル名をkey, valueで持っていれば、問題なくできそうでる。

### テンプレートエンジンの変数の扱い
hashmapでkeyとvalueを格納して、htmlの{{ @name }} とkeyが一致したものをvalueに変換すればできそうである。。

let mut context = tera::Context::new();
context.insert("title", "Index page");
{{title}}

### Rustで{{ }}を置換するように実装

    let mut content = HashMap::new();
    content.insert(
        "title",
        "index page",
    );
    let mut data = HashMap::new();
    for (key, value) in content {
        let k = format!("{{{{ {} }}}}", key);
        data.insert(
            k, value,
        );
    };
    println!("{:?}", data);

    let mut str = "<h1>Welcome to the {{ title }} server!</h1>";

    for (key, value) in data {
        let s = &str.replace(&key, value);
        println!("{}", s);
    };

### テンプレートに引き渡す変数がある時
pathにhashmapで変数のセットを紐づけて、後で置換するようにする。

let mut content = HashMap::new();
    content.insert(
        "title",
        "index page",
    );
    let mut data = HashMap::new();
    for (key, value) in content {
        let k = format!("{{{{ {} }}}}", key);
        data.insert(
            k, value,
        );
    };

    let mut str = "<h1>Welcome to the {{ title }} server!</h1>";
    
    let mut paths: Vec<(&str, Option<HashMap<String, &str>>)> = Vec::new();
    paths.push(("index", Some(data)));
    paths.push(("hello", None));

    println!("{:?}", paths);

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.33s
Running `target/debug/ares`
[(“index”, Some({“{{ title }}”: “index page”})), (“hello”, None)]

### 全部繋げる

let mut paths: Vec<(&str, Option<HashMap<String, &str>>)> = Vec::new();

    let mut content = HashMap::new();
    content.insert(
        "title",
        "index page",
    );
    content.insert(
        "data1",
        "10000",
    );
    let mut data = HashMap::new();
    for (key, value) in content {
        let k = format!("{{{{ {} }}}}", key);
        data.insert(
            k, value,
        );
    };

    paths.push(("index", Some(data)));
    paths.push(("hello", None));

    let mut routes = HashMap::new();

    for (path, v) in paths {
        let temp_path = format!("./templates/{}.html", path);
        let mut content = fs::read_to_string(temp_path).unwrap();
        match v {
            Some(data) => 
                for (key, value) in data {
                    content = content.replace(&key, value);
                },
            None => {},
        }
        let route_path = format!("/{}", path);
        routes.insert(
            route_path, content,
        );
    };
    println!("{:?}", routes);

{“/index”: “

Hello from Rust! title:index page

“, “/hello”: “

Welcome to the Rust server!

“}

三角形の周長

fn main() {

   let mut ans: i32 = 0;
   let n: i32 = 10;

   for i in 0..n {
      let j = i + 1;
      for j in j..n {
         let k = j + 1;
         for k in k..n {
            let len = i + j + k;
            let ma = std::cmp::max(i, std::cmp::max(j, k));
            let rest = len - ma;

            if (ma < rest) {
               ans = std::cmp::max(ans, len);
            }
         }
      }
   }
   println!("{}", ans);
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s
Running `target/debug/basic`
24

【Rust】プロセススケジュラー

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ProcessState {
    Ready,
    Running,
    Waiting,
    Terminated,
}

#[derive(Debug)]
struct Process {
    pid: usize,
    state: ProcessState,
}

struct Scheduler {
    processes: Vec<Process>,
    current_index: usize,
}

impl Scheduler {
    fn new(processes: Vec<Process>) -> Self {
        Scheduler {
            processes,
            current_index: 0,
        }
    }

    fn schedule(&mut self) -> Option<&Process> {
        if self.processes.is_empty() {
            return None;
        }

        let len = self.processes.len();
        for _ in 0..len {
            self.current_index = (self.current_index + 1) % len;
            let proc = &mut self.processes[self.current_index];
            if proc.state == ProcessState::Ready {
                proc.state = ProcessState::Running;
                return Some(proc);
            }
        }
        None
    }

    fn update_state(&mut self, pid: usize, new_state: ProcessState) {
        if let Some(proc) = self.processes.iter_mut().find(|p| p.pid == pid) {
            proc.state = new_state;
        }
    }
}


fn main(){
    
    let mut scheduler = Scheduler::new(vec![
        Process { pid: 1, state: ProcessState::Ready},
        Process { pid: 2, state: ProcessState::Ready},
        Process { pid: 3, state: ProcessState::Waiting},
    ]);

    for _ in 0..5 {
        if let Some(proc) = scheduler.schedule() {
            println!("Running process: {:?}", proc.pid);
            scheduler.update_state(proc.pid, ProcessState::Ready);
        } else {
            println!("No Runnable processes.");
            break;
        }
    }
}

【Rust】メモリ管理

const FRAME_SIZE: usize = 4096;
const TOTAL_MEMORY: usize = 1024 * 1024 * 128;
const TOTAL_FRAMES: usize = TOTAL_MEMORY / FRAME_SIZE;

static mut FRAME_BITMAP: [bool; TOTAL_FRAMES] = [false; TOTAL_FRAMES];

pub struct FrameAllocator;

impl FrameAllocator {
    pub fn alloc() -> Option<usize> {
        unsafe {
            for (i, used) in FRAME_BITMAP.iter_mut() enumerate() {
                if !*used {
                    *used = true;
                    return Some(i * FRAME_SIZE);
                }
            }
            None
        }
    }

    pub fn dealloc(addr: usize) {
        let index = addr / FRAME_SIZE;
        if index < TOTAL_FRAMES {
            unsafe {
                FRAME_BITMAP[index] = false;
            }
        }
    }
}

【Rust】バイナリデータへの処理

### 文字列の変換

fn main() {
    let text = "Hello, Rust!";
    let binary = text.as_bytes();

    println!("binary: {:?}", binary);
}

binary: [72, 101, 108, 108, 111, 44, 32, 82, 117, 115, 116, 33]

### テキストファイルの変換
input.txt

Hello Rust!
This is sample
use std::fs::File;
use std::io::{self, Read, Write};

fn main() -> io::Result<()>{
    let mut input_file = File::open("./data/input.txt")?;

    let mut buffer = Vec::new();
    input_file.read_to_end(&mut buffer)?;

    println!("binary: {:?}", &buffer);

    Ok(())
}

### データのchunk

fn main(){
    
    let data = vec![0u8; 10000];

    let chunk_size = 1024;

    for(i, chunk) in data.chunks(chunk_size).enumerate() {
        println!("チャンク {}: サイズ = {}", i, chunk.len());
    }
}

チャンク 0: サイズ = 1024
チャンク 1: サイズ = 1024
チャンク 2: サイズ = 1024
チャンク 3: サイズ = 1024
チャンク 4: サイズ = 1024
チャンク 5: サイズ = 1024
チャンク 6: サイズ = 1024
チャンク 7: サイズ = 1024
チャンク 8: サイズ = 1024
チャンク 9: サイズ = 784

### 画像データのchunk

use std::fs::File;
use std::io::{self, Read, Write};

fn main()-> io::Result<()>{
    
    let mut input_file = File::open("./data/test.jpg")?;

    let mut buffer = Vec::new();
    input_file.read_to_end(&mut buffer)?;

    let chunk_size = 1024;

    for(i, chunk) in buffer.chunks(chunk_size).enumerate() {
        println!("チャンク {}: 値 = {:?}", i, chunk);
    }

    Ok(())
}

Rustでmalloc, free

use std::alloc::{alloc, dealloc, Layout};
use std::ptr;

fn main() {
    unsafe {
        let layout = Layout::new::<i32>();
        let ptr = alloc(layout) as *mut i32;

        if ptr.is_null() {
            panic!("メモリ確保に失敗しました");
        }

        *ptr = 42;
        println!("ptrが指す値: {}", *ptr);

        dealloc(ptr as *mut u8, layout);
    }
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.83s
Running `target/debug/memory`
ptrが指す値: 42

【Rust】getblockのCLIコマンド機能を作る

CLIでgetblockと引数にblock heightのi32を受け取って、ブロック情報を表示します。

#[derive(Subcommand)]
enum Command {
  Getnewaddress,
  Getinfo,
  Getbalance(BalanceArgs),
  Getaccount,
  Getblock(BlockArgs),
}

fn main() {
   let args = App::parse();
   match args.command {
      Command::Getnewaddress => getnewaddress(),
      Command::Getinfo => getinfo(),
      Command::Getbalance(args) => args.run(),
      Command::Getaccount => getaccount(),
      Command::Getblock(args) => args.run(),
   }
}

#[derive(Args)]
struct BlockArgs {
  height: i32,
}

impl BlockArgs {
  fn run(&self) {
    let _ = cli::get_block(self.height);
  }
}

$ ./target/debug/ruscal getblock 8
Block { height: 8, version: “0.1.0”, time: 2025-02-25T06:48:54.561868021Z, transactions: [SignedTransaction { version: “0.1.0”, time: 2025-02-25T06:47:49.809920514Z, sender: “0410f87429e89498e928d00b6a6186fdc9ccbde2ca55d5746f0e1bf8f1a1fbdb7634de5c1d5b4e49dc29bb78613fefb199d93eb8eb73545791a245c1f1ca6d5ce0”, receiver: “1FsHydJvr3nKj3KcwFhSozBf2WBKMf9jeo”, amount: 50, nft_data: “”, nft_origin: “”, op_code: “”, signature: “8A5216CD62C309A4923E9088CA9284AFC7C0261D89754FCE84735EC34A193A089B7A4C62FC32F91AE0B83549D575CE3D5EBAC1C4B9A61F0EF67D1B851F3F3E53” }, SignedTransaction { version: “0.1.0”, time: 2025-02-25T06:48:45.817409745Z, sender: “0410f87429e89498e928d00b6a6186fdc9ccbde2ca55d5746f0e1bf8f1a1fbdb7634de5c1d5b4e49dc29bb78613fefb199d93eb8eb73545791a245c1f1ca6d5ce0”, receiver: “1FsHydJvr3nKj3KcwFhSozBf2WBKMf9jeo”, amount: 60, nft_data: “”, nft_origin: “”, op_code: “”, signature: “6E73E636DDA5A5F360280442BEA308CD3C38C7A4C769BC8D4965B5106F9F6647D15B0382DE90ECFB84AE462BB81F26099DB1F8A9F0276541C44C60376FF31D16” }], hash: “0000ddb477024b0fb97c02e34bfad38eaee9891524759ba8801e06283b12cb9c”, nonce: “270778”, merkle: “713f5eb6109b03eb22cf8236171040f6bbf6171fbb2513b782b8b9fa9f41807f”, miner: “MTYwLjE2LjExNy44MA==”, validator: “MTYwLjE2LjExNy44MA==”, op: “” }

同じような容量で、gettransactionも同様に作ります。
$ ./target/debug/ruscal gettransaction 8A5216CD62C309A4923E9088CA9284AFC7C0261D89754FCE84735EC34A193A089B7A4C62FC32F91AE0B83549D575CE3D5EBAC1C4B9A61F0EF67D1B851F3F3E53

transactionの送信の場合

#[derive(Args)]
struct MoveArgs {
  pub_key: String,
  address: String,
  amount: i32
}

impl MoveArgs {
  fn run(&self) {
    // transaction送信関数を呼び出す
  }
}

$ ./target/debug/ruscal move de2ca55d5746f0e1bf8f1a1fbdb7634de5c1d5b4e49dc29bb78613fefb199d93eb8eb73545791a245c1f1ca6d5ce0 12sSVCmfi7kgsdG4ZmaargPppDRvkE5zvD 1000

なるほど、大分わかってきました。

【Rust】getnewaddressのAPI機能を作る

### コマンドライン引数の書き方

fn main() {

   let command_name = std::env::args().nth(0).unwrap_or("CLI".to_string());
   let name = std::env::args().nth(1).unwrap_or("World".to_string());

   println!("Hello {} via {}!", name, command_name);
}

$ cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/ruscal`
Hello World via target/debug/ruscal!

### 上記を応用する

fn main() {
   let args = App::parse();
   match args.command {
    Command::Getnewaddress => getnewaddress(),
   }
}

fn getnewaddress() {
  address::get_address();
}
use p256::{
    ecdsa::{SigningKey},
    pkcs8::EncodePrivateKey, PublicKey,SecretKey};
use rand_core::{OsRng};
use std::{fs::File, io::Write, path::Path};
use ripemd::{Ripemd160};
use sha2::{Digest, Sha256};

pub fn get_address() {
    init_secret();
    let contents = std::fs::read_to_string("./config/tmp.pem")
        .expect("something went wrong reading the file");
    let secret_pem = contents.trim();

    let secret_key = secret_pem.parse::<SecretKey>().unwrap();
    let _private_key_serialized = hex::encode(&secret_key.to_bytes());

    let public_key = secret_key.public_key();
    let _public_key_serialized = hex::encode(&public_key.to_sec1_bytes());
    let address = create_address(&public_key);  
    println!("{}", address);
}

pub fn init_secret() {
    let path = Path::new("./config/tmp.pem");
    if path.is_file() {
        // println!("The private key, public key, and address have already been created.");
    } else {
        let secret_key = SigningKey::random(&mut OsRng);
        let secret_key_serialized = secret_key
            .to_pkcs8_pem(Default::default())
            .unwrap()
            .to_string();
        let mut file = File::create("./config/tmp.pem").expect("file not found.");
        writeln!(file, "{}", secret_key_serialized).expect("can not write.");
        println!("created a private key.");
    }
}

pub fn create_address(public_key: &PublicKey) -> String {

    let vk = public_key.to_sec1_bytes();
    let mut hasher = Sha256::new();
    hasher.update(vk);
    let hashed_sha256 = hasher.finalize();

    let mut hasher = Ripemd160::new();
    hasher.update(hashed_sha256);
    let account_id = hasher.finalize();

    let mut payload = account_id.to_vec();
    payload.insert(0, 0x00);

    let mut hasher = Sha256::new();
    hasher.update(&payload);
    let hash = hasher.finalize();

    let mut hasher = Sha256::new();
    hasher.update(hash);
    let checksum = hasher.finalize();

    payload.append(&mut checksum[0..4].to_vec());

    const ALPHABET: &str = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
    let address = base_x::encode(ALPHABET, &payload);

    return address;
}

$ cargo run getnewaddress
Compiling ruscal v0.1.0 (/home/vagrant/dev/rust/api)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.92s
Running `target/debug/ruscal getnewaddress`
1BnpTXdSbSgMZwWYq7cdU3LPSw1g4YhyHZ

【Rust】スタックマシンで関数を実装する

変数と同じように、関数名とinstructionをhashmapに保存する

/x 2 def /double { x 2 * } fn

        "fn" => {
            let tmp = func(&mut stack, instructions.clone());
            for (key, value) in &tmp {
                funcs.insert(key.to_string(), value.to_vec());
            }
            println!("{:?}", funcs); 
        } 

// 省略

fn func(stack: &mut Vec<String>, instructions: Vec<String>) -> HashMap<String, Vec<String>> {
    let mut funcs: HashMap<String, Vec<String>> = HashMap::new();
    let name = stack.pop().unwrap();

    funcs.insert(name, instructions);
    funcs
}

{“double”: [“x”, “2”, “*”]}

関数名と式をhashmapに保存したので、後は関数名が書かれていた際に、関数の内容を呼び出すだけ


    } else if funcs.contains_key(word) {
      let _ = op_fn(&mut stack, funcs.get(word).unwrap(), vars.clone());
//

fn op_fn(stack: &mut Vec<String>, input: &Vec<String>, vars: HashMap<String, String>) {
    let mut words = input.clone();
    while let Some((&ref word, rest)) = words.split_first() {
        if word.is_empty() {
          break;
        }
        if let Ok(parsed) = word.parse::<i32>() {
          stack.push(parsed.to_string());
        } else if word.starts_with("/") {
          stack.push((word[1..]).to_string());
        } else if vars.contains_key(word) {
          stack.push(vars.get(word).unwrap().to_string());
        } else {
          match word.as_str() {
            "+" => add(stack),
            "-" => sub(stack),
            "*" => mul(stack),
            "/" => div(stack),
            "%" => r#mod(stack),
            "<" => lt(stack),
            ">" => rt(stack),
            "==" => equal(stack),
            "!=" => nequal(stack),
            "sqrt" => r#sqrt(stack),
            "if" => op_if(stack),
            _ => todo!(),
            }
        }
        words = rest.to_vec();
      }
}

/x 2 def /double { x 2 * } fn double
=> [“4”]