# Lab11 網頁架構、Flask建立Web API
本次上課錄影(有剪掉一些作業程式碼的地方),請依照上課說的與作業要求完成作業。
然後沒調整到錄影的畫面所以歪掉了[上課錄影](https://www.youtube.com/playlist?list=PLurU1hsS9ns-RwuyLIo3zKjUrMU8FcDL3)
助教 : 威仁
## HTML 介紹
HTML(超文本標記語言,HyperText Markup Language)是一種用於建立網頁的標準標記語言。它是構建網站的基礎,所有網頁都依賴HTML來定義其結構和內容。
1. HTML 是一種標記語言,這意味著它使用標籤(tags)來“標記”網頁內容。
標籤通常成對出現,包含開始標籤(如 \<p>,\<h1>,\<a>)和結束標籤(如 \</p>,\</h1>,\</a>)。
3. 元素(Elements):
一個元素由開始標籤、內容、和結束標籤組成。例如,\<p>This is a paragraph.\</p> 表示一個段落元素,其中 \<p> 是段落的開始標籤,“This is a paragraph.” 是內容,\</p> 是結束標籤。
1. 屬性(Attributes):
標籤可以包含屬性,以提供更多的信息或控制元素的行為。
例如,\<a href="https://www.example.com">This is a link</a> 中,href 是屬性,用於指定連結的目標網址。
1. HTML 文件通常有一個明確的結構,包含以下部分:
* \<!DOCTYPE html>: 聲明文件類型,告知瀏覽器使用HTML5標準。
* \<html>: 包含整個HTML文件。
* \<head>: 包含頁面的元數據(如字符集、標題、樣式表連結等)。
* \<title>: 頁面標題,顯示在瀏覽器標籤中。
* \<body>: 包含網頁的可見內容,如文本、圖片、連結、表格等。
## HTML 的特點
* 簡單易學
* 跨平台相容
* 靈活性強
## 網頁概論
* 從點下某個網頁到呈現,中間發生了什麼事?

* 前端主要的工作就是「**呈現**」
* HTML: 網頁的「骨架」。
* CSS: 描述如何「裝飾」網頁。
* JavaScripts: 讓你的菜單可以「互動」。
* [bootstrap](https://themes.getbootstrap.com/)
* 後端的工作就是「**回應**」前端需要的「資料」
* 處理各個需求(Request)並做出回應(Response)。
* 保存不同使用者的資料。
## 網址(Web address 或 URL)
統一資源定位符(英語:Uniform Resource Locator,縮寫:URL,或稱統一資源定位器、定位位址、URL位址)俗稱網頁位址,簡稱網址,是網際網路上標準的資源的位址(Address),如同在網路上的門牌。
### 網址組成
HTTP 的統一資源定位符將從網際網路取得資訊的五個基本元素包括在一個簡單的位址中:
1. **協定類型 (scheme)**
這告訴瀏覽器或其他客戶端如何連接到資源。
2. 訪問資源需要的憑證資訊(userinfo)(可省略)
用於指定訪問資源所需的身份驗證信息。
3. **伺服器 (host)**(通常為域名,有時為IP位址)
指定要訪問的主機地址,通常是一個域名或 IP 地址。這是 URL 的核心部分,決定了訪問哪個服務器。
4. 埠號 (Port)(以數字方式表示,若為預設值可省略)
指定要連接的伺服器的端口號。如果未指定,默認端口號會根據協議決定,例如 HTTP 默認為 80,HTTPS 默認為 443。
5. **路徑 (Path)**(以「/」字元區別路徑中的每一個目錄名稱)
指定資源在伺服器上的具體位置,通常對應於伺服器上的文件路徑或應用程序路由。
6. **查詢 (query)**(GET模式的表單參數,以「?」字元為起點,每個參數以「&」隔開,再以「=」分開參數名稱與資料,通常以UTF-8的URL編碼,避開字元衝突的問題)
透過鍵值的形式傳遞額外的參數或數據給伺服器,通常用於搜尋請求或過濾結果。
7. **片段 (fragment)**(以「#」字元為起點)
指定資源中的一個片段或子部分,通常用於網頁內的錨點,讓瀏覽器跳轉到指定位置。

* 網址範例 ( google 搜尋結果 )
https://www.google.com/search?q=test
* \[協定類型] : https
* \[伺服器位址] : www.google.com
* \[埠號] : 可以不顯示(https預設443)
* \[路徑] : /search
* \[查詢]: q=test
## MVC架構

## request

https://youtu.be/Zex0g8Llj24?si=4dDnZLdhJgbb0EdB&t=28
* Get
單純的跟 server 要一個連結或圖片,通常網頁都是 Get 的 request 比較多
最常使用的 Method
例如:要去某個網址、看某張圖片
傳送的資料會以 Query String 的方式加在 url 上
* Post
需要執行一些私人資料的傳輸時,會傳送 Post request
例如:登入會員、送出表單
獲取「指定的」資訊,放在 request body(Form data)裡面
注重隱私的資料傳輸方法,因為他不會將資料顯示在url上
### response
status code
* 主要分為五種:
1XX 資訊回應、2XX 成功回應、3XX 重定向、4XX 用戶端錯誤、5XX 伺服器端錯誤
* 100 Continue:Server 成功接收、但 Client 還要進行一些處理
* 200 OK:成功
* 204 No Content:成功,但沒有回傳的內容( 例如當你發出 Delete 的 request )
* 301 Moved Permanently:資源「 永久 」移到其他位置,再下一次發出 request 時,瀏覽器會直接到新位置
* 302 Found(Moved Temporarily):資源「 暫時 」移到其他位置
* 304 Not Modified:東西跟之前長一樣,可以從快取拿就好
* 400 Bad Request:請求語法錯誤、或資源太大…等等
* 401 Unauthorized:未認證,可能需要登入或 Token
* 403 Forbidden:沒有權限
* 404 Not Found:找不到資源
* 500 Internal Server Error:伺服器出錯,搶票時很可能發生
* 501 Not Implemented
* 502 Bad Gateway:通常是伺服器的某個服務沒有正確執行
https://noob.tw/http-status-code/
## API
### API 是什麼?
API 應用程式介面,是 Application Programming Interface 的縮寫。簡單來說,API 就如接收要求的信差、技術與數據的接口,是串接兩端裝置或應用程式的橋樑,創造連結,實現資訊或技術的交換、溝通。使用 API 能大幅減少開發成本,只需傳送需求,就能回傳資料,讓程式設計更便利。
Web API 是指藉由 http 通訊協定,進行請求、運算、回應的 API,提供 JSON、XML 兩種傳輸格式,基本上透過「網路」交換資訊,皆為 Web API 的範疇。
## Flask
Flask 是一個輕量級的 Python Web 框架,設計簡單、靈活且易於擴展,非常適合構建中小型的 Web 應用程序。
### 如何使用
* 安裝 Flask 套件
確認是否已經安裝,終端機中輸入
`flask --version`
如果沒有,使用以下命令來安裝
`$ pip install Flask`
在樹梅派上面安裝務必使用虛擬環境,否則權限不足無法使用Pip進行安裝,
創建虛擬環境指令
`python3 -m venv venv` (後面的venv為虛擬環境名稱,可以自己輸入不過建議使用venv即可)
進入虛擬環境
`source venv/bin/activate`
<font color="#96999A">本地端也建議先弄好虛擬環境來安裝</font>
* 簡單的範例
```python=
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return "Hello, World!"
if __name__ == '__main__':
app.run(debug=True)
```
### Get Request
* 基礎教學
```python=
# coding=utf-8
# 不加上就無法使用中文註解
from flask import Flask
app = Flask(__name__) #建立 flask 物件
# 路由設定
# 透過函式的裝飾器( @ )來設定路由
# 若有get request傳送到後端,就會執行他下面的function,function名稱隨意,但不可重複
# 設定根目錄(通常代表首頁)之路由
@app.route('/',methods=['GET'])
def index():
return "Hello World !"
# 設定路徑為 '/get_data'之路由
@app.route('/get_data',methods=['GET'])
def get_data():
return "Student_ID : Exxxxxxxx"
# host="0.0.0.0" 將伺服器公開,否則只能使用127.0.0.1 or localhost
# 將webserver執行,監聽任意來源ip,port開在3000,開啟debug模式
# debug模式代表,每次檔案更新後,webserver會自動重啟,不需要手動重啟
app.run(host="0.0.0.0", port=5000, debug=True)
```
* 動態路由
```python=
from flask import Flask
app = Flask(__name__)
# 設定根目錄(通常代表首頁)之路由
@app.route('/',methods=['GET'])
def index():
return "Hello World"
# 同樣設定路徑為 '/get_data'之路由,但使用動態路由設定
# 動態路由設定 : 使用<>來表示路由為需要被接收的變數,並且把該變數放到function的parameter中,就可以在function中使用這個變數
@app.route('/get_data/<student_id>',methods=['GET'])
def get_data(student_id):
return f'Student_ID : {student_id}'
app.run(host="0.0.0.0", port=3000, debug=True)
```
\<student_id> >> 預設為型態為str
\<converter:student_id> 可以在前面加入型態指定

* 要求字串( Query string )
```python=
from flask import request,Flask # 也可以直接打一個 * 就好,這樣代表import函式庫所有的函式 (from flask import *)
app = Flask(__name__)
# 設定根目錄(通常代表首頁)之路由
@app.route('/',methods=['GET'])
def index():
return "Hello World"
# 在網址中的路徑的後面加上?參數1=值1&參數2=值2......
# 範例 : http://127.0.0.1:3000/string_test?name=Pikachu
@app.route('/string_test',methods = ['GET'])
def string_test():
# 使用 request.args.get('參數名稱',預設值)
string1 = request.args.get('name',default= "Pikachu")
return 'Hi ! '+ string1
app.run(host="0.0.0.0", port=3000, debug=True)
```
### Post Request
與前面使用的 Get Request不同,為了避免隱私性資料外洩,不會直接在url上做資料的輸入或顯示,通常會在前端設立表單來作為使用者輸入資料的窗口,表單需要使用html語言呈現
* HTML(前端):
```html=
<!DOCTYPE html>
<html lang="en">
<!-- 注意 : 樹梅派上的html檔 ,請使用英文註解 ,否則會有解碼問題 -->
<!-- 基本的html架構,可以在vs code 裡 新增空的html檔,然後在第一行打 ! 再按 Tab鍵 即會幫你生成 -->
<!-- head 裡主要存放css code 去對網頁做排版以及美觀,但這次課程不會教,作業也不需要寫到,頂多需要修改title文字 -->
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>POST Example</title>
</head>
<!-- body區域,主要寫html的架構以及javascript(這次也不會用到), 這次作業會需要做修改的地方,主要是body裡的html code,以及可能會需要上網查一些相關用法 -->
<body>
<!-- 標題 -->
<h3>Simple Form</h3>
<!-- 水平分割線 -->
<hr>
<!-- 定義form表單 -->
<!-- action = 表單輸入並送出之後,要切換過去之路由,而表單內容基本上屬於比較私密之資料,因此會使用POST request -->
<form action='/set' method = 'POST'>
<!-- 輸入欄定義,懂都懂 -->
Text <input type="text" name="string1"/>
<!-- 定義一個按鈕,按下會送出表單內容,懂都懂 -->
<button>submit</button>
</form>
</body>
</html>
```
* python flask (後端)
注意 : 請在你執行python檔案的目錄底下新增一個名為 "templates" 的資料夾
```python!
#coding=utf-8
# 不加上就無法使用中文註解
from flask import *
app = Flask(__name__)
# 使用render_template函式讓首頁的路由能夠回傳html檔(簡單架構)給前端,來顯示首頁畫面,而不是僅用return文字的方式
@app.route("/")
def index():
# 回傳首頁畫面 ( 請確保你的html檔是放在名為 templates的資料夾)
return render_template('post_example.html')
# 設定路由為/set
# 使用 request.form['變數名稱'] 來直接取用表單的輸入資料
# 使用 request.form 來接收前端輸入之資料的資料,接著用to_dict()這個function來轉成python的dict格式,可以做資料的儲存
@app.route('/set',methods = ['POST'])
def root():
string1 = request.form['string1']
data = request.form.to_dict()
print(data)
return 'ok ! your text is : '+ string1 # 發送response為 ok + 傳送的文字內容
app.run(host="0.0.0.0", port=3000, debug=True)
```
## Lab11
作業繳交注意事項:
1. 基礎題與加分題直接用一個資料夾上傳至github,資料夾名: 「學號_Lab11」。
2. 程式碼需要註解,server需要在樹梅派上面執行。
3. 將程式碼上傳且需要包含html檔案,
基礎題py檔取名`Lab11.py`而加分題為`Lab11_plus.py`,
html檔一樣放在`templates`中,基礎題為`Lab11.html`加分題為`Lab11_plus.html`。

4. 截圖需要截圖到網址部分!
5. 本次作業截圖較多,請將截圖都放在PDF中,取名為`Lab11.pdf`和`Lab11_plus.pdf`,存放在學號_Lab11資料夾中,並標明題號,若未標明將會扣分。基礎題API1、API2、API3..能夠讓人看得懂就可以(每題扣10分)。
6. 作業繳交時間:12/23 12:00 不開放補交請在時限內繳交作業。
7. 注意一下截圖的時候,要控制一下不要每張都截圖很大,然後請用成png檔再放進去,如果檔案大小超過50MB會沒辦法用指令的方式上傳PDF會導致卡死然後分支炸裂,修復很麻煩要退版本重新弄。(應該不會遇到但還是請大家小心)
## Lab11 基礎題
* 請依照提供的html,完成一個網頁,包含前端與後端,並具有以下API :
| 配分 | API名稱 | 傳送方法 | 傳送參數 | 功能 |
| --- | ------------- | -------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 20% | / | GET | 無 | 接收到request之後,回傳 Lab11.html 至前端呈現首頁畫面,</br> Lab11.html 會包含文字與表單,title為 " Lab 11",</br>建立兩個表單,表單一為Student Data,表單二為BMI |
| 40% | /student_data | POST | 姓名(name)、學號(student_id) | 依照上lab11.html,與Student Data表單互動,</br>表單輸入姓名與學號,按下送出按鈕後,</br>切換到/student_data路由,在server端的terminal顯示資料,並回傳 ' OK! ' 給前端 |
| 40% | /BMI | GET | 身高(m)、體重(Kg) | 依照上lab11.html,與BMI表單互動,</br>表單輸入姓名與學號,按下送出按鈕後,</br>將資料送給後端的server計算且在server端的terminal顯示資料,並回傳 ' 你的BMI是 "數值","結果(過輕、正常、過重)" ' 透過javascript動態改變網頁讓前端顯示</br>範例:你的BMI是 18.81,正常 |
```html=
<body>
<!-- 標題 -->
<h3>Student Data</h3>
<!-- 定義form表單 -->
<!-- action = 表單輸入並送出之後,要切換過去之路由,而表單內容基本上屬於比較私密之資料,因此會使用POST request -->
<form action='/student_data' method='POST'>
<table>
<tr>
<td><label for="name">Name</label></td>
<td><input type="text" name="name" id="name"/></td>
</tr>
<tr>
<td text-align="center"><label for="id">Std_ID</label></td>
<td><input type="text" name="student_id" id="id"/></td>
</tr>
</table>
<br>
<button type="submit">Submit</button>
</form>
<br><br>
<!-- 水平分割線 -->
<hr>
<h2>BMI 計算器</h2>
<form id="bmi-form">
<table>
<tr>
<td><label for="height">Height (m)</label></td>
<td><input type="text" name="height" id="height" placeholder="例如: 1.75" required /></td>
</tr>
<tr>
<td><label for="weight">Weight (kg)</label></td>
<td><input type="text" name="weight" id="weight" placeholder="例如: 70" required /></td>
</tr>
</table>
<button type="button" onclick="calculateBMI()">計算BMI</button>
</form>
<p id="output"></p>
<script>
function calculateBMI() {
const height = document.getElementById("height").value;
const weight = document.getElementById("weight").value;
if (!height || !weight) {
document.getElementById("output").textContent = "請輸入身高和體重!";
return;
}
// 發送 GET 請求,帶上輸入的數據
fetch(`/BMI?height=${height}&weight=${weight}`)
.then(response => response.json())
.then(data => {
document.getElementById("output").textContent = data.output;
})
.catch(error => {
document.getElementById("output").textContent = "計算失敗,請檢查輸入!";
});
}
</script>
</body>
</html>
```
Hint: 使用 JavaScript 動態更新網頁資訊時,需要接收json格式,所以Return時請用{}包住,如```return {'output': return_string}```
作業截圖呈現範例
1. 截圖畫面首頁
2. /student_data 的樹莓派終端機與網頁畫面(截圖部分要有SSH連線的畫面)

2. /BMI 的樹莓派終端與網頁畫面(截圖部分要有SSH連線的畫面) 

## Lab11 加分題
註 :給分方式,總分低於45分 不加分 超過45分+1分 90分以上+2分 ,每個錯誤扣5分
* 請依照提供的html,完成一個網頁,包含前端與後端,並具有以下API :
| 配分 | API名稱 | 傳送方法 | 傳送參數 | 功能 |
| --- | ------------- | -------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 10% | / | GET | 無 | 接收到request之後,回傳 Lab11_plus.html 至前端呈現首頁畫面,</br> Lab11_plus.html 會包含文字與表單,title為 " Lab11_plus",</br>建立一個表單來填入數列 |
| 50% | /sort_numbers | POST | 數列(以,分隔) | 依照上Lab11_plus.html,在表單輸入數列,按下送出按鈕後,</br>切換到/sort_numbers路由,在Server端的terminal顯示排序前資料與排序後資料,</br>並回傳插入排序法的各個步驟回傳給前端(請參考下圖) |
| 40% |/generate_random | GET | 無 | 依照上Lab11_plus.html,按下"生成隨機數列"按鈕後,透過Server生成一組數列,</br>該數列隨機生成五個數字且數值在0~100之間,並在Server端的terminal顯示該數列且回傳,</br>並透過JavaScript使用Get方法將數列填入表單中|
```html=
<body>
<h2>插入排序法</h2>
<form action="/sort_numbers" method="POST">
<label>輸入數字 (逗號分隔):</label>
<input type="text" name="numbers" id="numbers" placeholder="例如: 5,3,8,1,2" required>
<button type="submit">排序</button>
</form>
<button onclick="generateRandom()">生成隨機數列</button>
<p id="random-output"></p>
<script>
function generateRandom() {
fetch('/generate_random')
.then(response => response.json())
.then(data => {
document.getElementById("numbers").value = data.numbers;
document.getElementById("random-output").textContent = "隨機數列: " + data.numbers;
});
}
</script>
</body>
</html>
```
作業截圖呈現範例
(終端機畫面要有ssh連線,也就是要截圖到這裡,
或者你用終端機有顯示ssh連線狀態的截圖也可以)
1. 截圖畫面首頁
2. 截圖終端機需要有排序前與排序後的數列,與網頁畫面且需要把每個步驟都顯示一遍

4. 點擊隨機產生數列,會產生五個數字的數列動態放進輸入數字的表格中,數值限定0~100
## 補充 : 插入排序法


圖片來源:https://hackmd.io/@coherent17/Sy79MIyju