【Rust】rustで冪乗の計算をする

println!("{}", power(3));

//

fn power(x: u32) -> i32 {
    10_i32.pow(x)
}

Compiling sample v0.1.0 (/home/vagrant/dev/rust/sample)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.19s
Running `target/debug/sample`
1000

【Rust】axumでjsonデータを受け取ってjsonで返す

値を整形せずに、そのまま返しています。

async fn handle_service(extract::Json(payload): extract::Json<Service>) -> Json<Service>{

    println!("{:?}", payload);
    Json(payload)
}

curl -X POST -H “Content-Type: application/json” -d “{\”version\”:\”1\”,\”nonce\”:\”1482680747\”,\”connections\”:21}” 127.0.0.1:3000/service
{“version”:”1″,”nonce”:”1482680747″,”connections”:21}

なるほどー

【Rust】自分のIPアドレスを取得する

IPを返却するサイトからIPアドレスを取得します。
httpbinにreqwest::getすると、String型で返却されるので、serde_jsonを使ってIpの構造体に変換しています。

use reqwest::Client;
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct Ip {
    origin: String,
}

#[tokio::main]
async fn main() {
    let _ = get_ip().await;
}

// get ip
async fn get_ip() -> Result<(), Box<dyn std::error::Error>> {

    let ip: Ip = serde_json::from_str(&reqwest::get("https://httpbin.org/ip")
        .await?
        .text()
        .await?)?;
    
    println!("ip = {:?}", ip.origin);
    Ok(())
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.08s
Running `target/debug/sample`
ip = “***.***.*.***”

うーむ、なんだかな〜

【Rust】トランザクションにnftデータを追加する

UnsignedTransaction, SignedTransactionの構造体に、nft_dataと、nft_originを追加する。
nft_dataは、nftとして保存するデータ。
nft_originはnftを発行したトランザクションのhashデータ。
NFTを譲渡する場合は、nft_dataを空にして、nft_originに値を入れる。

#[derive(Serialize, Deserialize, Clone, Debug)]
struct UnsignedTransaction {
    time: String,
    sender: String,
    receiver: String,
    amount: i32,
    nft_data: String,
    nft_origin: String,
}

nft_holderというhashmapを作成して、そこにnftを発行したトランザクションのhashデータと、最後にNTFを譲渡されたreceiverの連想配列を入れていく。
誰が何を持っているかは、トランザクションのhashデータをデコードして、nft_dataの値を取り出す。

#[tokio::main]
async fn main(){

    let mut transaction_pool: Vec<SignedTransaction> = Vec::new();
    let transaction1 = UnsignedTransaction {time:Utc::now().to_string(), sender:"047683c00f6z".to_string(), receiver:"DyH5SHvezz".to_string(), amount: 0, nft_data:"hello world".to_string(),nft_origin:"".to_string()};
    let transaction2 = UnsignedTransaction {time:Utc::now().to_string(), sender:"DyH5SHvezz".to_string(), receiver:"655EFC80ss".to_string(), amount: 0, nft_data:"".to_string(),nft_origin:"eyJ0aW1lIjoiMjAyNS0wMS0xNyAwODoyOTo0Ni42NzgzNDU3OTQgVVRDIiwic2VuZGVyIjoiMDQ3NjgzYzAwZjZ6IiwicmVjZWl2ZXIiOiJEeUg1U0h2ZXp6IiwiYW1vdW50IjowLCJuZnRfZGF0YSI6ImhlbGxvIHdvcmxkIiwibmZ0X29yaWdpbiI6IiJ9".to_string()};
    let transaction3 = UnsignedTransaction {time:Utc::now().to_string(), sender:"047683c00f6z".to_string(), receiver:"DyH5SHvezz".to_string(), amount: 0, nft_data:"Milk Cafe".to_string(),nft_origin:"".to_string()};
    let transaction4 = UnsignedTransaction {time:Utc::now().to_string(), sender:"047683c00f6z".to_string(), receiver:"DyH5SHvezz".to_string(), amount: 1000, nft_data:"".to_string(),nft_origin:"".to_string()};

    // println!("{}", BASE64_STANDARD.encode(serde_json::to_vec(&transaction1.clone()).unwrap()));
    // println!("{}", base64_decode(&str));

    transaction_pool.push(sign_transaction(&transaction1));
    transaction_pool.push(sign_transaction(&transaction2));
    transaction_pool.push(sign_transaction(&transaction3));
    transaction_pool.push(sign_transaction(&transaction4));

    let nft_holder:HashMap<String, String> = nft_calc(transaction_pool);
    for (k, v) in nft_holder {
        let transaction_str = base64_decode(&k);
        let transaction:UnsignedTransaction = serde_json::from_str(&transaction_str).unwrap();
        println!("保有者:{}, NFT:{}", v, transaction.nft_data);
    }
}

fn nft_calc(transaction_pool: Vec<SignedTransaction>) -> HashMap<String, String> {
    let mut nft_holder: HashMap<String, String> = HashMap::new();
    for transaction in transaction_pool {
        if transaction.amount == 0 {
            let transaction_hash: String = BASE64_STANDARD.encode(serde_json::to_vec(&transaction.clone()).unwrap());
            if transaction.nft_origin == "" && transaction.nft_data != "" && (nft_holder.get(&transaction_hash) == None) {
                nft_holder.insert(transaction_hash, transaction.receiver);
            } else if (nft_holder.get(&transaction_hash) == Some(&transaction.sender)) && transaction.nft_data == "" {
                nft_holder.insert(transaction.nft_origin, transaction.receiver);
            }
        }
    }
    return nft_holder
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.13s
Running `target/debug/sample`
保有者:DyH5SHvezz, NFT:hello world
保有者:DyH5SHvezz, NFT:Milk Cafe

これは中々凄いな…

【Rust】hashmapのキーの存在チェック

map.get(&target) != None でチェックできる。

    let mut holder: HashMap<String, String> = HashMap::new();

    holder.insert("yamada".to_string(), "A".to_string());
    holder.insert("sato".to_string(), "B".to_string());
    holder.insert("tanaka".to_string(), "C".to_string());

    for (name, item) in &holder {
        println!("{}:{}", name, item);
    }

    let target = "yamada".to_string();
    if holder.get(&target) != None {
        println!("{}", holder["yamada"]);
    }

Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.33s
Running `target/debug/my_web_app`
yamada:A
tanaka:C
sato:B
A

pythonのように target in map ではチェックできない。
なるほど…

【Rust】トランザクションをbase64でエンコード、デコード

hello worldで挙動を確認します。

let str:String = "hello world".to_string();
println!("{}", BASE64_STANDARD.encode(str.clone()));

let b:String = "aGVsbG8gd29ybGQ=".to_string();
println!("{}", base64_decode(&b));

pub fn base64_decode_bytes(b64str: &str) -> Vec {
let t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let mut table: [u8; 256] = [0; 256];
for (i, v) in t.as_bytes().iter().enumerate() {
table[*v as usize] = i as u8;
}
let b64 = String::from(b64str).replace("\r", "").replace("\n", "");
let b64bytes = b64.as_bytes();
let mut result: Vec = vec![];
let cnt = b64bytes.len() / 4;
for i in 0..cnt {
let i0 = b64bytes[i*4+0];
let i1 = b64bytes[i*4+1];
let i2 = b64bytes[i*4+2];
let i3 = b64bytes[i*4+3];
let c0 = table[i0 as usize] as usize;
let c1 = table[i1 as usize] as usize;
let c2 = table[i2 as usize] as usize;
let c3 = table[i3 as usize] as usize;
let b24 = (c0 << 18) | (c1 << 12) | (c2 << 6) | (c3 << 0); let b0 = ((b24 >> 16) & 0xFF) as u8;
let b1 = ((b24 >> 8) & 0xFF) as u8;
let b2 = ((b24 >> 0) & 0xFF) as u8;
result.push(b0);
if i2 as char != '=' { result.push(b1); }
if i3 as char != '=' { result.push(b2); }
}
result
}
pub fn base64_decode(b64str: &str) -> String {
String::from_utf8(base64_decode_bytes(b64str)).unwrap()
}
[/cpde]
aGVsbG8gd29ybGQ=
hello world

上手くいっているようです。
これをトランザクションでやります。

let transaction1 = UnsignedTransaction {time:Utc::now().to_string(), sender:"047683c00f6z".to_string(), receiver:"DyH5SHvezz".to_string(), amount: 0, nft_data:"hello world".to_string(),nft_origin:"".to_string()};

println!("{}", BASE64_STANDARD.encode(serde_json::to_vec(&transaction1.clone()).unwrap()));

let str:String = "eyJ0aW1lIjoiMjAyNS0wMS0xNyAwODoyNzo1MS4wNTA0MjYxMDMgVVRDIiwic2VuZGVyIjoiMDQ3NjgzYzAwZjZ6IiwicmVjZWl2ZXIiOiJEeUg1U0h2ZXp6IiwiYW1vdW50IjowLCJuZnRfZGF0YSI6ImhlbGxvIHdvcmxkIiwibmZ0X29yaWdpbiI6IiJ9".to_string();
println!("{}", base64_decode(&str));
[/cpde]

eyJ0aW1lIjoiMjAyNS0wMS0xNyAwODoyOTo0Ni42NzgzNDU3OTQgVVRDIiwic2VuZGVyIjoiMDQ3NjgzYzAwZjZ6IiwicmVjZWl2ZXIiOiJEeUg1U0h2ZXp6IiwiYW1vdW50IjowLCJuZnRfZGF0YSI6ImhlbGxvIHdvcmxkIiwibmZ0X29yaWdpbiI6IiJ9
{"time":"2025-01-17 08:27:51.050426103 UTC","sender":"047683c00f6z","receiver":"DyH5SHvezz","amount":0,"nft_data":"hello world","nft_origin":""}
SignedTransaction { time: "2025-01-17 08:29:46.678345794 UTC", sender: "047683c00f6z", receiver: "DyH5SHvezz", amount: 0, nft_data: "hello world", nft_origin: "", signature: "568082531D765278ACF8999B94BAA7C621C538B00857DAC165208DB8C73FE4D4CB8FDBC742A4A47BC1D0B421A543134D869B2EC1AC8CEA0DDE902D39B034EA84" }

よし、O~K

【Rust】axumでログアウトの処理

async fn handle_logout()-> impl IntoResponse{

    let tera = tera::Tera::new("templates/*").unwrap();

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

    let output = tera.render("index.html", &context);
    let cookie:String = format!("session_token=_");
    let headers = AppendHeaders(vec![("Set-Cookie", cookie)]);
    (headers, axum::response::Html(output.unwrap()))
}

特にコメントなし。
さー次行こう^^

【Rust】axumでセッション(cookie)の有無でルーティングを分ける

cookieを判定するmiddlewareを作成し、privateなページのみミドルウェアをつけて、publicなページにはつけない。
該当のsession_tokenがない場合は、Redirectでpublicのページにリダイレクトさせる。

async fn main() {

    let public_router = Router::new()
        .route("/setcookie", get(handle_setcookie))
        .route("/public", get(handle_public));

    let private_router = Router::new()
        .route("/test", get(handle_index))
        .route("/private", get(handle_private))
        .layer(middleware::from_fn(my_middleware));

    let app = Router::new()
        .nest("/", public_router)
        .nest("/", private_router);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}
//

    if session_token == None {
        Redirect::permanent("/public").into_response()
    } else {
        let mut res = next.run(req).await;
        res
    }

おおおおおおお、昨日長時間悶絶してただけあって、ここは割と簡単に上手くいった。

【Rust】axumでcookieを取得する

routeにmiddlewareをくっつけます。
Requestのheadersからcookieを取得します。

#[tokio::main]
async fn main() {
    let app = axum::Router::new()
        .route("/", axum::routing::get(handle_index))
        .layer(middleware::from_fn(my_middleware));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}
//
async fn my_middleware(req: Request,
    next: Next,) -> impl IntoResponse {

    let mut session_token = req
        .headers()
        .get_all("Cookie")
        .iter()
        .filter_map(|cookie| {
            cookie
                .to_str()
                .ok()
                .and_then(|cookie| cookie.parse::<cookie::Cookie>().ok())
        })
        .find_map(|cookie| {
            (cookie.name() == "hoge".to_string()).then(move || cookie.value().to_owned())
        });
    println!("{:?}", &session_token);

    let cookies = req.headers().get("cookie");
    println!("{:?}", &cookies);

    let mut res = next.run(req).await;
    res
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.60s
Running `target/debug/axum`
Some(“value”)
Some(“hoge=value”)
hoge=value; Max-Age=600

ほう、なるほど。middlewareにリダイレクトを追加したい。

【Rust】セッションをcookieに保存したい

AppendHeadersに中々辿り着けず、layerを堂々巡りした挙句、chromのcookieの設定などをミスって、これ作るのに丸2日かかりました。。。シクシク、泣きたい。

async fn handle_index()-> impl IntoResponse {
    
    let mut random = ChaCha8Rng::seed_from_u64(OsRng.next_u64());
    let mut u128_pool = [0u8; 16];
    random.fill_bytes(&mut u128_pool);
    let session_token = u128::from_le_bytes(u128_pool);
    println!("{:?}", &session_token);

    let tera = tera::Tera::new("templates/*").unwrap();

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

    let output = tera.render("index.html", &context);
    let cookie:String = format!("session_token={}; Max-Age=600", &session_token);
    let headers = AppendHeaders(vec![("Set-Cookie", cookie)]);
    (headers, axum::response::Html(output.unwrap()))
}

psqlにセッションテーブルを作って、ログインしたタイミングでセッションテーブルにもsessionのvalueを保存するようにする。