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

ホーム  

概要

前回までで、 Actix-web + Tera + SQLite の組み合わせで CRUD を実装しました。Html も複数使って画面も切り替えています。 CSS も使って画面を整えています。 しかし、これを通常の方法でサーバーに公開した場合、IP もしくは ドメイン:8080 という形でアクセスすることになります。 これを IP もしくは ドメイン/tera というアドレスでアクセスできるようにします。

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 = "tera"
version = "0.1.0"
edition = "2024"

[dependencies]
actix-files = "0.6.8"
actix-web = "4.11.0"
rusqlite = "0.37.0"
serde = { version = "1.0.226", features = ["derive"] }
tera = "1.20.0"


main.rs

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


//--------------------------------
// main.rs
// copyright    : vivacocoa.jp
// last modified: Oct. 01, 2025
//--------------------------------
use actix_web::{web, App, Responder, HttpResponse, HttpServer};
use actix_web::web::Redirect;
use tera::{Tera, Context};
use rusqlite::{Connection, params};
use serde::{Serialize, Deserialize};
use actix_files as fs;
// use std::env;


// Todoのデータ構造を定義
#[derive(Serialize, Deserialize)]
struct Todo {
    id: i32,
    title: String,
    completed: bool,
}

#[actix_web::main]
async fn main() -> std::io::Result<()>
{
    let conn = Connection::open("todo.db");
    // テーブルが存在しない場合に作成する
    let _ = conn.expect("REASON").execute(
        "CREATE TABLE IF NOT EXISTS todos (
            id INTEGER PRIMARY KEY,
            title TEXT NOT NULL,
            completed BOOLEAN NOT NULL
        )",
        [],
    );
    HttpServer::new(|| {
        // templatesディレクトリ内のHTMLを読み込む
        let tera = Tera::new("templates/**/*").expect("Failed to initialize Tera");
        App::new()
            .app_data(web::Data::new(tera))
            .service(fs::Files::new("/static", "./static").show_files_listing())
            .service(web::resource("/").to(index))
            .service(web::resource("/create").to(create))
            .service(web::resource("/update").to(update))
            .service(web::resource("/delete").to(delete))
            .service(web::resource("/edit").to(edit))
    })
    .bind("0.0.0.0:8080")?
    .run()
    .await
}

async fn index(tera: web::Data<Tera>) -> impl Responder
{
    let conn = Connection::open("todo.db").unwrap();
    let mut stat = conn.prepare("SELECT * FROM todos").unwrap();
    let mut rows = stat.query([]).unwrap();

    let mut todos = Vec::new();
    while let Ok(Some(row)) =  rows.next() {
            let i_d: i32 = row.get(0).unwrap();
            let todo: String = row.get(1).unwrap();
            let comp: bool = row.get(2).unwrap();
            todos.push(Todo {id: i_d, title: todo, completed: comp});
    }
    let mut context = tera::Context::new();
    context.insert("todos", &todos);
    let rendered = tera.render("index.html", &context).unwrap();
    HttpResponse::Ok().body(rendered)
}

async fn create(form: web::Form<Todo>) -> Redirect
{
    let conn = rusqlite::Connection::open("todo.db").unwrap();
    conn. execute ( "INSERT INTO todos (title, completed) VALUES (?1, ?2)", params![form.title, form.completed],).unwrap();
    Redirect::to("/tera")                     // リダイレクト先を / ではなく /tera に変更しています。
}

async fn update(form: web::Form<Todo>) -> Redirect
{
    let conn = rusqlite::Connection::open("todo.db").unwrap();
    conn. execute (
        "UPDATE todos SET completed = ?1, title = ?2 WHERE id = ?3",
        params![form.completed, form.title, form.id],).unwrap();
    Redirect::to("/tera")                     // リダイレクト先を / ではなく /tera に変更しています。
}

async fn delete(form: web::Form<Todo>) -> Redirect
{
    let conn = rusqlite::Connection::open("todo.db").unwrap();
    conn. execute (
        "DELETE FROM todos WHERE id = ?1",
        params![form.id],).unwrap();
    Redirect::to("/tera")                     // リダイレクト先を / ではなく /tera に変更しています。
}

async fn edit(tera: web::Data<Tera>, form: web::Form<Todo>) -> impl Responder
{
    let mut context = Context::new();
    context.insert("i_d", &form.id);
    context.insert("msg", &form.title);
    context.insert("comp", &form.completed);
    let rendered = tera.render("edit.html", &context).unwrap();
    HttpResponse::Ok().body(rendered)
}


style.css

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

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


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

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

h1 {
  font-size: 20px;
}

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

button {
  background-color: white;
  color: gray;
  border: solid 1px lightgray;
  border-radius: 4px;
  margin-left: 4px;
}

.todo {
  display: flex;
  height: 80px;
  width: 100%;
  padding: 0px;
  border-radius: 4px;
  padding: 0px 0px;
  line-height: 80px;
  box-sizing: border-box;
  background-color: white;
  margin-bottom: 0px;
  margin-top: 16px;
  position: relative;
}

.checkbox {
  margin: 33px 4px;
  width: 14px;
  height: 14px;
}

.btn-edit {
  position: absolute;
  width: 50px;
  height: 30px;
  right: 4px;
  bottom: 4px;
}

.btn-delete {
  position: absolute;
  margin-top: 8px;
  margin-left: 0px;
  height: 35px;
  left: 0px;
}

.btn-back {
  position: absolute;
  margin-top: 8px;
  height: 35px;
  right: 0px;
}

.h-layout {
  display: flex;
}

.h-layout2 {
  display: flex;
  position: relative;
}


index.html

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

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


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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Tera</title>
    <link rel="stylesheet" href="/tera/static/style.css">         <-- 変更しました -->
    <script src="/tera/static/staticposition.js"></script>        <-- 追加しました -->
</head>

<body>
    <div class="container">
        <h1>Tera</h1>
        <form method="post" action="/tera/create">                <-- 変更しました -->
            <div class="h-layout">
                <input type="text" name="title" required maxlength="29">
                <input type="hidden" name="completed" value="false">
                <input type="hidden" name="id" value="0">
                <button>Create</button>
            </div>
        </form>
        {% for todo in todos %}
        <div class="todo">
            <form method="post" action="/tera/update" id=f{{todo.id}}>  <-- 変更しました -->
                <input type="checkbox" {% if todo.completed %}checked{% endif %} onchange="cbchange('f{{todo.id}}')"
                    class="checkbox">
                <input type="hidden" name="id" value={{todo.id}}>
                <input type="hidden" name="title" value="{{todo.title}}">
                {% if todo.completed %}
                <input type="hidden" name="completed" value="false">
                {% else %}
                <input type="hidden" name="completed" value="true">
                {% endif %}
                <button hidden>submit</button>
            </form>
            {% if todo.completed %}
            <del>{{ todo.title }}</del>
            {% else %}
            {{ todo.title }}
            {% endif %}
            <form method="post" action="/tera/edit">                    <-- 変更しました -->
                <input type="hidden" name="id" value={{todo.id}}>
                <input type="hidden" name="title" value="{{todo.title}}">
                <input type="hidden" name="completed" value={{todo.completed}}>
                <button class="btn-edit">Edit</button>
            </form>
        </div>
        {% else %}
        <div class="todo">No todo</div>
        {% endfor %}
        <script>
            function cbchange(f) {
                const form = document.getElementById(f);
                form.submit();
            }
        </script>
    </div>
</body>

</html>


edit.html

templates ディレクトリに次の edit.html を作ってください。

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


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

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Edit | Tera</title>
  <link rel="stylesheet" href="/tera/static/style.css"> <-- 変更しています -->
</head>

<body>
  <div class="container">
    <h1>Edit</h1>

    <form method="post" action="/tera/update">          <-- 変更しています -->
      <div class="h-layout">
        <input type="text" name="title" required maxlength="29" value="{{msg}}">
        <input type="hidden" name="completed" value={{comp}}>
        <input type="hidden" name="id" value={{i_d}}>
        <button>Update</button>
      </div>
    </form>
    <div class="h-layout2">
      <form method="post" action="/tera/delete">        <-- 変更しています -->
        <input type="hidden" name="id" value={{i_d}}>
        <input type="hidden" name="title" value="{{msg}}">
        <input type="hidden" name="completed" value={{comp}}>
        <button class="btn-delete" onclick="return confirm('削除してよろしいですか?');">Delete</button>
      </form>
      <form method="get" action="/tera">                <-- 変更しています -->
        <button class="btn-back">Back to index</button>
      </form>

    </div>
  </div>
</body>

</html>


staticposition.js

static ディレクトリに次の 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>
# 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

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


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

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

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

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

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

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



1044 visits
Posted: Oct. 01, 2025
Update: Oct. 01, 2025

ホーム   目次