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
当たり前だが、同じサーバ内のレスポンスは早い。
それ以外でも、サイトごとによって、レスポンスタイムが早いものと遅いものの違いが生じている。
なるほど、なかなか面白いね。