在現代網頁應用程式開發中,將靜態網站佈署到雲端儲存服務已成為常見做法。本文將探討如何利用 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.html、add.html和index.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();
}
程式碼解析
- 建立S3客戶端:使用
aws_config::load_from_env().await載入AWS設定,並建立S3客戶端。 - 上傳網站檔案:使用
ByteStream::from_path讀取網站檔案,並使用put_object方法上傳至S3儲存桶。 - 設定網站組態:使用
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.rs 和 src/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();
程式碼解密:
- CorsRule 建構: 透過
CorsRule::builder()建構 CORS 規則,第一個規則允許PUT和POST請求來自http://*.amazonaws.com的來源;第二個規則允許所有來源的GET請求。 - CorsConfiguration 建構: 將兩個 CORS 規則加入到
CorsConfiguration中。 - 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),
});
}
內容解密:
Attack結構體定義了攻擊元件,包含攻擊力和攻擊範圍兩個屬性。Transform結構體定義了變換元件,用於追蹤實體的位置、方向和縮放比例。- 在
main函式中,我們建立了一個新的實體,並新增了Attack和Transform元件。 - 元件被附加到實體上後,可以用於實作各種遊戲邏輯,例如計算傷害或更新實體位置。
Bevy引擎的使用
Bevy引擎提供了豐富的功能和工具,幫助開發者快速構建遊戲。在接下來的章節中,我們將探討如何使用Bevy引擎開發我們的貓咪排球遊戲。