【Rust】axumでファイルのアップロードを受け取る

HTML側

<form action="/upload" method="post" enctype="multipart/form-data">
    <div>
        <p>name</p>
        <input type="text" name="name"><br><br>
        <input type="file" name="testfile" onchange="previewFile(this);">
    </div>
    <div>
        <input type="submit" value="送信する">
    </div>
</form>
<p>プレビュー</p>
<img id="preview">

<script>
function previewFile(file) {
    var fileData = new FileReader();
    fileData.onload = (function() {
        document.getElementById('preview').setAttribute("style","width:100px;height:100px");
        document.getElementById('preview').src = fileData.result;
        
    });
    fileData.readAsDataURL(file.files[0]);
}
</script>

axum側
axum = { version=”0.8.1″, features = [“multipart”] }
tokio = { version = “1.25”, features = [“full”] }

use tower_http::services::{ServeDir, ServeFile};
use axum::{
    extract::Multipart,
    extract::DefaultBodyLimit,
    routing::post,
    routing::get,
    Router,
};
use tokio::fs::File;
use tokio::io::AsyncWriteExt;


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

    let serve_dir = ServeDir::new("static").not_found_service(ServeFile::new("static"));

    let app = Router::new()
        .route("/", get(handle_index))
        .route("/upload", post(handle_upload))
        .layer(DefaultBodyLimit::max(1024 * 1024 * 1024))
        .nest_service("/static", serve_dir.clone())
        .fallback_service(serve_dir);

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

async fn handle_upload(mut multipart: Multipart)-> axum::response::Html<String> {

    while let Some(field) = multipart.next_field().await.unwrap(){
        let param_name = field.name().unwrap().to_string();
        match param_name.as_str() {
            "name" => {
                let name = field.text().await.unwrap();
                println!("tags: {}", name);
            }
            "testfile" => {
                let file_name = match field.file_name() {
                    Some(name) => name.to_owned(),
                    None => panic!("file_name is None"),
                };
                match field.bytes().await {
                    Ok(data) => {
                        println!("Length of `{}` is {} bytes", param_name, data.len());
                        let mut file = File::create(format!("./tmp/{}", file_name)).await.unwrap();
                        file.write_all(&data).await.unwrap();
                    }
                    Err(e) => {
                        eprintln!("Error reading `{}`: {}", param_name, e);
                        // return;
                    }
                }

            }
            _ => {
                println!("unknown param_name: {}", param_name);
            }
        }
    }

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

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

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

なるほど、なかなか面白い。

【javascript】アップロードファイルのプレビュー

<form action="/post" method="post" enctype="multipart/from-data">
    <div>
        <input type="file" name="test" onchange="previewFile(this);">
    </div>
    <div>
        <input type="submit" value="送信する">
    </div>
</form>
<p>プレビュー</p>
<img id="preview" width="100px" height="100px">

<script>
function previewFile(file) {
    var fileData = new FileReader();
    fileData.onload = (function() {
        document.getElementById('preview').src = fileData.result;
    });
    fileData.readAsDataURL(file.files[0]);
}
</script>

次はaxumでデータのやり取りだな

【Rust】S3のbucketへ画像アップロード

use dotenv::dotenv;
use std::env;
use tokio::io::AsyncReadExt;
use rusoto_s3::*;
use rusoto_core::*;

#[tokio::main]
async fn main() {
    dotenv();
    let aws_access_key = env::var("AWS_ACCESS_KEY_ID").unwrap();
    let aws_secret_key = env::var("AWS_SECRET_ACCESS_KEY").unwrap();

    std::env::set_var("AWS_ACCESS_KEY_ID", aws_access_key);
    std::env::set_var("AWS_SECRET_ACCESS_KEY", aws_secret_key);

    s3_upload().await;
}

async fn s3_upload() -> Result<(), Box<dyn std::error::Error>> {
    let s3_client = S3Client::new("ap-northeast-1".parse().unwrap());
    let mut file = tokio::fs::File::open("./data/test.png").await?;
    let mut buffer = Vec::new();
    file.read_to_end(&mut buffer).await?;

    let result = s3_client.put_object(PutObjectRequest {
        bucket: String::from("hpscript"),
        key: "test.png".to_string(),
        body: Some(StreamingBody::from(buffer)),
        ..Default::default()
    }).await?;
    // do thing with result
    Ok(())
}

データがうまくアップロードされているのが分かります。なるほどね〜

【Rust】base64で画像を文字列に変換

想像以上に文字列のデータ量が多すぎる…

use std::fs::File;
use std::io::Write;
use std::path::Path;

static FILE_NAME: &'static str = "test";

fn main() {

    let path = "./data/img1.png";
    let base64 = image_base64::to_base64(path);
    println!("{:?}", base64);
    
    let img = image_base64::from_base64(base64);

    let file_type = "png";
    let mut output = File::create(&Path::new(&format!("./data/{}.{}", FILE_NAME, file_type))).unwrap();
    output.write_all(img.as_slice()).unwrap();

}


+mQS0RoL4XWkGRlzADREjATCW58eL2T9dzP754iZx5OBzfBHPz51wNvO7sxlwI3lWnIa/An5fSMLuH42HOzfZiABLKqp+KD7hAPsQB9L4eP1gFPJgwPDTUympfvNM9TWq/Z7iX4pAK4463hDa3MXJ4veh2slWxtqowOOXgnUoyHp4H24dllHc5QPw85K0EzUDsE7MjIY5YWH4WMe2ynw7WP0+Het1k3Xn6ReoDw9mo6EY5sccKOU2GADLZaDiBOZF38EDiSclHU0Imzpp7KCNqs9jUGmtGD3drKFe0HEcAGtKAjBmxnFJHgFg2QqCQFHPz6RzMLwzEMP8kUjwkRhB4EWQ7RGUlUzycFSDHCUF/uqs0bdbrPcs4NlPF3JuH46pOBAFj7DHJ5IAP9+fzYLO9dcreXgl8Kj67drmF5dzfzqfA6D9tiQdNfw7BXHgSJ/7NOjkRxsP53jvT/fcnbr6s6RVmMmzI3Y5ZtNujliCEQVbI5duj16GL36atHJ3yuq96R4Hs7xAFyrdGVi+Jxy/7xdFifhxIHwBpH+5tAmleNhSgpv2n2tbMFwIkiQMfgBUA6fx/gCSy/eG38mPwdf/fSEbb5HPYgdDnX61VJjxK3OHIhfkoKQq4WE2SD1gpdPl3WGoQ4D4/bAwvuIAoFdGa87SA/CycQ0D4GBn43A3U2UlBf4NaK7emUxiqOu2HaFlpdlVUH9eUpocIyphAiT5DQbAchnY88L7xj5pGbcO8jgyGNNNDO3qtoMewrODJp9gXQiBG2UDAXjeSALA1ValS7tlF4CpSAlSHMDwq2tbgHPhnu5GEhjDZXuKBFfQwa3hIPCjiFAStkVbKSsB2ou2bBDd/1j0bA/QDpCDWisQF/ofJJolHwfsTlud4jfXf9m0ZTNt3ScNmzDC0MpQC8Kkzp3aKyO5eaMHkvMO7dr069XFRK//mMH6zhMsF7par1s0JdprxvbY5WhtAnQfFCYA+4HKeJ0AaeTW+Cf2BH/e/Ai7hGv7I3JDFy12tzbRU4fdtzQ/FL/IUOMBH86d8K+45XeOxIDejOfBhXEzP6ZCHrJeIQCfAQD71gDANg0AYJwW5mrq52goEOKPJEbQ9QCwZrcOrfkOlMrt20SJTmBAS+ALGV2mWDAAbp6BKnSE6LjPVzsFVegB3dvXXcvCt4DQMJ4VrCM+dK54QwB4/kjtGgD481AZB2AKw1xR+ngygOHl5dyznwaGeriNMNMBXDUc4wC6ipgDsfEDR6SewDDRg4+fnUrBCNuEDXPWvmfvaGsO6OrbQ40IohpxVKI+H/gV6Rah2i6hcc+PrL575w56mr3GDTNYOt02eu1MZNXwzEJXGLYYotvbRc/3ir7eI3r0GX477CGKtqzfGb8yaeMcoHjIKpew1W7xPrNBZt6d5nHqI1/sbzC/D0D+6urmH89nodSM2jvS65syf1XUAcD4vf53AIZoGPbRgjd/gmHvmUPrYm+4D9aYat5XUH82f28iqz/LezAAltcIeHGAVqEVqqrQ+B96Ra6DpXWjpKsJDH0aqqaIm2GxYBQ
….

うーん、これはちょっと無理ですね。。

SassにおけるMedia Query

一般的なmedia query

@media screen and (max-width: 600px) {
  h1 {
    color: red;
  }
}

_breakpoint.scss

$breakpoints: (
    "sm": "screen and (min-width: 400px)",
    "md": "screen and (min-width: 768px)",
    "lg": "screen and (min-width: 1000px)",
    "xl": "screen and (min-width: 1200px)",
) !default;

_mixin.scss

@mixin mq($breakpoint) {
    @media #{map-get($breakpoints, $breakpoint)} {
        @content;
    }
}

style.scss

@import "_breakpoint";
@import "_mixin";

.test {
    color: blue;

    @include mq(sm) {
        color: yellow;
    }

    @include mq(lg) {
        color: red;
    }
}


style.css

.test {
  color: blue;
}
@media screen and (min-width: 400px) {
  .test {
    color: yellow;
  }
}
@media screen and (min-width: 1000px) {
  .test {
    color: red;
  }
}

うわ、これは凄い!!!!

Sassの入れ子/ import

└── scss/
├── _variables.scss ※色、フォントサイズなどの変数を定義するファイル
├── _mixins.scss ※ミックインを定義するファイル
├── _buttons.scss ※ボタンに関するスタイルを定義するファイル
├── _layout.scss ※ページのレイアウトやグリッドレイアウトに関するスタイルを定義するファイル
└── main.scss

モジュールのインポート
_colors.scss

$base-color:#3498db;

_buttons.scss

$base-color:#e74c3c

style.scss

@use 'buttons';
@use 'colors';

.btn {
    background-color:buttons.$base-color;
    color:colors.$base-color;
}


style.css

.btn {
  background-color: #e74c3c;
  color: #3498db;
}

おおおお、素晴らしい!これは使いたい^^

ちなみに、mixinとは、よく利用するCSSスタイルを定義して、他の場所でも使いまわせるようにする機能のこと。
引数を使って、関数のように利用することができる。

Sass基本

### Sassの特徴
– ネスト構造を利用できる
– スタイルシートを分けられる
– 条件分岐を利用できる
– mixin機能の利用

### 変数

$black: #000000;
$position: left;

.item1 {
    color: $black;
}

.item2 {
    background: $black;
    margin-#{$position}: 30px;
}

$black: #000000;
$position: left;

.item1 {
    color: $black;
}

.item2 {
    background: $black;
    margin-#{$position}: 30px;
}

### ネスト

.item1 {
    width: 200px;

    .item2 {
        width: 100px;

        .item3 {
            width: 50px;
        }
    }
}

.item1 {
    width: 200px;

    .item2 {
        width: 100px;

        .item3 {
            width: 50px;
        }
    }
}

### 条件分岐

$position: left;
$width: 250px;

.item1 {
    @if $position == left {
        padding-left: 10px;
    }
}

.item2 {
    @if $width > 300px {
        margin-right: 10px;
    } @else if $width < 200px {
        margin-left: 10px;
    } @else {
        margin-top: 10px;
    }
}

.item1 {
  padding-left: 10px;
}

.item2 {
  margin-top: 10px;
}

break pointの時に利用したいですね。

gulpでjsの圧縮

$ mkdir gulp_js
$ cd gulp_js
$ npm init -y

$ npm install -D gulp
$ npm install -D gulp-uglify
$ npm install -D gulp-rename

gulpfile.js

var gulp = require("gulp");
var rename = require("gulp-rename");
var uglify = require("gulp-uglify");

gulp.task("default", function(){
    gulp.src('src/something.js')
        .pipe(uglify())
        .pipe(rename({
            extname: '.min.js'
        }))
        .pipe(gulp.dest('dest/'));
    done();
});

something.min.js

console.log("hello world!"),console.log("hello world! 2");

minifyされていることが確認できます。
一旦はSassだけでスタートして、JSは後からやりますか…

gulpの初期設定(gulpfile.js)

$ npm init -y
// gulpのインストール
$ npm install gulp –save-dev

gulpfile.js

const gulp = require('gulp');

gulp.task('default', function(){
    console.log('Gulp is running');
});

### sassの導入
$ npm install –save-dev gulp-sass sass

const gulp = require('gulp');
const sass = require('gulp-sass')(require('sass'));

gulp.task('default', function(){
    console.log('Gulp is running');
    done();
});

gulp.task('sass', function() {
    return gulp.src('dev/scss/**/*.scss')
        .pipe(sass().on('error', sass.logError))
        .pipe(gulp.dest('dist/css'));
});

gulp.task('default', gulp.series('sass'));
$primary-color: #333;

body {
    color: $primary-color;
}

$ npx gulp

cssファイルはできるんだけど、サーバを動かしながら見たいな…