use lettre:: { transport::smtp::authentication::Credentials, Message, message::{header, SinglePart}, SmtpTransport, Transport, message::Mailbox, Address }; #[tokio::main] async fn main() { let username = "hogehoge@gmail.com"; let app_password = "**** **** **** ****"; let smtp = "smtp.gmail.com"; let email = Message::builder() .to(Mailbox::new(None, username.parse::<Address>().unwrap())) .from(Mailbox::new(None, username.parse::<Address>().unwrap())) .subject("題名: Rust Mail Test") .singlepart( SinglePart::builder() .header(header::ContentType::TEXT_PLAIN) .body(String::from("本文 Test")) ) .unwrap(); let credentials = Credentials::new(username.into(), app_password.into()); let mailer = SmtpTransport::starttls_relay(smtp) .unwrap() .credentials(credentials) .build(); let result = mailer.send(&email); println!("{:?}", result); }
Month: March 2025
【Rust】カレンダーを作る【Algorithm】
fn leap_year(year: u32) -> bool { if year % 4 == 0 && year % 100 != 0 || year % 400 == 0 { return true; } false } fn day_of_week(year: usize, month: usize) -> usize { let mut days_of_month = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; if leap_year(year.try_into().unwrap()) { days_of_month[2] = 29; } let day = 1; let mut days = 0; for y in 1..year { if leap_year(y.try_into().unwrap()) { days += 366 } else { days += 365 } } for m in 1..month { days += days_of_month[m as usize] } days += day; return days % 7 } fn main() { let days_of_week_name = ["日".to_string(), "月".to_string(),"火".to_string(),"水".to_string(),"木".to_string(),"金".to_string(),"土".to_string()]; let mut days_of_month = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; let year = 2040; let month = 3; if leap_year(year.try_into().unwrap()) { days_of_month[2] = 29; } let first_day = day_of_week(year, month); println!("{}年 {}月", year, month); println!("日 月 火 水 木 金 土"); let emp = " ".to_string(); print!("{}", emp.repeat(first_day*3)); for day in 1..(1 + days_of_month[month]) { print!("{:<2} ", day); if(day + first_day - 1) % 7 == 6 { print!("\n"); } } print!("\n"); }
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s
Running `target/debug/basic`
2040年 3月
日 月 火 水 木 金 土
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
これは中々面白い!!! そんなに難しくないけど、中々思いつかない!
【Rust】axumのlayer middlewareにstateを渡す方法
from_fn_with_state という書き方がある。
https://docs.rs/axum/latest/axum/middleware/fn.from_fn_with_state.html
let app_state = model::connection_pool().await.unwrap(); let app = Router::new() .route("/", get(handler)) .layer(middleware::from_fn_with_state(app_state.clone(),auth_middleware)) .layer(CsrfLayer::new(config)) .with_state(app_state); // pub async fn auth_middleware(State(state): State<model::AppState>, req: Request, next: Next) -> impl IntoResponse { let res = next.run(req).await; res }
なるほどね、先回りしてよく作られてるw
【Rust】with_state(app_state)とCsrfConfigを共存させたい時
### ダメな例
with_stateを複数繋げるのは上手くいかない
let config = CsrfConfig::default(); let app = axum::Router::new() .merge(public_router) .merge(private_router) .with_state(config.clone()) .with_state(app_state);
pub async fn handle_hoge(token: CsrfToken, State(state): State<model::AppState>)-> Response {
### 上手くいく例
csrfの方をstateではなく、layerで渡してあげる。
axum_csrf = {version = “0.11.0”,features = [“layer”]}
use axum_csrf::{CsrfToken, CsrfConfig, CsrfLayer}; let config = CsrfConfig::default(); let app = Router::new() .merge(public_router) .merge(private_router) .layer(CsrfLayer::new(config)) .with_state(app_state);
pub async fn handle_hoge(token: CsrfToken, State(state): State<model::AppState>)-> Response {
なるほどね~
【Rust】Durationをmillisecondでcountする
.subsec_millis() でミリセカンドで取得できる。
.subsec_nanos() だとナノセカンドで値が大きすぎてしまう。
use std::{time}; let now = time::Instant::now(); let mut post_url = format!("http://www.jsontest.com/"); let client = reqwest::Client::new(); let resp = client.post(post_url) .header(reqwest::header::CONTENT_TYPE, "application/json") .json(&str) .send() .await .unwrap(); println!("{}", now.elapsed().subsec_millis());
278
【Rust】anyhowを使ったエラーハンドリング
anyhow = “1.0.97”
https://docs.rs/anyhow/latest/anyhow/
anyhowを使うと、エラー処理が簡単にできるようになる。
anyhow::Errorはエラー型のラッパー、あらゆるエラー型からanyhow::Errorに変換できる。変換できる。
Result
contextやwith_contextを使うと、Noneをanyhow::Errorとして移譲することも可能
anyhow::Result
use anyhow::{ensure, Context, Result}; use std::fs; #[tokio::main] async fn main() { let path_123 = "./data/temp/123.txt"; println!("{}", with_unwrap(&path_123)); // let path_abc = "./data/temp/abc.txt"; // println!("{}", with_unwrap(&path_abc)); let path_non_existent = "./data/to/non_existent.txt"; println!("{}", with_unwrap(&path_non_existent)); } fn with_unwrap(path: &str) -> i32 { let s = fs::read_to_string(path).unwrap(); let i = s.parse::<i32>().unwrap(); i * 10 }
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.76s
Running `target/debug/app`
1230
thread ‘main’ panicked at src/main.rs:16:30:
called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: “No such file or directory” }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
vagrant@vagrant:~/dev/rust/app$
エラーメッセージが、InvalidDigit、No such file or directoryでそれぞれ異なる。
#[tokio::main] async fn main() { let path_123 = "./data/temp/123.txt"; println!("{}", with_unwrap(&path_123).unwrap()); let path_abc = "./data/temp/abc.txt"; println!("{:?}", with_unwrap(&path_abc).unwrap_err()); let path_non_existent = "./data/to/non_existent.txt"; println!("{:?}", with_unwrap(&path_non_existent).unwrap_err()); } fn with_unwrap(path: &str) -> Result<i32> { let s = fs::read_to_string(path)?; let i = s.parse::<i32>()?; Ok(i * 10) }
1230
invalid digit found in string
No such file or directory (os error 2)
### anyhow::Contextでコンテキストを追加
with_contextは遅延評価でエラーの時のみ指定
fn with_unwrap(path: &str) -> Result<i32> { let s = fs::read_to_string(path).with_context(|| format!("Failed to read {path:?}"))?; let i = s.parse::<i32>() .with_context(|| format!("Failed to parse {s:?}"))?; Ok(i * 10) }
1230
Failed to parse “abc”
Caused by:
invalid digit found in string
Failed to read “./data/to/non_existent.txt”
Caused by:
No such file or directory (os error 2)
### OptionをResultに変換
contextおよび with_contextはResultだけでなく、Optionからも呼べる。Noneも呼び出し元に移譲できる。
async fn main() { let path_max = "data/temp/max.txt"; fs::write(path_max, i32::MAX.to_string()).unwrap(); println!("{:?}", with_anyhow_option(path_max).unwrap_err()); let path_123 = "./data/temp/123.txt"; println!("{}", with_unwrap(&path_123).unwrap()); let path_abc = "./data/temp/abc.txt"; println!("{:?}", with_unwrap(&path_abc).is_err()); let path_non_existent = "./data/to/non_existent.txt"; println!("{:?}", with_unwrap(&path_non_existent).is_err()); } fn with_anyhow_option(path: &str) -> Result<i32> { let s = fs::read_to_string(path).with_context(|| format!("Failed to read {path:?}"))?; let i = s.parse::<i32>() .with_context(|| format!("Failed to parse {s:?}"))?; let i = i.checked_add(100).context("Overflow occurred")?; Ok(i) }
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.86s
Running `target/debug/app`
Overflow occurred
1230
true
true
### マクロ(anyhow!, bail!, ensure!)で単発のエラーを返す
ensureで値に応じてエラーとすることもできる。
fn with_anyhow_macro(path: &str) -> Result<i32> { let s = fs::read_to_string(path).with_context(|| format!("Failed to read {path:?}"))?; let i = s.parse::<i32>() .with_context(|| format!("Failed to parse {s:?}"))?; let i = i.checked_add(100).context("Overflow occurred")?; ensure!(i >= 1000, "Value must be at least 1000, got {}", i); Ok(i) }
match resultでの処理
let path_123 = "./data/temp/123.txt"; let result = with_anyhow_macro(&path_123); match result { Ok(n) => { println!("{}", n); } Err(error) => { println!("{}", error); } }
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.81s
Running `target/debug/app`
Value must be at least 1000, got 223
【JavaScript】ファイルの更新があれば、値を更新する
setTimeoutでループ処理ができる。
data.split(“\n”).lengthで、ファイルの行数を取得できる。
async function fetchData() { try { fetch('./data/names.txt') .then(response => response.text()) .then(data => { console.log('テキストファイルの内容:'); console.log(data.split("\n").length - 1); document.getElementById('target').textContent = data.split("\n").length - 1; }) .catch(error => { console.error('エラー:', error); }); } catch (error) { console.error('リクエストエラー:', error); } setTimeout(fetchData, 3000); } fetchData();
これだと、更新自体はできますが、データをpublicな場所には置きたくないですね…
【Rust】シャミアの秘密計算(SSS)をRustで動かしてみる
private keyを複数のシェアに分割して、それをrestoreして元のprivate keyに戻すということは割と簡単にできそうではある。
shamirsecretsharing = “0.1.5”
use shamirsecretsharing::*; async fn main() { let private_key = "i3HdSBYb5fmuD4RxAf77VnYbyAy6b8ab5LEddfQLM2VaYrUUFNNp6Pk22dsTAibj".to_string(); let data = private_key.into_bytes(); println!("{:?}", data); let count = 3; let treshold = 2; let mut shares = create_shares(&data, count, treshold).unwrap(); for share in shares.clone() { print_base_encode(share); } let restored = combine_shares(&shares.clone()).unwrap(); println!("{:?}", String::from_utf8(restored.unwrap()).unwrap()); } fn print_base_encode(s: Vec<u8>) { // println!("{:?}", s.clone()); let base64_encoded = base64::encode(&s); println!("base64 encoded: {}", base64_encoded); // println!("{:?}", BASE64_STANDARD.decode(base64_encoded).unwrap()); }
base64 encoded: AZv7eBLYhiBxgjpD3kI0YbLnnC88DnTWPghszvPWHLt4D+dFwsC4CE7R2mlSAlDsuituyPlTHSzE7rSNJylQvj1kn7sHEhEDqxC5b0qEj0SjfkVoaPcgdXdHOmX8rhOjO+xy9n5qlHpAcLJDHLcj2hA=
base64 encoded: AhM+urNxZnzJsekDjygSpCqresvZz1Kg/zFE8/zdbLkWD+dFwsC4CE7R2mlSAlDsuituyPlTHSzE7rSNJylQvj1kn7sHEhEDqxC5b0qEj0SjfkVoaPcgdXdHOmX8rhOjO+xy9n5qlHpAcLJDHLcj2hA=
base64 encoded: A2t9DSUWz0ihoFHKSQ7556tm0Zdzebl7SSZcEfkttU7FD+dFwsC4CE7R2mlSAlDsuituyPlTHSzE7rSNJylQvj1kn7sHEhEDqxC5b0qEj0SjfkVoaPcgdXdHOmX8rhOjO+xy9n5qlHpAcLJDHLcj2hA=
“i3HdSBYb5fmuD4RxAf77VnYbyAy6b8ab5LEddfQLM2VaYrUUFNNp6Pk22dsTAibj”
【Python】シャミアの秘密計算
import random from typing import List, Tuple def generate_coefficients(secret: int, threshold: int) -> List[int]: coefficients = [secret] for _ in range(threshold - 1): coefficients.append(random.randint(1, 256)) return coefficients def create_shares( secret: int, total_shares: int, threshold: int ) -> List[Tuple[int, int]]: coefficients = generate_coefficients(secret, threshold) shares = [] for x in range(1, total_shares + 1): y = sum(coeff * (x**exp) for exp, coeff in enumerate(coefficients)) shares.append((x, y)) return shares def reconstruct_secret(shares: List[Tuple[int, int]], threshold: int) -> int: def _lagrange_interpolation(x: int, x_s: List[int], y_s: List[int]) -> int: def _basis(j: int) -> int: num = 1 den = 1 for m in range(len(x_s)): if m != j: num *= x - x_s[m] den *= x_s[j] - x_s[m] return num // den result = 0 for j in range(len(y_s)): result += y_s[j] * _basis(j) return result x_s, y_s = zip(*shares) return _lagrange_interpolation(0, x_s, y_s) if __name__ == "__main__": secret = 2732 total_shares = 5 threshold = 2 shares = create_shares(secret, total_shares, threshold) print("shares:", shares) selected_shares = shares[:threshold] print(zip(*selected_shares)) x_s, y_s = zip(*selected_shares) print(x_s, y_s) recovered_secret = reconstruct_secret(selected_shares, threshold) print("Recovered Secret:", recovered_secret)
$ python3 test.py
shares: [(1, 2961), (2, 3190), (3, 3419), (4, 3648), (5, 3877)]
(1, 2) (2961, 3190)
Recovered Secret: 2732
【Rust】reqwestによるPostのレスポンスタイムの差
幾つかのサイトにデータをPostして、レスポンスタイムに差があるかを調査する。
let now = time::Instant::now(); let mut post_url = format!("http://www.jsontest.com/"); let client = reqwest::Client::new(); let resp = client.post(post_url) .header(reqwest::header::CONTENT_TYPE, "application/json") .json(&str) .send() .await .unwrap(); println!("{:?}", resp); println!("{:?}", now.elapsed());
↓レスポンスタイムの差
同じサーバ内
67.997892ms
76.798063ms
73.945108ms
http://httpbin.org/post
626.023117ms
560.575466ms
1.050126063s
https://dummyjson.com/posts
695.071869ms
825.34323ms
676.196368ms
http://www.jsontest.com/
229.820077ms
256.854971ms
203.667686ms
当たり前だが、同じサーバ内のレスポンスは早い。
それ以外でも、サイトごとによって、レスポンスタイムが早いものと遅いものの違いが生じている。
なるほど、なかなか面白いね。