Rust   |   Askama を Proxy サーバーで動かす

ホーム  

概要

前回までで、 Axum + Askama + SQLite の組み合わせで CRUD を実装しました。CSS で画面も整えています。 しかし、これを通常の方法でサーバーに公開した場合、IP もしくは ドメイン:3000 という形でアクセスすることになります。 これを IP もしくは ドメイン/askama というアドレスでアクセスできるようにします。

SQLite

このコーナーでは、データベースとして SQLite を使っています。 SQLite を次のようにしてインストールしてください。


# Debian 系にインストールする
sudo apt install sqlite3
sudo apt install libsqlite3-dev

# RPM 系にインストールする
sudo dnf install sqlite
sudo dnf install sqlite-devel

# macOS にインストールする、次のコマンドだけでデベロッパーライブラリもインストールされます。
brew install sqlite

# Windows にインストールする
SQLite をインストールすることはできますが、デベロッパーライブラリのインストールにはまだ成功していません。
ライブラリのインストールは不要との説もありますが、実際には動作しません。


Cargo.toml


[package]
name = "askama"
version = "0.1.0"
edition = "2024"

[dependencies]
askama = "0.14.0"
axum = "0.8.4"
tokio = { version = "1.0", features = ["full"] }
tower-http = { version = "0.6.6", features = ["full"] }
serde = { version = "1.0.225", features = ["derive"] }
rusqlite = { version = "0.37.0", features = ["bundled"] }


main.rs

プロキシーサーバを使わない場合から3箇所変更しています。


//******************************
// main.rs
// copyright   : vivacocoa.jp
// last modified: Oct. 02. 2025
//******************************
use axum::{response::{Html, IntoResponse, Redirect}, routing::{get, post}, Router, Form};
use askama::Template;
use serde::Deserialize;
use tower_http::services::ServeDir;
use rusqlite::params;

#[derive(Template)]
#[template(path = "index.html")]
struct Todos
{
    todos: Vec<Todo>,
}
#[derive(Clone)]
struct Todo
{
    id: u64,
    message: String,
}
#[derive(Deserialize)]
struct FormData {
    id: u64,
    message: String,
}

#[tokio::main]
async fn main() {
    let connection = rusqlite::Connection::open("test.db").unwrap();

    connection.execute("
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            todo TEXT
        )
    ", []).unwrap();

    let serve_dir = ServeDir::new("public");

    let app = Router::new()
        .nest_service("/static", serve_dir)
        .route("/", get(index))
        .route("/create", post(create))
        .route("/update", post(update))
        .route("/delete", post(delete));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    let addr = listener.local_addr().unwrap();
    println!("Listening on {}", addr);
    axum::serve(listener, app).await.unwrap();
}

async fn index() -> impl IntoResponse
{
    let connection = rusqlite::Connection::open("test.db").unwrap();
    let mut stat = connection.prepare("SELECT * FROM users").unwrap();
    let mut rows = stat.query([]).unwrap();

    let mut todos = Vec::new();
    while let Some(row) =  rows.next().unwrap() {
        let i_d: u64 = row.get(0).unwrap();
            let todo: String = row.get(1).unwrap();
            todos.push(Todo {message: todo, id: i_d});
    }
    let _ = todos.clone().into_iter().collect::<Vec<_>>();
    let todos = Todos { todos };
    Html(todos.render().unwrap()).into_response()
}

async fn create(Form(data): Form<FormData>) -> Redirect
{
    let connection = rusqlite::Connection::open("test.db").unwrap();
    connection. execute ( "INSERT INTO users (todo) VALUES (?1)", (data.message,)). unwrap ();
    let _ = connection.close().unwrap();
    Redirect::to("/askama")         // ここを変更しています
}

async fn update(Form(data): Form<FormData>) -> Redirect
{
    let _msg = data.message;
    let _id = data.id;
    let connection = rusqlite::Connection::open("test.db").unwrap();
    let _ = connection.execute("UPDATE users SET todo = ?1 WHERE id = ?2", params![_msg, _id],);
    let _ = connection.close().unwrap();
    Redirect::to("/askama")         // ここを変更しています
}

async fn delete(Form(data): Form<FormData>) -> Redirect
{
    let connection = rusqlite::Connection::open("test.db").unwrap();
    let _ = connection.execute("DELETE FROM users WHERE id = ?", (data.id,));
    let _ = connection.close().unwrap();
    Redirect::to("/askama")         // ここを変更しています
}


style.css

プロジェクトのルートディレクトリに public というディレクトリを作り、 その中に次の style.css を作ってください。

プロキシサーバーを使わない場合から変更はありません。


/*****************************
style.css
copyright    : vivacocoa.jp
last modified: Sep. 20, 2025
******************************/
body {
  background-color: #efefef;
}

.container {
  width: 500px;
  margin: 16px auto;
}

h1 {
  font-size: 20px;
}

.todo {
  height: 80px;
  width: 100%;
  border-radius: 4px;
  padding: 0px 8px;
  line-height: 80px;
  box-sizing: border-box;
  background-color: white;
  margin-bottom: 0px;
}

.todo2 {
  height: 35px;
  width: 100%;
  border-radius: 4px;
  padding: 0px 8px;
  line-height: 80px;
  box-sizing: border-box;
  background-color: #efefef;
  margin-bottom: 0px;
}

.h-layout {
  display: flex;
}

input {
  height: 25px;
  width: 100%;
  padding: 4px;
  border: solid 0px #efefef;
  border-radius: 4px;
}

.btn-black {
  background-color: black;
  width: 60px;
  color: white;
  border-radius: 4px;
}

.h-layout-2 {
  display: flex;
  position: relative;
}

.btn-white {
  background-color: white;
  border: solid 1px lightgray;
  border-radius: 4px;
  color: gray;
  width: 50px;
  height: 30px;
  position: absolute;
  right: 4px;
  bottom: 4px;
}

.btn-white2 {
  background-color: white;
  border: solid 1px lightgray;
  border-radius: 4px;
  color: gray;
  width: 60px;
  height: 30px;
  position: absolute;
  right: 0px;
  top: 10px;
}

.btn-white3 {
  background-color: white;
  border: solid 1px lightgray;
  border-radius: 4px;
  color: gray;
  width: 55px;
  height: 30px;
  position: absolute;
  right: 65px;
  top: 10px;
}


index.html

プロジェクトのルートディレクトリに templates というディレクトリを作り、 その中に次の index.html を作ります。

プロキシサーバーを使わない場合から5箇所変更になっています


<!-----------------------------                                                                                                         
  index.html                                                                                                                            
  copyright    : vivacocoa.jp                                                                                                           
  last modified: Oct. 02, 2025                                                                                                          
 ------------------------------>
<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Askama</title>
  <link rel="stylesheet" href="/askama/static/style.css">         <!-- 変更しています -->
  <scritp src="/askama/static/staticposition.js"></script>        <!-- 追加しています -->
</head>

<body>
  <div class="container">
    <h1>Askama</h1>
    <form method="post" action="/askama/create">                   <!-- 変更しています -->
      <div class="h-layout">
        <input type="text" name="message" required maxlength="30">
        <input type="hidden" name="id" value="1">
        <button class="btn-black">Create</button>
      </div>
    </form>
    {%for todo in todos %}

    <div class="h-layout-2">
      <p class="todo" id=p{{todo.id}}>{{ todo.message }}</p>
      <button type="button" class="btn-white" onclick="toggle('elm{{todo.id}}')">Edit</button>
    </div>

    <div id=elm{{todo.id}} hidden class="todo2">
      <div class="h-layout-2">
        <form method="post" action="/askama/update" id=f{{todo.id}}>      <!-- 変更しています -->
          <input type="hidden" name="id" value={{todo.id}}>
          <input type="hidden" name="message" value={{todo.message}} id=i{{todo.id}}>
          <button hidden>submit</button>
        </form>
        <button type="submit" class="btn-white2" id=b{{todo.id}}
          onclick="updatebtn('i{{todo.id}}', 'f{{todo.id}}', '{{todo.message}}')">Update</button>
        <form method="post" action="/askama/delete">                      <!-- 変更しています -->
          <input type="hidden" name="id" value={{todo.id}}>
          <input type="hidden" name="message" value={{todo.message}}>
          <button class="btn-white3" onclick="return confirm('削除してよろしいですか?');">Delete</button>
        </form>
      </div>
    </div>


    {% endfor %}
  </div>
  <script>
    function toggle(elm) {
      const e = document.getElementById(elm);
      e.hidden = !e.hidden;
    }
    function updatebtn(i, f, msg) {
      var msg = prompt('アップデートを入力してください。', msg)
      if (msg !== null && msg !== '') {
        document.getElementById(i).setAttribute('value', msg);
        document.getElementById(f).submit();
      }
    }
  </script>
</body>

</html>


staticposition.js

public ディレクトリに次の staticposition.js を作ります。

プロキシサーバーを使わない場合から追加されています。ページをリロードした時に、 ページトップに戻らないようにするためのスクリプトです。


history.scrollRestoration = "manual";

window.addEventListener("load", () => {
    const scrollPosition = sessionStorage.getItem("scrollPosition");
    if (scrollPosition) {
        window.scrollTo(0, parseInt(scrollPosition));
    }
});

window.addEventListener("scroll", () => {
    sessionStorage.setItem("scrollPosition", window.scrollY.toString());
});

window.addEventListener("beforeunload", () => {
    sessionStorage.setItem("scrollPosition", window.scrollY.toString());
});


GitHub

GitHub 経由でサーバーにデプロイします。


(1) ローカルからリモートへアップロードします。
git init
git add .
git commit -m "任意の文字列"
git branch -M main
git remote add origin https://github.com/あなたのアカウント名/リポジトリー名.git
git push -u origin main


(2) リモートからローカルへダウンロードします。
git clone https://github.com/あなたのアカウント名/リポジトリー名.git

(3) ダウンロードしたディレクトリに移動して次のようにビルドしてくださお。
cargo build --release


プロキシの利用

アドレス:8080 という形ではなく、アドレス/アプリケーション名でアクセスしたくなると思います。 その場合は、apach などの Web サーバーを立ち上げて、プロキシを利用します。 ポート 3000 の場合は適宜読み替えてください。

apache2 の場合は、/etc/apache2/apache2.conf に次のコードを追加してください。
httpd の場合は /etc/httpd/conf/httpd.conf に次のコードを追加してください。
追加する場所はファイルの一番最後で大丈夫です。


<Location "/アプリケーション名">
ProxyPass http://0.0.0.0:8080
ProxyPassReverse http://0.0.0.0:8080
</Location>
# アプリケーション名のところは tera とか askama です。
# 0.0.0.0 ではなく正規の IP でも大丈夫ですが、0.0.0.0 の方が色々と対応できて便利です。

<Location "/違うアプリケーション名">
ProxyPass http://0.0.0.0:3000
ProxyPassReverse http://0.0.0.0:3000
</Location>

apache2.conf を書き換えた後は、apache2 を再起動します。


# Apache2 の場合
sudo service apache2 restart

# Httpd の場合
sudo systemctl restart httpd

ポートを開放して、新しい Askama アプリケーションを起動します。


# ポートの使用状況の確認
sudo lsof -i :3000

# 古い tera が起動していたら、その PID 番号で古い askama を終了します。
sudo kill -9 PID番号

# askama_proxy ディレクトリに移動して、新しい askama を起動します。
nohup ./target/release/askama &

# もし何かのログが表示されたら、コードのどこかが間違っています。ビルドから修正してやり直してください。

これで、サーバーのIPアドレス/アプリケーション名もしくはドメイン/アプリケーション名でアクセスできるようになります。

なおプロキシはいくつでも書き足すことができます。



1038 visits
Posted: Oct. 02, 2025
Update: Oct. 02, 2025

ホーム   目次