在現代網頁應用程式開發中,將靜態網站佈署到雲端儲存服務已成為常見做法。本文將探討如何利用 AWS SDK for Rust 將靜態網站內容佈署至 Amazon S3,並設定 CORS 以允許跨域資源分享。過程中,我們將使用 Rust 語言操作 AWS SDK,建立 S3 儲存桶,上傳網站檔案,並設定必要的網站組態和 CORS 規則,確保網站能正常運作並與後端 API 順利互動。同時,我們也會涵蓋一些安全注意事項,以確保網站的安全性。最後,我們將簡要介紹使用 Rust 和 Bevy 引擎開發遊戲的可能性,為讀者提供更廣闊的技術視野。

使用AWS SDK for Rust佈署靜態網站至S3

本篇文章將介紹如何使用AWS SDK for Rust來佈署靜態網站至Amazon S3。在前面的章節中,我們已經建立了一個簡單的Catdex應用程式,現在我們將使用Rust和AWS SDK來佈署前端網站。

網站檔案結構

首先,我們需要建立網站的HTML、CSS和JavaScript檔案。主要的檔案包括index.htmladd.htmlindex.css

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>Catdex</title>
    <link rel="stylesheet" href="css/index.css" type="text/css">
</head>
<body>
    <h1>Catdex</h1>
    <p>
        <a href="/add.html">Add a new cat</a>
    </p>
    <section class="cats" id="cats">
        <p>No cats yet</p>
    </section>
    <script charset="utf-8">
        document.addEventListener("DOMContentLoaded", () => {
            fetch('<INSERT URL>')
                .then((response) => response.json())
                .then((cats) => {
                    document.getElementById("cats").innerText = ""
                    for (cat of cats.cats) {
                        const catElement = document.createElement("article")
                        catElement.classList.add("cat")
                        const catTitle = document.createElement("h3")
                        const catLink = document.createElement("a")
                        catLink.innerText = cat
                        const catImage = document.createElement("img")
                        catImage.src = `images/${cat}`
                        catTitle.appendChild(catLink)
                        catElement.appendChild(catTitle)
                        catElement.appendChild(catImage)
                        document.getElementById("cats").appendChild(catElement)
                    }
                })
        })
    </script>
</body>
</html>

add.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>Catdex</title>
    <link rel="stylesheet" href="css/index.css" type="text/css">
</head>
<body>
    <script>
        async function submitForm(e) {
            e.preventDefault()
            const cat_name = document.getElementById('name').value
            const cat_post_response = await fetch('<INSERT URL>', {
                method: 'POST',
                mode: 'cors',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ name: cat_name })
            })
            const image_upload_url = (await cat_post_response.json()).upload_url
            const image = document.getElementById("image").files[0]
            const image_upload_response = await fetch(image_upload_url, {
                method: 'PUT',
                body: image,
            })
            if (image_upload_response.status === 200) {
                alert("Success")
            } else {
                alert("Failed")
            }
            return false
        }
    </script>
    <h1>Add a new cat</h1>
    <form onsubmit="return submitForm(event)">
        <label for="name">Name:</label>
        <input type="text" name="name" id="name" value="" />
        <label for="image">Image:</label>
        <input type="file" name="image" id="image" value="" />
        <button type="submit">Submit</button>
    </form>
</body>
</html>

index.css

.cats {
    display: flex;
}

.cat {
    border: 1px solid grey;
    min-width: 200px;
    min-height: 350px;
    margin: 5px;
    padding: 5px;
    text-align: center;
}

.cat > img {
    width: 190px;
}

使用AWS SDK for Rust佈署網站

要佈署網站,我們需要使用AWS SDK for Rust建立一個S3客戶端並上傳網站檔案。

建立S3客戶端

首先,我們需要在Cargo.toml中新增AWS SDK for Rust的依賴項。

[dependencies]
aws-sdk-s3 = "0.23.0"
tokio = { version = "1", features = ["full"] }

然後,建立一個名為create-bucket.rs的檔案,用於建立S3儲存桶。

use aws_sdk_s3 as s3;

#[tokio::main]
async fn main() {
    let config = aws_config::load_from_env().await;
    let s3_client = s3::Client::new(&config);
    
    // 建立S3儲存桶
    let bucket_name = "catdex-frontend";
    s3_client.create_bucket().bucket(bucket_name).send().await.unwrap();
}

上傳網站檔案

建立一個名為put-website.rs的檔案,用於上傳網站檔案至S3儲存桶。

use aws_sdk_s3 as s3;
use aws_sdk_s3::model::{IndexDocument, WebsiteConfiguration};
use aws_sdk_s3::types::ByteStream;
use std::path::Path;

#[tokio::main]
async fn main() {
    let config = aws_config::load_from_env().await;
    let s3_client = s3::Client::new(&config);

    // 上傳index.html
    let body = ByteStream::from_path(Path::new("../../client/dist/index.html")).await.unwrap();
    s3_client.put_object().body(body).bucket("catdex-frontend").key("index.html").content_type("text/html").send().await.unwrap();

    // 上傳add.html
    let body = ByteStream::from_path(Path::new("../../client/dist/add.html")).await.unwrap();
    s3_client.put_object().body(body).bucket("catdex-frontend").key("add.html").content_type("text/html").send().await.unwrap();

    // 上傳index.css
    let body = ByteStream::from_path(Path::new("../../client/dist/css/index.css")).await.unwrap();
    s3_client.put_object().body(body).bucket("catdex-frontend").key("css/index.css").content_type("text/css").send().await.unwrap();

    // 設定網站組態
    let cfg = WebsiteConfiguration::builder()
        .index_document(IndexDocument::builder().suffix("index.html").build())
        .build();
    s3_client.put_bucket_website().bucket("catdex-frontend").website_configuration(cfg).send().await.unwrap();
}

程式碼解析

  1. 建立S3客戶端:使用aws_config::load_from_env().await載入AWS設定,並建立S3客戶端。
  2. 上傳網站檔案:使用ByteStream::from_path讀取網站檔案,並使用put_object方法上傳至S3儲存桶。
  3. 設定網站組態:使用WebsiteConfiguration設定網站的索引檔案,並使用put_bucket_website方法更新S3儲存桶的網站組態。

佈署網站與啟用 CORS

在前面的章節中,我們已經成功地將網站內容上傳到 S3 儲存桶中。然而,當我們嘗試存取網站時,卻發現 API 請求失敗。這是因為瀏覽器的同源政策(same-origin policy)所導致的。

同源政策與 CORS

同源政策是一種安全機制,限制了網頁從不同來源載入資源。由於我們的網站是託管在 http://catdex-frontend.s3-website.us-east-2.amazonaws.com/index.html,而 API 則是託管在 https://abc0123def.execute-api.eu-central-1.amazonaws.com/,兩者的來源不同,因此瀏覽器會阻止 API 請求。

為瞭解決這個問題,我們需要啟用 CORS(Cross-Origin Resource Sharing)機制。CORS 允許伺服器指定哪些來源可以存取其資源。

更新 template.yaml 以啟用 CORS

首先,我們需要在 template.yaml 中更新 Globals 區段,以啟用 CORS:

Globals:
  Function:
    Timeout: 3
  Api:
    Cors:
      AllowMethods: "'GET,POST'"
      AllowHeaders: "'content-type'"
      AllowOrigin: "'*'"

在 API 回應中新增 CORS 標頭

接下來,我們需要在 API 回應中新增 CORS 標頭,以允許跨來源請求。我們可以修改 src/bin/lambda/get-cats.rssrc/bin/lambda/post-cat.rs,如下所示:

let resp = Response::builder()
    .status(StatusCode::OK)
    .header("content-type", "text/html")
    .header("Access-Control-Allow-Origin", "*")
    .header("Access-Control-Allow-Headers", "Content-Type")
    .header("Access-Control-Allow-Methods", "GET")
    .body(serde_json::to_string(&response_body).unwrap().into())
    .map_err(Box::new)?;
Ok(resp)

更新 put-website.rs 以設定 CORS

最後,我們需要更新 put-website.rs,以設定 S3 儲存桶的 CORS 設定:

let cors_rule_1 = CorsRule::builder()
    .allowed_headers("*")
    .allowed_methods("PUT")
    .allowed_methods("POST")
    .allowed_origins("http://*.amazonaws.com")
    .max_age_seconds(0)
    .build();

let cors_rule_2 = CorsRule::builder()
    .allowed_headers("*")
    .allowed_methods("GET")
    .allowed_origins("*")
    .build();

let cfg = CorsConfiguration::builder()
    .cors_rules(cors_rule_1)
    .cors_rules(cors_rule_2)
    .build();

s3_client
    .put_bucket_cors()
    .bucket(BUCKET)
    .cors_configuration(cfg)
    .send()
    .await
    .unwrap();

程式碼解密:

  1. CorsRule 建構: 透過 CorsRule::builder() 建構 CORS 規則,第一個規則允許 PUTPOST 請求來自 http://*.amazonaws.com 的來源;第二個規則允許所有來源的 GET 請求。
  2. CorsConfiguration 建構: 將兩個 CORS 規則加入到 CorsConfiguration 中。
  3. S3 CORS 設定: 使用 put_bucket_cors 方法將 CORS 設定套用到指定的 S3 儲存桶。

重新佈署專案

完成上述更新後,我們可以重新佈署專案:

$ cargo lambda build --release --arm64
$ sam deploy
$ cargo run --bin put-website

現在,我們應該可以從任何公開的電腦存取 Catdex 網站,並顯示貓咪圖片,以及新增更多的貓咪。

安全注意事項

在結束本章節之前,我們需要強調安全最佳實踐的重要性。在學習新技術時,我們通常會忽略一些細節,以加快學習進度。然而,在實際的工作環境中,安全是至關重要的。忽略安全可能會導致駭客攻擊、資料遺失或意外的巨額帳單。因此,我們應該認真對待安全問題,並花時間學習相關的最佳實踐。

回顧與未來方向

本章中,我們成功結合了Lambda函式、DynamoDB資料函式庫表以及S3儲存桶,開發了一個功能完整的網站並對外發布。您可以進一步擴充前端功能,甚至整合第4章提到的單頁應用程式。此外,也可以新增後端Lambda函式,例如提供刪除錯誤新增貓咪的功能。

在DevOps方面,您可以將多步驟的佈署流程和指令碼整合成單一的佈署指令碼,並在CI/CD環境中執行。甚至可以探索不同的框架來取代部分DevOps程式碼,使您能夠專注於網頁服務的核心功能開發。

AWS SDK for Rust目前仍處於開發者預覽階段,這意味著未來可能會有所變動。我們已經見證了它在Lambda函式以及本地執行以設定基礎設施即程式碼(Infrastructure as Code)的強大功能。這些應用僅僅觸及了這個SDK的冰山一角,它支援超過200種AWS服務。您可以嘗試擴充這個服務以使用AWS CloudFront,或是嘗試建立一個更傳統的伺服器和SQL資料函式庫網頁模型,使用EC2和RDB。

使用Rust開發遊戲

電子遊戲的發展經歷了從早期簡單實作到現今複雜龐大的過程。曾經紅極一時的超級瑪利歐兄弟執行在8位元CPU上,時脈僅1.79 MHz,遊戲本身大小約31 kilobytes。如今,遊戲PC的CPU核心數達到8核心,時脈達3-5 GHz,遊戲大小動輒50-70 gigabytes。這代表著計算能力的千倍提升和儲存空間的百萬倍增長。遊戲的複雜度也隨之增加,使得遊戲程式設計師的工作變得更加具有挑戰性。

為何選擇Rust

Rust因其低階記憶體安全保證和卓越的效能表現,成為開發遊戲引擎和遊戲的理想選擇。同時,其高階語法允許您以乾淨和模組化的方式撰寫遊戲邏輯。

儘管Rust的遊戲生態系統仍在發展中,但已經有一些優秀的遊戲採用Rust開發。同時,也有幾款遊戲引擎可供選擇。在本章中,我們將使用Bevy遊戲引擎來展示如何使用Rust開發遊戲。

我們的目標

我們將重現一部懷舊遊戲——皮卡丘排球。這是一款簡單卻極具吸引力的遊戲,特色是兩個皮卡丘角色進行沙灘排球比賽。玩家可以使用鍵盤與電腦對戰或與他人競爭。

遊戲功能

  • 2D遊戲介面,左右兩側各有一名玩家(貓咪角色)
  • 左側玩家使用WASD鍵控制,右側玩家使用方向鍵控制
  • 球從中間發出,玩家需使用頭部和身體將球擊回對方
  • 球受重力影響,會彈跳和下落
  • 當球接觸到對方場地的地面時,玩家得分
  • 包含音樂和音效

Bevy與實體元件系統(ECS)架構模式

Bevy是一款根據實體元件系統(ECS)架構模式的遊戲引擎。ECS的核心思想是促進組合優於繼承,同時最佳化記憶體存取順序以提升效能。例如,在一個角色扮演遊戲(RPG)中,我們有玩家、怪物和可毀壞的樹木等實體。這些實體可能分享某些屬性,如移動和攻擊能力,同時也需要追蹤其位置和生命值。

在ECS架構中,我們將實體的各個方面分離成元件(Component),然後將這些元件附加到實體上,從而組合出遊戲中的物件。例如:

  • 攻擊元件:包含攻擊力和攻擊範圍
  • 變換元件:追蹤位置、方向和縮放比例
  • 碰撞元件:偵測碰撞
  • 生命值元件:追蹤生命值和死亡狀態

程式碼範例

// 定義實體元件
struct Attack {
    power: i32,
    range: f32,
}

struct Transform {
    position: Vec3,
    rotation: Quat,
    scale: Vec3,
}

// 將元件附加到實體上
fn main() {
    // 建立一個實體
    let mut entity = Entity::new();

    // 新增元件
    entity.insert(Attack { power: 10, range: 5.0 });
    entity.insert(Transform {
        position: Vec3::new(0.0, 0.0, 0.0),
        rotation: Quat::identity(),
        scale: Vec3::new(1.0, 1.0, 1.0),
    });
}

內容解密:

  1. Attack結構體定義了攻擊元件,包含攻擊力和攻擊範圍兩個屬性。
  2. Transform結構體定義了變換元件,用於追蹤實體的位置、方向和縮放比例。
  3. main函式中,我們建立了一個新的實體,並新增了AttackTransform元件。
  4. 元件被附加到實體上後,可以用於實作各種遊戲邏輯,例如計算傷害或更新實體位置。

Bevy引擎的使用

Bevy引擎提供了豐富的功能和工具,幫助開發者快速構建遊戲。在接下來的章節中,我們將探討如何使用Bevy引擎開發我們的貓咪排球遊戲。