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
ソフトウェアエンジニアの技術ブログ:Software engineer tech blog
随机应变 ABCD: Always Be Coding and … : хороший
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
値を整形せずに、そのまま返しています。
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}
なるほどー
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 = “***.***.*.***”
うーむ、なんだかな〜
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
これは中々凄いな…
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 ではチェックできない。
なるほど…
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
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
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())) }
特にコメントなし。
さー次行こう^^
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 }
おおおおおおお、昨日長時間悶絶してただけあって、ここは割と簡単に上手くいった。
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にリダイレクトを追加したい。
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を保存するようにする。