【Rust】SocketAddrによるipv4とipv6の判定

SocketAddrには、is_ipv4、is_ipv6があるが、SocketAddrV6にはない。
socket.ip() で取得した場合、ipv6は [ip]:port としなければならないので、reqwestで送る際も、ipv4/ipv6の判定処理が必要。

use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV6};

#[tokio::main]
async fn main() {
    let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192,168,33,10)), 3000);
    println!("{}", socket);
    println!("{}", socket.port());
    println!("{}", socket.ip());
    println!("{}", socket.is_ipv4());
    println!("{}", socket.is_ipv6());

    let socket = SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 8080);
    println!("{}", socket);
    println!("{}", socket.port());
    println!("{}", socket.ip());
    println!("{}", socket.is_ipv4());
    println!("{}", socket.is_ipv6());

    // let socket_v6 = SocketAddrV6::new(Ipv6Addr::new(0000,0000,0000,0000,0000,ffff,c0a8,210a), 3000, 0, 0);

    let socket_v6 = SocketAddrV6::new(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 8080, 0, 0);

    println!("{}", socket_v6);
    println!("{}", socket_v6.port());
    println!("{}", socket_v6.ip());
}

###ipv4
192.168.33.10:3000
3000
192.168.33.10
true
false

###ipv6
[::1]:8080
8080
::1
false
true

###ipv6
[2001:db8::1]:8080
8080
2001:db8::1

axumの場合も同様

async fn handle_index(ConnectInfo(addr): ConnectInfo<SocketAddr>) -> axum::response::Html<String> {
    println!("{}", addr);
    println!("{}", addr.port());
    println!("{}", addr.ip());

【Rust】axumでページ遷移してもtokio::spawnは実行される?

axumで重い処理を tokio::spawn で別スレッドにしてページのレンダリングを先にした場合、ユーザが別ページに飛んだ場合でも、別スレッドの関数は止まらない。

async fn handle_index() -> axum::response::Html<String> {

    let handle = tokio::spawn(async move {
        let _ = delay().await;
    });
    println!("main thread");
    
    let tera = tera::Tera::new("templates/*").unwrap();
    let mut context = tera::Context::new();
    context.insert("title", "Index page");
    let output = tera.render("app.html", &context);
    return axum::response::Html(output.unwrap());
}

async fn delay() {
    thread::sleep(Duration::from_secs(10));
    println!("sub thread");
}

async fn handle_sub() -> axum::response::Html<String> {
    
    let tera = tera::Tera::new("templates/*").unwrap();

    let data = 100;
    let mut context = tera::Context::new();
    context.insert("title", "Index page");
    let output = tera.render("app.html", &context);
    return axum::response::Html(output.unwrap());
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 9.57s
Running `target/debug/axum`
main thread
sub thread

そりゃそうだと思っていても、実際にテストしないと安心できませんね。

【Rust】ファイルの拡張子の取得

### 駄目な例
ファイル名の”.”以降の値を取得する

let pos = file_name.find('.').unwrap();
let file_extension = &file_name[(pos+1)..].to_string();

これだと、img.test.png という画像ファイルだった場合、拡張子が test.png になってしまう。
一度、ファイルを保存して、use std::path::PathBuf; を使って、ちゃんと取得する。

                let pathbuf = PathBuf::from(format!("./tmp/{}", file_name));
                let ext_string = pathbuf
                    .extension()
                    .unwrap()
                    .to_string_lossy()
                    .into_owned();
                println!("{}", ext_string);
                let ex_filename = format!("./tmp/{}", file_name);
                let new_filename = format!("./tmp/nft.{}", ext_string);
                fs::rename(ex_filename, new_filename).unwrap();

ファイル名に、”.”が入らないなんてのは勝手な思い込みだ。

【Rust】Unitテストでtype_name_of_valを使って型をチェックする

std::any::type_name::のassertの使い方がよくわからない。type_name_of_valだと、簡単にチェックできる。
https://doc.rust-lang.org/beta/std/any/fn.type_name_of_val.html

use std::any::type_name_of_val;

#[tokio::main]
async fn main() {
    let aws_credential = check_aws_credential().await;
    print_typename(aws_credential);
    assert!(type_name_of_val(&aws_credential).contains("bool"));
}

fn print_typename<T>(_: T) {
    println!("{}", std::any::type_name::<T>());
}

assert_eq!(true || false, hoge); みたいなことはできない。この場合、leftはtrueになってしまう。

【Rust】.envファイルの設定有無をチェックする

.envの設定の有無によって、処理を切り替える。is_empty()で値がセットされているか確認ができる。

#[tokio::main]
async fn main() {
    let aws_credential = check_aws_credential().await;
    println!("{}", aws_credential);
}

async fn check_aws_credential() -> bool {
    let _ = dotenv();
    let aws_access_key = env::var("AWS_ACCESS_KEY_ID").unwrap();
    let aws_secret_key = env::var("AWS_SECRET_ACCESS_KEY").unwrap();
    let aws_bucket_name = env::var("AWS_BUCKET_NAME").unwrap();
    let aws_region = env::var("AWS_REGION").unwrap();

    if aws_access_key.is_empty() || aws_secret_key.is_empty() || aws_bucket_name.is_empty() || aws_region.is_empty() {
        return false;
    }
    return true
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.70s
Running `target/debug/app`
false

なるほど!

【Rust】サーバ側からTera/JavaScriptにデータを渡すとき

JSに渡すときに、let value = {{ data }} と書くと宜しくない。let value = “{{ data }}” とダブルクオテーションで囲って渡してあげる必要がある。

サーバ側

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

    let data = 100;

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

    let output = tera.render("index.html", &context);
    return axum::response::Html(output.unwrap());

html/JS側

//
<input type="range" class="form-range" id="customRange1" min="0" max="{{data}}" v-model="amount">
//
<script>
    let value = "{{ data / 2 }}";
</script>

↓ これは動くには動くが駄目。

<script>
    let value = {{ data / 2 }};
</script>

【Rust】Vectorの要素のスライスとVecDequeのdrain(n..)

Vectorの場合は、以下のように書いて、要素のスライスができる。

    let vec: Vec<String> = vec!["A".to_string(), "B".to_string(),"C".to_string(),"D".to_string(),"E".to_string(),"F".to_string()];
    let data = &vec[..3];
    println!("{:?}", data);

Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.84s
Running `target/debug/app`
[“A”, “B”, “C”]

これをVecDequeでやろうとするとエラーになる。

    let vecdeque: VecDeque<String> = VecDeque::from(["A".to_string(), "B".to_string(),"C".to_string(),"D".to_string(),"E".to_string(),"F".to_string()]);
    let data = &vecdeque[..3];
    println!("{:?}", data);

error[E0308]: mismatched types
–> src/main.rs:37:26
|
37 | let data = &vecdeque[..3];
| ^^^ expected `usize`, found `RangeTo<{integer}>`
|
= note: expected type `usize`
found struct `RangeTo<{integer}>`

let mut vecdeque: VecDeque<String> = VecDeque::from(["A".to_string(), "B".to_string(),"C".to_string(),"D".to_string(),"E".to_string(),"F".to_string()]);
    vecdeque.drain(3..);
    println!("{:?}", vecdeque);

[“A”, “B”, “C”]

【Rust】Axumでスレッドを並列化させて、async関数を実行させたい

axumでPostされたデータの処理で重い処理があるため、スレッドで並列化させて、先にURLを表示させたい時。

### ダメな例(asyncでない関数を呼び出す)
これでも動くのは動く(先にレンダリングされる)が、spawnの中の関数は、asyncは使えない。

async fn handle_index() -> axum::response::Html<String> {

    let handle = std::thread::spawn(move||{
        let _ = delay();
    });
    println!("main thread");
    
    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);
    return axum::response::Html(output.unwrap())
   handle.join().unwrap();
}

fn delay() {
    thread::sleep(Duration::from_secs(2));
    println!("sub thread");
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.65s
Running `target/debug/axum`
main thread
sub thread

### asyncで並列化
tokio::spawn で、別スレッドでasync関数を実行することができる。handle.joinの記述も不要。

async fn handle_index() -> axum::response::Html<String> {

    let handle = tokio::spawn(async move {
        let _ = delay().await;
    });
    println!("main thread");
    
    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);
    return axum::response::Html(output.unwrap());
}

async fn delay() {
    thread::sleep(Duration::from_secs(2));
    println!("sub thread");
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.65s
Running `target/debug/axum`
main thread
sub thread

うおおおおおおおおお、中々良い^^

【Rust】ECDSA署名のverifyのエラーハンドリング

署名のverifyはtrue or falseで返したいが、、、

pub async fn verify_signature(signedtransaction: &kernel::SignedTransaction) -> bool {
    // 省略
    return verifying_key.verify(posted_serialized.as_bytes(), &signature).is_ok()
}

偽のpublic keyを送ってきた可能性もあるため、Result型で返却しないといけない。

pub async fn verify_signature(signedtransaction: &SignedTransaction) -> Result<bool, Box<dyn std::error::Error>>{
    // 省略
    Ok(verifying_key.verify(posted_serialized.as_bytes(), &signature).is_ok())
}

なるほど、こういうのは、UnitTestを書いてテストしないと気づかない…

【Rust】文字列が全て数字か調べる(unit test)

pub fn create_session_token() -> String {
    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);
    return session_token.to_string();
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_create_session_token() {
        let token = create_session_token();
        let token_filter = token.clone().chars().filter(|&c| matches!(c, '0' ..= '9')).collect::<String>();
        assert_eq!(token_filter.len(), token.len());
    }
}

warning: `app` (bin “app” test) generated 7 warnings (run `cargo fix –bin “app” –tests` to apply 7 suggestions)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.10s
Running unittests src/main.rs (target/debug/deps/app-22ae7a27958c3b49)

running 1 test
test tests::test_create_session_token … ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

うーん、渋い